From cc0b25e515cf7e38cd08ac265afa81e9d96cd249 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Tue, 15 Mar 2016 13:48:31 -0700 Subject: [PATCH 01/10] Pending verification of TreatControlCAsInput --- src/Microsoft.PowerShell.Linux.Host/main.cs | 75 +++-- .../readline.cs | 280 +++++++++++------- 2 files changed, 224 insertions(+), 131 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs index 2411e79d5c..2965c93869 100644 --- a/src/Microsoft.PowerShell.Linux.Host/main.cs +++ b/src/Microsoft.PowerShell.Linux.Host/main.cs @@ -360,14 +360,8 @@ OPTIONS { this.currentPowerShell.Runspace = this.myRunSpace; - if (incompleteLine) - { - this.currentPowerShell.AddScript(partialLine + cmd); - } - else - { - this.currentPowerShell.AddScript(cmd); - } + string fullCommand = incompleteLine ? (partialLine + cmd) : cmd; + this.currentPowerShell.AddScript(fullCommand); incompleteLine = false; // Add the default outputter to the end of the pipe and then call the @@ -394,11 +388,7 @@ OPTIONS catch (IncompleteParseException) { incompleteLine = true; - cmd = cmd.Trim(); - partialLine += cmd; - - partialLine += System.Environment.NewLine; - + partialLine = $"{partialLine}{cmd}{System.Environment.NewLine}"; } finally @@ -526,7 +516,6 @@ OPTIONS this.currentPowerShell.Stop(); } } - e.Cancel = true; } catch (Exception exception) @@ -565,7 +554,19 @@ OPTIONS } this.myHost.UI.Write(ConsoleColor.White, Console.BackgroundColor, prompt); - string cmd = consoleReadLine.Read(this.myHost.Runspace, false); + + bool abort; + string cmd = consoleReadLine.Read(this.myHost.Runspace, false, out abort); + if (string.IsNullOrEmpty(cmd)) + { + if (abort) + { + incompleteLine = false; + partialLine = string.Empty; + } + continue; + } + this.Execute(cmd); } } @@ -585,9 +586,21 @@ OPTIONS // loop to process Debugger commands. while (resumeAction == null) { - Console.Write("[DBG] PS >> "); - string command = consoleReadLine.Read(this.myHost.Runspace, true); - Console.WriteLine(); + string prompt = incompleteLine ? ">> " : "[DBG] PS >> "; + Console.Write(prompt); + + bool abort; + string command = consoleReadLine.Read(this.myHost.Runspace, true, out abort); + + if (string.IsNullOrEmpty(command)) + { + if (abort) + { + incompleteLine = false; + partialLine = string.Empty; + } + continue; + } // Stream output from command processing to console. var output = new PSDataCollection(); @@ -606,10 +619,30 @@ OPTIONS // command or script. The returned DebuggerCommandResults object will indicate // whether the command was evaluated by the debugger and if the debugger should // be released with a specific resume action. + PSCommand psCommand = new PSCommand(); - psCommand.AddScript(command).AddCommand("Out-String").AddParameter("Stream", true); - DebuggerCommandResults results = debugger.ProcessCommand(psCommand, output); - if (results.ResumeAction != null) + + string fullCommand = incompleteLine ? (partialLine + command) : command; + psCommand.AddScript(fullCommand).AddCommand("Out-String").AddParameter("Stream", true); + incompleteLine = false; + + DebuggerCommandResults results = null; + try + { + results = debugger.ProcessCommand(psCommand, output); + } + catch (IncompleteParseException) + { + incompleteLine = true; + partialLine = $"{partialLine}{command}{System.Environment.NewLine}"; + } + + if (!incompleteLine) + { + partialLine = string.Empty; + } + + if (!incompleteLine && results.ResumeAction != null) { resumeAction = results.ResumeAction; } diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index 7a2df6a234..7c7d8c7b14 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -124,10 +124,14 @@ namespace Microsoft.PowerShell.Linux.Host /// Read a line of text, colorizing while typing. /// /// The command line read - public string Read(Runspace runspace, bool nested) + public string Read(Runspace runspace, bool nested, out bool abort) { this.powershell.Runspace = runspace; this.Initialize(); + bool finishedTyping = false; + abort = false; +// Console.WriteLine("Default value of TreatCtrlCAsInput: {0}", Console.TreatControlCAsInput); +// Console.TreatControlCAsInput = false; while (true) { @@ -136,129 +140,174 @@ namespace Microsoft.PowerShell.Linux.Host // Basic Emacs-style readline implementation if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) { - switch (key.Key) + finishedTyping = ProcessControlKey(key, nested, ref abort); + if (abort) { - case ConsoleKey.A: - this.OnHome(); - break; - case ConsoleKey.E: - this.OnEnd(); - break; - // TODO: save buffer, yank with Ctrl-Y - /* - case ConsoleKey.K: - this.OnEscape(); - break; - */ - case ConsoleKey.D: - this.OnDelete(); - break; - case ConsoleKey.B: - this.OnLeft(key.Modifiers); - break; - case ConsoleKey.F: - this.OnRight(key.Modifiers); - break; - case ConsoleKey.P: - // TODO: incremental search - case ConsoleKey.R: - this.OnUpArrow(nested); - break; - case ConsoleKey.N: - // TODO: incremental search - case ConsoleKey.S: - this.OnDownArrow(nested); - break; - case ConsoleKey.J: - this.OnEnter(); - break; - // TODO: save and restore buffer - /* - case ConsoleKey.L: - this.BufferFromString("clear"); - previousKeyPress = key; - return this.OnEnter(); - */ + Console.WriteLine(); +// Console.TreatControlCAsInput = true; + return String.Empty; } } else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) { - switch (key.Key) - { - // TODO: OnDelete(key) - // TODO: OnBackspace(key) - case ConsoleKey.B: - this.OnLeft(key.Modifiers); - break; - case ConsoleKey.F: - this.OnRight(key.Modifiers); - break; - } + finishedTyping = ProcessAltKey(key); } // Unmodified keys else { - switch (key.Key) - { - case ConsoleKey.Backspace: - this.OnBackspace(); - break; - case ConsoleKey.Delete: - this.OnDelete(); - break; - case ConsoleKey.Enter: - previousKeyPress = key; - return this.OnEnter(); - case ConsoleKey.RightArrow: - this.OnRight(key.Modifiers); - break; - case ConsoleKey.LeftArrow: - this.OnLeft(key.Modifiers); - break; - case ConsoleKey.Escape: - this.OnEscape(); - break; - case ConsoleKey.Home: - this.OnHome(); - break; - case ConsoleKey.End: - this.OnEnd(); - break; - case ConsoleKey.Tab: - this.OnTab(); - break; - case ConsoleKey.UpArrow: - this.OnUpArrow(nested); - break; - case ConsoleKey.DownArrow: - this.OnDownArrow(nested); - break; - - // 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 - } - - if (key.KeyChar == '\x08') - { - goto case ConsoleKey.Backspace; // Ctrl-H - } - - this.Insert(key.KeyChar); - - this.Render(); - break; - } + finishedTyping = ProcessNormalKey(key, nested); } + previousKeyPress = key; + + if (finishedTyping) + { +// Console.TreatControlCAsInput = true; + return this.OnEnter(); + } } } + /// + /// Process Control-Key combo + /// + private bool ProcessControlKey(ConsoleKeyInfo key, bool nested, ref bool abort) + { + switch (key.Key) + { + case ConsoleKey.A: + this.OnHome(); + break; + case ConsoleKey.E: + this.OnEnd(); + break; + // TODO: save buffer, yank with Ctrl-Y + /* + case ConsoleKey.K: + this.OnEscape(); + break; + */ + case ConsoleKey.D: + this.OnDelete(); + break; + case ConsoleKey.B: + this.OnLeft(key.Modifiers); + break; + case ConsoleKey.F: + this.OnRight(key.Modifiers); + break; + case ConsoleKey.P: + // TODO: incremental search + case ConsoleKey.R: + this.OnUpArrow(nested); + break; + case ConsoleKey.N: + // TODO: incremental search + case ConsoleKey.S: + this.OnDownArrow(nested); + break; + case ConsoleKey.J: + this.OnEnter(); + break; + // TODO: save and restore buffer + /* + case ConsoleKey.L: + this.BufferFromString("clear"); + previousKeyPress = key; + return this.OnEnter(); + */ + case ConsoleKey.C: + Console.WriteLine("received Ctrl-C"); + this.Abort(); + abort = true; + break; + } + return false; + } + + /// + /// Process Alt-Key combo + /// + private bool ProcessAltKey(ConsoleKeyInfo key) + { + switch (key.Key) + { + // TODO: OnDelete(key) + // TODO: OnBackspace(key) + case ConsoleKey.B: + this.OnLeft(key.Modifiers); + break; + case ConsoleKey.F: + this.OnRight(key.Modifiers); + break; + } + return false; + } + + /// + /// Process normal key + /// + private bool ProcessNormalKey(ConsoleKeyInfo key, bool nested) + { + switch (key.Key) + { + case ConsoleKey.Backspace: + this.OnBackspace(); + break; + case ConsoleKey.Delete: + this.OnDelete(); + break; + case ConsoleKey.Enter: + previousKeyPress = key; + return true; + case ConsoleKey.RightArrow: + this.OnRight(key.Modifiers); + break; + case ConsoleKey.LeftArrow: + this.OnLeft(key.Modifiers); + break; + case ConsoleKey.Escape: + this.OnEscape(); + break; + case ConsoleKey.Home: + this.OnHome(); + break; + case ConsoleKey.End: + this.OnEnd(); + break; + case ConsoleKey.Tab: + this.OnTab(); + break; + case ConsoleKey.UpArrow: + this.OnUpArrow(nested); + break; + case ConsoleKey.DownArrow: + this.OnDownArrow(nested); + break; + + // 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 + } + + if (key.KeyChar == '\x08') + { + goto case ConsoleKey.Backspace; // Ctrl-H + } + + this.Insert(key.KeyChar); + + this.Render(); + break; + } + return false; + } + /// /// Initializes the buffer. /// @@ -733,6 +782,17 @@ namespace Microsoft.PowerShell.Linux.Host this.cursor.Place(this.current); } + /// + /// Abort current command + /// + public void Abort() + { + ConsoleColor saveFGColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine("^C"); + Console.ForegroundColor = saveFGColor; + } + /// /// A helper class for maintaining the cursor while editing the command line. /// From 2deb871ab227b2c994870755efc4b70753ea1841 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Wed, 16 Mar 2016 09:29:00 -0700 Subject: [PATCH 02/10] Tested, except for reversed TreatControlCAsInput --- .../readline.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index 7c7d8c7b14..768f15f710 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -128,10 +128,10 @@ namespace Microsoft.PowerShell.Linux.Host { this.powershell.Runspace = runspace; this.Initialize(); - bool finishedTyping = false; + bool commandComplete = false; abort = false; // Console.WriteLine("Default value of TreatCtrlCAsInput: {0}", Console.TreatControlCAsInput); -// Console.TreatControlCAsInput = false; + Console.TreatControlCAsInput = false; while (true) { @@ -140,29 +140,28 @@ namespace Microsoft.PowerShell.Linux.Host // Basic Emacs-style readline implementation if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) { - finishedTyping = ProcessControlKey(key, nested, ref abort); + commandComplete = ProcessControlKey(key, nested, ref abort); if (abort) { - Console.WriteLine(); -// Console.TreatControlCAsInput = true; + Console.TreatControlCAsInput = true; return String.Empty; } } else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) { - finishedTyping = ProcessAltKey(key); + commandComplete = ProcessAltKey(key); } // Unmodified keys else { - finishedTyping = ProcessNormalKey(key, nested); + commandComplete = ProcessNormalKey(key, nested); } previousKeyPress = key; - if (finishedTyping) + if (commandComplete) { -// Console.TreatControlCAsInput = true; + Console.TreatControlCAsInput = true; return this.OnEnter(); } } @@ -217,10 +216,9 @@ namespace Microsoft.PowerShell.Linux.Host return this.OnEnter(); */ case ConsoleKey.C: - Console.WriteLine("received Ctrl-C"); this.Abort(); abort = true; - break; + return true; } return false; } From e713af87bbe7d86f792b4e2673b27ef514d9c162 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Wed, 16 Mar 2016 16:17:07 -0700 Subject: [PATCH 03/10] Adding support for Ctrl-K, Ctrl-Y, and Ctrl-L --- src/Microsoft.PowerShell.Linux.Host/main.cs | 52 +++++--- .../readline.cs | 115 ++++++++++++++---- 2 files changed, 121 insertions(+), 46 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs index 2965c93869..19abe349b2 100644 --- a/src/Microsoft.PowerShell.Linux.Host/main.cs +++ b/src/Microsoft.PowerShell.Linux.Host/main.cs @@ -535,6 +535,8 @@ OPTIONS Console.CancelKeyPress += new ConsoleCancelEventHandler(this.HandleControlC); //Console.TreatControlCAsInput = false; + string initialCommand = String.Empty; + // Read commands and run them until the ShouldExit flag is set by // the user calling "exit". while (!this.ShouldExit && this.myHost.Runspace != null) @@ -555,19 +557,24 @@ OPTIONS this.myHost.UI.Write(ConsoleColor.White, Console.BackgroundColor, prompt); - bool abort; - string cmd = consoleReadLine.Read(this.myHost.Runspace, false, out abort); - if (string.IsNullOrEmpty(cmd)) + ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, false, initialCommand); + + switch(result.state) { - if (abort) - { + case ConsoleReadLine.ReadResult.State.Abort: incompleteLine = false; partialLine = string.Empty; - } - continue; + initialCommand = String.Empty; + break; + case ConsoleReadLine.ReadResult.State.Redraw: + initialCommand = result.command; + break; + case ConsoleReadLine.ReadResult.State.Complete: + default: + this.Execute(result.command); + initialCommand = String.Empty; + break; } - - this.Execute(cmd); } } @@ -583,23 +590,30 @@ OPTIONS WriteDebuggerStopMessages(args); + string initialCommand = String.Empty; + // loop to process Debugger commands. while (resumeAction == null) { string prompt = incompleteLine ? ">> " : "[DBG] PS >> "; Console.Write(prompt); - bool abort; - string command = consoleReadLine.Read(this.myHost.Runspace, true, out abort); - - if (string.IsNullOrEmpty(command)) + ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, true, initialCommand); + + switch(result.state) { - if (abort) - { + case ConsoleReadLine.ReadResult.State.Abort: incompleteLine = false; partialLine = string.Empty; - } - continue; + initialCommand = String.Empty; + continue; + case ConsoleReadLine.ReadResult.State.Redraw: + initialCommand = result.command; + continue; + case ConsoleReadLine.ReadResult.State.Complete: + default: + initialCommand = String.Empty; + break; } // Stream output from command processing to console. @@ -622,7 +636,7 @@ OPTIONS PSCommand psCommand = new PSCommand(); - string fullCommand = incompleteLine ? (partialLine + command) : command; + string fullCommand = incompleteLine ? (partialLine + result.command) : result.command; psCommand.AddScript(fullCommand).AddCommand("Out-String").AddParameter("Stream", true); incompleteLine = false; @@ -634,7 +648,7 @@ OPTIONS catch (IncompleteParseException) { incompleteLine = true; - partialLine = $"{partialLine}{command}{System.Environment.NewLine}"; + partialLine = $"{partialLine}{result.command}{System.Environment.NewLine}"; } if (!incompleteLine) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index 768f15f710..0a2a77cc52 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -90,6 +90,29 @@ namespace Microsoft.PowerShell.Linux.Host /// private string preTabBuffer; + /// + /// kill buffer used by Ctrl-K and Ctrl-Y + /// + private string killBuffer = String.Empty; + + /// + /// What Read() outputs + /// + public class ReadResult + { + public ReadResult(State s, string cmd) + { + this.command = cmd; + this.state = s; + Console.TreatControlCAsInput = true; + } + + public enum State {Complete, Abort, Redraw} + + public string command; + public State state; + } + /// /// Initializes a new instance of the ConsoleReadLine class. /// @@ -124,13 +147,20 @@ namespace Microsoft.PowerShell.Linux.Host /// Read a line of text, colorizing while typing. /// /// The command line read - public string Read(Runspace runspace, bool nested, out bool abort) + public ReadResult Read(Runspace runspace, bool nested, string initialValue) { this.powershell.Runspace = runspace; this.Initialize(); bool commandComplete = false; - abort = false; -// Console.WriteLine("Default value of TreatCtrlCAsInput: {0}", Console.TreatControlCAsInput); + bool abort = false; + bool redraw = false; + + if (!String.IsNullOrEmpty(initialValue)) + { + this.BufferFromString(initialValue); + this.Render(); + } + Console.TreatControlCAsInput = false; while (true) @@ -140,12 +170,7 @@ namespace Microsoft.PowerShell.Linux.Host // Basic Emacs-style readline implementation if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) { - commandComplete = ProcessControlKey(key, nested, ref abort); - if (abort) - { - Console.TreatControlCAsInput = true; - return String.Empty; - } + commandComplete = ProcessControlKey(key, nested, ref abort, ref redraw); } else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) { @@ -159,10 +184,19 @@ namespace Microsoft.PowerShell.Linux.Host previousKeyPress = key; + if (abort) + { + return new ReadResult(ReadResult.State.Abort, String.Empty); + } + + if (redraw) + { + return new ReadResult(ReadResult.State.Redraw, this.buffer.ToString()); + } + if (commandComplete) { - Console.TreatControlCAsInput = true; - return this.OnEnter(); + return new ReadResult(ReadResult.State.Complete, this.OnEnter()); } } } @@ -170,7 +204,7 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Process Control-Key combo /// - private bool ProcessControlKey(ConsoleKeyInfo key, bool nested, ref bool abort) + private bool ProcessControlKey(ConsoleKeyInfo key, bool nested, ref bool abort, ref bool redraw) { switch (key.Key) { @@ -180,12 +214,12 @@ namespace Microsoft.PowerShell.Linux.Host case ConsoleKey.E: this.OnEnd(); break; - // TODO: save buffer, yank with Ctrl-Y - /* - case ConsoleKey.K: - this.OnEscape(); - break; - */ + case ConsoleKey.K: + this.OnKill(); + break; + case ConsoleKey.Y: + this.OnYank(); + break; case ConsoleKey.D: this.OnDelete(); break; @@ -206,15 +240,12 @@ namespace Microsoft.PowerShell.Linux.Host this.OnDownArrow(nested); break; case ConsoleKey.J: - this.OnEnter(); - break; - // TODO: save and restore buffer - /* - case ConsoleKey.L: - this.BufferFromString("clear"); - previousKeyPress = key; - return this.OnEnter(); - */ + previousKeyPress = key; + return true; + case ConsoleKey.L: + Console.Clear(); + redraw = true; + return false; case ConsoleKey.C: this.Abort(); abort = true; @@ -688,6 +719,36 @@ namespace Microsoft.PowerShell.Linux.Host } } + /// + /// Ctrl-K was entered. + /// + private void OnKill() + { + if (this.buffer.Length > 0 && this.current < this.buffer.Length) + { + this.killBuffer = this.buffer.ToString().Substring(this.current); + this.buffer.Remove(this.current, killBuffer.Length); + this.Render(); + } + else + { + this.killBuffer = String.Empty; + } + } + + /// + /// Ctrl-Y was entered. + /// + private void OnYank() + { + if (!String.IsNullOrEmpty(killBuffer)) + { + this.buffer.Insert(this.current, killBuffer); + this.current += killBuffer.Length; + this.Render(); + } + } + /// /// The Backspace key was entered. /// From 89839b69e76b08f58b5cc3d287d8e73f9b70f6fa Mon Sep 17 00:00:00 2001 From: George Fleming Date: Thu, 17 Mar 2016 14:16:12 -0700 Subject: [PATCH 04/10] Render multi-line command as a single-line command --- src/Microsoft.PowerShell.Linux.Host/readline.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index 0a2a77cc52..d8b3935aa0 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -771,6 +771,10 @@ namespace Microsoft.PowerShell.Linux.Host string text = this.buffer.ToString(); + // TODO: Rendering of a multiline command on separate lines. + // For now, just display it as a single-line command. + text = text.Replace(System.Environment.NewLine, " "); + // 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. From 2708d2bc08df9e566475c7eb69ca3c15bbe58ee1 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Thu, 17 Mar 2016 16:50:55 -0700 Subject: [PATCH 05/10] Add Ctrl-R reverse search --- .../readline.cs | 102 ++++++++++++++++-- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index d8b3935aa0..d7e40dd202 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -229,16 +229,8 @@ namespace Microsoft.PowerShell.Linux.Host case ConsoleKey.F: this.OnRight(key.Modifiers); break; - case ConsoleKey.P: - // TODO: incremental search case ConsoleKey.R: - this.OnUpArrow(nested); - break; - case ConsoleKey.N: - // TODO: incremental search - case ConsoleKey.S: - this.OnDownArrow(nested); - break; + return this.ReverseSearch(nested, ref abort); case ConsoleKey.J: previousKeyPress = key; return true; @@ -572,6 +564,98 @@ namespace Microsoft.PowerShell.Linux.Host this.newHistory = false; } + /// + /// Reverse search through history + /// + private bool ReverseSearch(bool nested, ref bool abort) + { + GetHistory(nested); + + if (historyResult.Count == 0) + { + return false; + } + int searchPos = historyResult.Count - 1; + + StringBuilder searchString = new StringBuilder(); + string searchResult = String.Empty; + string failed = String.Empty; + + while (!abort) + { + OnEscape(); + Console.Out.Write("({0}reverse-i-search)'{1}':{2} ", failed, searchString.ToString(), searchResult); + + ConsoleKeyInfo key = Console.ReadKey(true); + + if (key.Modifiers.HasFlag(ConsoleModifiers.Control) && key.Key == ConsoleKey.R) + { + // do nothing + } + else if (key.Modifiers.HasFlag(ConsoleModifiers.Control) && key.Key == ConsoleKey.C) + { + Abort(); + return true; + } + else if (key.Key == ConsoleKey.Backspace) + { + if (searchString.Length > 0) + { + searchString.Length--; + } + searchPos = historyResult.Count - 1; + } + else if (key.Key == ConsoleKey.Enter) + { + BufferFromString(searchResult); + return true; + } + else if (((key.Key >= ConsoleKey.A && key.Key <= ConsoleKey.Z) + || (key.Key >= ConsoleKey.D0 && key.Key <= ConsoleKey.D9)) + && !key.Modifiers.HasFlag(ConsoleModifiers.Control) + && !key.Modifiers.HasFlag(ConsoleModifiers.Alt)) + { + searchString.Append(key.KeyChar); + } + else + { + OnEscape(); + BufferFromString(searchResult); + Render(); + return false; + } + + string result = searchHistory(searchString.ToString(), ref searchPos); + if (String.IsNullOrEmpty(result)) + { + failed = "failed "; + } + else + { + searchResult = result; + } + } + return false; // for compiler + } + + /// + /// Reverse search command history for one that match pattern + /// + private string searchHistory(string pattern, ref int pos) + { + for (int i = pos; i >= 0; --i) + { + string command = historyResult[i].Members["CommandLine"].Value.ToString(); + if (command.Contains(pattern)) + { + pos = i; + return command; + } + } + pos = -1; + return String.Empty; + } + /// /// Helper function to get command history /// From d234ca24996df99de2174122f74e382622ca7be2 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Fri, 18 Mar 2016 10:23:23 -0700 Subject: [PATCH 06/10] Add color to reverse search --- .../readline.cs | 116 +++++++++++++----- 1 file changed, 84 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index d7e40dd202..c1e59c0c7e 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -581,67 +581,110 @@ namespace Microsoft.PowerShell.Linux.Host string searchResult = String.Empty; string failed = String.Empty; - while (!abort) + while (true) { OnEscape(); - Console.Out.Write("({0}reverse-i-search)'{1}':{2} ", failed, searchString.ToString(), searchResult); + ConsoleColor saveFGColor = Console.ForegroundColor; + Console.Out.Write("("); + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.Write("{0}", failed); + Console.ForegroundColor = saveFGColor; + Console.Out.Write("reverse-i-search)'{0}", searchString.ToString()); + this.current = this.cursor.GetPosition(); + Console.Out.Write("': "); + Console.ForegroundColor = ConsoleColor.Green; + Console.Out.Write("{0}", searchResult); + Console.ForegroundColor = saveFGColor; + this.rendered = this.cursor.GetPosition(); + this.cursor.Place(this.current); ConsoleKeyInfo key = Console.ReadKey(true); - if (key.Modifiers.HasFlag(ConsoleModifiers.Control) && key.Key == ConsoleKey.R) + bool terminateSearch = false; + if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) { - // do nothing - } - else if (key.Modifiers.HasFlag(ConsoleModifiers.Control) && key.Key == ConsoleKey.C) - { - Abort(); - return true; - } - else if (key.Key == ConsoleKey.Backspace) - { - if (searchString.Length > 0) + switch (key.Key) { - searchString.Length--; + case ConsoleKey.R: // repeat search + searchPos--; + break; + case ConsoleKey.C: + Abort(); + abort = true; + return true; + default: + terminateSearch = true; + break; } - searchPos = historyResult.Count - 1; } - else if (key.Key == ConsoleKey.Enter) + else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) { - BufferFromString(searchResult); - return true; - } - else if (((key.Key >= ConsoleKey.A && key.Key <= ConsoleKey.Z) - || (key.Key >= ConsoleKey.D0 && key.Key <= ConsoleKey.D9)) - && !key.Modifiers.HasFlag(ConsoleModifiers.Control) - && !key.Modifiers.HasFlag(ConsoleModifiers.Alt)) - { - searchString.Append(key.KeyChar); + terminateSearch = true; } else { - OnEscape(); + switch (key.Key) + { + case ConsoleKey.Backspace: + if (searchString.Length > 0) + { + searchString.Length--; + } + searchPos = historyResult.Count - 1; + break; + case ConsoleKey.Enter: + BufferFromString(searchResult); + Render(); + return true; + case ConsoleKey.Delete: + case ConsoleKey.RightArrow: + case ConsoleKey.LeftArrow: + case ConsoleKey.Escape: + case ConsoleKey.Home: + case ConsoleKey.End: + case ConsoleKey.Tab: + case ConsoleKey.UpArrow: + case ConsoleKey.DownArrow: + terminateSearch = true; + break; + default: + searchString.Append(key.KeyChar); + break; + } + } + + if (terminateSearch) + { BufferFromString(searchResult); Render(); return false; } - string result = searchHistory(searchString.ToString(), ref searchPos); - if (String.IsNullOrEmpty(result)) + if (searchString.Length == 0) { - failed = "failed "; + searchResult = String.Empty; + failed = String.Empty; } else { - searchResult = result; + string result = reverseSearchHistory(searchString.ToString(), ref searchPos); + if (String.IsNullOrEmpty(result)) + { + failed = "failed "; + } + else + { + failed = String.Empty; + searchResult = result; + } } } - return false; // for compiler } /// /// Reverse search command history for one that match pattern /// - private string searchHistory(string pattern, ref int pos) + private string reverseSearchHistory(string pattern, ref int pos) { for (int i = pos; i >= 0; --i) { @@ -1003,6 +1046,15 @@ namespace Microsoft.PowerShell.Linux.Host Console.CursorTop = cursorTop; } + + /// + /// Return current position, relative to anchorLeft + /// + internal int GetPosition() + { + return Console.CursorLeft - this.anchorLeft; + } + } // End Cursor } From 5a487f08367d21c57be7858ddd24b6d45af4d52b Mon Sep 17 00:00:00 2001 From: George Fleming Date: Fri, 18 Mar 2016 16:31:46 -0700 Subject: [PATCH 07/10] Fix rendering issue with leading spaces, and added support for additional ctrl commands --- .../readline.cs | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index c1e59c0c7e..e70e5c0031 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -224,10 +224,10 @@ namespace Microsoft.PowerShell.Linux.Host this.OnDelete(); break; case ConsoleKey.B: - this.OnLeft(key.Modifiers); + this.OnLeft(false); break; case ConsoleKey.F: - this.OnRight(key.Modifiers); + this.OnRight(false); break; case ConsoleKey.R: return this.ReverseSearch(nested, ref abort); @@ -242,6 +242,24 @@ namespace Microsoft.PowerShell.Linux.Host this.Abort(); abort = true; return true; + case ConsoleKey.P: + this.OnUpArrow(nested); + break; + case ConsoleKey.N: + this.OnDownArrow(nested); + break; + case ConsoleKey.RightArrow: + this.OnRight(true); + break; + case ConsoleKey.LeftArrow: + this.OnLeft(true); + break; + case ConsoleKey.Home: + this.OnKillBackward(); + break; + case ConsoleKey.End: + this.OnKill(); + break; } return false; } @@ -256,10 +274,10 @@ namespace Microsoft.PowerShell.Linux.Host // TODO: OnDelete(key) // TODO: OnBackspace(key) case ConsoleKey.B: - this.OnLeft(key.Modifiers); + this.OnLeft(true); break; case ConsoleKey.F: - this.OnRight(key.Modifiers); + this.OnRight(true); break; } return false; @@ -282,10 +300,10 @@ namespace Microsoft.PowerShell.Linux.Host previousKeyPress = key; return true; case ConsoleKey.RightArrow: - this.OnRight(key.Modifiers); + this.OnRight(false); break; case ConsoleKey.LeftArrow: - this.OnLeft(key.Modifiers); + this.OnLeft(false); break; case ConsoleKey.Escape: this.OnEscape(); @@ -715,11 +733,11 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Moves to the left of the cursor position. /// - /// Enumeration for Alt, Control, + /// move by word instead of by letter /// and Shift keys. - private void OnLeft(ConsoleModifiers consoleModifiers) + private void OnLeft(bool byWord) { - if (consoleModifiers.HasFlag(ConsoleModifiers.Alt)) + if (byWord) { // Move back to the start of the previous word. if (this.buffer.Length > 0 && this.current != 0) @@ -762,11 +780,11 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Moves to what is to the right of the cursor position. /// - /// Enumeration for Alt, Control, + /// move by word instead of by letter /// and Shift keys. - private void OnRight(ConsoleModifiers consoleModifiers) + private void OnRight(bool byWord) { - if (consoleModifiers.HasFlag(ConsoleModifiers.Alt)) + if (byWord) { // Move to the next word. if (this.buffer.Length != 0 && this.current < this.buffer.Length) @@ -847,7 +865,7 @@ namespace Microsoft.PowerShell.Linux.Host } /// - /// Ctrl-K was entered. + /// Ctrl-K or Ctrl-End was entered. /// private void OnKill() { @@ -863,6 +881,24 @@ namespace Microsoft.PowerShell.Linux.Host } } + /// + /// Ctrl-Home was entered, kill from begin-of-line to current position + /// + private void OnKillBackward() + { + if (this.current > 0) + { + this.killBuffer = this.buffer.ToString().Substring(0, this.current); + this.buffer.Remove(0, this.current); + this.current = 0; + this.Render(); + } + else + { + this.killBuffer = String.Empty; + } + } + /// /// Ctrl-Y was entered. /// @@ -910,15 +946,10 @@ namespace Microsoft.PowerShell.Linux.Host if (tokens.Count > 0) { - // We can skip rendering tokens that end before the cursor. - int i; - - for (i = 0; i < tokens.Count; ++i) + // Print leading blanks + if (tokens[0].Start != 0) { - if (this.current >= tokens[i].Start) - { - break; - } + Console.Out.Write(new string(' ', tokens[0].Start)); } // Place the cursor at the start of the first token to render. The @@ -926,7 +957,8 @@ namespace Microsoft.PowerShell.Linux.Host // preceding the cursor. //this.cursor.Place(tokens[i].Start); - for (; i < tokens.Count; ++i) + int i; + for (i = 0; i < tokens.Count; ++i) { // Write out the token. We don't use tokens[i].Content, instead we // use the actual text from our input because the content sometimes From d56546228690ce7b0dbc40e12308ae2084533499 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Mon, 21 Mar 2016 15:31:43 -0700 Subject: [PATCH 08/10] Reverse TreatControlCAsInput now that CoreFx fix has arrived --- src/Microsoft.PowerShell.Linux.Host/readline.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index e70e5c0031..1b60a901f4 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -104,7 +104,7 @@ namespace Microsoft.PowerShell.Linux.Host { this.command = cmd; this.state = s; - Console.TreatControlCAsInput = true; + Console.TreatControlCAsInput = false; } public enum State {Complete, Abort, Redraw} @@ -161,7 +161,7 @@ namespace Microsoft.PowerShell.Linux.Host this.Render(); } - Console.TreatControlCAsInput = false; + Console.TreatControlCAsInput = true; while (true) { From aab00e2ee1af65c4d90d99046e7dc4287c209ca6 Mon Sep 17 00:00:00 2001 From: George Fleming Date: Tue, 22 Mar 2016 14:01:59 -0700 Subject: [PATCH 09/10] Fixes UI-related bugs, and migrate Console.Write to myHost.UI.Write --- src/Microsoft.PowerShell.Linux.Host/main.cs | 23 +++++------ src/Microsoft.PowerShell.Linux.Host/rawui.cs | 30 +++++++++++++- .../readline.cs | 41 ++++++++++++------- src/Microsoft.PowerShell.Linux.Host/ui.cs | 10 ++++- 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs index 19abe349b2..b4847dc60a 100644 --- a/src/Microsoft.PowerShell.Linux.Host/main.cs +++ b/src/Microsoft.PowerShell.Linux.Host/main.cs @@ -533,7 +533,6 @@ OPTIONS { // Set up the control-C handler. Console.CancelKeyPress += new ConsoleCancelEventHandler(this.HandleControlC); - //Console.TreatControlCAsInput = false; string initialCommand = String.Empty; @@ -557,7 +556,7 @@ OPTIONS this.myHost.UI.Write(ConsoleColor.White, Console.BackgroundColor, prompt); - ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, false, initialCommand); + ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, this.myHost.UI, false, initialCommand); switch(result.state) { @@ -596,9 +595,9 @@ OPTIONS while (resumeAction == null) { string prompt = incompleteLine ? ">> " : "[DBG] PS >> "; - Console.Write(prompt); + this.myHost.UI.Write(prompt); - ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, true, initialCommand); + ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, this.myHost.UI, true, initialCommand); switch(result.state) { @@ -622,7 +621,7 @@ OPTIONS { foreach (var item in output.ReadAll()) { - Console.WriteLine(item); + this.myHost.UI.WriteLine(item.ToString()); } }; @@ -679,8 +678,8 @@ OPTIONS // Show help message only once. if (!_showHelpMessage) { - Console.WriteLine("Entering debug mode. Type 'h' to get help."); - Console.WriteLine(); + this.myHost.UI.WriteLine("Entering debug mode. Type 'h' to get help."); + this.myHost.UI.WriteLine(); _showHelpMessage = true; } @@ -688,12 +687,12 @@ OPTIONS // pertain to this debugger execution stop point. if (args.Breakpoints.Count > 0) { - Console.WriteLine("Debugger hit breakpoint on:"); + this.myHost.UI.WriteLine("Debugger hit breakpoint on:"); foreach (var breakPoint in args.Breakpoints) { - Console.WriteLine(breakPoint.ToString()); + this.myHost.UI.WriteLine(breakPoint.ToString()); } - Console.WriteLine(); + this.myHost.UI.WriteLine(); } // Script position stop information. @@ -701,8 +700,8 @@ OPTIONS // there is one. if (args.InvocationInfo != null) { - Console.WriteLine(args.InvocationInfo.PositionMessage); - Console.WriteLine(); + this.myHost.UI.WriteLine(args.InvocationInfo.PositionMessage); + this.myHost.UI.WriteLine(); } Console.ForegroundColor = saveFGColor; diff --git a/src/Microsoft.PowerShell.Linux.Host/rawui.cs b/src/Microsoft.PowerShell.Linux.Host/rawui.cs index 48a83b508a..9b359a2daa 100644 --- a/src/Microsoft.PowerShell.Linux.Host/rawui.cs +++ b/src/Microsoft.PowerShell.Linux.Host/rawui.cs @@ -122,14 +122,40 @@ namespace Microsoft.PowerShell.Linux.Host set { Console.SetWindowSize(value.Width, value.Height); } } + /// + /// Cached Window Title, for systems that needs it + /// + private string title = String.Empty; + /// /// Gets or sets the title of the displayed window. The example /// maps the Console.Title property to the value of this property. /// public override string WindowTitle { - get { return Console.Title; } - set { Console.Title = value; } + get + { + // In Unix/Linux systems, Console.Title current results in a not-implemented + // exception. In that case, we return a cached copy of title that was set earlier. + // Obviously, this will not work if: 1) Title was never set in PowerShell, or 2) + // one sets the windows's title outside of PowerShell. + string result; + try + { + result = Console.Title; + } + catch (PlatformNotSupportedException) + { + return title; + } + return result; + } + + set + { + Console.Title = value; + title = value; + } } /// diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index 1b60a901f4..b8695f2b65 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -4,6 +4,7 @@ namespace Microsoft.PowerShell.Linux.Host using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; + using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Text; @@ -19,6 +20,11 @@ namespace Microsoft.PowerShell.Linux.Host /// private PowerShell powershell = PowerShell.Create(); + /// + /// Host's UserInterface + /// + private PSHostUserInterface ui; + /// /// The buffer used to edit. /// @@ -78,7 +84,7 @@ namespace Microsoft.PowerShell.Linux.Host /// We do not pick different colors for every token, those tokens /// use this default. /// - private ConsoleColor defaultColor = ConsoleColor.White; + private ConsoleColor defaultColor = Console.ForegroundColor; /// /// To keep track of whether we have hit "enter" key since last arrow up/down key @@ -147,9 +153,10 @@ namespace Microsoft.PowerShell.Linux.Host /// Read a line of text, colorizing while typing. /// /// The command line read - public ReadResult Read(Runspace runspace, bool nested, string initialValue) + public ReadResult Read(Runspace runspace, PSHostUserInterface hostUI, bool nested, string initialValue) { this.powershell.Runspace = runspace; + this.ui = hostUI; this.Initialize(); bool commandComplete = false; bool abort = false; @@ -603,15 +610,15 @@ namespace Microsoft.PowerShell.Linux.Host { OnEscape(); ConsoleColor saveFGColor = Console.ForegroundColor; - Console.Out.Write("("); + ui.Write("("); Console.ForegroundColor = ConsoleColor.Red; - Console.Out.Write("{0}", failed); + ui.Write(String.Format("{0}", failed)); Console.ForegroundColor = saveFGColor; - Console.Out.Write("reverse-i-search)'{0}", searchString.ToString()); + ui.Write(String.Format("reverse-i-search)'{0}", searchString.ToString())); this.current = this.cursor.GetPosition(); - Console.Out.Write("': "); + ui.Write("': "); Console.ForegroundColor = ConsoleColor.Green; - Console.Out.Write("{0}", searchResult); + ui.Write(String.Format("{0}", searchResult)); Console.ForegroundColor = saveFGColor; this.rendered = this.cursor.GetPosition(); this.cursor.Place(this.current); @@ -848,7 +855,7 @@ namespace Microsoft.PowerShell.Linux.Host private string OnEnter() { this.newHistory = true; - Console.Out.WriteLine(); + ui.WriteLine(); return this.buffer.ToString(); } @@ -946,10 +953,12 @@ namespace Microsoft.PowerShell.Linux.Host if (tokens.Count > 0) { + ConsoleColor saveFGColor = Console.ForegroundColor; + // Print leading blanks if (tokens[0].Start != 0) { - Console.Out.Write(new string(' ', tokens[0].Start)); + ui.Write(new string(' ', tokens[0].Start)); } // Place the cursor at the start of the first token to render. The @@ -964,7 +973,7 @@ namespace Microsoft.PowerShell.Linux.Host // use the actual text from our input because the content sometimes // excludes part of the token, e.g. the quote characters of a string. Console.ForegroundColor = this.tokenColors[(int)tokens[i].Type]; - Console.Out.Write(text.Substring(tokens[i].Start, tokens[i].Length)); + ui.Write(text.Substring(tokens[i].Start, tokens[i].Length)); // Whitespace doesn't show up in the array of tokens. Write it out here. if (i != (tokens.Count - 1)) @@ -972,7 +981,7 @@ namespace Microsoft.PowerShell.Linux.Host Console.ForegroundColor = this.defaultColor; for (int j = (tokens[i].Start + tokens[i].Length); j < tokens[i + 1].Start; ++j) { - Console.Out.Write(text[j]); + ui.Write(text[j].ToString()); } } } @@ -982,8 +991,10 @@ namespace Microsoft.PowerShell.Linux.Host Console.ForegroundColor = this.defaultColor; for (int j = tokens[i - 1].Start + tokens[i - 1].Length; j < text.Length; ++j) { - Console.Out.Write(text[j]); + ui.Write(text[j].ToString()); } + + Console.ForegroundColor = saveFGColor; } else { @@ -991,13 +1002,13 @@ namespace Microsoft.PowerShell.Linux.Host // happens most frequently when the first token is incomplete, like a string // literal missing a closing quote. this.cursor.Reset(); - Console.Out.Write(text); + ui.Write(text); } // If characters were deleted, we must write over previously written characters if (text.Length < this.rendered) { - Console.Out.Write(new string(' ', this.rendered - text.Length)); + ui.Write(new string(' ', this.rendered - text.Length)); } this.rendered = text.Length; @@ -1011,7 +1022,7 @@ namespace Microsoft.PowerShell.Linux.Host { ConsoleColor saveFGColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; - Console.Out.WriteLine("^C"); + ui.WriteLine("^C"); Console.ForegroundColor = saveFGColor; } diff --git a/src/Microsoft.PowerShell.Linux.Host/ui.cs b/src/Microsoft.PowerShell.Linux.Host/ui.cs index 4d105d6556..04aed71c5c 100644 --- a/src/Microsoft.PowerShell.Linux.Host/ui.cs +++ b/src/Microsoft.PowerShell.Linux.Host/ui.cs @@ -349,10 +349,13 @@ ReadNext: ConsoleColor backgroundColor, string value) { + ConsoleColor oldFg = Console.ForegroundColor; + ConsoleColor oldBg = Console.BackgroundColor; Console.ForegroundColor = foregroundColor; Console.BackgroundColor = backgroundColor; Console.Write(value); - Console.ResetColor(); + Console.ForegroundColor = oldFg; + Console.BackgroundColor = oldBg; } @@ -368,10 +371,13 @@ ReadNext: ConsoleColor backgroundColor, string value) { + ConsoleColor oldFg = Console.ForegroundColor; + ConsoleColor oldBg = Console.BackgroundColor; Console.ForegroundColor = foregroundColor; Console.BackgroundColor = backgroundColor; Console.WriteLine(value); - Console.ResetColor(); + Console.ForegroundColor = oldFg; + Console.BackgroundColor = oldBg; } /// From 5e9c31107c4fdbc8671e504d30bd20065b01eace Mon Sep 17 00:00:00 2001 From: George Fleming Date: Wed, 23 Mar 2016 16:17:06 -0700 Subject: [PATCH 10/10] Changes per Andy comments, and add revert of colors at end of PS --- src/Microsoft.PowerShell.Linux.Host/main.cs | 16 +++- .../readline.cs | 94 +++++++++++-------- 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs index b4847dc60a..028913dc27 100644 --- a/src/Microsoft.PowerShell.Linux.Host/main.cs +++ b/src/Microsoft.PowerShell.Linux.Host/main.cs @@ -109,6 +109,9 @@ OPTIONS } // TODO: check for input on stdin + ConsoleColor InitialForegroundColor = Console.ForegroundColor; + ConsoleColor InitialBackgroundColor = Console.BackgroundColor; + // Create the listener and run it Listener listener = new Listener(initialScript, loadProfiles); @@ -118,6 +121,9 @@ OPTIONS listener.Run(); } + Console.ForegroundColor = InitialForegroundColor; + Console.BackgroundColor = InitialBackgroundColor; + // Exit with the desired exit code that was set by the exit command. // The exit code is set in the host by the MyHost.SetShouldExit() method. System.Environment.Exit(listener.ExitCode); @@ -129,7 +135,7 @@ OPTIONS /// /// Used to read user input. /// - internal ConsoleReadLine consoleReadLine = new ConsoleReadLine(); + internal ConsoleReadLine consoleReadLine; /// /// Holds a reference to the runspace for this interpeter. @@ -213,6 +219,7 @@ OPTIONS InitialSessionState iss = InitialSessionState.CreateDefault2(); this.myRunSpace = RunspaceFactory.CreateRunspace(this.myHost, iss); this.myRunSpace.Open(); + this.consoleReadLine = new ConsoleReadLine(this.myHost.Runspace, this.myHost.UI); if (this.myRunSpace.Debugger != null) { @@ -222,7 +229,6 @@ OPTIONS // feature. In order to debug Workflow script functions the debugger // DebugMode must include the DebugModes.LocalScript flag. this.myRunSpace.Debugger.SetDebugMode(DebugModes.LocalScript); - } if (loadProfiles) @@ -554,9 +560,9 @@ OPTIONS prompt = "PS> "; } - this.myHost.UI.Write(ConsoleColor.White, Console.BackgroundColor, prompt); + this.myHost.UI.Write(prompt); - ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, this.myHost.UI, false, initialCommand); + ConsoleReadLine.ReadResult result = consoleReadLine.Read(false, initialCommand); switch(result.state) { @@ -597,7 +603,7 @@ OPTIONS string prompt = incompleteLine ? ">> " : "[DBG] PS >> "; this.myHost.UI.Write(prompt); - ConsoleReadLine.ReadResult result = consoleReadLine.Read(this.myHost.Runspace, this.myHost.UI, true, initialCommand); + ConsoleReadLine.ReadResult result = consoleReadLine.Read(true, initialCommand); switch(result.state) { diff --git a/src/Microsoft.PowerShell.Linux.Host/readline.cs b/src/Microsoft.PowerShell.Linux.Host/readline.cs index b8695f2b65..f44bdbb764 100644 --- a/src/Microsoft.PowerShell.Linux.Host/readline.cs +++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs @@ -101,6 +101,21 @@ namespace Microsoft.PowerShell.Linux.Host /// private string killBuffer = String.Empty; + /// + /// Indicator that command input is complete + /// + private bool commandComplete; + + /// + /// Indicator that current command is to be aborted + /// + private bool abort; + + /// + /// Indicator that we are to redraw current command + /// + private bool redraw; + /// /// What Read() outputs /// @@ -122,7 +137,7 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Initializes a new instance of the ConsoleReadLine class. /// - public ConsoleReadLine() + public ConsoleReadLine(Runspace runspace, PSHostUserInterface hostUI) { this.tokenColors = new ConsoleColor[] { @@ -147,20 +162,17 @@ namespace Microsoft.PowerShell.Linux.Host this.defaultColor, // LineContinuation this.defaultColor, // Position }; + this.powershell.Runspace = runspace; + this.ui = hostUI; } /// /// Read a line of text, colorizing while typing. /// /// The command line read - public ReadResult Read(Runspace runspace, PSHostUserInterface hostUI, bool nested, string initialValue) + public ReadResult Read(bool nested, string initialValue) { - this.powershell.Runspace = runspace; - this.ui = hostUI; this.Initialize(); - bool commandComplete = false; - bool abort = false; - bool redraw = false; if (!String.IsNullOrEmpty(initialValue)) { @@ -168,28 +180,34 @@ namespace Microsoft.PowerShell.Linux.Host this.Render(); } - Console.TreatControlCAsInput = true; - while (true) { - ConsoleKeyInfo key = Console.ReadKey(true); + try + { + ConsoleKeyInfo key = Console.ReadKey(true); - // Basic Emacs-style readline implementation - if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) - { - commandComplete = ProcessControlKey(key, nested, ref abort, ref redraw); - } - else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) - { - commandComplete = ProcessAltKey(key); - } - // Unmodified keys - else - { - commandComplete = ProcessNormalKey(key, nested); - } + // Basic Emacs-style readline implementation + if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + commandComplete = ProcessControlKey(key, nested); + } + else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) + { + commandComplete = ProcessAltKey(key); + } + // Unmodified keys + else + { + commandComplete = ProcessNormalKey(key, nested); + } - previousKeyPress = key; + previousKeyPress = key; + } + catch + { + // if anything goes wrong, abort + abort = true; + } if (abort) { @@ -211,7 +229,7 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Process Control-Key combo /// - private bool ProcessControlKey(ConsoleKeyInfo key, bool nested, ref bool abort, ref bool redraw) + private bool ProcessControlKey(ConsoleKeyInfo key, bool nested) { switch (key.Key) { @@ -237,7 +255,7 @@ namespace Microsoft.PowerShell.Linux.Host this.OnRight(false); break; case ConsoleKey.R: - return this.ReverseSearch(nested, ref abort); + return this.ReverseSearch(nested); case ConsoleKey.J: previousKeyPress = key; return true; @@ -247,7 +265,6 @@ namespace Microsoft.PowerShell.Linux.Host return false; case ConsoleKey.C: this.Abort(); - abort = true; return true; case ConsoleKey.P: this.OnUpArrow(nested); @@ -365,6 +382,10 @@ namespace Microsoft.PowerShell.Linux.Host this.current = 0; this.rendered = 0; this.cursor = new Cursor(); + this.commandComplete = false; + this.abort = false; + this.redraw = false; + Console.TreatControlCAsInput = true; } /// @@ -592,7 +613,7 @@ namespace Microsoft.PowerShell.Linux.Host /// /// Reverse search through history /// - private bool ReverseSearch(bool nested, ref bool abort) + private bool ReverseSearch(bool nested) { GetHistory(nested); @@ -609,17 +630,12 @@ namespace Microsoft.PowerShell.Linux.Host while (true) { OnEscape(); - ConsoleColor saveFGColor = Console.ForegroundColor; ui.Write("("); - Console.ForegroundColor = ConsoleColor.Red; - ui.Write(String.Format("{0}", failed)); - Console.ForegroundColor = saveFGColor; - ui.Write(String.Format("reverse-i-search)'{0}", searchString.ToString())); + ui.Write(ConsoleColor.Red, Console.BackgroundColor, $"{failed}"); + ui.Write($"bck-i-search)'{searchString}"); this.current = this.cursor.GetPosition(); ui.Write("': "); - Console.ForegroundColor = ConsoleColor.Green; - ui.Write(String.Format("{0}", searchResult)); - Console.ForegroundColor = saveFGColor; + ui.Write(ConsoleColor.Green, Console.BackgroundColor, $"{searchResult}"); this.rendered = this.cursor.GetPosition(); this.cursor.Place(this.current); @@ -635,7 +651,6 @@ namespace Microsoft.PowerShell.Linux.Host break; case ConsoleKey.C: Abort(); - abort = true; return true; default: terminateSearch = true; @@ -695,7 +710,7 @@ namespace Microsoft.PowerShell.Linux.Host string result = reverseSearchHistory(searchString.ToString(), ref searchPos); if (String.IsNullOrEmpty(result)) { - failed = "failed "; + failed = "failed-"; } else { @@ -1024,6 +1039,7 @@ namespace Microsoft.PowerShell.Linux.Host Console.ForegroundColor = ConsoleColor.Red; ui.WriteLine("^C"); Console.ForegroundColor = saveFGColor; + this.abort = true; } ///