@@ -31,7 +31,7 @@ class PdbAdapter:
3131 """Adapter between DAP protocol and MicroPython's sys.settrace functionality."""
3232
3333 def __init__ (self ):
34- self .breakpoints = {} # filename -> {line_no: breakpoint_info}
34+ self .breakpoints : dict [ str , dict [ int , dict ]] = {} # filename -> {line_no: breakpoint_info} # todo - simplify - reduce info stored
3535 self .current_frame = None
3636 self .step_mode = None # None, 'over', 'into', 'out'
3737 self .step_frame = None
@@ -40,8 +40,8 @@ def __init__(self):
4040 self .continue_event = False
4141 self .variables_cache = {} # frameId -> variables
4242 self .frame_id_counter = 1
43- self .path_mappings : list [tuple [str ,str ]] = [] # runtime_path -> vscode_path mapping
44- self .file_mappings : dict [str ,str ] = {} # runtime_path -> vscode_path mapping
43+ self .path_mappings : list [tuple [str ,str ]] = [] # runtime_path -> vscode_path mapping # todo: move to session level
44+ self .file_mappings : dict [str ,str ] = {} # runtime_path -> vscode_path mapping # todo : merge with .breakpoints
4545
4646 def _debug_print (self , message ):
4747 """Print debug message only if debug logging is enabled."""
@@ -69,17 +69,61 @@ def set_trace_function(self, trace_func):
6969 else :
7070 raise RuntimeError ("sys.settrace not available" )
7171
72- def set_breakpoints (self , filename , breakpoints :list [dict ]):
72+ def _filename_as_debugee (self , path :str ):
73+ # check if we have a 1:1 file mapping for this path
74+ if self .file_mappings .get (path ):
75+ return self .file_mappings [path ]
76+ # Check if we have a folder mapping for this path
77+ for runtime_path , vscode_path in self .path_mappings :
78+ if path .startswith (vscode_path ):
79+ path = path .replace (vscode_path , runtime_path , 1 )
80+ if path .startswith ('//' ):
81+ path = path [1 :]
82+ # If no mapping found, return the original path
83+ return path
84+
85+ def _filename_as_debugger (self , path :str ):
86+ """Convert a file path to the debugger's expected format."""
87+ path = path or ""
88+ if not path :
89+ return path
90+ if path .startswith ('<' ):
91+ # Special case for <stdin> or similar
92+ return path
93+ # Check if we have a 1:1 file mapping for this path
94+ for runtime_path , vscode_path in self .path_mappings :
95+ if path .startswith (runtime_path ):
96+ path = path .replace (runtime_path , vscode_path , 1 )
97+ return path
98+
99+ # Check if we have a folder mapping for this path
100+ for runtime_path , vscode_path in self .path_mappings :
101+ if path .startswith (runtime_path ):
102+ path = path .replace (runtime_path , vscode_path , 1 )
103+ if path .startswith ('//' ):
104+ path = path [1 :]
105+ # If no mapping found, return the original path
106+ return path
107+
108+ def set_breakpoints (self , filename :str , breakpoints :list [dict ]):
73109 """Set breakpoints for a file."""
74110 self .breakpoints [filename ] = {}
111+ local_name = self ._filename_as_debugee (filename )
112+ self .file_mappings [local_name ] = filename
75113 actual_breakpoints = []
76-
77- # Debug log the breakpoint path
78114 self ._debug_print (f"[PDB] Setting breakpoints for file: { filename } " )
79115
80116 for bp in breakpoints :
81117 line = bp .get ("line" )
82118 if line :
119+ if local_name != filename :
120+ self .breakpoints [local_name ] = {}
121+ self ._debug_print (f"[>>>] Setting breakpoints for local: { local_name } :{ line } " )
122+ self .breakpoints [local_name ][line ] = {
123+ "line" : line ,
124+ "verified" : True ,
125+ "source" : {"path" : filename }
126+ }
83127 self .breakpoints [filename ][line ] = {
84128 "line" : line ,
85129 "verified" : True ,
@@ -91,6 +135,8 @@ def set_breakpoints(self, filename, breakpoints:list[dict]):
91135 "source" : {"path" : filename }
92136 })
93137
138+ self ._debug_print (f"[PDB] Breakpoints set : { self .breakpoints } " )
139+
94140 return actual_breakpoints
95141
96142 def should_stop (self , frame , event :str , arg ):
@@ -106,33 +152,18 @@ def should_stop(self, frame, event:str, arg):
106152 if lineno in self .breakpoints [filename ]:
107153 self ._debug_print (f"[PDB] HIT BREAKPOINT (exact match) at { filename } :{ lineno } " )
108154 # Record the path mapping (in this case, they're already the same)
109- self .file_mappings [filename ] = filename
155+ self .file_mappings [filename ] = self . _filename_as_debugger ( filename )
110156 self .hit_breakpoint = True
111157 return True
112-
113- file_basename = basename (filename )
114- self ._debug_print (f"[PDB] Fallback basename match: '{ file_basename } ' vs available files" )
115- for bp_file in self .breakpoints :
116- bp_basename = basename (bp_file )
117- self ._debug_print (f"[PDB] Comparing '{ file_basename } ' == '{ bp_basename } ' ?" )
118- if bp_basename == file_basename :
119- self ._debug_print (f"[PDB] Basename match found! Checking line { lineno } in { list (self .breakpoints [bp_file ].keys ())} " )
120- if lineno in self .breakpoints [bp_file ]:
121- self ._debug_print (f"[PDB] HIT BREAKPOINT (fallback basename match) at { filename } :{ lineno } -> { bp_file } " )
122- # Record the path mapping so we can report the correct path in stack traces
123- self .file_mappings [filename ] = bp_file
124- self .hit_breakpoint = True
125- return True
126-
127- # Also check if the runtime path might be relative and the breakpoint path absolute
128- if ends_with_path (bp_file , filename ):
129- self ._debug_print (f"[PDB] Relative path match: { bp_file } ends with { filename } " )
130- if lineno in self .breakpoints [bp_file ]:
131- self ._debug_print (f"[PDB] HIT BREAKPOINT (relative path match) at { filename } :{ lineno } -> { bp_file } " )
132- # Record the path mapping so we can report the correct path in stack traces
133- self .file_mappings [filename ] = bp_file
134- self .hit_breakpoint = True
135- return True
158+ # path/file.py matched - but not the line number - keep running
159+ else :
160+ # file not (yet) matched - this is slow so we do not want to do this often.
161+ # TODO: use builins - sys.path method to find the file
162+ # if we have a path match , but no breakpoints - add it to the file_mappings dict avoid this check
163+ self .breakpoints [filename ] = {} # Ensure the filename is in the breakpoints dict
164+ if not filename in self .file_mappings :
165+ self .file_mappings [filename ] = self ._filename_as_debugger (filename )
166+ self ._debug_print (f"[PDB] add mapping for :'{ filename } ' -> '{ self .file_mappings [filename ]} '" )
136167
137168 # Check stepping
138169 if self .step_mode == 'into' :
@@ -216,15 +247,20 @@ def get_stack_trace(self):
216247 else :
217248 hint = 'normal'
218249
250+ # self._debug_print("=" * 40 )
251+ # self._debug_print(f"[PDB] file mappings: {repr(self.file_mappings)} " )
252+ # self._debug_print(f"[PDB] path mappings: {repr(self.path_mappings)}" )
253+ # self._debug_print("=" * 40 )
254+
219255 # Use the VS Code path if we have a mapping, otherwise use the original path
220- display_path = self .file_mappings . get ( filename , filename )
221- if filename != display_path :
222- self ._debug_print (f"[PDB] Stack trace path mapping: { filename } -> { display_path } " )
256+ debugger_path = self ._filename_as_debugger ( filename )
257+ if filename != debugger_path :
258+ self ._debug_print (f"[PDB] Stack trace path mapping: { filename } -> { debugger_path } " )
223259 # Create StackFrame info
224260 frames .append ({
225261 "id" : frame_id ,
226262 "name" : name ,
227- "source" : {"path" : display_path },
263+ "source" : {"path" : debugger_path },
228264 "line" : line ,
229265 "column" : 1 ,
230266 "endLine" : line ,
0 commit comments