From 8bfae3cb19811662330e510e2ae00f9ab36e0c73 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 17 May 2022 12:39:19 -0700 Subject: [PATCH 1/2] Fix execution of debug prompt commands Thanks to a brilliant idea from Patrick, this simplifies how we choose which commands to execute through PowerShell's debugger API, and also fixes an issue where a user's custom prompt function wasn't aware that it was in the debugger. --- .../Execution/SynchronousPowerShellTask.cs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 71accf212..21bdff213 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -48,10 +48,6 @@ public SynchronousPowerShellTask( public override ExecutionOptions ExecutionOptions => PowerShellExecutionOptions; - // These are PowerShell's intrinsic debugger commands that must be run via - // `ProcessDebugCommand`. - private static readonly string[] DebuggerCommands = { "continue", "c", "k", "h", "?", "list", "l", "stepInto", "s", "stepOut", "o", "stepOver", "v", "quit", "q", "detach", "d" }; - public override IReadOnlyList Run(CancellationToken cancellationToken) { _psesHost.Runspace.ThrowCancelledIfUnusable(); @@ -65,8 +61,14 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) _psesHost.WriteWithPrompt(_psCommand, cancellationToken); } + // If we're in a breakpoint it means we're executing either interactive commands in + // a debug prompt, or our own special commands to query the PowerShell debugger for + // state that we sync with the LSP debugger. The former commands we want to send + // through PowerShell's `Debugger.ProcessCommand` so that they work as expected, but + // the latter we must not send through it else they pollute the history as this + // PowerShell API does not let us exclude them from it. return _pwsh.Runspace.Debugger.InBreakpoint - && (IsDebuggerCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote) + && (PowerShellExecutionOptions.AddToHistory || IsPromptCommand(_psCommand)) ? ExecuteInDebugger(cancellationToken) : ExecuteNormally(cancellationToken); } @@ -78,7 +80,7 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) public override string ToString() => _psCommand.GetInvocationText(); - private static bool IsDebuggerCommand(PSCommand command) + private static bool IsPromptCommand(PSCommand command) { if (command.Commands.Count is not 1 || command.Commands[0] is { IsScript: false } or { Parameters.Count: > 0 }) @@ -87,15 +89,7 @@ private static bool IsDebuggerCommand(PSCommand command) } string commandText = command.Commands[0].CommandText; - foreach (string knownCommand in DebuggerCommands) - { - if (commandText.Equals(knownCommand, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; + return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase); } private IReadOnlyList ExecuteNormally(CancellationToken cancellationToken) @@ -124,7 +118,7 @@ private IReadOnlyList ExecuteNormally(CancellationToken cancellationTok result = _pwsh.InvokeCommand(_psCommand, invocationSettings); cancellationToken.ThrowIfCancellationRequested(); } - // Allow terminate exceptions to propogate for flow control. + // Allow terminate exceptions to propagate for flow control. catch (TerminateException) { throw; @@ -222,12 +216,12 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT // // Unfortunately, this API does not allow us to pass in the InvocationSettings, // which means (for instance) that we cannot instruct it to avoid adding our - // debugger implmentation's commands to the history. So instead we now only call + // debugger implementation's commands to the history. So instead we now only call // `ExecuteInDebugger` for PowerShell's own intrinsic debugger commands. debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection); cancellationToken.ThrowIfCancellationRequested(); } - // Allow terminate exceptions to propogate for flow control. + // Allow terminate exceptions to propagate for flow control. catch (TerminateException) { throw; @@ -281,7 +275,7 @@ private IReadOnlyList ExecuteInDebugger(CancellationToken cancellationT _psesHost.DebugContext.ProcessDebuggerResult(debuggerResult); - // Optimisation to save wasted computation if we're going to throw the output away anyway + // Optimization to save wasted computation if we're going to throw the output away anyway if (PowerShellExecutionOptions.WriteOutputToHost) { return Array.Empty(); From b52c0d62d92e972eda1bf9d8e84382d6d4eafcc7 Mon Sep 17 00:00:00 2001 From: Andy Schwartzmeyer Date: Tue, 17 May 2022 12:46:05 -0700 Subject: [PATCH 2/2] Update src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs Co-authored-by: Patrick Meinecke --- .../Services/PowerShell/Execution/SynchronousPowerShellTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs index 21bdff213..354827d94 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs @@ -68,7 +68,7 @@ public override IReadOnlyList Run(CancellationToken cancellationToken) // the latter we must not send through it else they pollute the history as this // PowerShell API does not let us exclude them from it. return _pwsh.Runspace.Debugger.InBreakpoint - && (PowerShellExecutionOptions.AddToHistory || IsPromptCommand(_psCommand)) + && (PowerShellExecutionOptions.AddToHistory || IsPromptCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote) ? ExecuteInDebugger(cancellationToken) : ExecuteNormally(cancellationToken); }