diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs index 2411e79d5c..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) @@ -360,14 +366,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 +394,7 @@ OPTIONS catch (IncompleteParseException) { incompleteLine = true; - cmd = cmd.Trim(); - partialLine += cmd; - - partialLine += System.Environment.NewLine; - + partialLine = $"{partialLine}{cmd}{System.Environment.NewLine}"; } finally @@ -526,7 +522,6 @@ OPTIONS this.currentPowerShell.Stop(); } } - e.Cancel = true; } catch (Exception exception) @@ -544,7 +539,8 @@ OPTIONS { // Set up the control-C handler. 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". @@ -564,9 +560,26 @@ OPTIONS prompt = "PS> "; } - this.myHost.UI.Write(ConsoleColor.White, Console.BackgroundColor, prompt); - string cmd = consoleReadLine.Read(this.myHost.Runspace, false); - this.Execute(cmd); + this.myHost.UI.Write(prompt); + + ConsoleReadLine.ReadResult result = consoleReadLine.Read(false, initialCommand); + + switch(result.state) + { + case ConsoleReadLine.ReadResult.State.Abort: + incompleteLine = false; + partialLine = string.Empty; + 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; + } } } @@ -582,12 +595,31 @@ OPTIONS WriteDebuggerStopMessages(args); + string initialCommand = String.Empty; + // 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 >> "; + this.myHost.UI.Write(prompt); + + ConsoleReadLine.ReadResult result = consoleReadLine.Read(true, initialCommand); + + switch(result.state) + { + case ConsoleReadLine.ReadResult.State.Abort: + incompleteLine = false; + partialLine = string.Empty; + 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. var output = new PSDataCollection(); @@ -595,7 +627,7 @@ OPTIONS { foreach (var item in output.ReadAll()) { - Console.WriteLine(item); + this.myHost.UI.WriteLine(item.ToString()); } }; @@ -606,10 +638,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 + result.command) : result.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}{result.command}{System.Environment.NewLine}"; + } + + if (!incompleteLine) + { + partialLine = string.Empty; + } + + if (!incompleteLine && results.ResumeAction != null) { resumeAction = results.ResumeAction; } @@ -632,8 +684,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; } @@ -641,12 +693,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. @@ -654,8 +706,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 7a2df6a234..f44bdbb764 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 @@ -90,10 +96,48 @@ namespace Microsoft.PowerShell.Linux.Host /// private string preTabBuffer; + /// + /// kill buffer used by Ctrl-K and Ctrl-Y + /// + 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 + /// + public class ReadResult + { + public ReadResult(State s, string cmd) + { + this.command = cmd; + this.state = s; + Console.TreatControlCAsInput = false; + } + + public enum State {Complete, Abort, Redraw} + + public string command; + public State state; + } + /// /// Initializes a new instance of the ConsoleReadLine class. /// - public ConsoleReadLine() + public ConsoleReadLine(Runspace runspace, PSHostUserInterface hostUI) { this.tokenColors = new ConsoleColor[] { @@ -118,147 +162,215 @@ 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 string Read(Runspace runspace, bool nested) + public ReadResult Read(bool nested, string initialValue) { - this.powershell.Runspace = runspace; this.Initialize(); + if (!String.IsNullOrEmpty(initialValue)) + { + this.BufferFromString(initialValue); + this.Render(); + } + while (true) { - ConsoleKeyInfo key = Console.ReadKey(true); - - // Basic Emacs-style readline implementation - if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) + try { - switch (key.Key) + ConsoleKeyInfo key = Console.ReadKey(true); + + // Basic Emacs-style readline implementation + if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) { - 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(); - */ + commandComplete = ProcessControlKey(key, nested); } + else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) + { + commandComplete = ProcessAltKey(key); + } + // Unmodified keys + else + { + commandComplete = ProcessNormalKey(key, nested); + } + + previousKeyPress = key; } - else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) + catch { - 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; - } + // if anything goes wrong, abort + abort = true; } - // Unmodified keys - else + + if (abort) { - 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; - } + return new ReadResult(ReadResult.State.Abort, String.Empty); + } + + if (redraw) + { + return new ReadResult(ReadResult.State.Redraw, this.buffer.ToString()); + } + + if (commandComplete) + { + return new ReadResult(ReadResult.State.Complete, this.OnEnter()); } - previousKeyPress = key; } } + /// + /// Process Control-Key combo + /// + private bool ProcessControlKey(ConsoleKeyInfo key, bool nested) + { + switch (key.Key) + { + case ConsoleKey.A: + this.OnHome(); + break; + case ConsoleKey.E: + this.OnEnd(); + break; + case ConsoleKey.K: + this.OnKill(); + break; + case ConsoleKey.Y: + this.OnYank(); + break; + case ConsoleKey.D: + this.OnDelete(); + break; + case ConsoleKey.B: + this.OnLeft(false); + break; + case ConsoleKey.F: + this.OnRight(false); + break; + case ConsoleKey.R: + return this.ReverseSearch(nested); + case ConsoleKey.J: + previousKeyPress = key; + return true; + case ConsoleKey.L: + Console.Clear(); + redraw = true; + return false; + case ConsoleKey.C: + this.Abort(); + 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; + } + + /// + /// Process Alt-Key combo + /// + private bool ProcessAltKey(ConsoleKeyInfo key) + { + switch (key.Key) + { + // TODO: OnDelete(key) + // TODO: OnBackspace(key) + case ConsoleKey.B: + this.OnLeft(true); + break; + case ConsoleKey.F: + this.OnRight(true); + 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(false); + break; + case ConsoleKey.LeftArrow: + this.OnLeft(false); + 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. /// @@ -270,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; } /// @@ -494,6 +610,135 @@ namespace Microsoft.PowerShell.Linux.Host this.newHistory = false; } + /// + /// Reverse search through history + /// + private bool ReverseSearch(bool nested) + { + 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 (true) + { + OnEscape(); + ui.Write("("); + ui.Write(ConsoleColor.Red, Console.BackgroundColor, $"{failed}"); + ui.Write($"bck-i-search)'{searchString}"); + this.current = this.cursor.GetPosition(); + ui.Write("': "); + ui.Write(ConsoleColor.Green, Console.BackgroundColor, $"{searchResult}"); + this.rendered = this.cursor.GetPosition(); + this.cursor.Place(this.current); + + ConsoleKeyInfo key = Console.ReadKey(true); + + bool terminateSearch = false; + if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) + { + switch (key.Key) + { + case ConsoleKey.R: // repeat search + searchPos--; + break; + case ConsoleKey.C: + Abort(); + return true; + default: + terminateSearch = true; + break; + } + } + else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) + { + terminateSearch = true; + } + else + { + 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; + } + + if (searchString.Length == 0) + { + searchResult = String.Empty; + failed = String.Empty; + } + else + { + string result = reverseSearchHistory(searchString.ToString(), ref searchPos); + if (String.IsNullOrEmpty(result)) + { + failed = "failed-"; + } + else + { + failed = String.Empty; + searchResult = result; + } + } + } + } + + /// + /// Reverse search command history for one that match pattern + /// + private string reverseSearchHistory(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 /// @@ -510,11 +755,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) @@ -557,11 +802,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) @@ -625,7 +870,7 @@ namespace Microsoft.PowerShell.Linux.Host private string OnEnter() { this.newHistory = true; - Console.Out.WriteLine(); + ui.WriteLine(); return this.buffer.ToString(); } @@ -641,6 +886,54 @@ namespace Microsoft.PowerShell.Linux.Host } } + /// + /// Ctrl-K or Ctrl-End 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-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. + /// + private void OnYank() + { + if (!String.IsNullOrEmpty(killBuffer)) + { + this.buffer.Insert(this.current, killBuffer); + this.current += killBuffer.Length; + this.Render(); + } + } + /// /// The Backspace key was entered. /// @@ -663,6 +956,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. @@ -671,15 +968,12 @@ namespace Microsoft.PowerShell.Linux.Host if (tokens.Count > 0) { - // We can skip rendering tokens that end before the cursor. - int i; + ConsoleColor saveFGColor = Console.ForegroundColor; - for (i = 0; i < tokens.Count; ++i) + // Print leading blanks + if (tokens[0].Start != 0) { - if (this.current >= tokens[i].Start) - { - break; - } + ui.Write(new string(' ', tokens[0].Start)); } // Place the cursor at the start of the first token to render. The @@ -687,13 +981,14 @@ 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 // 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)) @@ -701,7 +996,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()); } } } @@ -711,8 +1006,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 { @@ -720,19 +1017,31 @@ 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; this.cursor.Place(this.current); } + /// + /// Abort current command + /// + public void Abort() + { + ConsoleColor saveFGColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + ui.WriteLine("^C"); + Console.ForegroundColor = saveFGColor; + this.abort = true; + } + /// /// A helper class for maintaining the cursor while editing the command line. /// @@ -796,6 +1105,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 } 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; } ///