Skip to content

Commit a218596

Browse files
committed
Fix debugging script blocks that aren't in files
The code relied on running `list 1 <MaxInt>` but that wasn't being run in the debug context because we allow-list commands to prevent pollution of the history, and missed it. Like the `prompt` and interactive commands (which `list` could be but is not when we run it) we need to check for this exact `list` command and run it under the debugger. Moreover, we also weren't locking the `debugInfoHandle`, nor were we correctly checking if `scriptListingLines` was empty (it was never null), and our shortcut to skip allocation was broken. Actually we can't skip allocation, but we can at least skip superfluous conversions.
1 parent 9e19755 commit a218596

File tree

2 files changed

+27
-13
lines changed

2 files changed

+27
-13
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs

+16-5
Original file line numberDiff line numberDiff line change
@@ -930,13 +930,24 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e)
930930
if (_remoteFileManager is not null && string.IsNullOrEmpty(localScriptPath))
931931
{
932932
// Get the current script listing and create the buffer
933-
PSCommand command = new PSCommand().AddScript($"list 1 {int.MaxValue}");
933+
IReadOnlyList<PSObject> scriptListingLines;
934+
await debugInfoHandle.WaitAsync().ConfigureAwait(false);
935+
try
936+
{
937+
// This command must be run through `ExecuteInDebugger`!
938+
PSCommand psCommand = new PSCommand().AddScript($"list 1 {int.MaxValue}");
934939

935-
IReadOnlyList<PSObject> scriptListingLines =
936-
await _executionService.ExecutePSCommandAsync<PSObject>(
937-
command, CancellationToken.None).ConfigureAwait(false);
940+
scriptListingLines =
941+
await _executionService.ExecutePSCommandAsync<PSObject>(
942+
psCommand,
943+
CancellationToken.None).ConfigureAwait(false);
944+
}
945+
finally
946+
{
947+
debugInfoHandle.Release();
948+
}
938949

939-
if (scriptListingLines is not null)
950+
if (scriptListingLines.Count > 0)
940951
{
941952
int linePrefixLength = 0;
942953

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

+11-8
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)
7373
// state that we sync with the LSP debugger. The former commands we want to send
7474
// through PowerShell's `Debugger.ProcessCommand` so that they work as expected, but
7575
// the latter we must not send through it else they pollute the history as this
76-
// PowerShell API does not let us exclude them from it.
76+
// PowerShell API does not let us exclude them from it. Notably we also need to send
77+
// the `prompt` command and our special `list 1 <MaxInt>` through the debugger too.
78+
// The former needs the context in order to show `DBG 1>` etc., and the latter is
79+
// used to gather the lines when debugging a script that isn't in a file.
7780
return _pwsh.Runspace.Debugger.InBreakpoint
78-
&& (PowerShellExecutionOptions.AddToHistory || IsPromptCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote)
81+
&& (PowerShellExecutionOptions.AddToHistory || IsPromptOrListCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote)
7982
? ExecuteInDebugger(cancellationToken)
8083
: ExecuteNormally(cancellationToken);
8184
}
@@ -87,7 +90,7 @@ public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)
8790

8891
public override string ToString() => _psCommand.GetInvocationText();
8992

90-
private static bool IsPromptCommand(PSCommand command)
93+
private static bool IsPromptOrListCommand(PSCommand command)
9194
{
9295
if (command.Commands.Count is not 1
9396
|| command.Commands[0] is { IsScript: false } or { Parameters.Count: > 0 })
@@ -96,7 +99,8 @@ private static bool IsPromptCommand(PSCommand command)
9699
}
97100

98101
string commandText = command.Commands[0].CommandText;
99-
return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase);
102+
return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase)
103+
|| commandText.Equals($"list 1 {int.MaxValue}", StringComparison.OrdinalIgnoreCase);
100104
}
101105

102106
private IReadOnlyList<TResult> ExecuteNormally(CancellationToken cancellationToken)
@@ -301,11 +305,10 @@ private IReadOnlyList<TResult> ExecuteInDebugger(CancellationToken cancellationT
301305
return Array.Empty<TResult>();
302306
}
303307

304-
// If we've been asked for a PSObject, no need to allocate a new collection
305-
if (typeof(TResult) == typeof(PSObject)
306-
&& outputCollection is IReadOnlyList<TResult> resultCollection)
308+
// If we've been asked for a PSObject, no need to convert
309+
if (typeof(TResult) == typeof(PSObject))
307310
{
308-
return resultCollection;
311+
return new List<PSObject>(outputCollection) as IReadOnlyList<TResult>;
309312
}
310313

311314
// Otherwise, convert things over

0 commit comments

Comments
 (0)