Skip to content

Commit 3cad765

Browse files
committed
Avoid recording debugger commands in the history
This changes how we decide to run a given command through `ExecuteInDebugger` or `ExecuteNormally`. It now restricts the former such that it is only used for PowerShell's intrinsic debugger commands (like `s`, `c`, etc.) and the latter is used for any other user command or, more importantly, our own implementation's debugger commands (like when we call `Get-Variable`).
1 parent 6754359 commit 3cad765

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

src/PowerShellEditorServices/Services/PowerShell/Execution/SynchronousPowerShellTask.cs

+28-6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public SynchronousPowerShellTask(
4545

4646
public override ExecutionOptions ExecutionOptions => PowerShellExecutionOptions;
4747

48+
// These are PowerShell's intrinsic debugger commands that must be run via
49+
// `ProcessDebugCommand`.
50+
private static readonly string[] DebuggerCommands = {"continue", "c", "k", "h", "?", "list", "l", "stepInto", "s", "stepOut", "o", "stepOver", "v", "quit", "q", "detach", "d"};
51+
4852
public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)
4953
{
5054
_pwsh = _psesHost.CurrentPowerShell;
@@ -55,6 +59,8 @@ public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)
5559
}
5660

5761
return _pwsh.Runspace.Debugger.InBreakpoint
62+
// TODO: Case insensitive?
63+
&& Array.Exists(DebuggerCommands, c => c == _psCommand.GetInvocationText())
5864
? ExecuteInDebugger(cancellationToken)
5965
: ExecuteNormally(cancellationToken);
6066
}
@@ -89,9 +95,15 @@ private IReadOnlyList<TResult> ExecuteNormally(CancellationToken cancellationTok
8995
result = _pwsh.InvokeCommand<TResult>(_psCommand, invocationSettings);
9096
cancellationToken.ThrowIfCancellationRequested();
9197
}
92-
// Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException effectively means the pipeline was stopped.
98+
// Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException
99+
// effectively means the pipeline was stopped.
93100
catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException)
94101
{
102+
// ExecuteNormally handles user commands in a debug session. Perhaps we should clean all this up somehow.
103+
if (_pwsh.Runspace.Debugger.InBreakpoint)
104+
{
105+
StopDebuggerIfRemoteDebugSessionFailed();
106+
}
95107
throw new OperationCanceledException();
96108
}
97109
// We only catch RuntimeExceptions here in case writing errors to output was requested
@@ -124,6 +136,8 @@ private IReadOnlyList<TResult> ExecuteNormally(CancellationToken cancellationTok
124136

125137
private IReadOnlyList<TResult> ExecuteInDebugger(CancellationToken cancellationToken)
126138
{
139+
// TODO: How much of this method can we remove now that it only processes PowerShell's
140+
// intrinsic debugger commands?
127141
cancellationToken.Register(CancelDebugExecution);
128142

129143
var outputCollection = new PSDataCollection<PSObject>();
@@ -148,14 +162,22 @@ private IReadOnlyList<TResult> ExecuteInDebugger(CancellationToken cancellationT
148162
DebuggerCommandResults debuggerResult = null;
149163
try
150164
{
151-
// In the PowerShell debugger, extra debugger commands are made available, like "l", "s", "c", etc.
152-
// Executing those commands produces a result that needs to be set on the debugger stop event args.
153-
// So we use the Debugger.ProcessCommand() API to properly execute commands in the debugger
154-
// and then call DebugContext.ProcessDebuggerResult() later to handle the command appropriately
165+
// In the PowerShell debugger, intrinsic debugger commands are made available, like
166+
// "l", "s", "c", etc. Executing those commands produces a result that needs to be
167+
// set on the debugger stop event args. So we use the Debugger.ProcessCommand() API
168+
// to properly execute commands in the debugger and then call
169+
// DebugContext.ProcessDebuggerResult() later to handle the command appropriately
170+
//
171+
// Unfortunately, this API does not allow us to pass in the InvocationSettings,
172+
// which means (for instance) that we cannot instruct it to avoid adding our
173+
// debugger implmentation's commands to the history. So instead we now only call
174+
// `ExecuteInDebugger` for PowerShell's own intrinsic debugger commands.
155175
debuggerResult = _pwsh.Runspace.Debugger.ProcessCommand(_psCommand, outputCollection);
156176
cancellationToken.ThrowIfCancellationRequested();
157177
}
158-
// Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException effectively means the pipeline was stopped.
178+
179+
// Test if we've been cancelled. If we're remoting, PSRemotingDataStructureException
180+
// effectively means the pipeline was stopped.
159181
catch (Exception e) when (cancellationToken.IsCancellationRequested || e is PipelineStoppedException || e is PSRemotingDataStructureException)
160182
{
161183
StopDebuggerIfRemoteDebugSessionFailed();

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -586,10 +586,9 @@ await debugService.SetLineBreakpointsAsync(
586586
VariableDetailsBase[] variables = GetVariables(VariableContainerDetails.LocalScopeName);
587587

588588
// Test set of a local string variable (not strongly typed)
589-
const string newStrValue = "Goodbye";
589+
const string newStrValue = "\"Goodbye\"";
590590
VariableScope localScope = Array.Find(scopes, s => s.Name == VariableContainerDetails.LocalScopeName);
591-
// TODO: Fix this so it has the second quotes again?
592-
string setStrValue = await debugService.SetVariableAsync(localScope.Id, "$strVar", '"' + newStrValue + '"').ConfigureAwait(true);
591+
string setStrValue = await debugService.SetVariableAsync(localScope.Id, "$strVar", newStrValue).ConfigureAwait(true);
593592
Assert.Equal(newStrValue, setStrValue);
594593

595594
// Test set of script scope int variable (not strongly typed)

0 commit comments

Comments
 (0)