PageRenderTime 56ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/XNAComponentFramework/Debugging/XConsole.cs

https://bitbucket.org/AcyclicMonk/xna-component-based-entity-system
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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using Microsoft.Xna.Framework;
  8. using Microsoft.Xna.Framework.Content;
  9. using Microsoft.Xna.Framework.Graphics;
  10. using Microsoft.Xna.Framework.Input;
  11. using ComponentFramework;
  12. namespace ComponentFramework.Debugging
  13. {
  14. /// <summary>
  15. /// A singleton game component that provides a debug console with the ability to both
  16. /// write text and register new commands.
  17. /// </summary>
  18. public class XConsole
  19. {
  20. /// <summary>
  21. /// Gets the singleton instance of XConsole; only valid after the constructor is called.
  22. /// </summary>
  23. public static XConsole Instance { get; private set; }
  24. // information about the height of the console
  25. private float currentHeight = 0;
  26. private int heightInLines = 15;
  27. private int totalHeight;
  28. // extra padding at the bottom of the console
  29. private const int bottomPadding = 2;
  30. // data about animation
  31. private float animationTime = .25f;
  32. private float heightSpeed;
  33. // the prompt information
  34. private const string prompt = "> ";
  35. private int promptWidth;
  36. // cursor information
  37. private const string cursor = "_";
  38. private int cursorSize;
  39. private int cursorPosition = 0;
  40. private float cursorBlinkTimer = 0f;
  41. private const float cursorBlinkRate = .5f;
  42. private bool cursorVisible = true;
  43. // key repeat information
  44. private Keys pressedKey;
  45. private const float keyRepeatDuration = .05f;
  46. // the list of lines for the console output
  47. private List<OutputLine> output = new List<OutputLine>();
  48. // an offset (in lines) to shift for drawing update when using ctrl+up/down to scroll the output
  49. private int outputShift = 0;
  50. // the current input text
  51. private StringBuilder inputText = new StringBuilder();
  52. // the history of console input
  53. private List<string> inputHistory = new List<string>();
  54. // current position in the inputHistory list when using the up/down arrows
  55. private int inputHistoryPosition = 0;
  56. // the location in the inputText where the tab completion should be inserted
  57. private int tabCompletePosition;
  58. // a list of suggestions for tab completion
  59. private IList<string> tabCompletionSuggestions;
  60. // the index into our suggestions list for the current suggestion
  61. private int tabCompleteIndex;
  62. // a suggestion provider for command names, used by our initial tab behavior for commands as well as for
  63. // suggesting command names for the 'help' command
  64. private readonly CommandSuggestionProvider commandSuggestionProvider = new CommandSuggestionProvider();
  65. // the list of selected objects for use with the 'info', 'get', 'set', and 'invoke' commands
  66. private List<object> selectedObjects = new List<object>();
  67. // all of the currently registered commands
  68. private Dictionary<string, CommandInfo> commands = new Dictionary<string, CommandInfo>();
  69. /// <summary>
  70. /// Gets the current state of the console.
  71. /// </summary>
  72. public ConsoleState CurrentState { get; private set; }
  73. /// <summary>
  74. /// Gets or sets the key to use to toggle the console.
  75. /// </summary>
  76. public Keys ToggleKey { get; set; }
  77. /// <summary>
  78. /// Gets or sets the height of the console in lines.
  79. /// </summary>
  80. public int Height
  81. {
  82. get { return heightInLines; }
  83. set
  84. {
  85. heightInLines = value;
  86. if (DebugContent.ConsoleFont != null)
  87. totalHeight = value * DebugContent.ConsoleFont.LineSpacing + bottomPadding;
  88. }
  89. }
  90. /// <summary>
  91. /// Gets or sets the length of time it should take the console to go from fully closed to fully open.
  92. /// </summary>
  93. public float AnimationLength
  94. {
  95. get { return animationTime; }
  96. set
  97. {
  98. animationTime = value;
  99. if (totalHeight > 0)
  100. heightSpeed = totalHeight / animationTime;
  101. }
  102. }
  103. /// <summary>
  104. /// Gets or sets the opacity of the background of the console.
  105. /// </summary>
  106. public float BackgroundOpacity { get; set; }
  107. /// <summary>
  108. /// Gets or sets the delegate that converts a key into a character.
  109. /// </summary>
  110. /// <remarks>
  111. /// The default is generally "good enough" for standard US keyboards, but if you have
  112. /// a non-standard US keyboard or a non-US keyboard you'll likely want to replace this
  113. /// with a function that more properly maps the XNA Framework Keys values to characters
  114. /// for your particular language.
  115. /// </remarks>
  116. public KeyToCharacterDelegate KeyToCharacter { get; set; }
  117. /// <summary>
  118. /// Gets or sets an object to handle parsing strings for the "set" and "invoke" commands.
  119. /// </summary>
  120. public IStringParser StringParser { get; set; }
  121. /// <summary>
  122. /// Gets the list of selected objects.
  123. /// </summary>
  124. public List<object> SelectedObjects { get { return selectedObjects; } }
  125. public static void AddObject(object aObjectToAdd)
  126. {
  127. Instance.selectedObjects.Add(aObjectToAdd);
  128. }
  129. /// <summary>
  130. /// Initializes a new XConsole.
  131. /// </summary>
  132. /// <param name="services">A service provider where the console can find the IKeyboardInputService and IGraphicsDeviceService.</param>
  133. /// <param name="fontName">The name of a font to use for the console. Must be relative path from executable.</param>
  134. public XConsole()
  135. {
  136. // only allow one instance at a time
  137. if (Instance != null)
  138. throw new InvalidOperationException("Only one XConsole can exist. Use XConsole.Instance to access it.");
  139. Instance = this;
  140. // initialize some state
  141. CurrentState = ConsoleState.Closed;
  142. ToggleKey = Keys.OemTilde;
  143. KeyToCharacter = KeyString.KeyToString;
  144. StringParser = new DefaultStringParser();
  145. BackgroundOpacity = .6f;
  146. // register all the default console commands
  147. RegisterDefaultCommands();
  148. }
  149. /// <summary>
  150. /// Opens the console.
  151. /// </summary>
  152. public void Open()
  153. {
  154. Open(true);
  155. }
  156. /// <summary>
  157. /// Opens the console.
  158. /// </summary>
  159. /// <param name="animated">Whether or not to animate the console opening.</param>
  160. public void Open(bool animated)
  161. {
  162. if (animated)
  163. {
  164. if (CurrentState != ConsoleState.Open)
  165. CurrentState = ConsoleState.Opening;
  166. }
  167. else
  168. {
  169. currentHeight = totalHeight;
  170. CurrentState = ConsoleState.Open;
  171. }
  172. }
  173. /// <summary>
  174. /// Closes the console.
  175. /// </summary>
  176. public void Close()
  177. {
  178. Close(true);
  179. }
  180. /// <summary>
  181. /// Closes the console.
  182. /// </summary>
  183. /// <param name="animated">Whether or not to animate the console closing.</param>
  184. public void Close(bool animated)
  185. {
  186. if (animated)
  187. {
  188. if (CurrentState != ConsoleState.Closed)
  189. CurrentState = ConsoleState.Closing;
  190. }
  191. else
  192. {
  193. currentHeight = 0;
  194. CurrentState = ConsoleState.Closed;
  195. }
  196. }
  197. /// <summary>
  198. /// Toggles the console, opening it if it's closed/closing or closing it if it's open/opening.
  199. /// </summary>
  200. public void Toggle()
  201. {
  202. Toggle(true);
  203. }
  204. /// <summary>
  205. /// Toggles the console, opening it if it's closed/closing or closing it if it's open/opening.
  206. /// </summary>
  207. /// <param name="animated">Whether or not to animate the console.</param>
  208. public void Toggle(bool animated)
  209. {
  210. if (CurrentState == ConsoleState.Closed || CurrentState == ConsoleState.Closing)
  211. Open(animated);
  212. else if (CurrentState == ConsoleState.Open || CurrentState == ConsoleState.Opening)
  213. Close(animated);
  214. }
  215. public static void CreateConsole()
  216. {
  217. if (Instance == null)
  218. {
  219. Instance = new XConsole();
  220. }
  221. Instance.Initialize();
  222. }
  223. /// <summary>
  224. /// Allows a way to invoke commands programmatically.
  225. /// </summary>
  226. /// <remarks>
  227. /// Make sure you quote arguments with spaces, just like you have to manually.
  228. /// </remarks>
  229. /// <param name="command">The full command to invoke including arguments.</param>
  230. public void InvokeCommand(string input)
  231. {
  232. // make sure we didn't have a blank line
  233. if (string.IsNullOrEmpty(input.Trim()))
  234. return;
  235. // parse the input to find the command and arguments
  236. string command;
  237. List<string> arguments;
  238. ParseInput(input, out command, out arguments);
  239. // write out the prompt and text
  240. WriteLine(string.Format("{0}{1}", prompt, input));
  241. // record the input in the history
  242. inputHistory.Add(input);
  243. inputHistoryPosition = inputHistory.Count;
  244. // get the action for the command
  245. CommandInfo info;
  246. if (commands.TryGetValue(command, out info))
  247. {
  248. // invoke the action with the arguments
  249. try { info.Action(command, arguments); }
  250. catch (Exception e) { WriteError(string.Format("'{0}' threw an exception: {1}", command, e.Message)); }
  251. }
  252. else
  253. {
  254. // if there is no registered command, write an error
  255. WriteError(string.Format("'{0}' is not a recognized command.", command));
  256. }
  257. // jump to the end of the history so we can see this output
  258. outputShift = 0;
  259. }
  260. /// <summary>
  261. /// Registers a new console command with help text and a tab completion provider.
  262. /// </summary>
  263. /// <param name="command">The name of the command.</param>
  264. /// <param name="helpText">Text to display for this command when used as an argument to the 'help' command.</param>
  265. /// <param name="action">The delegate to invoke for the command.</param>
  266. public void RegisterCommand(string command, string helpText, CommandActionDelegate action)
  267. {
  268. RegisterCommand(command, helpText, action, null);
  269. }
  270. /// <summary>
  271. /// Registers a new console command with help text and a tab completion provider.
  272. /// </summary>
  273. /// <param name="command">The name of the command.</param>
  274. /// <param name="helpText">Text to display for this command when used as an argument to the 'help' command.</param>
  275. /// <param name="action">The delegate to invoke for the command.</param>
  276. /// <param name="tabCompletionProvider">The tab completion provider to use for the command arguments. Pass null to disable tab completion of arguments.</param>
  277. public void RegisterCommand(string command, string helpText, CommandActionDelegate action, ISuggestionProvider tabCompletionProvider)
  278. {
  279. if (string.IsNullOrEmpty(command)) throw new ArgumentNullException("command");
  280. if (action == null) throw new ArgumentNullException("action");
  281. if (string.IsNullOrEmpty(helpText)) throw new ArgumentNullException("helpText");
  282. commands.Add(command, new CommandInfo(command, action, helpText, tabCompletionProvider));
  283. }
  284. /// <summary>
  285. /// Unregisters a console command.
  286. /// </summary>
  287. /// <param name="command">The name of the command.</param>
  288. public void UnregisterCommand(string command)
  289. {
  290. commands.Remove(command);
  291. }
  292. /// <summary>
  293. /// Writes a line of text to the console.
  294. /// </summary>
  295. /// <param name="text">The text to write.</param>
  296. /// <param name="color">The color in which to display the text.</param>
  297. public void WriteLine(string text, Color color)
  298. {
  299. if (text.Contains('\n'))
  300. {
  301. // If the text already has line breaks, split by line and invoke WriteLine for each line individually
  302. string[] lines = text.Split('\n');
  303. foreach (var l in lines)
  304. WriteLine(l, color);
  305. }
  306. else
  307. {
  308. // First we want to do some word wrapping on the text
  309. List<string> lines = new List<string>();
  310. // Split the text into words
  311. string[] words = text.Split(' ');
  312. // Now go through the words trying to build up lines until we run out of horizontal space
  313. StringBuilder line = new StringBuilder();
  314. int lineSize = 0;
  315. int maxSize = FrameworkServices.GraphicsDevice.Viewport.Width;
  316. for (int i = 0; i < words.Length; i++)
  317. {
  318. // Get the word
  319. string word = words[i];
  320. // Empty words are really spaces that got trimmed by the Split method
  321. if (word == string.Empty)
  322. word = " ";
  323. // If this isn't the first word on a line, add a space before it
  324. else if (line.Length > 0)
  325. word = " " + word;
  326. // Measure the size of the word
  327. int wordSize = (int)DebugContent.ConsoleFont.MeasureString(word).X;
  328. // If the line is too long with this word, write out the line
  329. if (lineSize + wordSize >= maxSize && line.Length > 0)
  330. {
  331. lines.Add(line.ToString());
  332. line.Clear();
  333. lineSize = 0;
  334. // Remove the space from the word and remeasure it
  335. word = words[i];
  336. wordSize = (int)DebugContent.ConsoleFont.MeasureString(word).X;
  337. }
  338. // Add the word to the line
  339. line.Append(word);
  340. lineSize += wordSize;
  341. }
  342. // Make sure to add the last line if anything is left in our builder
  343. if (line.Length > 0)
  344. lines.Add(line.ToString());
  345. // Now we can write out each line into our output
  346. foreach (var l in lines)
  347. output.Add(new OutputLine { Text = l, Color = color });
  348. }
  349. }
  350. /// <summary>
  351. /// Writes a line of text to the console.
  352. /// </summary>
  353. /// <param name="text">The text to write.</param>
  354. public void WriteLine(string text)
  355. {
  356. WriteLine(text, Color.White);
  357. }
  358. /// <summary>
  359. /// Writes a warning to the console.
  360. /// </summary>
  361. /// <param name="text">The warning text.</param>
  362. public void WriteWarning(string text)
  363. {
  364. WriteLine(string.Format("WARNING: {0}", text), Color.Orange);
  365. }
  366. /// <summary>
  367. /// Writes an error to the console.
  368. /// </summary>
  369. /// <param name="text">The error text.</param>
  370. public void WriteError(string text)
  371. {
  372. WriteLine(string.Format("ERROR: {0}", text), Color.Red);
  373. }
  374. /// <summary>
  375. /// Prints the help text of a command.
  376. /// </summary>
  377. /// <param name="command">The command name for which help should be displayed.</param>
  378. public void WriteCommandHelp(string command)
  379. {
  380. // Try to find our command info
  381. CommandInfo info;
  382. if (!commands.TryGetValue(command, out info))
  383. {
  384. // Write an error if the command isn't registered
  385. WriteError(string.Format("'{0}' is not a recognized command.", command));
  386. }
  387. else
  388. {
  389. // Write out the help text in gold
  390. WriteLine(info.HelpText, Color.Gold);
  391. }
  392. // jump to the end of the history so we can see this output
  393. outputShift = 0;
  394. }
  395. // We explicitly implement the three methods because while they are important, user code shouldn't
  396. // ever need to call them. By making them explicit, they won't show up in Intellisense when using
  397. // XConsole unless you explicitly cast it to one of the interfaces.
  398. public void Initialize()
  399. {
  400. // set these values to themselves as properties as they generate other values using the font size
  401. Height = heightInLines;
  402. AnimationLength = animationTime;
  403. // measure some strings
  404. promptWidth = (int)DebugContent.ConsoleFont.MeasureString(prompt).X;
  405. cursorSize = (int)DebugContent.ConsoleFont.MeasureString(cursor).X;
  406. Instance.StringParser = new CustomStringParser();
  407. }
  408. public void Update()
  409. {
  410. // check for the toggle key to toggle our state
  411. if (InputManager.WasKeyPressed(ToggleKey))
  412. {
  413. Toggle();
  414. }
  415. UpdateConsoleState();
  416. }
  417. private void UpdateConsoleState()
  418. {
  419. // if we're closed, don't do anything
  420. if (CurrentState == ConsoleState.Closed)
  421. return;
  422. // animate our opening or closing
  423. if (CurrentState == ConsoleState.Opening)
  424. {
  425. currentHeight += (float)TimeManager.SecondDifference * heightSpeed;
  426. if (currentHeight >= Height * DebugContent.ConsoleFont.LineSpacing)
  427. {
  428. currentHeight = totalHeight;
  429. CurrentState = ConsoleState.Open;
  430. }
  431. }
  432. else if (CurrentState == ConsoleState.Closing)
  433. {
  434. currentHeight -= (float)TimeManager.SecondDifference * heightSpeed;
  435. if (currentHeight <= 0)
  436. {
  437. currentHeight = 0;
  438. CurrentState = ConsoleState.Closed;
  439. }
  440. }
  441. else
  442. {
  443. // apply the cursor blinking animation
  444. cursorBlinkTimer -= (float)TimeManager.SecondDifference;
  445. if (cursorBlinkTimer <= 0f)
  446. {
  447. cursorVisible = !cursorVisible;
  448. cursorBlinkTimer = cursorBlinkRate;
  449. }
  450. // handle user input
  451. HandleInput();
  452. }
  453. }
  454. public void Draw(SpriteBatch spriteBatch)
  455. {
  456. if (CurrentState == ConsoleState.Closed)
  457. return;
  458. // use a matrix for the translation animation so that the rest of our drawing code doesn't
  459. // have to concern itself with the math to put things in the right place
  460. spriteBatch.Begin(
  461. SpriteSortMode.Deferred, null, null, null, null, null,
  462. Matrix.CreateTranslation(0f, -totalHeight + currentHeight, 0f));
  463. // draw the background
  464. spriteBatch.Draw(DebugContent.FillTexture, new Rectangle(0, 0, FrameworkServices.GraphicsDevice.Viewport.Width, totalHeight), Color.Black * BackgroundOpacity);
  465. int promptY = totalHeight - DebugContent.ConsoleFont.LineSpacing - bottomPadding;
  466. // draw the prompt at the bottom
  467. spriteBatch.DrawString(DebugContent.ConsoleFont, prompt, new Vector2(0, promptY), Color.Lime);
  468. // draw the input string next to the prompt
  469. spriteBatch.DrawString(DebugContent.ConsoleFont, inputText, new Vector2(promptWidth, promptY), Color.Lime);
  470. // draw the cursor
  471. if (cursorVisible)
  472. {
  473. spriteBatch.DrawString(DebugContent.ConsoleFont, cursor, new Vector2(promptWidth + cursorSize * cursorPosition, promptY), Color.Lime);
  474. }
  475. // draw the log
  476. if (output.Count > 0)
  477. {
  478. int start = Math.Max(output.Count - heightInLines + 1 - outputShift, 0);
  479. int end = Math.Min(output.Count, start + heightInLines - 1);
  480. for (int i = start; i < end; i++)
  481. {
  482. OutputLine l = output[i];
  483. spriteBatch.DrawString(DebugContent.ConsoleFont, l.Text, new Vector2(0, (i - start) * DebugContent.ConsoleFont.LineSpacing), l.Color);
  484. }
  485. }
  486. spriteBatch.End();
  487. }
  488. private void HandleInput()
  489. {
  490. bool control = InputManager.IsKeyDown(Keys.LeftControl) || InputManager.IsKeyDown(Keys.RightControl);
  491. bool shift = InputManager.IsKeyDown(Keys.LeftShift) || InputManager.IsKeyDown(Keys.RightShift);
  492. // check for backspace which deletes the character behind the
  493. // cursor and moves the cursor back
  494. if (InputManager.WasKeyPressed(Keys.Back))
  495. {
  496. if (cursorPosition > 0)
  497. {
  498. inputText.Remove(cursorPosition - 1, 1);
  499. cursorPosition--;
  500. InvalidateTabCompletion();
  501. }
  502. }
  503. // check for delete which deletes the character in front of the cursor
  504. else if (InputManager.WasKeyPressed(Keys.Delete))
  505. {
  506. if (cursorPosition < inputText.Length)
  507. {
  508. inputText.Remove(cursorPosition, 1);
  509. InvalidateTabCompletion();
  510. }
  511. }
  512. // check for the left/right arrow keys which move the cursor
  513. else if (InputManager.WasKeyPressed(Keys.Left))
  514. {
  515. cursorPosition = Math.Max(cursorPosition - 1, 0);
  516. }
  517. else if (InputManager.WasKeyPressed(Keys.Right))
  518. {
  519. cursorPosition = Math.Min(cursorPosition + 1, inputText.Length);
  520. }
  521. // Ctrl+Up/Ctrl+Down scroll the output up and down
  522. else if (control && InputManager.WasKeyPressed(Keys.Up))
  523. {
  524. outputShift = Math.Min(outputShift + 1, output.Count - heightInLines + 1);
  525. }
  526. else if (control && InputManager.WasKeyPressed(Keys.Down))
  527. {
  528. outputShift = Math.Max(outputShift - 1, 0);
  529. }
  530. // Up/Down without Ctrl goes back through the input history
  531. else if (InputManager.WasKeyPressed(Keys.Up))
  532. {
  533. if (inputHistory.Count > 0)
  534. {
  535. inputHistoryPosition = Math.Max(inputHistoryPosition - 1, 0);
  536. inputText.Clear();
  537. inputText.Append(inputHistory[inputHistoryPosition]);
  538. cursorPosition = inputText.Length;
  539. InvalidateTabCompletion();
  540. }
  541. }
  542. else if (InputManager.WasKeyPressed(Keys.Down))
  543. {
  544. if (inputHistory.Count > 0)
  545. {
  546. inputHistoryPosition = Math.Min(inputHistoryPosition + 1, inputHistory.Count - 1);
  547. inputText.Clear();
  548. inputText.Append(inputHistory[inputHistoryPosition]);
  549. cursorPosition = inputText.Length;
  550. InvalidateTabCompletion();
  551. }
  552. }
  553. // Tab does suggestion completion for commands and arguments
  554. else if (InputManager.WasKeyPressed(Keys.Tab))
  555. {
  556. PerformTabCompletion();
  557. }
  558. // Esc clears the input
  559. else if (InputManager.WasKeyPressed(Keys.Escape))
  560. {
  561. inputText.Clear();
  562. cursorPosition = 0;
  563. InvalidateTabCompletion();
  564. }
  565. // Enter submits the input
  566. else if (InputManager.WasKeyPressed(Keys.Enter))
  567. {
  568. SubmitInput();
  569. }
  570. else
  571. {
  572. // If our pressed key is no longer down, reset it
  573. if (!InputManager.IsKeyDown(pressedKey))
  574. pressedKey = Keys.None;
  575. // get all the pressed keys so we can do some typing
  576. Keys[] keys = InputManager.PressedKeys;
  577. foreach (Keys key in keys)
  578. {
  579. // skip our toggle key
  580. if (key == ToggleKey) continue;
  581. // convert the key to a character with our delegate.
  582. // we also use the IsKeyPressed method to track our repeat delay if we have a key with a valid character.
  583. char ch;
  584. if (KeyToCharacter(key, shift, out ch) && InputManager.WasKeyPressed(key))
  585. {
  586. inputText = inputText.Insert(cursorPosition, ch);
  587. cursorPosition++;
  588. InvalidateTabCompletion();
  589. }
  590. }
  591. }
  592. }
  593. private void PerformTabCompletion()
  594. {
  595. // if tab completion is invalid
  596. if (tabCompletionSuggestions == null)
  597. {
  598. // parse the current input to get the command and the arguments
  599. string command;
  600. List<string> arguments;
  601. ParseInput(inputText.ToString(), out command, out arguments);
  602. if (arguments.Count == 0)
  603. {
  604. // if there are no arguments, we are doing tab completion using the command as our root
  605. tabCompletionSuggestions = commandSuggestionProvider.GetSuggestions(command, 0, command, arguments);
  606. // set our completion position to the start of the input since we'll replace the entire input string with our suggestions
  607. tabCompletePosition = 0;
  608. }
  609. else
  610. {
  611. // otherwise we're doing argument completion so we need to figure out if we can do that for the current command
  612. CommandInfo info;
  613. if (commands.TryGetValue(command, out info) && info.TabCompletionProvider != null)
  614. {
  615. // get the index of the argument and the argument string
  616. int argIndex = arguments.Count - 1;
  617. string currentArgument = arguments[argIndex];
  618. // remove the current argument so arguments is just the previous arguments
  619. arguments.RemoveAt(argIndex);
  620. // now use the command's tab completion provider to get the suggestions for the command based on the last argument
  621. tabCompletionSuggestions = info.TabCompletionProvider.GetSuggestions(command, argIndex, currentArgument, arguments);
  622. // we need to set our completion position to the start of this argument so that suggestions are properly inserted
  623. tabCompletePosition = inputText.ToString().LastIndexOf(currentArgument);
  624. }
  625. }
  626. }
  627. if (tabCompletionSuggestions != null && tabCompletionSuggestions.Count > 0)
  628. {
  629. // wrap the tab completion index
  630. if (tabCompleteIndex >= tabCompletionSuggestions.Count)
  631. tabCompleteIndex = 0;
  632. // find the suggestion at our index
  633. string suggestion = tabCompletionSuggestions[tabCompleteIndex];
  634. // clear the input text from our tab completion position forward
  635. inputText.Remove(tabCompletePosition, inputText.Length - tabCompletePosition);
  636. // append the suggestion to the input text
  637. inputText.Append(suggestion);
  638. // and now fix the cursor position
  639. cursorPosition = inputText.Length;
  640. // increment the index
  641. tabCompleteIndex++;
  642. }
  643. }
  644. private void InvalidateTabCompletion()
  645. {
  646. tabCompletePosition = 0;
  647. tabCompleteIndex = 0;
  648. tabCompletionSuggestions = null;
  649. }
  650. /// <summary>
  651. /// Parses a string for the command name (first "argument") and the rest of the arguments of the command.
  652. /// </summary>
  653. private void ParseInput(string input, out string command, out List<string> arguments)
  654. {
  655. arguments = new List<string>();
  656. // the first argument is the command
  657. GetNextArgument(input, 0, out command);
  658. if (command == null)
  659. return;
  660. // then we can parse out the rest of the arguments
  661. int start = command.Length;
  662. string argument;
  663. while ((start = GetNextArgument(input, start, out argument)) > 0)
  664. arguments.Add(argument);
  665. }
  666. private void SubmitInput()
  667. {
  668. // Simply send the input text to our InvokeCommand method
  669. InvokeCommand(inputText.ToString());
  670. // clear out our input and invalidate tab completion
  671. inputText.Clear();
  672. cursorPosition = 0;
  673. InvalidateTabCompletion();
  674. }
  675. /// <summary>
  676. /// Helper for parsing input strings to find arguments separated by spaces while respecting quotes.
  677. /// </summary>
  678. private int GetNextArgument(string input, int start, out string argument)
  679. {
  680. // make sure we are in the bounds of the input
  681. if (start >= input.Length)
  682. {
  683. argument = null;
  684. return -1;
  685. }
  686. // move forward to the first non-space character
  687. while (start < input.Length && input[start] == ' ')
  688. start++;
  689. // if we're at the end, return null
  690. if (start == input.Length)
  691. {
  692. argument = null;
  693. return -1;
  694. }
  695. // move forward until we're done with this argument (we've found a space and
  696. // are outside of a pair of quotes)
  697. bool inQuotes = false;
  698. int end;
  699. for (end = start; end < input.Length; end++)
  700. {
  701. if (input[end] == ' ' && !inQuotes)
  702. break;
  703. if (input[end] == '"' && (end - 1 < 0 || input[end - 1] != '\\'))
  704. inQuotes = !inQuotes;
  705. }
  706. // return the substring, without quotes, trimmed
  707. argument = input.Substring(start, end - start).Replace("\"", "");
  708. return end;
  709. }
  710. private void RegisterDefaultCommands()
  711. {
  712. StringBuilder helpTextBuilder = new StringBuilder();
  713. // suggestion provider that will handle our get, set, and invoke commands to help with autocompletion of member names
  714. SelectionMemberSuggestionProvider memberSuggestionProvider = new SelectionMemberSuggestionProvider();
  715. // Register our 'commands' command to list all registered commands
  716. helpTextBuilder.Append("Lists all commands currently registered with the console.");
  717. RegisterCommand("commands", helpTextBuilder.ToString(), CommandsCommandAction);
  718. // Register 'help' to display help text for commands
  719. helpTextBuilder.Clear();
  720. helpTextBuilder.Append("Displays help information for registered commands.");
  721. RegisterCommand("help", helpTextBuilder.ToString(), HelpCommandAction, commandSuggestionProvider);
  722. // Register 'info' to display all public fields, properties, and methods on the selected objects
  723. helpTextBuilder.Clear();
  724. helpTextBuilder.Append("Displays public fields, properties, and methods that exist on the selected object(s).");
  725. RegisterCommand("info", helpTextBuilder.ToString(), InfoCommandAction, null);
  726. // Register 'get' for retrieving values from the selected objects
  727. helpTextBuilder.Clear();
  728. helpTextBuilder.AppendLine("Gets the value of a field or property on the selected object(s).");
  729. helpTextBuilder.AppendLine("Usage: get [propertyOrFieldName]");
  730. helpTextBuilder.Append("propertyOrFieldName The name of a field or property to get from the selected object(s).");
  731. RegisterCommand("get", helpTextBuilder.ToString(), GetCommandAction, memberSuggestionProvider);
  732. // Register 'set' for setting values on the selected objects
  733. helpTextBuilder.Clear();
  734. helpTextBuilder.AppendLine("Sets the value of a field or property on the selected object(s).");
  735. helpTextBuilder.AppendLine("Usage: set [propertyOrFieldName] [newPropertyValue]");
  736. helpTextBuilder.AppendLine("propertyOrFieldName The name of a field or property to set on the selected object(s).");
  737. helpTextBuilder.Append("newPropertyValue A string representation of the new value to assign to the field or property.");
  738. RegisterCommand("set", helpTextBuilder.ToString(), SetCommandAction, memberSuggestionProvider);
  739. // Register 'invoke' for calling methods on the selected objects
  740. helpTextBuilder.Clear();
  741. helpTextBuilder.AppendLine("Invokes a method on the selected object(s).");
  742. helpTextBuilder.AppendLine("Usage: invoke [methodName] (arg1 arg2 arg3 etc)");
  743. helpTextBuilder.AppendLine("methodName The name of a method to invoke on the selected object(s).");
  744. helpTextBuilder.Append("arg1 etc (Optional) The arguments to parse and pass to the method.");
  745. RegisterCommand("invoke", helpTextBuilder.ToString(), InvokeCommandAction, memberSuggestionProvider);
  746. }
  747. private void CommandsCommandAction(string command, IList<string> args)
  748. {
  749. StringBuilder temp = new StringBuilder();
  750. foreach (var c in commands.Keys)
  751. temp.AppendFormat("{0}, ", c);
  752. temp.Remove(temp.Length - 2, 2);
  753. WriteLine(temp.ToString());
  754. }
  755. private void HelpCommandAction(string command, IList<string> args)
  756. {
  757. // Validate arguments
  758. if (args.Count == 0)
  759. {
  760. WriteError("help requires at least one argument.");
  761. WriteCommandHelp(command);
  762. return;
  763. }
  764. // Write out the help for the command
  765. foreach (var a in args)
  766. {
  767. // If we have multiple commands, write the command as well for clarity
  768. if (args.Count > 1)
  769. WriteLine(string.Format("Command: {0}", a), Color.Gold);
  770. WriteCommandHelp(a);
  771. }
  772. }
  773. private void InfoCommandAction(string command, IList<string> args)
  774. {
  775. // Make sure we have a selection
  776. if (selectedObjects.Count == 0)
  777. {
  778. WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'info'.");
  779. return;
  780. }
  781. // get all the base types of the selected objects
  782. var baseTypes = FindCommonBaseTypesOfSelection();
  783. // First write out all fields and properties
  784. WriteLine("Fields/Properties:");
  785. foreach (var t in baseTypes)
  786. {
  787. foreach (var f in t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
  788. WriteLine(string.Format(" {0} {1}", f.FieldType.Name, f.Name));
  789. foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
  790. {
  791. // Build up the accessors string for the property
  792. StringBuilder accessors = new StringBuilder();
  793. // Check each accessor and append them to our string
  794. if (p.GetGetMethod() != null && p.GetGetMethod().IsPublic)
  795. accessors.Append(" get;");
  796. if (p.GetSetMethod() != null && p.GetSetMethod().IsPublic)
  797. accessors.Append(" set;");
  798. // Write out the property type, name, and accessors
  799. WriteLine(string.Format(" {0} {1} {{{2} }}", p.PropertyType.Name, p.Name, accessors));
  800. }
  801. }
  802. // Then write out all methods
  803. WriteLine("Methods:");
  804. foreach (var t in baseTypes)
  805. {
  806. foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
  807. {
  808. // Special name methods are property accessors so skip those
  809. if (m.IsSpecialName) continue;
  810. // Build up the argument list for this method
  811. StringBuilder argumentList = new StringBuilder("(");
  812. foreach (var p in m.GetParameters())
  813. argumentList.AppendFormat("{0}, ", p.ParameterType.Name);
  814. if (argumentList.Length > 1)
  815. argumentList.Remove(argumentList.Length - 2, 2);
  816. argumentList.Append(")");
  817. // Output the method with the return type and argument list
  818. WriteLine(string.Format(" {0} {1}{2}", m.ReturnType.Name, m.Name, argumentList));
  819. }
  820. }
  821. }
  822. private void GetCommandAction(string command, IList<string> args)
  823. {
  824. // Basic validation of arguments
  825. if (args.Count != 1)
  826. {
  827. // Write an error and display the help for the command
  828. WriteError("get requires a single argument");
  829. WriteCommandHelp(command);
  830. return;
  831. }
  832. // Make sure we have a selection
  833. if (selectedObjects.Count == 0)
  834. {
  835. WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'get'.");
  836. return;
  837. }
  838. // Get the value for each selected object
  839. foreach (var o in selectedObjects)
  840. {
  841. // Try to find a field or property with the given name
  842. FieldInfo f = o.GetType().GetField(args[0]);
  843. PropertyInfo p = o.GetType().GetProperty(args[0]);
  844. // Error if neither are found
  845. if (f == null && p == null)
  846. {
  847. WriteError(string.Format("{0} doesn't have a field or property called {1}.", o, args[0]));
  848. }
  849. else
  850. {
  851. // Get the value of the field or property from the object and write out the value
  852. try
  853. {
  854. object value = f != null ? f.GetValue(o) : p.GetValue(o, null);
  855. WriteLine(string.Format("{0}.{1} = {2}", o, args[0], value == null ? "(null)" : StringParser.ToString(value)));
  856. }
  857. catch (Exception e)
  858. {
  859. WriteError(string.Format("Failed to get {0}.{1}: {2}", o, args[0], e.Message));
  860. }
  861. }
  862. }
  863. }
  864. private void SetCommandAction(string command, IList<string> args)
  865. {
  866. // Basic validation of arguments
  867. if (args.Count != 2)
  868. {
  869. WriteError("set requires two arguments");
  870. WriteCommandHelp(command);
  871. return;
  872. }
  873. // Make sure we have a selection
  874. if (selectedObjects.Count == 0)
  875. {
  876. WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'set'.");
  877. return;
  878. }
  879. // Validate the string parsing delegate
  880. if (StringParser == null)
  881. {
  882. WriteError("No string parsing delegate was given to XConsole; no way to parse argument types.");
  883. return;
  884. }
  885. // Set the value for each selected object
  886. foreach (var o in selectedObjects)
  887. {
  888. // Try to find a field or property with the given name
  889. FieldInfo f = o.GetType().GetField(args[0]);
  890. PropertyInfo p = o.GetType().GetProperty(args[0]);
  891. // Error if neither are found
  892. if (f == null && p == null)
  893. {
  894. WriteError(string.Format("{0} doesn't have a field or property called {1}.", o, args[0]));
  895. }
  896. else
  897. {
  898. // Try to parse the value argument using our delegate
  899. Type valueType = f != null ? f.FieldType : p.PropertyType;
  900. object value;
  901. try { value = StringParser.Parse(valueType, args[1]); }
  902. catch (Exception e)
  903. {
  904. WriteError(string.Format("Failed to parse '{0}' as {1}: {2}", args[1], valueType, e.Message));
  905. continue;
  906. }
  907. // Now set the value on the field or property
  908. try
  909. {
  910. if (f != null)
  911. f.SetValue(o, value);
  912. else
  913. p.SetValue(o, value, null);
  914. }
  915. catch (Exception e)
  916. {
  917. WriteError(string.Format("Failed to set {0}.{1}: {2}", o, args[0], e.Message));
  918. continue;
  919. }
  920. // And log the new value
  921. WriteLine(string.Format("{0}.{1} = {2}", o, args[0], value == null ? "(null)" : StringParser.ToString(value)));
  922. }
  923. }
  924. }
  925. private void InvokeCommandAction(string command, IList<string> args)
  926. {
  927. // Basic validation of arguments
  928. if (args.Count < 1)
  929. {
  930. WriteError("invoke requires at least one argument");
  931. WriteCommandHelp(command);
  932. return;
  933. }
  934. // Make sure we have a selection
  935. if (selectedObjects.Count == 0)
  936. {
  937. WriteError("Nothing is selected. Add objects to the SelectedObjects list before using 'invoke'.");
  938. return;
  939. }
  940. // Validate the string parsing delegate
  941. if (StringParser == null)
  942. {
  943. WriteError("No string parsing delegate was given to XConsole; no way to parse argument types.");
  944. return;
  945. }
  946. // Invoke the method for each selected object
  947. foreach (var o in selectedObjects)
  948. {
  949. // Try to get the method
  950. MethodInfo method = o.GetType().GetMethod(args[0]);
  951. int currentIndex = selectedObjects.IndexOf(o);
  952. // Error if no method was found
  953. if (method == null)
  954. {
  955. if (currentIndex == selectedObjects.Count - 1)
  956. {
  957. WriteError(string.Format("No selected objects have a method called {1}.", o, args[0]));
  958. }
  959. else
  960. {
  961. continue;
  962. }
  963. }
  964. // Error if we have a mismatch of parameter counts (make sure to -1 on a.Count since a[0] is the method name)
  965. else if (method.GetParameters().Length != args.Count - 1)
  966. {
  967. WriteError(string.Format("{0}.{1} requires {2} arguments, {3} were given to 'invoke'.", o, args[0], method.GetParameters().Length, args.Count - 1));
  968. }
  969. else
  970. {
  971. // Parse out the parameters for the method
  972. ParameterInfo[] methodParams = method.GetParameters();
  973. object[] methodArguments = new object[methodParams.Length];
  974. try
  975. {
  976. for (int i = 0; i < methodParams.Length; i++)
  977. methodArguments[i] = StringParser.Parse(methodParams[i].ParameterType, args[i + 1]);
  978. }
  979. catch (Exception e)
  980. {
  981. WriteError(string.Format("Error while parsing arguments: {0}", e.Message));
  982. return;
  983. }
  984. // Invoke the method on the object
  985. object output = method.Invoke(o, methodArguments);
  986. // If the method isn't a void return type, write out the result
  987. if (method.ReturnType != typeof(void))
  988. WriteLine(string.Format("Result: {0}", output == null ? "(null)" : StringParser.ToString(output)));
  989. return;
  990. }
  991. }
  992. }
  993. // Finds all of the common base classes and interfaces of the selection
  994. private IEnumerable<Type> FindCommonBaseTypesOfSelection()
  995. {
  996. List<Type> baseTypes = new List<Type>();
  997. foreach (var o in selectedObjects)
  998. {
  999. Type t = o.GetType();
  1000. // add in all the interfaces from this type
  1001. foreach (var i in t.GetInterfaces())
  1002. {
  1003. if (!baseTypes.Contains(i))
  1004. baseTypes.Add(i);
  1005. }
  1006. // add in all base classes from this type
  1007. while (t != null)
  1008. {
  1009. if (!baseTypes.Contains(t))
  1010. baseTypes.Add(t);
  1011. t = t.BaseType;
  1012. }
  1013. }
  1014. // now we can return the types from this collection that are assignable from
  1015. // all of the selected object types
  1016. return baseTypes.Where(t =>
  1017. {
  1018. foreach (var o in selectedObjects)
  1019. if (!t.IsAssignableFrom(o.GetType()))
  1020. return false;
  1021. return true;
  1022. }).ToList();
  1023. }
  1024. #region Public Support Types
  1025. /// <summary>
  1026. /// Defines the possible states of the XConsole.
  1027. /// </summary>
  1028. public enum ConsoleState
  1029. {
  1030. /// <summary>
  1031. /// The XConsole is completely closed.
  1032. /// </summary>
  1033. Closed,
  1034. /// <summary>
  1035. /// The XConsole is animating to the Open state.
  1036. /// </summary>
  1037. Opening,
  1038. /// <summary>
  1039. /// The XConsole is completely open.
  1040. /// </summary>
  1041. Open,
  1042. /// <summary>
  1043. /// The XConsole is animating to the Closed state.
  1044. /// </summary>
  1045. Closing
  1046. }
  1047. /// <summary>
  1048. /// The delegate takes in a key as well as whether or not the shift key is down and
  1049. /// uses that to return a character which is then added to the current input string.
  1050. /// The function needs to return true if there is a valid character for the key or
  1051. /// false if the input should be ignored.
  1052. /// </summary>
  1053. /// <param name="key">The key that …

Large files files are truncated, but you can click here to view the full file