Skip to content

Commit 3ad0f39

Browse files
Merge pull request #1702 from PowerShell/andschwa/untitled-with-args
Fix running untitled scripts with arguments (but break line breakpoints)
2 parents c123557 + 29b56ce commit 3ad0f39

File tree

9 files changed

+37
-215
lines changed

9 files changed

+37
-215
lines changed

src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ public PsesDebugServer CreateDebugServerWithLanguageServer(
119119
inputStream,
120120
outputStream,
121121
languageServer.LanguageServer.Services,
122-
useTempSession: false,
123122
usePSReadLine);
124123
}
125124

@@ -144,7 +143,6 @@ public PsesDebugServer RecreateDebugServer(
144143
inputStream,
145144
outputStream,
146145
debugServer.ServiceProvider,
147-
useTempSession: false,
148146
usePSReadLine);
149147
}
150148

@@ -184,7 +182,6 @@ public PsesDebugServer CreateDebugServerForTempSession(
184182
inputStream,
185183
outputStream,
186184
serviceProvider,
187-
useTempSession: true,
188185
usePSReadLine: hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine);
189186
}
190187

src/PowerShellEditorServices/Server/PsesDebugServer.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal class PsesDebugServer : IDisposable
2323
{
2424
private readonly Stream _inputStream;
2525
private readonly Stream _outputStream;
26-
private readonly bool _useTempSession;
2726
private readonly bool _usePSReadLine;
2827
private readonly TaskCompletionSource<bool> _serverStopped;
2928

@@ -40,14 +39,12 @@ public PsesDebugServer(
4039
Stream inputStream,
4140
Stream outputStream,
4241
IServiceProvider serviceProvider,
43-
bool useTempSession,
4442
bool usePSReadLine)
4543
{
4644
_loggerFactory = factory;
4745
_inputStream = inputStream;
4846
_outputStream = outputStream;
4947
ServiceProvider = serviceProvider;
50-
_useTempSession = useTempSession;
5148
_serverStopped = new TaskCompletionSource<bool>();
5249
_usePSReadLine = usePSReadLine;
5350
}
@@ -74,7 +71,7 @@ public async Task StartAsync()
7471
serviceCollection
7572
.AddLogging()
7673
.AddOptions()
77-
.AddPsesDebugServices(ServiceProvider, this, _useTempSession))
74+
.AddPsesDebugServices(ServiceProvider, this))
7875
// TODO: Consider replacing all WithHandler with AddSingleton
7976
.WithHandler<LaunchAndAttachHandler>()
8077
.WithHandler<DisconnectHandler>()

src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ public static IServiceCollection AddPsesLanguageServices(
5959
public static IServiceCollection AddPsesDebugServices(
6060
this IServiceCollection collection,
6161
IServiceProvider languageServiceProvider,
62-
PsesDebugServer psesDebugServer,
63-
bool useTempSession)
62+
PsesDebugServer psesDebugServer)
6463
{
6564
PsesInternalHost internalHost = languageServiceProvider.GetService<PsesInternalHost>();
6665

@@ -74,10 +73,7 @@ public static IServiceCollection AddPsesDebugServices(
7473
.AddSingleton<PsesDebugServer>(psesDebugServer)
7574
.AddSingleton<DebugService>()
7675
.AddSingleton<BreakpointService>()
77-
.AddSingleton<DebugStateService>(new DebugStateService
78-
{
79-
OwnsEditorSession = useTempSession
80-
})
76+
.AddSingleton<DebugStateService>()
8177
.AddSingleton<DebugEventHandlerService>();
8278
}
8379
}

src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ internal class DebugStateService
2525

2626
internal string ScriptToLaunch { get; set; }
2727

28-
internal bool OwnsEditorSession { get; set; }
29-
3028
internal bool ExecutionCompleted { get; set; }
3129

3230
internal bool IsInteractiveDebugSession { get; set; }
@@ -39,14 +37,8 @@ internal class DebugStateService
3937
// This gets set at the end of the Launch/Attach handler which set debug state.
4038
internal TaskCompletionSource<bool> ServerStarted { get; set; }
4139

42-
internal void ReleaseSetBreakpointHandle()
43-
{
44-
_setBreakpointInProgressHandle.Release();
45-
}
40+
internal int ReleaseSetBreakpointHandle() => _setBreakpointInProgressHandle.Release();
4641

47-
internal async Task WaitForSetBreakpointHandleAsync()
48-
{
49-
await _setBreakpointInProgressHandle.WaitAsync().ConfigureAwait(false);
50-
}
42+
internal Task WaitForSetBreakpointHandleAsync() => _setBreakpointInProgressHandle.WaitAsync();
5143
}
5244
}

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/ConfigurationDoneHandler.cs

