using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; namespace HotKey.Graphics { public delegate void InputMadeEventHandler (string input); public delegate bool SingleCommandCompletedEventHandler (string command); public delegate void RestoreAfterSingleCommandEventHandler (); public partial class RichTextConsole : RichTextBox { /// /// When the return key is pressed the current input is taken and issued to any /// registrants of the event through the event argument. /// public event InputMadeEventHandler OnInputMade; /// /// When a command is fully typed out or autocompleted this event is triggered /// so that the registrant can set a new list of commands for command sensitive /// auto completion. /// public event SingleCommandCompletedEventHandler OnSingleCommandCompleted; /// /// Triggers when a command was fully typed out or autocompleted and then /// was deleted or changed. /// public event RestoreAfterSingleCommandEventHandler OnRestoreAfterSingleCommand; #region Public variables public List Commands { get; set; } public string Prompt { get; set; } #endregion #region Private variables private class internalInput { public string[] words; public int index; } private int offset; private List history = new List(); private int historyPos = 0; private string curLine; private bool isSingleCommand = false; #endregion public RichTextConsole() { InitializeComponent(); } protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); SelectionStart = TextLength; } protected override void OnKeyDown(KeyEventArgs e) { internalInput tmp = GetInput(); if (SelectionStart < offset) { if (!(e.Control & e.KeyCode == Keys.C)) // Enable ctrl+c'ing of marked text { e.Handled = true; e.SuppressKeyPress = true; return; } } if (e.KeyCode == Keys.Up | e.KeyCode == Keys.Down) { e.Handled = true; if (historyPos == history.Count && e.KeyCode == Keys.Up) curLine = Text.Substring(offset); if ((historyPos == history.Count && e.KeyCode == Keys.Down) | (historyPos == 0 && e.KeyCode == Keys.Up)) return; historyPos += e.KeyCode == Keys.Up ? -1 : 1; if (historyPos == history.Count) SetInput(curLine); else SetInput(history.ElementAt(historyPos)); } if (e.KeyCode == Keys.Left | e.KeyCode == Keys.Back) { if (SelectionStart == offset) e.Handled = true; } if (e.KeyCode == Keys.Enter) { e.Handled = true; string str = Text.Substring(offset).Trim(); if (str.Length == 0) return; OnInputMade(str); history.Add(str); historyPos = history.Count(); NewPrompt(); } if (e.KeyCode == Keys.Tab) { e.SuppressKeyPress = true; e.Handled = true; // Do completion! var matches = Commands.Where(c => c.StartsWith(tmp.words[tmp.index])); switch (matches.Count()) { case 0: // Simple break; case 1: // Insert completed command Write(matches.First().Substring(tmp.words[tmp.index].Length)); if (SelectionStart == TextLength) Write(" "); break; default: // Show matches var orderedMatches = matches.OrderBy(m => m); int max = orderedMatches.Max(m => m.Length) + 2; int colcount = 80 / max; // Character width for autocomplete suggestions is hardcoded :( string tmpstr = ""; for (int i = 0; i < orderedMatches.Count(); i++) tmpstr += (i % colcount == 0 ? "\n" : "") + orderedMatches.ElementAt(i).PadRight(max); Write(tmpstr); // Detect how far all matches are equal int x = tmp.words[tmp.index].Length; var charArrays = matches.Select(m => m.ToCharArray()); while ( x < charArrays.Min(ca => ca.Count()) && charArrays.All(ca => ca[x] == charArrays.First()[x]) ) x++; // Reconstruct GetInput line NewPrompt(); for (int i = 0; i < tmp.words.Count(); i++) { if (tmp.index == i) Write(matches.First().Substring(0, x)); else Write(tmp.words[i]); if (i != tmp.words.Count() - 1) Write(" "); } SelectionStart = offset + tmp.words.Take(tmp.index).Sum(w => w.Length + 1) + x; break; } } base.OnKeyDown(e); // See if there is an exact match for command sensitive auto completion. tmp = GetInput(); if (tmp.words.Count() == 2 && Commands.Contains(tmp.words[0])) isSingleCommand = OnSingleCommandCompleted(tmp.words[0]); else if (isSingleCommand) OnRestoreAfterSingleCommand(); } #region Public functions public void NewPrompt() { if (TextLength == 0) Write(Prompt); else WriteLine(Prompt); offset = TextLength; } public void Write(string text) { Enabled = false; // Disabling makes the caret disappear while // updating and prevents flickering. int tmp = SelectionStart; Text = Text.Insert(SelectionStart, text); SelectionStart = tmp + text.Length; ScrollToCaret(); Enabled = true; Focus(); } public void WriteLine(string line) { Enabled = false; // Disabling makes the caret disappear while // updating and prevents flickering. Text += "\n" + line; SelectionStart = TextLength; ScrollToCaret(); Enabled = true; Focus(); } public void Empty() { Text = ""; NewPrompt(); } #endregion #region Private methods private internalInput GetInput() { internalInput i = new internalInput(); i.words = Text.Substring(offset).Split(" ".ToCharArray()); i.index = 0; int sum = 0; for (int k = 0; k < i.words.Count(); k++) { sum += i.words[k].Length + 1; if (sum > SelectionStart - offset) { i.index = k; break; } } return i; } private void SetInput(string s) { if (TextLength > offset) Text = Text.Remove(offset); Text += s; SelectionStart = TextLength; } #endregion } }