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;
}
///