1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
+ using System . Management . Automation ;
5
+ using System . Management . Automation . Language ;
4
6
using System . Threading ;
5
7
using System . Threading . Tasks ;
6
8
using Microsoft . Extensions . Logging ;
7
9
using Microsoft . PowerShell . EditorServices . Services ;
10
+ using Microsoft . PowerShell . EditorServices . Services . DebugAdapter ;
8
11
using Microsoft . PowerShell . EditorServices . Services . PowerShell ;
9
12
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Debugging ;
10
13
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
14
+ using Microsoft . PowerShell . EditorServices . Services . PowerShell . Runspace ;
11
15
using Microsoft . PowerShell . EditorServices . Services . TextDocument ;
12
16
using Microsoft . PowerShell . EditorServices . Utility ;
13
17
using OmniSharp . Extensions . DebugAdapter . Protocol . Events ;
@@ -18,6 +22,9 @@ namespace Microsoft.PowerShell.EditorServices.Handlers
18
22
{
19
23
internal class ConfigurationDoneHandler : IConfigurationDoneHandler
20
24
{
25
+ // TODO: We currently set `WriteInputToHost` as true, which writes our debugged commands'
26
+ // `GetInvocationText` and that reveals some obscure implementation details we should
27
+ // instead hide from the user with pretty strings (or perhaps not write out at all).
21
28
private static readonly PowerShellExecutionOptions s_debuggerExecutionOptions = new ( )
22
29
{
23
30
MustRunInForeground = true ,
@@ -35,7 +42,10 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler
35
42
private readonly IInternalPowerShellExecutionService _executionService ;
36
43
private readonly WorkspaceService _workspaceService ;
37
44
private readonly IPowerShellDebugContext _debugContext ;
45
+ private readonly IRunspaceContext _runspaceContext ;
38
46
47
+ // TODO: Decrease these arguments since they're a bunch of interfaces that can be simplified
48
+ // (i.e., `IRunspaceContext` should just be available on `IPowerShellExecutionService`).
39
49
public ConfigurationDoneHandler (
40
50
ILoggerFactory loggerFactory ,
41
51
IDebugAdapterServerFacade debugAdapterServer ,
@@ -44,7 +54,8 @@ public ConfigurationDoneHandler(
44
54
DebugEventHandlerService debugEventHandlerService ,
45
55
IInternalPowerShellExecutionService executionService ,
46
56
WorkspaceService workspaceService ,
47
- IPowerShellDebugContext debugContext )
57
+ IPowerShellDebugContext debugContext ,
58
+ IRunspaceContext runspaceContext )
48
59
{
49
60
_logger = loggerFactory . CreateLogger < ConfigurationDoneHandler > ( ) ;
50
61
_debugAdapterServer = debugAdapterServer ;
@@ -54,6 +65,7 @@ public ConfigurationDoneHandler(
54
65
_executionService = executionService ;
55
66
_workspaceService = workspaceService ;
56
67
_debugContext = debugContext ;
68
+ _runspaceContext = runspaceContext ;
57
69
}
58
70
59
71
public Task < ConfigurationDoneResponse > Handle ( ConfigurationDoneArguments request , CancellationToken cancellationToken )
@@ -90,16 +102,51 @@ public Task<ConfigurationDoneResponse> Handle(ConfigurationDoneArguments request
90
102
91
103
private async Task LaunchScriptAsync ( string scriptToLaunch )
92
104
{
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 , '"' ) ;
105
+ PSCommand command ;
106
+ if ( ScriptFile . IsUntitledPath ( scriptToLaunch ) )
107
+ {
108
+ ScriptFile untitledScript = _workspaceService . GetFile ( scriptToLaunch ) ;
109
+ if ( BreakpointApiUtils . SupportsBreakpointApis ( _runspaceContext . CurrentRunspace ) )
110
+ {
111
+ // Parse untitled files with their `Untitled:` URI as the filename which will
112
+ // cache the URI and contents within the PowerShell parser. By doing this, we
113
+ // light up the ability to debug untitled files with line breakpoints. This is
114
+ // only possible with PowerShell 7's new breakpoint APIs since the old API,
115
+ // Set-PSBreakpoint, validates that the given path points to a real file.
116
+ ScriptBlockAst ast = Parser . ParseInput (
117
+ untitledScript . Contents ,
118
+ untitledScript . DocumentUri . ToString ( ) ,
119
+ out Token [ ] _ ,
120
+ out ParseError [ ] _ ) ;
121
+
122
+ // In order to use utilize the parser's cache (and therefore hit line
123
+ // breakpoints) we need to use the AST's `ScriptBlock` object. Due to
124
+ // limitations in PowerShell's public API, this means we must use the
125
+ // `PSCommand.AddArgument(object)` method, hence this hack where we dot-source
126
+ // `$args[0]. Fortunately the dot-source operator maintains a stack of arguments
127
+ // on each invocation, so passing the user's arguments directly in the initial
128
+ // `AddScript` surprisingly works.
129
+ command = PSCommandHelpers
130
+ . BuildDotSourceCommandWithArguments ( "$args[0]" , _debugStateService . Arguments )
131
+ . AddArgument ( ast . GetScriptBlock ( ) ) ;
132
+ }
133
+ else
134
+ {
135
+ // Without the new APIs we can only execute the untitled script's contents.
136
+ // Command breakpoints and `Wait-Debugger` will work.
137
+ command = PSCommandHelpers . BuildDotSourceCommandWithArguments (
138
+ string . Concat ( "{ " , untitledScript . Contents , " }" ) , _debugStateService . Arguments ) ;
139
+ }
140
+ }
141
+ else
142
+ {
143
+ // For a saved file we just execute its path (after escaping it).
144
+ command = PSCommandHelpers . BuildDotSourceCommandWithArguments (
145
+ string . Concat ( '"' , scriptToLaunch , '"' ) , _debugStateService . Arguments ) ;
146
+ }
100
147
101
148
await _executionService . ExecutePSCommandAsync (
102
- PSCommandHelpers . BuildCommandFromArguments ( command , _debugStateService . Arguments ) ,
149
+ command ,
103
150
CancellationToken . None ,
104
151
s_debuggerExecutionOptions ) . ConfigureAwait ( false ) ;
105
152
_debugAdapterServer . SendNotification ( EventNames . Terminated ) ;
0 commit comments