From 65b2cb9831c4a7a529f9b9a211870853dd252a34 Mon Sep 17 00:00:00 2001 From: v-alexjo Date: Wed, 16 Dec 2015 22:13:52 -0800 Subject: [PATCH] Implemented readline, readkey, special keys --- src/Microsoft.PowerShell.Linux.Host/main.cs | 2 +- .../readline.cs | 249 ++++++++++++++---- 2 files changed, 205 insertions(+), 46 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs index 9f2a0753b..b562d2b45 100644 --- a/src/Microsoft.PowerShell.Linux.Host/main.cs +++ b/src/Microsoft.PowerShell.Linux.Host/main.cs @@ -419,7 +419,7 @@ namespace Microsoft.PowerShell.Linux.Host string prompt = Prompt(this.myHost.Runspace); this.myHost.UI.Write(ConsoleColor.White, ConsoleColor.Black, prompt); - string cmd = this.myHost.UI.ReadLine(); + string cmd = consoleReadLine.Read(this.myHost.Runspace); this.Execute(cmd); } diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index 3662cb7f6..1b0892862 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -3,6 +3,7 @@ namespace Microsoft.PowerShell.Linux.Host using System; using System.Collections.ObjectModel; using System.Management.Automation; + using System.Management.Automation.Runspaces; using System.Text; /// @@ -12,17 +13,62 @@ namespace Microsoft.PowerShell.Linux.Host /// internal class ConsoleReadLine { + /// + /// Holds a reference to the runspace (for history access). + /// + private Runspace runspace; + /// /// The buffer used to edit. /// private StringBuilder buffer = new StringBuilder(); + /// + /// integeger for tracking up and down arrow history + /// + private int historyIndex; + + /// + /// Used for storing tab completion + /// + private CommandCompletion cmdCompleteOpt; + /// /// The position of the cursor within the buffer. /// private int current; + /// + /// Detects previously pressed key for TabCompletion + /// /// + private ConsoleKeyInfo previousKeyPress; + + /// + /// Retains TabCompletion position + /// + private int tabCompletionPos; + + /// + /// History Queue + /// + private Collection historyResult; + + /// + /// Hashtable for command completion options + /// + private System.Collections.Hashtable options = new System.Collections.Hashtable(); + + /// + /// Retain original buffer for TabCompletion + /// + private StringBuilder tabBuffer; + + /// + /// tabbuffer for storing result of tabcompletion + /// + private string tabResult; + /// The count of characters in buffer rendered. /// private int rendered; @@ -30,7 +76,7 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Store the anchor and handle cursor movement /// - //private Cursor cursor; + private Cursor cursor; /// /// The array of colors for tokens, indexed by PSTokenType @@ -77,30 +123,26 @@ namespace Microsoft.PowerShell.Linux.Host /// Read a line of text, colorizing while typing. /// /// The command line read - public string Read() + public string Read(Runspace runspace) { + this.runspace = runspace; this.Initialize(); while (true) - { - // no support for Console.ReadKey in CoreCLR yet - int key = Console.Read(); - char keyChar = Convert.ToChar(key); - this.Insert(keyChar); -/* + { ConsoleKeyInfo key = Console.ReadKey(true); switch (key.Key) { - case ConsoleKey.Backspace: + case ConsoleKey.Backspace: this.OnBackspace(); break; - case ConsoleKey.Delete: + case ConsoleKey.Delete: this.OnDelete(); - break; - case ConsoleKey.Enter: + break; + case ConsoleKey.Enter: return this.OnEnter(); - case ConsoleKey.RightArrow: + case ConsoleKey.RightArrow: this.OnRight(key.Modifiers); break; case ConsoleKey.LeftArrow: @@ -115,14 +157,21 @@ namespace Microsoft.PowerShell.Linux.Host case ConsoleKey.End: this.OnEnd(); break; + case ConsoleKey.Tab: + this.OnTab(); + break; case ConsoleKey.UpArrow: + this.OnUpArrow(); + break; case ConsoleKey.DownArrow: - case ConsoleKey.LeftWindows: - case ConsoleKey.RightWindows: - // ignore these - continue; + // TODO: this.OnDownArrow(); + continue; + // TODO: case ConsoleKey.LeftWindows: not available in linux + // TODO: case ConsoleKey.RightWindows: not available in linux + default: + if (key.KeyChar == '\x0D') { goto case ConsoleKey.Enter; // Ctrl-M @@ -133,9 +182,12 @@ namespace Microsoft.PowerShell.Linux.Host goto case ConsoleKey.Backspace; // Ctrl-H } - this.Insert(key); - break; - }*/ + this.Insert(key.KeyChar); + + this.Render(); + break; + } + previousKeyPress = key; } } @@ -144,10 +196,12 @@ namespace Microsoft.PowerShell.Linux.Host /// private void Initialize() { - this.buffer.Length = 0; + this.tabCompletionPos = 0; + this.historyIndex = 0; + this.buffer.Length = 0; this.current = 0; this.rendered = 0; - //this.cursor = new Cursor(); + this.cursor = new Cursor(); } /// @@ -162,9 +216,8 @@ namespace Microsoft.PowerShell.Linux.Host this.Render(); } -/* /// - /// The End key was enetered.. + /// The End key was entered. /// private void OnEnd() { @@ -172,15 +225,84 @@ namespace Microsoft.PowerShell.Linux.Host this.cursor.Place(this.rendered); } + /// + /// The Tab key was entered + /// + private void OnTab() + { + if (previousKeyPress.Key != ConsoleKey.Tab || previousKeyPress.Key == ConsoleKey.Enter) + { + tabBuffer = this.buffer; + using (PowerShell powershell = PowerShell.Create()) + { + cmdCompleteOpt = CommandCompletion.CompleteInput(this.tabBuffer.ToString(), this.current, options, powershell); + } + } + + try + { + tabResult = cmdCompleteOpt.CompletionMatches[tabCompletionPos].CompletionText; + } + catch (Exception ex) + { + //todo continue nicely + } + + tabCompletionPos++; + + //if there is a command for the user before the uncompleted option + if (!String.IsNullOrEmpty(tabResult)) + { + //handle file path slashes + if (tabResult.Contains(".\\")) + { + tabResult = tabResult.Replace(".\\", ""); + } + + if (this.buffer.ToString().Contains(" ")) + { + var replaceIndex = cmdCompleteOpt.ReplacementIndex; + string replaceBuffer = this.buffer.ToString(); + + replaceBuffer = replaceBuffer.Remove(replaceIndex); + tabResult = replaceBuffer + tabResult; + } + + OnEscape(); + + BufferFromString(tabResult); + + //re-render + this.Render(); + } + + } //end of OnTab() + + /// + /// Set buffer to a string rather than inserting char by char + /// + private void BufferFromString(string endResult) + { + //reset prompt and buffer + OnEscape(); + + //set the buffer to the string + for (int i = 0; i < endResult.Length; i++) + { + this.Insert(endResult[i]); + } + + } + /// - /// The Home key was eneterd. + /// The Home key was entered. /// private void OnHome() { - this.current = 0; + this.current = 0; this.cursor.Reset(); } - + /// /// The Escape key was enetered. /// @@ -191,6 +313,44 @@ namespace Microsoft.PowerShell.Linux.Host this.Render(); } + /// + /// The up arrow was pressed to retrieve history + /// + private void OnUpArrow() { + if (previousKeyPress.Key != ConsoleKey.UpArrow || previousKeyPress.Key == ConsoleKey.Enter) + { + //first time getting the history + using (Pipeline pipeline = this.runspace.CreatePipeline("Get-History")) + { + historyResult = pipeline.Invoke(); + + } + + BufferFromString(historyResult[historyIndex].Members["CommandLine"].Value.ToString()); + this.Render(); + + historyIndex++; + } + + else + { + BufferFromString(historyResult[historyIndex].Members["CommandLine"].Value.ToString()); + this.Render(); + + if (historyIndex < historyResult.Count) + { + historyIndex++; + } + + if (historyIndex == historyResult.Count) + { + historyIndex--; + } + + } + + } + /// /// Moves to the left of the cursor position. /// @@ -226,7 +386,6 @@ namespace Microsoft.PowerShell.Linux.Host this.MoveLeft(); } } -*/ /// /// Determines if a character is a seperator. @@ -238,7 +397,7 @@ namespace Microsoft.PowerShell.Linux.Host { return !Char.IsLetter(ch); } -/* + /// /// Moves to what is to the right of the cursor position. /// @@ -291,7 +450,7 @@ namespace Microsoft.PowerShell.Linux.Host Cursor.Move(1); } } - + /// /// Moves the cursor one character to the left. /// @@ -304,7 +463,7 @@ namespace Microsoft.PowerShell.Linux.Host Cursor.Move(-1); } } - + /// /// The Enter key was entered. /// @@ -314,7 +473,7 @@ namespace Microsoft.PowerShell.Linux.Host Console.Out.Write("\n"); return this.buffer.ToString(); } - + /// /// The delete key was entered. /// @@ -326,7 +485,7 @@ namespace Microsoft.PowerShell.Linux.Host this.Render(); } } - + /// /// The Backspace key was entered. /// @@ -339,25 +498,28 @@ namespace Microsoft.PowerShell.Linux.Host this.Render(); } } -*/ + /// /// Displays the line. /// private void Render() { - string text = this.buffer.ToString(); + this.cursor.Reset(); + string text = this.buffer.ToString(); + // The PowerShell tokenizer is used to decide how to colorize // the input. Any errors in the input are returned in 'errors', // but we won't be looking at those here. Collection errors = null; Collection tokens = PSParser.Tokenize(text, out errors); - + if (tokens.Count > 0) { // We can skip rendering tokens that end before the cursor. int i; - for (i = 0; i < tokens.Count; ++i) + + for (i = 0; i < tokens.Count; ++i) { if (this.current >= tokens[i].Start) { @@ -402,7 +564,7 @@ namespace Microsoft.PowerShell.Linux.Host // If tokenization completely failed, just redraw the whole line. This // happens most frequently when the first token is incomplete, like a string // literal missing a closing quote. - //this.cursor.Reset(); + this.cursor.Reset(); Console.Out.Write(text); } @@ -413,10 +575,9 @@ namespace Microsoft.PowerShell.Linux.Host } this.rendered = text.Length; - //this.cursor.Place(this.current); + this.cursor.Place(this.current); } - -/* + /// /// A helper class for maintaining the cursor while editing the command line. /// @@ -431,7 +592,7 @@ namespace Microsoft.PowerShell.Linux.Host /// The left anchor for repositioning the cursor. /// private int anchorLeft; - + /// /// Initializes a new instance of the Cursor class. /// @@ -478,8 +639,6 @@ namespace Microsoft.PowerShell.Linux.Host Console.CursorTop = cursorTop; } - } // End Cursor - */ + } // End Cursor } } -