Lines changed: 18 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Threading;
5+
using System.Threading.Tasks;
46
using Microsoft.Extensions.Logging;
57
using Microsoft.PowerShell.EditorServices.Services;
6-
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
78
using Microsoft.PowerShell.EditorServices.Services.PowerShell;
89
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Debugging;
910
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
10-
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Runspace;
1111
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
1212
using Microsoft.PowerShell.EditorServices.Utility;
1313
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
1414
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
1515
using OmniSharp.Extensions.DebugAdapter.Protocol.Server;
16-
using System.Management.Automation;
17-
using System.Management.Automation.Language;
18-
using System.Threading;
19-
using System.Threading.Tasks;
2016

2117
namespace Microsoft.PowerShell.EditorServices.Handlers
2218
{
@@ -38,9 +34,7 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler
3834
private readonly DebugEventHandlerService _debugEventHandlerService;
3935
private readonly IInternalPowerShellExecutionService _executionService;
4036
private readonly WorkspaceService _workspaceService;
41-
4237
private readonly IPowerShellDebugContext _debugContext;
43-
private readonly IRunspaceContext _runspaceContext;
4438

4539
public ConfigurationDoneHandler(
4640
ILoggerFactory loggerFactory,
@@ -50,8 +44,7 @@ public ConfigurationDoneHandler(
5044
DebugEventHandlerService debugEventHandlerService,
5145
IInternalPowerShellExecutionService executionService,
5246
WorkspaceService workspaceService,
53-
IPowerShellDebugContext debugContext,
54-
IRunspaceContext runspaceContext)
47+
IPowerShellDebugContext debugContext)
5548
{
5649
_logger = loggerFactory.CreateLogger<ConfigurationDoneHandler>();
5750
_debugAdapterServer = debugAdapterServer;
@@ -61,23 +54,18 @@ public ConfigurationDoneHandler(
6154
_executionService = executionService;
6255
_workspaceService = workspaceService;
6356
_debugContext = debugContext;
64-
_runspaceContext = runspaceContext;
6557
}
6658

6759
public Task<ConfigurationDoneResponse> Handle(ConfigurationDoneArguments request, CancellationToken cancellationToken)
6860
{
6961
_debugService.IsClientAttached = true;
7062

71-
if (_debugStateService.OwnsEditorSession)
72-
{
73-
// TODO: If this is a debug-only session, we need to start the command loop manually
74-
//
75-
//_powerShellContextService.ConsoleReader.StartCommandLoop();
76-
}
77-
7863
if (!string.IsNullOrEmpty(_debugStateService.ScriptToLaunch))
7964
{
80-
LaunchScriptAsync(_debugStateService.ScriptToLaunch).HandleErrorsAsync(_logger);
65+
// NOTE: This is an unawaited task because responding to "configuration done" means
66+
// setting up the debugger, and in our case that means starting the script but not
67+
// waiting for it to finish.
68+
Task _ = LaunchScriptAsync(_debugStateService.ScriptToLaunch).HandleErrorsAsync(_logger);
8169
}
8270

8371
if (_debugStateService.IsInteractiveDebugSession && _debugService.IsDebuggerStopped)
@@ -102,48 +90,18 @@ public Task<ConfigurationDoneResponse> Handle(ConfigurationDoneArguments request
10290

10391
private async Task LaunchScriptAsync(string scriptToLaunch)
10492
{
105-
// Is this an untitled script?
106-
if (ScriptFile.IsUntitledPath(scriptToLaunch))
107-
{
108-
ScriptFile untitledScript = _workspaceService.GetFile(scriptToLaunch);
109-
110-
if (BreakpointApiUtils.SupportsBreakpointApis(_runspaceContext.CurrentRunspace))
111-
{
112-
// Parse untitled files with their `Untitled:` URI as the file name which will cache the URI & contents within the PowerShell parser.
113-
// By doing this, we light up the ability to debug Untitled files with breakpoints.
114-
// This is only possible via the direct usage of the breakpoint APIs in PowerShell because
115-
// Set-PSBreakpoint validates that paths are actually on the filesystem.
116-
ScriptBlockAst ast = Parser.ParseInput(untitledScript.Contents, untitledScript.DocumentUri.ToString(), out Token[] tokens, out ParseError[] errors);
117-
118-
// This seems to be the simplest way to invoke a script block (which contains breakpoint information) via the PowerShell API.
119-
//
120-
// TODO: Fix this so the added script doesn't show up.
121-
var cmd = new PSCommand().AddScript(". $args[0]").AddArgument(ast.GetScriptBlock());
122-
await _executionService
123-
.ExecutePSCommandAsync<object>(cmd, CancellationToken.None, s_debuggerExecutionOptions)
124-
.ConfigureAwait(false);
125-
}
126-
else
127-
{
128-
await _executionService
129-
.ExecutePSCommandAsync(
130-
new PSCommand().AddScript(untitledScript.Contents),
131-
CancellationToken.None,
132-
s_debuggerExecutionOptions)
133-
.ConfigureAwait(false);
134-
}
135-
}
136-
else
137-
{
138-
// TODO: Fix this so the added script doesn't show up.
139-
await _executionService
140-
.ExecutePSCommandAsync(
141-
PSCommandHelpers.BuildCommandFromArguments(scriptToLaunch, _debugStateService.Arguments),
142-
CancellationToken.None,
143-
s_debuggerExecutionOptions)
144-
.ConfigureAwait(false);
145-
}
93+
// TODO: Theoretically we can make PowerShell respect line breakpoints in untitled
94+
// files, but the previous method was a hack that conflicted with correct passing of
95+
// arguments to the debugged script. We are prioritizing the latter over the former, as
96+
// command breakpoints and `Wait-Debugger` work fine.
97+
string command = ScriptFile.IsUntitledPath(scriptToLaunch)
98+
? string.Concat("{ ", _workspaceService.GetFile(scriptToLaunch).Contents, " }")
99+
: string.Concat('"', scriptToLaunch, '"');
146100

101+
await _executionService.ExecutePSCommandAsync(
102+
PSCommandHelpers.BuildCommandFromArguments(command, _debugStateService.Arguments),
103+
CancellationToken.None,
104+
s_debuggerExecutionOptions).ConfigureAwait(false);
147105
_debugAdapterServer.SendNotification(EventNames.Terminated);
148106
}
149107
}

src/PowerShellEditorServices/Utility/ArgumentUtils.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/PowerShellEditorServices/Utility/PSCommandExtensions.cs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,25 +129,11 @@ private static StringBuilder AddCommandText(this StringBuilder sb, Command comma
129129
return sb;
130130
}
131131

132-
public static PSCommand BuildCommandFromArguments(string command, IReadOnlyList<string> arguments)
132+
public static PSCommand BuildCommandFromArguments(string command, IEnumerable<string> arguments)
133133
{
134134
// HACK: We use AddScript instead of AddArgument/AddParameter to reuse Powershell parameter binding logic.
135-
// We quote the command parameter so that expressions can still be used in the arguments.
136-
var sb = new StringBuilder()
137-
.Append('.')
138-
.Append(' ')
139-
.Append('"')
140-
.Append(command)
141-
.Append('"');
142-
143-
foreach (string arg in arguments ?? System.Linq.Enumerable.Empty<string>())
144-
{
145-
sb
146-
.Append(' ')
147-
.Append(ArgumentEscaping.Escape(arg));
148-
}
149-
150-
return new PSCommand().AddScript(sb.ToString());
135+
string script = string.Concat(". ", command, " ", string.Join(" ", arguments ?? Array.Empty<string>()));
136+
return new PSCommand().AddScript(script);
151137
}
152138
}
153139
}

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ private VariableDetailsBase[] GetVariables(string scopeName)
9696
private Task ExecutePowerShellCommand(string command, params string[] args)
9797
{
9898
return psesHost.ExecutePSCommandAsync(
99-
PSCommandHelpers.BuildCommandFromArguments(command, args),
99+
PSCommandHelpers.BuildCommandFromArguments(string.Concat('"', command, '"'), args),
100100
CancellationToken.None);
101101
}
102102

@@ -176,8 +176,16 @@ await debugService.SetCommandBreakpointsAsync(
176176
Assert.Equal("[ArrayList: 0]", var.ValueString);
177177
}
178178

179-
[Fact]
180-
public async Task DebuggerAcceptsScriptArgs()
179+
// See https://www.thomasbogholm.net/2021/06/01/convenient-member-data-sources-with-xunit/
180+
public static IEnumerable<object[]> DebuggerAcceptsScriptArgsTestData => new List<object[]>()
181+
{
182+
new object[] { new object[] { "Foo -Param2 @('Bar','Baz') -Force Extra1" } },
183+
new object[] { new object[] { "Foo", "-Param2", "@('Bar','Baz')", "-Force", "Extra1" } }
184+
};
185+
186+
[Theory]
187+
[MemberData(nameof(DebuggerAcceptsScriptArgsTestData))]
188+
public async Task DebuggerAcceptsScriptArgs(string[] args)
181189
{
182190
// The path is intentionally odd (some escaped chars but not all) because we are testing
183191
// the internal path escaping mechanism - it should escape certains chars ([, ] and space) but
@@ -197,9 +205,6 @@ public async Task DebuggerAcceptsScriptArgs()
197205
Assert.True(breakpoint.Verified);
198206
});
199207

200-
// TODO: This test used to also pass the args as a single string, but that doesn't seem
201-
// to work any more. Perhaps that's a bug?
202-
var args = new[] { "Foo", "-Param2", "@('Bar','Baz')", "-Force", "Extra1" };
203208
Task _ = ExecutePowerShellCommand(debugWithParamsFile.FilePath, args);
204209

205210
AssertDebuggerStopped(debugWithParamsFile.FilePath, 3);

0 commit comments

Comments
 (0)