/XNAComponentFramework/Debugging/XConsole.cs
C# | 1633 lines | 1308 code | 110 blank | 215 comment | 116 complexity | 9bfcb70f151af82f799365c44e4f8348 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Content;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- using ComponentFramework;
-
- namespace ComponentFramework.Debugging
- {
- /// <summary>
- /// A singleton game component that provides a debug console with the ability to both
- /// write text and register new commands.
- /// </summary>
- public class XConsole
- {
- /// <summary>
- /// Gets the singleton instance of XConsole; only valid after the constructor is called.
- /// </summary>
- public static XConsole Instance { get; private set; }
-
- // information about the height of the console
- private float currentHeight = 0;
- private int heightInLines = 15;
- private int totalHeight;
-
- // extra padding at the bottom of the console
- private const int bottomPadding = 2;
-
- // data about animation
- private float animationTime = .25f;
- private float heightSpeed;
-
- // the prompt information
- private const string prompt = "> ";
- private int promptWidth;
-
- // cursor information
- private const string cursor = "_";
- private int cursorSize;
- private int cursorPosition = 0;
- private float cursorBlinkTimer = 0f;
- private const float cursorBlinkRate = .5f;
- private bool cursorVisible = true;
-
- // key repeat information
- private Keys pressedKey;
- private const float keyRepeatDuration = .05f;
-
- // the list of lines for the console output
- private List<OutputLine> output = new List<OutputLine>();
-
- // an offset (in lines) to shift for drawing update when using ctrl+up/down to scroll the output
- private int outputShift = 0;
-
- // the current input text
- private StringBuilder inputText = new StringBuilder();
-
- // the history of console input
- private List<string> inputHistory = new List<string>();
-
- // current position in the inputHistory list when using the up/down arrows
- private int inputHistoryPosition = 0;
-
- // the location in the inputText where the tab completion should be inserted
- private int tabCompletePosition;
-
- // a list of suggestions for tab completion
- private IList<string> tabCompletionSuggestions;
-
- // the index into our suggestions list for the current suggestion
- private int tabCompleteIndex;
-
- // a suggestion provider for command names, used by our initial tab behavior for commands as well as for
- // suggesting command names for the 'help' command
- private readonly CommandSuggestionProvider commandSuggestionProvider = new CommandSuggestionProvider();
-
- // the list of selected objects for use with the 'info', 'get', 'set', and 'invoke' commands
- private List<object> selectedObjects = new List<object>();
-
- // all of the currently registered commands
- private Dictionary<string, CommandInfo> commands = new Dictionary<string, CommandInfo>();
-
- /// <summary>
- /// Gets the current state of the console.
- /// </summary>
- public ConsoleState CurrentState { get; private set; }
-
- /// <summary>
- /// Gets or sets the key to use to toggle the console.
- /// </summary>
- public Keys ToggleKey { get; set; }
-
- /// <summary>
- /// Gets or sets the height of the console in lines.
- /// </summary>
- public int Height
- {
- get { return heightInLines; }
- set
- {
- heightInLines = value;
- if (DebugContent.ConsoleFont != null)
- totalHeight = value * DebugContent.ConsoleFont.LineSpacing + bottomPadding;
- }
- }
-
- /// <summary>
- /// Gets or sets the length of time it should take the console to go from fully closed to fully open.
- /// </summary>
- public float AnimationLength
- {
- get { return animationTime; }
- set
- {
- animationTime = value;
- if (totalHeight > 0)
- heightSpeed = totalHeight / animationTime;
- }
- }
-
- /// <summary>
- /// Gets or sets the opacity of the background of the console.
- /// </summary>
- public float BackgroundOpacity { get; set; }
-
- /// <summary>
- /// Gets or sets the delegate that converts a key into a character.
- /// </summary>
- /// <remarks>
- /// The default is generally "good enough" for standard US keyboards, but if you have
- /// a non-standard US keyboard or a non-US keyboard you'll likely want to replace this
- /// with a function that more properly maps the XNA Framework Keys values to characters
- /// for your particular language.
- /// </remarks>
- public KeyToCharacterDelegate KeyToCharacter { get; set; }
-
- /// <summary>
- /// Gets or sets an object to handle parsing strings for the "set" and "invoke" commands.
- /// </summary>
- public IStringParser StringParser { get; set; }
-
- /// <summary>
- /// Gets the list of selected objects.
- /// </summary>
- public List<object> SelectedObjects { get { return selectedObjects; } }
-
- public static void AddObject(object aObjectToAdd)
- {
- Instance.selectedObjects.Add(aObjectToAdd);
- }
-
- /// <summary>
- /// Initializes a new XConsole.
- /// </summary>
- /// <param name="services">A service provider where the console can find the IKeyboardInputService and IGraphicsDeviceService.</param>
- /// <param name="fontName">The name of a font to use for the console. Must be relative path from executable.</param>
- public XConsole()
- {
- // only allow one instance at a time
- if (Instance != null)
- throw new InvalidOperationException("Only one XConsole can exist. Use XConsole.Instance to access it.");
- Instance = this;
-
- // initialize some state
- CurrentState = ConsoleState.Closed;
- ToggleKey = Keys.OemTilde;
- KeyToCharacter = KeyString.KeyToString;
- StringParser = new DefaultStringParser();
- BackgroundOpacity = .6f;
-
- // register all the default console commands
- RegisterDefaultCommands();
- }
-
- /// <summary>
- /// Opens the console.
- /// </summary>
- public void Open()
- {
- Open(true);
- }
-
- /// <summary>
- /// Opens the console.
- /// </summary>
- /// <param name="animated">Whether or not to animate the console opening.</param>
- public void Open(bool animated)
- {
- if (animated)
- {
- if (CurrentState != ConsoleState.Open)
- CurrentState = ConsoleState.Opening;
- }
- else
- {
- currentHeight = totalHeight;
- CurrentState = ConsoleState.Open;
- }
- }
-
- /// <summary>
- /// Closes the console.
- /// </summary>
- public void Close()
- {
- Close(true);
- }
-
- /// <summary>
- /// Closes the console.
- /// </summary>
- /// <param name="animated">Whether or not to animate the console closing.</param>
- public void Close(bool animated)
- {
- if (animated)
- {
- if (CurrentState != ConsoleState.Closed)
- CurrentState = ConsoleState.Closing;
- }
- else
- {
- currentHeight = 0;
- CurrentState = ConsoleState.Closed;
- }
- }
-
- /// <summary>
- /// Toggles the console, opening it if it's closed/closing or closing it if it's open/opening.
- /// </summary>
- public void Toggle()
- {
- Toggle(true);
- }
-
- /// <summary>
- /// Toggles the console, opening it if it's closed/closing or closing it if it's open/opening.
- /// </summary>
- /// <param name="animated">Whether or not to animate the console.</param>
- public void Toggle(bool animated)
- {
- if (CurrentState == ConsoleState.Closed || CurrentState == ConsoleState.Closing)
- Open(animated);
- else if (CurrentState == ConsoleState.Open || CurrentState == ConsoleState.Opening)
- Close(animated);
- }
-
- public static void CreateConsole()
- {
- if (Instance == null)
- {
- Instance = new XConsole();
- }
-
- Instance.Initialize();
- }
-
- /// <summary>
- /// Allows a way to invoke commands programmatically.
- /// </summary>
- /// <remarks>
- /// Make sure you quote arguments with spaces, just like you have to manually.
- /// </remarks>
- /// <param name="command">The full command to invoke including arguments.</param>
- public void InvokeCommand(string input)
- {
- // make sure we didn't have a blank line
- if (string.IsNullOrEmpty(input.Trim()))
- return;
-
- // parse the input to find the command and arguments
- string command;
- List<string> arguments;
- ParseInput(input, out command, out arguments);
-
- // write out the prompt and text
- WriteLine(string.Format("{0}{1}", prompt, input));
-
- // record the input in the history
- inputHistory.Add(input);
- inputHistoryPosition = inputHistory.Count;
-
- // get the action for the command
- CommandInfo info;
- if (commands.TryGetValue(command, out info))
- {
- // invoke the action with the arguments
- try { info.Action(command, arguments); }
- catch (Exception e) { WriteError(string.Format("'{0}' threw an exception: {1}", command, e.Message)); }
- }
- else
- {
- // if there is no registered command, write an error
- WriteError(string.Format("'{0}' is not a recognized command.", command));
- }
-
- // jump to the end of the history so we can see this output
- outputShift = 0;
- }
-
- /// <summary>
- /// Registers a new console command with help text and a tab completion provider.
- /// </summary>
- /// <param name="command">The name of the command.</param>
- /// <param name="helpText">Text to display for this command when used as an argument to the 'help' command.</param>
- /// <param name="action">The delegate to invoke for the command.</param>
- public void RegisterCommand(string command, string helpText, CommandActionDelegate action)
- {
- RegisterCommand(command, helpText, action, null);
- }
-
- /// <summary>
- /// Registers a new console command with help text and a tab completion provider.
- /// </summary>
- /// <param name="command">The name of the command.</param>
- /// <param name="helpText">Text to display for this command when used as an argument to the 'help' command.</param>
- /// <param name="action">The delegate to invoke for the command.</param>
- /// <param name="tabCompletionProvider">The tab completion provider to use for the command arguments. Pass null to disable tab completion of arguments.</param>
- public void RegisterCommand(string command, string helpText, CommandActionDelegate action, ISuggestionProvider tabCompletionProvider)
- {
- if (string.IsNullOrEmpty(command)) throw new ArgumentNullException("command");
- if (action == null) throw new ArgumentNullException("action");
- if (string.IsNullOrEmpty(helpText)) throw new ArgumentNullException("helpText");
-
- commands.Add(command, new CommandInfo(command, action, helpText, tabCompletionProvider));
- }
-
- /// <summary>
- /// Unregisters a console command.
- /// </summary>
- /// <param name="command">The name of the command.</param>
- public void UnregisterCommand(string command)
- {
- commands.Remove(command);
- }
-
- /// <summary>
- /// Writes a line of text to the console.
- /// </summary>
- /// <param name="text">The text to write.</param>
- /// <param name="color">The color in which to display the text.</param>
- public void WriteLine(string text, Color color)
- {
- if (text.Contains('\n'))
- {
- // If the text already has line breaks, split by line and invoke WriteLine for each line individually
- string[] lines = text.Split('\n');
- foreach (var l in lines)
- WriteLine(l, color);
- }
- else
- {
- // First we want to do some word wrapping on the text
- List<string> lines = new List<string>();
-
- // Split the text into words
- string[] words = text.Split(' ');
-
- // Now go through the words trying to build up lines until we run out of horizontal space
- StringBuilder line = new StringBuilder();
- int lineSize = 0;
- int maxSize = FrameworkServices.GraphicsDevice.Viewport.Width;
- for (int i = 0; i < words.Length; i++)
- {
- // Get the word
- string word = words[i];
-
- // Empty words are really spaces that got trimmed by the Split method
- if (word == string.Empty)
- word = " ";
- // If this isn't the first word on a line, add a space before it
- else if (line.Length > 0)
- word = " " + word;
-
- // Measure the size of the word
- int wordSize = (int)DebugContent.ConsoleFont.MeasureString(word).X;
-
- // If the line is too long with this word, write out the line
- if (lineSize + wordSize >= maxSize && line.Length > 0)
- {
- lines.Add(line.ToString());
- line.Clear();
- lineSize = 0;
-
- // Remove the space from the word and remeasure it
- word = words[i];
- wordSize = (int)DebugContent.ConsoleFont.MeasureString(word).X;
- }
-
- // Add the word to the line
- line.Append(word);
- lineSize += wordSize;
- }
-
- // Make sure to add the last line if anything is left in our builder
- if (line.Length > 0)
- lines.Add(line.ToString());
-
- // Now we can write out each line into our output
- foreach (var l in lines)
- output.Add(new OutputLine { Text = l, Color = color });
- }
- }
-
- /// <summary>
- /// Writes a line of text to the console.
- /// </summary>
- /// <param name="text">The text to write.</param>
- public void WriteLine(string text)
- {
- WriteLine(text, Color.White);
- }
-
- /// <summary>
- /// Writes a warning to the console.
- /// </summary>
- /// <param name="text">The warning text.</param>
- public void WriteWarning(string text)
- {
- WriteLine(string.Format("WARNING: {0}", text), Color.Orange);
- }
-
- /// <summary>
- /// Writes an error to the console.
- /// </summary>
- /// <param name="text">The error text.</param>
- public void WriteError(string text)
- {
- WriteLine(string.Format("ERROR: {0}", text), Color.Red);
- }
-
- /// <summary>
- /// Prints the help text of a command.
- /// </summary>
- /// <param name="command">The command name for which help should be displayed.</param>
- public void WriteCommandHelp(string command)
- {
- // Try to find our command info
- CommandInfo info;
- if (!commands.TryGetValue(command, out info))
- {
- // Write an error if the command isn't registered
- WriteError(string.Format("'{0}' is not a recognized command.", command));
- }
- else
- {
- // Write out the help text in gold
- WriteLine(info.HelpText, Color.Gold);
- }
-
- // jump to the end of the history so we can see this output
- outputShift = 0;
- }
-
- // We explicitly implement the three methods because while they are important, user code shouldn't
- // ever need to call them. By making them explicit, they won't show up in Intellisense when using
- // XConsole unless you explicitly cast it to one of the interfaces.
-
- public void Initialize()
- {
- // set these values to themselves as properties as they generate other values using the font size
- Height = heightInLines;
- AnimationLength = animationTime;
-
- // measure some strings
- promptWidth = (int)DebugContent.ConsoleFont.MeasureString(prompt).X;
- cursorSize = (int)DebugContent.ConsoleFont.MeasureString(cursor).X;
-
- Instance.StringParser = new CustomStringParser();
- }
-
- public void Update()
- {
- // check for the toggle key to toggle our state
- if (InputManager.WasKeyPressed(ToggleKey))
- {
- Toggle();
- }
-
- UpdateConsoleState();
- }
-
- private void UpdateConsoleState()
- {
- // if we're closed, don't do anything
- if (CurrentState == ConsoleState.Closed)
- return;
-
- // animate our opening or closing
- if (CurrentState == ConsoleState.Opening)
- {
- currentHeight += (float)TimeManager.SecondDifference * heightSpeed;
- if (currentHeight >= Height * DebugContent.ConsoleFont.LineSpacing)
- {
- currentHeight = totalHeight;
- CurrentState = ConsoleState.Open;
- }
- }
- else if (CurrentState == ConsoleState.Closing)
- {
- currentHeight -= (float)TimeManager.SecondDifference * heightSpeed;
- if (currentHeight <= 0)
- {
- currentHeight = 0;
- CurrentState = ConsoleState.Closed;
- }
- }
-
- else
- {
- // apply the cursor blinking animation
- cursorBlinkTimer -= (float)TimeManager.SecondDifference;
- if (cursorBlinkTimer <= 0f)
- {
- cursorVisible = !cursorVisible;
- cursorBlinkTimer = cursorBlinkRate;
- }
-
- // handle user input
- HandleInput();
- }
- }
-
- public void Draw(SpriteBatch spriteBatch)
- {
- if (CurrentState == ConsoleState.Closed)
- return;
-
- // use a matrix for the translation animation so that the rest of our drawing code doesn't
- // have to concern itself with the math to put things in the right place
- spriteBatch.Begin(
- SpriteSortMode.Deferred, null, null, null, null, null,
- Matrix.CreateTranslation(0f, -totalHeight + currentHeight, 0f));
-
- // draw the background
- spriteBatch.Draw(DebugContent.FillTexture, new Rectangle(0, 0, FrameworkServices.GraphicsDevice.Viewport.Width, totalHeight), Color.Black * BackgroundOpacity);
-
- int promptY = totalHeight - DebugContent.ConsoleFont.LineSpacing - bottomPadding;
-
- // draw the prompt at the bottom
- spriteBatch.DrawString(DebugContent.ConsoleFont, prompt, new Vector2(0, promptY), Color.Lime);
-
- // draw the input string next to the prompt
- spriteBatch.DrawString(DebugContent.ConsoleFont, inputText, new Vector2(promptWidth, promptY), Color.Lime);
-
- // draw the cursor
- if (cursorVisible)
- {
- spriteBatch.DrawString(DebugContent.ConsoleFont, cursor, new Vector2(promptWidth + cursorSize * cursorPosition, promptY), Color.Lime);
- }
-
- // draw the log
- if (output.Count > 0)
- {
- int start = Math.Max(output.Count - heightInLines + 1 - outputShift, 0);
- int end = Math.Min(output.Count, start + heightInLines - 1);
-
- for (int i = start; i < end; i++)
- {
- OutputLine l = output[i];
- spriteBatch.DrawString(DebugContent.ConsoleFont, l.Text, new Vector2(0, (i - start) * DebugContent.ConsoleFont.LineSpacing), l.Color);
- }
- }
-
- spriteBatch.End();
- }
-
- private void HandleInput()
- {
- bool control = InputManager.IsKeyDown(Keys.LeftControl) || InputManager.IsKeyDown(Keys.RightControl);
- bool shift = InputManager.IsKeyDown(Keys.LeftShift) || InputManager.IsKeyDown(Keys.RightShift);
-
- // check for backspace which deletes the character behind the
- // cursor and moves the cursor back
- if (InputManager.WasKeyPressed(Keys.Back))
- {
- if (cursorPosition > 0)
- {
- inputText.Remove(cursorPosition - 1, 1);
- cursorPosition--;
- InvalidateTabCompletion();
- }
- }
- // check for delete which deletes the character in front of the cursor
- else if (InputManager.WasKeyPressed(Keys.Delete))
- {
- if (cursorPosition < inputText.Length)
- {
- inputText.Remove(cursorPosition, 1);
- InvalidateTabCompletion();
- }
- }
- // check for the left/right arrow keys which move the cursor
- else if (InputManager.WasKeyPressed(Keys.Left))
- {
- cursorPosition = Math.Max(cursorPosition - 1, 0);
- }
- else if (InputManager.WasKeyPressed(Keys.Right))
- {
- cursorPosition = Math.Min(cursorPosition + 1, inputText.Length);
- }
- // Ctrl+Up/Ctrl+Down scroll the output up and down
- else if (control && InputManager.WasKeyPressed(Keys.Up))
- {
- outputShift = Math.Min(outputShift + 1, output.Count - heightInLines + 1);
- }
- else if (control && InputManager.WasKeyPressed(Keys.Down))
- {
- outputShift = Math.Max(outputShift - 1, 0);
- }
- // Up/Down without Ctrl goes back through the input history
- else if (InputManager.WasKeyPressed(Keys.Up))
- {
- if (inputHistory.Count > 0)
- {
- inputHistoryPosition = Math.Max(inputHistoryPosition - 1, 0);
- inputText.Clear();
- inputText.Append(inputHistory[inputHistoryPosition]);
- cursorPosition = inputText.Length;
- InvalidateTabCompletion();
- }
- }
- else if (InputManager.WasKeyPressed(Keys.Down))
- {
- if (inputHistory.Count > 0)
- {
- inputHistoryPosition = Math.Min(inputHistoryPosition + 1, inputHistory.Count - 1);
- inputText.Clear();
- inputText.Append(inputHistory[inputHistoryPosition]);
- cursorPosition = inputText.Length;
- InvalidateTabCompletion();
- }
- }
- // Tab does suggestion completion for commands and arguments
- else if (InputManager.WasKeyPressed(Keys.Tab))
- {
- PerformTabCompletion();
- }
- // Esc clears the input
- else if (InputManager.WasKeyPressed(Keys.Escape))
- {
- inputText.Clear();
- cursorPosition = 0;
- InvalidateTabCompletion();
- }
- // Enter submits the input
- else if (InputManager.WasKeyPressed(Keys.Enter))
- {
- SubmitInput();
- }
- else
- {
- // If our pressed key is no longer down, reset it
- if (!InputManager.IsKeyDown(pressedKey))
- pressedKey = Keys.None;
-
- // get all the pressed keys so we can do some typing
- Keys[] keys = InputManager.PressedKeys;
-
- foreach (Keys key in keys)
- {
- // skip our toggle key
- if (key == ToggleKey) continue;
-
- // convert the key to a character with our delegate.
- // we also use the IsKeyPressed method to track our repeat delay if we have a key with a valid character.
- char ch;
- if (KeyToCharacter(key, shift, out ch) && InputManager.WasKeyPressed(key))
- {
- inputText = inputText.Insert(cursorPosition, ch);
- cursorPosition++;
- InvalidateTabCompletion();
- }
- }
- }
- }
-
- private void PerformTabCompletion()
- {
- // if tab completion is invalid
- if (tabCompletionSuggestions == null)
- {
- // parse the current input to get the command and the arguments
- string command;
- List<string> arguments;
- ParseInput(inputText.ToString(), out command, out arguments);
-
- if (arguments.Count == 0)
- {
- // if there are no arguments, we are doing tab completion using the command as our root
- tabCompletionSuggestions = commandSuggestionProvider.GetSuggestions(command, 0, command, arguments);
-
- // set our completion position to the start of the input since we'll replace the entire input string with our suggestions
- tabCompletePosition = 0;
- }
- else
- {
- // otherwise we're doing argument completion so we need to figure out if we can do that for the current command
- CommandInfo info;
- if (commands.TryGetValue(command, out info) && info.TabCompletionProvider != null)
- {
- // get the index of the argument and the argument string
- int argIndex = arguments.Count - 1;
- string currentArgument = arguments[argIndex];
-
- // remove the current argument so arguments is just the previous arguments
- arguments.RemoveAt(argIndex);
-
- // now use the command's tab completion provider to get the suggestions for the command based on the last argument
- tabCompletionSuggestions = info.TabCompletionProvider.GetSuggestions(command, argIndex, currentArgument, arguments);
-
- // we need to set our completion position to the start of this argument so that suggestions are properly inserted
- tabCompletePosition = inputText.ToString().LastIndexOf(currentArgument);
- }
- }
- }
-
- if (tabCompletionSuggestions != null && tabCompletionSuggestions.Count > 0)
- {
- // wrap the tab completion index
- if (tabCompleteIndex >= tabCompletionSuggestions.Count)
- tabCompleteIndex = 0;
-
- // find the suggestion at our index
- string suggestion = tabCompletionSuggestions[tabCompleteIndex];
-
- // clear the input text from our tab completion position forward
- inputText.Remove(tabCompletePosition, inputText.Length - tabCompletePosition);
-
- // append the suggestion to the input text
- inputText.Append(suggestion);
-
- // and now fix the cursor position
- cursorPosition = inputText.Length;
-
- // increment the index
- tabCompleteIndex++;
- }
- }
-
- private void InvalidateTabCompletion()
- {
- tabCompletePosition = 0;
- tabCompleteIndex = 0;
- tabCompletionSuggestions = null;
- }
-
- /// <summary>
- /// Parses a string for the command name (first "argument") and the rest of the arguments of the command.
- /// </summary>
- private void ParseInput(string input, out string command, out List<string> arguments)
- {
- arguments = new List<string>();
-
- // the first argument is the command
- GetNextArgument(input, 0, out command);
- if (command == null)
- return;
-
- // then we can parse out the rest of the arguments
- int start = command.Length;
- string argument;
- while ((start = GetNextArgument(input, start, out argument)) > 0)
- arguments.Add(argument);
- }
-
- private void SubmitInput()
- {
- // Simply send the input text to our InvokeCommand method
- InvokeCommand(inputText.ToString());
-
- // clear out our input and invalidate tab completion
- inputText.Clear();
- cursorPosition = 0;
- InvalidateTabCompletion();
- }
-
- /// <summary>
- /// Helper for parsing input strings to find arguments separated by spaces while respecting quotes.
- /// </summary>
- private int GetNextArgument(string input, int start, out string argument)
- {
- // make sure we are in the bounds of the input
- if (start >= input.Length)
- {
- argument = null;
- return -1;
- }
-
- // move forward to the first non-space character
- while (start < input.Length && input[start] == ' ')
- start++;
-
- // if we're at the end, return null
- if (start == input.Length)
- {
- argument = null;
- return -1;
- }
-
- // move forward until we're done with this argument (we've found a space and
- // are outside of a pair of quotes)
- bool inQuotes = false;
- int end;
- for (end = start; end < input.Length; end++)
- {
- if (input[end] == ' ' && !inQuotes)
- break;
- if (input[end] == '"' && (end - 1 < 0 || input[end - 1] != '\\'))
- inQuotes = !inQuotes;
- }
-
- // return the substring, without quotes, trimmed
- argument = input.Substring(start, end - start).Replace("\"", "");
- return end;
- }
-
- private void RegisterDefaultCommands()
- {
- StringBuilder helpTextBuilder = new StringBuilder();
-
- // suggestion provider that will handle our get, set, and invoke commands to help with autocompletion of member names
- SelectionMemberSuggestionProvider memberSuggestionProvider = new SelectionMemberSuggestionProvider();
-
- // Register our 'commands' command to list all registered commands
- helpTextBuilder.Append("Lists all commands currently registered with the console.");
- RegisterCommand("commands", helpTextBuilder.ToString(), CommandsCommandAction);
-
- // Register 'help' to display help text for commands
- helpTextBuilder.Clear();
- helpTextBuilder.Append("Displays help information for registered commands.");
- RegisterCommand("help", helpTextBuilder.ToString(), HelpCommandAction, commandSuggestionProvider);
-
- // Register 'info' to display all public fields, properties, and methods on the selected objects
- helpTextBuilder.Clear();
- helpTextBuilder.Append("Displays public fields, properties, and methods that exist on the selected object(s).");
- RegisterCommand("info", helpTextBuilder.ToString(), InfoCommandAction, null);
-
- // Register 'get' for retrieving values from the selected objects
- helpTextBuilder.Clear();
- helpTextBuilder.AppendLine("Gets the value of a field or property on the selected object(s).");
- helpTextBuilder.AppendLine("Usage: get [propertyOrFieldName]");
- helpTextBuilder.Append("propertyOrFieldName The name of a field or property to get from the selected object(s).");
- RegisterCommand("get", helpTextBuilder.ToString(), GetCommandAction, memberSuggestionProvider);
-
- // Register 'set' for setting values on the selected objects
- helpTextBuilder.Clear();
- helpTextBuilder.AppendLine("Sets the value of a field or property on the selected object(s).");
- helpTextBuilder.AppendLine("Usage: set [propertyOrFieldName] [newPropertyValue]");
- helpTextBuilder.AppendLine("propertyOrFieldName The name of a field or property to set on the selected object(s).");
- helpTextBuilder.Append("newPropertyValue A string representation of the new value to assign to the field or property.");
- RegisterCommand("set", helpTextBuilder.ToString(), SetCommandAction, memberSuggestionProvider);
-
- // Register 'invoke' for calling methods on the selected objects
- helpTextBuilder.Clear();
- helpTextBuilder.AppendLine("Invokes a method on the selected object(s).");
- helpTextBuilder.AppendLine("Usage: invoke [methodName] (arg1 arg2 arg3 etc)");
- helpTextBuilder.AppendLine("methodName The name of a method to invoke on the selected object(s).");
- helpTextBuilder.Append("arg1 etc (Optional) The arguments to parse and pass to the method.");
- RegisterCommand("invoke", helpTextBuilder.ToString(), InvokeCommandAction, memberSuggestionProvider);
- }
-
- private void CommandsCommandAction(string command, IList<string> args)
- {
- StringBuilder temp = new StringBuilder();
- foreach (var c in commands.Keys)
- temp.AppendFormat("{0}, ", c);
- temp.Remove(temp.Length - 2, 2);
- WriteLine(temp.ToString());
- }
-
- private void HelpCommandAction(string command, IList<string> args)
- {
- // Validate arguments
- if (args.Count == 0)
- {
- WriteError("help requires at least one argument.");
- WriteCommandHelp(command);
- return;
- }
-
- // Write out the help for the command
- foreach (var a in args)
- {
- // If we have multiple commands, write the command as well for clarity
- if (args.Count > 1)
- WriteLine(string.Format("Command: {0}", a), Color.Gold);
- WriteCommandHelp(a);
- }
- }
-
- private void InfoCommandAction(string command, IList<string> args)
- {
- // Make sure we have a selection
- if (selectedObjects.Count == 0)
- {
- WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'info'.");
- return;
- }
-
- // get all the base types of the selected objects
- var baseTypes = FindCommonBaseTypesOfSelection();
-
- // First write out all fields and properties
- WriteLine("Fields/Properties:");
- foreach (var t in baseTypes)
- {
- foreach (var f in t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
- WriteLine(string.Format(" {0} {1}", f.FieldType.Name, f.Name));
- foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
- {
- // Build up the accessors string for the property
- StringBuilder accessors = new StringBuilder();
-
- // Check each accessor and append them to our string
- if (p.GetGetMethod() != null && p.GetGetMethod().IsPublic)
- accessors.Append(" get;");
- if (p.GetSetMethod() != null && p.GetSetMethod().IsPublic)
- accessors.Append(" set;");
-
- // Write out the property type, name, and accessors
- WriteLine(string.Format(" {0} {1} {{{2} }}", p.PropertyType.Name, p.Name, accessors));
- }
- }
-
- // Then write out all methods
- WriteLine("Methods:");
- foreach (var t in baseTypes)
- {
- foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
- {
- // Special name methods are property accessors so skip those
- if (m.IsSpecialName) continue;
-
- // Build up the argument list for this method
- StringBuilder argumentList = new StringBuilder("(");
- foreach (var p in m.GetParameters())
- argumentList.AppendFormat("{0}, ", p.ParameterType.Name);
- if (argumentList.Length > 1)
- argumentList.Remove(argumentList.Length - 2, 2);
- argumentList.Append(")");
-
- // Output the method with the return type and argument list
- WriteLine(string.Format(" {0} {1}{2}", m.ReturnType.Name, m.Name, argumentList));
- }
- }
- }
-
- private void GetCommandAction(string command, IList<string> args)
- {
- // Basic validation of arguments
- if (args.Count != 1)
- {
- // Write an error and display the help for the command
- WriteError("get requires a single argument");
- WriteCommandHelp(command);
- return;
- }
-
- // Make sure we have a selection
- if (selectedObjects.Count == 0)
- {
- WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'get'.");
- return;
- }
-
- // Get the value for each selected object
- foreach (var o in selectedObjects)
- {
- // Try to find a field or property with the given name
- FieldInfo f = o.GetType().GetField(args[0]);
- PropertyInfo p = o.GetType().GetProperty(args[0]);
-
- // Error if neither are found
- if (f == null && p == null)
- {
- WriteError(string.Format("{0} doesn't have a field or property called {1}.", o, args[0]));
- }
- else
- {
- // Get the value of the field or property from the object and write out the value
- try
- {
- object value = f != null ? f.GetValue(o) : p.GetValue(o, null);
- WriteLine(string.Format("{0}.{1} = {2}", o, args[0], value == null ? "(null)" : StringParser.ToString(value)));
- }
- catch (Exception e)
- {
- WriteError(string.Format("Failed to get {0}.{1}: {2}", o, args[0], e.Message));
- }
- }
- }
- }
-
- private void SetCommandAction(string command, IList<string> args)
- {
- // Basic validation of arguments
- if (args.Count != 2)
- {
- WriteError("set requires two arguments");
- WriteCommandHelp(command);
- return;
- }
-
- // Make sure we have a selection
- if (selectedObjects.Count == 0)
- {
- WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'set'.");
- return;
- }
-
- // Validate the string parsing delegate
- if (StringParser == null)
- {
- WriteError("No string parsing delegate was given to XConsole; no way to parse argument types.");
- return;
- }
-
- // Set the value for each selected object
- foreach (var o in selectedObjects)
- {
- // Try to find a field or property with the given name
- FieldInfo f = o.GetType().GetField(args[0]);
- PropertyInfo p = o.GetType().GetProperty(args[0]);
-
- // Error if neither are found
- if (f == null && p == null)
- {
- WriteError(string.Format("{0} doesn't have a field or property called {1}.", o, args[0]));
- }
- else
- {
- // Try to parse the value argument using our delegate
- Type valueType = f != null ? f.FieldType : p.PropertyType;
- object value;
- try { value = StringParser.Parse(valueType, args[1]); }
- catch (Exception e)
- {
- WriteError(string.Format("Failed to parse '{0}' as {1}: {2}", args[1], valueType, e.Message));
- continue;
- }
-
- // Now set the value on the field or property
- try
- {
- if (f != null)
- f.SetValue(o, value);
- else
- p.SetValue(o, value, null);
- }
- catch (Exception e)
- {
- WriteError(string.Format("Failed to set {0}.{1}: {2}", o, args[0], e.Message));
- continue;
- }
-
- // And log the new value
- WriteLine(string.Format("{0}.{1} = {2}", o, args[0], value == null ? "(null)" : StringParser.ToString(value)));
- }
- }
- }
-
- private void InvokeCommandAction(string command, IList<string> args)
- {
- // Basic validation of arguments
- if (args.Count < 1)
- {
- WriteError("invoke requires at least one argument");
- WriteCommandHelp(command);
- return;
- }
-
- // Make sure we have a selection
- if (selectedObjects.Count == 0)
- {
- WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'invoke'.");
- return;
- }
-
- // Validate the string parsing delegate
- if (StringParser == null)
- {
- WriteError("No string parsing delegate was given to XConsole; no way to parse argument types.");
- return;
- }
-
- // Invoke the method for each selected object
- foreach (var o in selectedObjects)
- {
- // Try to get the method
- MethodInfo method = o.GetType().GetMethod(args[0]);
-
- int currentIndex = selectedObjects.IndexOf(o);
-
- // Error if no method was found
- if (method == null)
- {
- if (currentIndex == selectedObjects.Count - 1)
- {
- WriteError(string.Format("No selected objects have a method called {1}.", o, args[0]));
- }
- else
- {
- continue;
- }
- }
- // Error if we have a mismatch of parameter counts (make sure to -1 on a.Count since a[0] is the method name)
- else if (method.GetParameters().Length != args.Count - 1)
- {
- WriteError(string.Format("{0}.{1} requires {2} arguments, {3} were given to 'invoke'.", o, args[0], method.GetParameters().Length, args.Count - 1));
- }
- else
- {
- // Parse out the parameters for the method
- ParameterInfo[] methodParams = method.GetParameters();
- object[] methodArguments = new object[methodParams.Length];
- try
- {
- for (int i = 0; i < methodParams.Length; i++)
- methodArguments[i] = StringParser.Parse(methodParams[i].ParameterType, args[i + 1]);
- }
- catch (Exception e)
- {
- WriteError(string.Format("Error while parsing arguments: {0}", e.Message));
- return;
- }
-
- // Invoke the method on the object
- object output = method.Invoke(o, methodArguments);
-
- // If the method isn't a void return type, write out the result
- if (method.ReturnType != typeof(void))
- WriteLine(string.Format("Result: {0}", output == null ? "(null)" : StringParser.ToString(output)));
-
- return;
- }
- }
- }
-
- // Finds all of the common base classes and interfaces of the selection
- private IEnumerable<Type> FindCommonBaseTypesOfSelection()
- {
- List<Type> baseTypes = new List<Type>();
-
- foreach (var o in selectedObjects)
- {
- Type t = o.GetType();
-
- // add in all the interfaces from this type
- foreach (var i in t.GetInterfaces())
- {
- if (!baseTypes.Contains(i))
- baseTypes.Add(i);
- }
-
- // add in all base classes from this type
- while (t != null)
- {
- if (!baseTypes.Contains(t))
- baseTypes.Add(t);
- t = t.BaseType;
- }
- }
-
- // now we can return the types from this collection that are assignable from
- // all of the selected object types
- return baseTypes.Where(t =>
- {
- foreach (var o in selectedObjects)
- if (!t.IsAssignableFrom(o.GetType()))
- return false;
- return true;
- }).ToList();
- }
-
- #region Public Support Types
-
- /// <summary>
- /// Defines the possible states of the XConsole.
- /// </summary>
- public enum ConsoleState
- {
- /// <summary>
- /// The XConsole is completely closed.
- /// </summary>
- Closed,
-
- /// <summary>
- /// The XConsole is animating to the Open state.
- /// </summary>
- Opening,
-
- /// <summary>
- /// The XConsole is completely open.
- /// </summary>
- Open,
-
- /// <summary>
- /// The XConsole is animating to the Closed state.
- /// </summary>
- Closing
- }
-
- /// <summary>
- /// The delegate takes in a key as well as whether or not the shift key is down and
- /// uses that to return a character which is then added to the current input string.
- /// The function needs to return true if there is a valid character for the key or
- /// false if the input should be ignored.
- /// </summary>
- /// <param name="key">The key that …
Large files files are truncated, but you can click here to view the full file