diff --git a/src/Microsoft.PowerShell.Linux.Host/main.cs b/src/Microsoft.PowerShell.Linux.Host/main.cs
index be52ea07159..9714a08d5ff 100644
--- a/src/Microsoft.PowerShell.Linux.Host/main.cs
+++ b/src/Microsoft.PowerShell.Linux.Host/main.cs
@@ -177,6 +177,16 @@ internal class Listener
///
private string partialLine = string.Empty;
+ ///
+ /// To keep track of whether CTRL-C is causing a command abort
+ ///
+ private bool abortCommand = false;
+
+ ///
+ /// To keep track of whether we are in input mode
+ ///
+ private bool awaitingUserInput = false;
+
///
/// Gets or sets a value indicating whether the host application
/// should exit.
@@ -342,6 +352,13 @@ private void ExecuteHelper(string cmd, object input)
// Ignore empty command lines.
if (string.IsNullOrEmpty(cmd))
{
+ if (this.abortCommand)
+ {
+ incompleteLine = false;
+ partialLine = string.Empty;
+ this.abortCommand = false;
+ }
+
return;
}
@@ -360,14 +377,8 @@ private void ExecuteHelper(string cmd, object input)
{
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 +405,7 @@ private void ExecuteHelper(string cmd, object input)
catch (IncompleteParseException)
{
incompleteLine = true;
- cmd = cmd.Trim();
- partialLine += cmd;
-
- partialLine += System.Environment.NewLine;
-
+ partialLine = $"{partialLine}{cmd}{System.Environment.NewLine}";
}
finally
@@ -514,6 +521,15 @@ private void Execute(string cmd)
/// ConsoleCancelEventHandler.
private void HandleControlC(object sender, ConsoleCancelEventArgs e)
{
+ e.Cancel = true;
+
+ if (this.awaitingUserInput)
+ {
+ this.abortCommand = true;
+ this.consoleReadLine.Abort();
+ return;
+ }
+
try
{
lock (this.instanceLock)
@@ -524,7 +540,6 @@ private void HandleControlC(object sender, ConsoleCancelEventArgs e)
}
}
- e.Cancel = true;
}
catch (Exception exception)
{
@@ -559,7 +574,12 @@ internal void Run()
}
this.myHost.UI.Write(ConsoleColor.White, Console.BackgroundColor, prompt);
+
+ // Since Console.TreatControlCAsInput is not implemented, we use awaitingUserInput to
+ // tell us how control-c should be handled
+ this.awaitingUserInput = true;
string cmd = consoleReadLine.Read(this.myHost.Runspace, false);
+ this.awaitingUserInput = false;
this.Execute(cmd);
}
}
@@ -579,9 +599,23 @@ private void HandleDebuggerStopEvent(object sender, DebuggerStopEventArgs args)
// loop to process Debugger commands.
while (resumeAction == null)
{
- Console.Write("[DBG] PS >> ");
+ string prompt = incompleteLine ? ">> " : "[DBG] PS >> ";
+ Console.Write(prompt);
+
+ this.awaitingUserInput = true;
string command = consoleReadLine.Read(this.myHost.Runspace, true);
- Console.WriteLine();
+ this.awaitingUserInput = false;
+
+ if (string.IsNullOrEmpty(command))
+ {
+ if (this.abortCommand)
+ {
+ incompleteLine = false;
+ partialLine = string.Empty;
+ this.abortCommand = false;
+ }
+ continue;
+ }
// Stream output from command processing to console.
var output = new PSDataCollection();
@@ -600,10 +634,30 @@ private void HandleDebuggerStopEvent(object sender, DebuggerStopEventArgs args)
// 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 7a2df6a234d..6b2198cb723 100644
--- a/src/Microsoft.PowerShell.Linux.Host/readline.cs
+++ b/src/Microsoft.PowerShell.Linux.Host/readline.cs
@@ -6,6 +6,7 @@ namespace Microsoft.PowerShell.Linux.Host
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
+ using System.Threading;
///
/// This class is used to read the command line and color the text as
@@ -90,6 +91,17 @@ internal class ConsoleReadLine
///
private string preTabBuffer;
+ ///
+ /// To keep track whether we should abort the Console.ReadKey loop
+ ///
+ private bool abortReadKey = false;
+
+ ///
+ /// Amount of time to sleep before checking for user keystroke,
+ /// in milliseconds
+ ///
+ private const int sleepTime = 80;
+
///
/// Initializes a new instance of the ConsoleReadLine class.
///
@@ -128,135 +140,176 @@ public string Read(Runspace runspace, bool nested)
{
this.powershell.Runspace = runspace;
this.Initialize();
+ bool finishedTyping = false;
- while (true)
+ while (!abortReadKey)
{
- ConsoleKeyInfo key = Console.ReadKey(true);
-
- // Basic Emacs-style readline implementation
- if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
+ if (Console.KeyAvailable)
{
- 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();
- */
+ finishedTyping = ProcessControlKey(key, nested);
}
- }
- else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt))
- {
- switch (key.Key)
+ else if (key.Modifiers.HasFlag(ConsoleModifiers.Alt))
{
- // 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
+ {
+ finishedTyping = ProcessNormalKey(key, nested);
}
+ previousKeyPress = 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
+ Thread.Sleep(sleepTime);
+ }
- default:
+ if (finishedTyping)
+ {
+ return this.OnEnter();
+ }
+ }
+ return string.Empty;
+ }
- if (key.KeyChar == '\x0D')
- {
- goto case ConsoleKey.Enter; // Ctrl-M
- }
+ ///
+ /// 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;
+ // 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();
+ */
+ }
+ return false;
+ }
- if (key.KeyChar == '\x08')
- {
- goto case ConsoleKey.Backspace; // Ctrl-H
- }
+ ///
+ /// 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;
+ }
- this.Insert(key.KeyChar);
+ ///
+ /// 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
+ }
- this.Render();
- break;
+ if (key.KeyChar == '\x08')
+ {
+ goto case ConsoleKey.Backspace; // Ctrl-H
}
- }
- previousKeyPress = key;
+
+ this.Insert(key.KeyChar);
+
+ this.Render();
+ break;
}
+ return false;
}
///
@@ -270,6 +323,7 @@ private void Initialize()
this.current = 0;
this.rendered = 0;
this.cursor = new Cursor();
+ this.abortReadKey = false;
}
///
@@ -733,6 +787,18 @@ private void Render()
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;
+ this.abortReadKey = true;
+ }
+
///
/// A helper class for maintaining the cursor while editing the command line.
///