11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
33
4+ using System . Management . Automation ;
5+ using System . Management . Automation . Language ;
46using System . Threading ;
57using System . Threading . Tasks ;
68using Microsoft . Extensions . Logging ;
79using Microsoft . PowerShell . EditorServices . Services ;
10+ using Microsoft . PowerShell . EditorServices . Services . DebugAdapter ;
811using Microsoft . PowerShell . EditorServices . Services . PowerShell ;
912using Microsoft . PowerShell . EditorServices . Services . PowerShell . Debugging ;
1013using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
14+ using Microsoft . PowerShell . EditorServices . Services . PowerShell . Runspace ;
1115using Microsoft . PowerShell . EditorServices . Services . TextDocument ;
1216using Microsoft . PowerShell . EditorServices . Utility ;
1317using OmniSharp . Extensions . DebugAdapter . Protocol . Events ;
@@ -18,6 +22,9 @@ namespace Microsoft.PowerShell.EditorServices.Handlers
1822{
1923 internal class ConfigurationDoneHandler : IConfigurationDoneHandler
2024 {
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).
2128 private static readonly PowerShellExecutionOptions s_debuggerExecutionOptions = new ( )
2229 {
2330 MustRunInForeground = true ,
@@ -35,7 +42,10 @@ internal class ConfigurationDoneHandler : IConfigurationDoneHandler
3542 private readonly IInternalPowerShellExecutionService _executionService ;
3643 private readonly WorkspaceService _workspaceService ;
3744 private readonly IPowerShellDebugContext _debugContext ;
45+ private readonly IRunspaceContext _runspaceContext ;
3846
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`).
3949 public ConfigurationDoneHandler (
4050 ILoggerFactory loggerFactory ,
4151 IDebugAdapterServerFacade debugAdapterServer ,
@@ -44,7 +54,8 @@ public ConfigurationDoneHandler(
4454 DebugEventHandlerService debugEventHandlerService ,
4555 IInternalPowerShellExecutionService executionService ,
4656 WorkspaceService workspaceService ,
47- IPowerShellDebugContext debugContext )
57+ IPowerShellDebugContext debugContext ,
58+ IRunspaceContext runspaceContext )
4859 {
4960 _logger = loggerFactory . CreateLogger < ConfigurationDoneHandler > ( ) ;
5061 _debugAdapterServer = debugAdapterServer ;
@@ -54,6 +65,7 @@ public ConfigurationDoneHandler(
5465 _executionService = executionService ;
5566 _workspaceService = workspaceService ;
5667 _debugContext = debugContext ;
68+ _runspaceContext = runspaceContext ;
5769 }
5870
5971 public Task < ConfigurationDoneResponse > Handle ( ConfigurationDoneArguments request , CancellationToken cancellationToken )
@@ -90,16 +102,51 @@ public Task<ConfigurationDoneResponse> Handle(ConfigurationDoneArguments request
90102
91103 private async Task LaunchScriptAsync ( string scriptToLaunch )
92104 {
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+ }
100147
101148 await _executionService . ExecutePSCommandAsync (
102- PSCommandHelpers . BuildCommandFromArguments ( command , _debugStateService . Arguments ) ,
149+ command ,
103150 CancellationToken . None ,
104151 s_debuggerExecutionOptions ) . ConfigureAwait ( false ) ;
105152 _debugAdapterServer . SendNotification ( EventNames . Terminated ) ;
0 commit comments