15
15
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
16
16
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Host ;
17
17
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Utility ;
18
- using Microsoft . PowerShell . EditorServices . Services . TextDocument ;
19
18
using Microsoft . PowerShell . EditorServices . Utility ;
20
19
21
20
namespace Microsoft . PowerShell . EditorServices . Services
@@ -49,6 +48,7 @@ internal class DebugService
49
48
private VariableContainerDetails scriptScopeVariables ;
50
49
private VariableContainerDetails localScopeVariables ;
51
50
private StackFrameDetails [ ] stackFrameDetails ;
51
+ private PathMapping [ ] _pathMappings ;
52
52
53
53
private readonly SemaphoreSlim debugInfoHandle = AsyncUtils . CreateSimpleLockingSemaphore ( ) ;
54
54
#endregion
@@ -123,22 +123,22 @@ public DebugService(
123
123
/// <summary>
124
124
/// Sets the list of line breakpoints for the current debugging session.
125
125
/// </summary>
126
- /// <param name="scriptFile ">The ScriptFile in which breakpoints will be set.</param>
126
+ /// <param name="scriptPath ">The path in which breakpoints will be set.</param>
127
127
/// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param>
128
128
/// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param>
129
+ /// <param name="skipRemoteMapping">If true, skips the remote file manager mapping of the script path.</param>
129
130
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
130
131
public async Task < IReadOnlyList < BreakpointDetails > > SetLineBreakpointsAsync (
131
- ScriptFile scriptFile ,
132
+ string scriptPath ,
132
133
IReadOnlyList < BreakpointDetails > breakpoints ,
133
- bool clearExisting = true )
134
+ bool clearExisting = true ,
135
+ bool skipRemoteMapping = false )
134
136
{
135
137
DscBreakpointCapability dscBreakpoints = await _debugContext . GetDscBreakpointCapabilityAsync ( ) . ConfigureAwait ( false ) ;
136
138
137
- string scriptPath = scriptFile . FilePath ;
138
-
139
139
_psesHost . Runspace . ThrowCancelledIfUnusable ( ) ;
140
140
// Make sure we're using the remote script path
141
- if ( _psesHost . CurrentRunspace . IsOnRemoteMachine && _remoteFileManager is not null )
141
+ if ( ! skipRemoteMapping && _psesHost . CurrentRunspace . IsOnRemoteMachine && _remoteFileManager is not null )
142
142
{
143
143
if ( ! _remoteFileManager . IsUnderRemoteTempPath ( scriptPath ) )
144
144
{
@@ -162,7 +162,7 @@ public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
162
162
{
163
163
if ( clearExisting )
164
164
{
165
- await _breakpointService . RemoveAllBreakpointsAsync ( scriptFile . FilePath ) . ConfigureAwait ( false ) ;
165
+ await _breakpointService . RemoveAllBreakpointsAsync ( scriptPath ) . ConfigureAwait ( false ) ;
166
166
}
167
167
168
168
return await _breakpointService . SetBreakpointsAsync ( breakpoints ) . ConfigureAwait ( false ) ;
@@ -603,6 +603,59 @@ public VariableScope[] GetVariableScopes(int stackFrameId)
603
603
} ;
604
604
}
605
605
606
+ internal void SetPathMappings ( PathMapping [ ] pathMappings ) => _pathMappings = pathMappings ;
607
+
608
+ internal void UnsetPathMappings ( ) => _pathMappings = null ;
609
+
610
+ internal bool TryGetMappedLocalPath ( string remotePath , out string localPath )
611
+ {
612
+ if ( _pathMappings is not null )
613
+ {
614
+ foreach ( PathMapping mapping in _pathMappings )
615
+ {
616
+ if ( string . IsNullOrWhiteSpace ( mapping . LocalRoot ) || string . IsNullOrWhiteSpace ( mapping . RemoteRoot ) )
617
+ {
618
+ // If either path mapping is null, we can't map the path.
619
+ continue ;
620
+ }
621
+
622
+ if ( remotePath . StartsWith ( mapping . RemoteRoot , StringComparison . OrdinalIgnoreCase ) )
623
+ {
624
+ localPath = mapping . LocalRoot + remotePath . Substring ( mapping . RemoteRoot . Length ) ;
625
+ return true ;
626
+ }
627
+ }
628
+ }
629
+
630
+ localPath = null ;
631
+ return false ;
632
+ }
633
+
634
+ internal bool TryGetMappedRemotePath ( string localPath , out string remotePath )
635
+ {
636
+ if ( _pathMappings is not null )
637
+ {
638
+ foreach ( PathMapping mapping in _pathMappings )
639
+ {
640
+ if ( string . IsNullOrWhiteSpace ( mapping . LocalRoot ) || string . IsNullOrWhiteSpace ( mapping . RemoteRoot ) )
641
+ {
642
+ // If either path mapping is null, we can't map the path.
643
+ continue ;
644
+ }
645
+
646
+ if ( localPath . StartsWith ( mapping . LocalRoot , StringComparison . OrdinalIgnoreCase ) )
647
+ {
648
+ // If the local path starts with the local path mapping, we can replace it with the remote path.
649
+ remotePath = mapping . RemoteRoot + localPath . Substring ( mapping . LocalRoot . Length ) ;
650
+ return true ;
651
+ }
652
+ }
653
+ }
654
+
655
+ remotePath = null ;
656
+ return false ;
657
+ }
658
+
606
659
#endregion
607
660
608
661
#region Private Methods
@@ -873,14 +926,19 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
873
926
StackFrameDetails stackFrameDetailsEntry = StackFrameDetails . Create ( callStackFrame , autoVariables , commandVariables ) ;
874
927
string stackFrameScriptPath = stackFrameDetailsEntry . ScriptPath ;
875
928
876
- if ( scriptNameOverride is not null
877
- && string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
929
+ bool isNoScriptPath = string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) ;
930
+ if ( scriptNameOverride is not null && isNoScriptPath )
878
931
{
879
932
stackFrameDetailsEntry . ScriptPath = scriptNameOverride ;
880
933
}
934
+ else if ( TryGetMappedLocalPath ( stackFrameScriptPath , out string localMappedPath )
935
+ && ! isNoScriptPath )
936
+ {
937
+ stackFrameDetailsEntry . ScriptPath = localMappedPath ;
938
+ }
881
939
else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
882
940
&& _remoteFileManager is not null
883
- && ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
941
+ && ! isNoScriptPath )
884
942
{
885
943
stackFrameDetailsEntry . ScriptPath =
886
944
_remoteFileManager . GetMappedPath ( stackFrameScriptPath , _psesHost . CurrentRunspace ) ;
@@ -981,9 +1039,13 @@ await _executionService.ExecutePSCommandAsync<PSObject>(
981
1039
// Begin call stack and variables fetch. We don't need to block here.
982
1040
StackFramesAndVariablesFetched = FetchStackFramesAndVariablesAsync ( noScriptName ? localScriptPath : null ) ;
983
1041
1042
+ if ( ! noScriptName && TryGetMappedLocalPath ( e . InvocationInfo . ScriptName , out string mappedLocalPath ) )
1043
+ {
1044
+ localScriptPath = mappedLocalPath ;
1045
+ }
984
1046
// If this is a remote connection and the debugger stopped at a line
985
1047
// in a script file, get the file contents
986
- if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
1048
+ else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
987
1049
&& _remoteFileManager is not null
988
1050
&& ! noScriptName )
989
1051
{
@@ -1034,8 +1096,12 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
1034
1096
{
1035
1097
// TODO: This could be either a path or a script block!
1036
1098
string scriptPath = lineBreakpoint . Script ;
1037
- if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
1038
- && _remoteFileManager is not null )
1099
+ if ( TryGetMappedLocalPath ( scriptPath , out string mappedLocalPath ) )
1100
+ {
1101
+ scriptPath = mappedLocalPath ;
1102
+ }
1103
+ else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
1104
+ && _remoteFileManager is not null )
1039
1105
{
1040
1106
string mappedPath = _remoteFileManager . GetMappedPath ( scriptPath , _psesHost . CurrentRunspace ) ;
1041
1107
0 commit comments