1515using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
1616using Microsoft . PowerShell . EditorServices . Services . PowerShell . Host ;
1717using Microsoft . PowerShell . EditorServices . Services . PowerShell . Utility ;
18- using Microsoft . PowerShell . EditorServices . Services . TextDocument ;
1918using Microsoft . PowerShell . EditorServices . Utility ;
2019
2120namespace Microsoft . PowerShell . EditorServices . Services
@@ -49,6 +48,7 @@ internal class DebugService
4948 private VariableContainerDetails scriptScopeVariables ;
5049 private VariableContainerDetails localScopeVariables ;
5150 private StackFrameDetails [ ] stackFrameDetails ;
51+ private PathMapping [ ] _pathMappings ;
5252
5353 private readonly SemaphoreSlim debugInfoHandle = AsyncUtils . CreateSimpleLockingSemaphore ( ) ;
5454 #endregion
@@ -123,22 +123,22 @@ public DebugService(
123123 /// <summary>
124124 /// Sets the list of line breakpoints for the current debugging session.
125125 /// </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>
127127 /// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param>
128128 /// <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>
129130 /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
130131 public async Task < IReadOnlyList < BreakpointDetails > > SetLineBreakpointsAsync (
131- ScriptFile scriptFile ,
132+ string scriptPath ,
132133 IReadOnlyList < BreakpointDetails > breakpoints ,
133- bool clearExisting = true )
134+ bool clearExisting = true ,
135+ bool skipRemoteMapping = false )
134136 {
135137 DscBreakpointCapability dscBreakpoints = await _debugContext . GetDscBreakpointCapabilityAsync ( ) . ConfigureAwait ( false ) ;
136138
137- string scriptPath = scriptFile . FilePath ;
138-
139139 _psesHost . Runspace . ThrowCancelledIfUnusable ( ) ;
140140 // 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 )
142142 {
143143 if ( ! _remoteFileManager . IsUnderRemoteTempPath ( scriptPath ) )
144144 {
@@ -162,7 +162,7 @@ public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
162162 {
163163 if ( clearExisting )
164164 {
165- await _breakpointService . RemoveAllBreakpointsAsync ( scriptFile . FilePath ) . ConfigureAwait ( false ) ;
165+ await _breakpointService . RemoveAllBreakpointsAsync ( scriptPath ) . ConfigureAwait ( false ) ;
166166 }
167167
168168 return await _breakpointService . SetBreakpointsAsync ( breakpoints ) . ConfigureAwait ( false ) ;
@@ -603,6 +603,59 @@ public VariableScope[] GetVariableScopes(int stackFrameId)
603603 } ;
604604 }
605605
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+
606659 #endregion
607660
608661 #region Private Methods
@@ -873,14 +926,19 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
873926 StackFrameDetails stackFrameDetailsEntry = StackFrameDetails . Create ( callStackFrame , autoVariables , commandVariables ) ;
874927 string stackFrameScriptPath = stackFrameDetailsEntry . ScriptPath ;
875928
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 )
878931 {
879932 stackFrameDetailsEntry . ScriptPath = scriptNameOverride ;
880933 }
934+ else if ( TryGetMappedLocalPath ( stackFrameScriptPath , out string localMappedPath )
935+ && ! isNoScriptPath )
936+ {
937+ stackFrameDetailsEntry . ScriptPath = localMappedPath ;
938+ }
881939 else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
882940 && _remoteFileManager is not null
883- && ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
941+ && ! isNoScriptPath )
884942 {
885943 stackFrameDetailsEntry . ScriptPath =
886944 _remoteFileManager . GetMappedPath ( stackFrameScriptPath , _psesHost . CurrentRunspace ) ;
@@ -981,9 +1039,13 @@ await _executionService.ExecutePSCommandAsync<PSObject>(
9811039 // Begin call stack and variables fetch. We don't need to block here.
9821040 StackFramesAndVariablesFetched = FetchStackFramesAndVariablesAsync ( noScriptName ? localScriptPath : null ) ;
9831041
1042+ if ( ! noScriptName && TryGetMappedLocalPath ( e . InvocationInfo . ScriptName , out string mappedLocalPath ) )
1043+ {
1044+ localScriptPath = mappedLocalPath ;
1045+ }
9841046 // If this is a remote connection and the debugger stopped at a line
9851047 // in a script file, get the file contents
986- if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
1048+ else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
9871049 && _remoteFileManager is not null
9881050 && ! noScriptName )
9891051 {
@@ -1034,8 +1096,12 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
10341096 {
10351097 // TODO: This could be either a path or a script block!
10361098 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 )
10391105 {
10401106 string mappedPath = _remoteFileManager . GetMappedPath ( scriptPath , _psesHost . CurrentRunspace ) ;
10411107
0 commit comments