@@ -187,6 +187,11 @@ class HitCountEntry(NamedTuple):
187
187
type : str
188
188
189
189
190
+ class PathMapping (NamedTuple ):
191
+ local_root : Optional [str ]
192
+ remote_root : Optional [str ]
193
+
194
+
190
195
class Debugger :
191
196
__instance = None
192
197
__lock = threading .RLock ()
@@ -217,7 +222,7 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any:
217
222
raise RuntimeError (f"Attempt to create a '{ cls .__qualname__ } ' instance outside of instance()" )
218
223
219
224
def __init__ (self ) -> None :
220
- self .breakpoints : Dict [str , BreakpointsEntry ] = {}
225
+ self .breakpoints : Dict [pathlib . PurePath , BreakpointsEntry ] = {}
221
226
222
227
self .exception_breakpoints : Set [ExceptionBreakpointsEntry ] = set ()
223
228
self .exception_breakpoints .add (
@@ -243,6 +248,7 @@ def __init__(self) -> None:
243
248
self .no_debug = False
244
249
self .terminated = False
245
250
self .attached = False
251
+ self .path_mappings : List [PathMapping ] = []
246
252
247
253
@property
248
254
def debug (self ) -> bool :
@@ -400,7 +406,11 @@ def set_breakpoints(
400
406
lines : Optional [List [int ]] = None ,
401
407
source_modified : Optional [bool ] = None ,
402
408
) -> List [Breakpoint ]:
403
- path = str (Path (source .path ).resolve ()) if source .path else ""
409
+
410
+ if self .is_windows_path (source .path or "" ):
411
+ path = pathlib .PureWindowsPath (source .path or "" )
412
+ else :
413
+ path = pathlib .PurePath (source .path or "" )
404
414
405
415
if path in self .breakpoints and not breakpoints and not lines :
406
416
self .breakpoints .pop (path )
@@ -409,7 +419,7 @@ def set_breakpoints(
409
419
tuple (breakpoints ) if breakpoints else (), tuple (lines ) if lines else ()
410
420
)
411
421
return [
412
- Breakpoint (id = id (v ), source = Source (path = path ), verified = True , line = v .line ) for v in result .breakpoints
422
+ Breakpoint (id = id (v ), source = Source (path = str ( path ) ), verified = True , line = v .line ) for v in result .breakpoints
413
423
]
414
424
else :
415
425
self ._logger .error ("not supported breakpoint" )
@@ -475,7 +485,7 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
475
485
self .requested_state = RequestedState .Nothing
476
486
477
487
if source is not None :
478
- source = str (Path (source ).resolve ( ))
488
+ source = self . map_path_to_client ( str (Path (source ).absolute () ))
479
489
if source in self .breakpoints :
480
490
breakpoints = [v for v in self .breakpoints [source ].breakpoints if v .line == line_no ]
481
491
if len (breakpoints ) > 0 :
@@ -520,7 +530,7 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
520
530
body = OutputEventBody (
521
531
output = message + os .linesep ,
522
532
category = OutputCategory .CONSOLE ,
523
- source = Source (path = source ) if source else None ,
533
+ source = Source (path = str ( source ) ) if source else None ,
524
534
line = line_no ,
525
535
)
526
536
),
@@ -583,7 +593,7 @@ def start_output_group(self, name: str, attributes: Dict[str, Any], type: Option
583
593
output = f"\u001b [38;5;14m{ (type + ' ' ) if type else '' } \u001b [0m{ name } \n " ,
584
594
category = OutputCategory .CONSOLE ,
585
595
group = OutputGroup .START ,
586
- source = Source (path = source ) if source else None ,
596
+ source = Source (path = str ( self . map_path_to_client ( source )) ) if source else None ,
587
597
line = line_no if source is not None else None ,
588
598
column = 0 if source is not None else None ,
589
599
)
@@ -602,7 +612,7 @@ def end_output_group(self, name: str, attributes: Dict[str, Any]) -> None:
602
612
output = "" ,
603
613
category = OutputCategory .CONSOLE ,
604
614
group = OutputGroup .END ,
605
- source = Source (path = source ) if source else None ,
615
+ source = Source (path = str ( self . map_path_to_client ( source )) ) if source else None ,
606
616
line = line_no ,
607
617
)
608
618
),
@@ -864,6 +874,30 @@ def get_threads(self) -> List[Thread]:
864
874
865
875
return [Thread (id = main_thread .ident if main_thread .ident else 0 , name = main_thread .name or "" )]
866
876
877
+ WINDOW_PATH_REGEX = re .compile (r"^(([a-z]:[\\/])|(\\\\)).*$" , re .RegexFlag .IGNORECASE )
878
+
879
+ @classmethod
880
+ def is_windows_path (cls , path : os .PathLike [str ]) -> bool :
881
+ return bool (cls .WINDOW_PATH_REGEX .fullmatch (str (path )))
882
+
883
+ def map_path_to_client (self , path : os .PathLike [str ]) -> pathlib .PurePath :
884
+ if not self .path_mappings :
885
+ return pathlib .PurePath (path )
886
+
887
+ for mapping in self .path_mappings :
888
+
889
+ remote_root_path = Path (mapping .remote_root or "." ).absolute ()
890
+
891
+ if Path (path ).is_relative_to (remote_root_path ):
892
+ if self .is_windows_path (mapping .local_root ):
893
+ local_root_path = str (pathlib .PureWindowsPath (mapping .local_root ))
894
+ return pathlib .PureWindowsPath (path .replace (str (remote_root_path ), local_root_path or "" ))
895
+ else :
896
+ local_root_path = str (pathlib .PurePath (mapping .local_root ))
897
+ return pathlib .PurePath (path .replace (str (remote_root_path ), local_root_path or "" ))
898
+
899
+ return path
900
+
867
901
def get_stack_trace (
868
902
self ,
869
903
thread_id : int ,
@@ -879,7 +913,7 @@ def get_stack_trace(
879
913
880
914
def source_from_entry (entry : StackFrameEntry ) -> Optional [Source ]:
881
915
if entry .source is not None and entry .is_file :
882
- return Source (path = entry .source )
916
+ return Source (path = str ( self . map_path_to_client ( entry .source )) )
883
917
else :
884
918
return None
885
919
@@ -927,7 +961,7 @@ def log_message(self, message: Dict[str, Any]) -> None:
927
961
self .last_fail_message = msg
928
962
929
963
current_frame = self .full_stack_frames [0 ] if self .full_stack_frames else None
930
- source = Source (path = current_frame .source ) if current_frame else None
964
+ source = Source (path = str ( self . map_path_to_client ( current_frame .source )) ) if current_frame else None
931
965
line = current_frame .line if current_frame else None
932
966
933
967
if self .output_log :
0 commit comments