Skip to content

Commit c35c6be

Browse files
committed
debugpy: Enhance PDB adapter with special variable processing.
Signed-off-by: Jos Verlinde <[email protected]>
1 parent 5d491e0 commit c35c6be

File tree

1 file changed

+84
-39
lines changed

1 file changed

+84
-39
lines changed

python-ecosys/debugpy/debugpy/server/pdb_adapter.py

Lines changed: 84 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import sys
44
import time
55
import os
6+
import json
67
from ..common.constants import (
78
TRACE_CALL, TRACE_LINE, TRACE_RETURN, TRACE_EXCEPTION,
89
SCOPE_LOCALS, SCOPE_GLOBALS
910
)
11+
VARREF_LOCALS = 1
12+
VARREF_GLOBALS = 2
13+
VARREF_LOCALS_SPECIAL = 3
14+
VARREF_GLOBALS_SPECIAL = 4
1015

1116

1217
class PdbAdapter:
@@ -26,7 +31,7 @@ def __init__(self):
2631

2732
def _debug_print(self, message):
2833
"""Print debug message only if debug logging is enabled."""
29-
if hasattr(self, '_debug_session') and self._debug_session.debug_logging:
34+
if hasattr(self, '_debug_session') and self._debug_session.debug_logging: # type: ignore
3035
print(message)
3136

3237
def _normalize_path(self, path):
@@ -197,7 +202,7 @@ def wait_for_continue(self):
197202
while not self.continue_event:
198203
# Process any pending DAP messages (scopes, variables, etc.)
199204
if hasattr(self, '_debug_session'):
200-
self._debug_session.process_pending_messages()
205+
self._debug_session.process_pending_messages() # type: ignore
201206
time.sleep(0.01)
202207

203208
def get_stack_trace(self):
@@ -213,21 +218,25 @@ def get_stack_trace(self):
213218
filename = frame.f_code.co_filename
214219
name = frame.f_code.co_name
215220
line = frame.f_lineno
216-
221+
if "<stdin>" in filename or filename.endswith("debugpy.py") :
222+
hint = 'subtle'
223+
else :
224+
hint = 'normal'
225+
217226
# Use the VS Code path if we have a mapping, otherwise use the original path
218227
display_path = self.path_mapping.get(filename, filename)
219228
if filename != display_path:
220229
self._debug_print(f"[PDB] Stack trace path mapping: {filename} -> {display_path}")
221-
222-
# Create frame info
230+
# Create StackFrame info
223231
frames.append({
224232
"id": frame_id,
225-
"name": name,
233+
"name": name + f" {type(frame.f_code.co_filename).__name__}",
226234
"source": {"path": display_path},
227235
"line": line,
228236
"column": 1,
229237
"endLine": line,
230-
"endColumn": 1
238+
"endColumn": 1,
239+
"presentationHint": hint
231240
})
232241

233242
# Cache frame for variable access
@@ -248,60 +257,97 @@ def get_scopes(self, frame_id):
248257
scopes = [
249258
{
250259
"name": "Locals",
251-
"variablesReference": frame_id * 1000 + 1,
260+
"variablesReference": frame_id * 1000 + VARREF_LOCALS,
252261
"expensive": False
253262
},
254263
{
255264
"name": "Globals",
256-
"variablesReference": frame_id * 1000 + 2,
265+
"variablesReference": frame_id * 1000 + VARREF_GLOBALS ,
257266
"expensive": False
258267
}
259268
]
260269
return scopes
261270

262-
def get_variables(self, variables_ref):
263-
"""Get variables for a scope."""
264-
frame_id = variables_ref // 1000
265-
scope_type = variables_ref % 1000
266-
267-
if frame_id not in self.variables_cache:
268-
return []
269-
270-
frame = self.variables_cache[frame_id]
271+
def _process_special_variables(self, var_dict):
272+
"""Process special variables (those starting and ending with __)."""
273+
variables = []
274+
for name, value in var_dict.items():
275+
if name.startswith('__') and name.endswith('__'):
276+
try:
277+
value_str = json.dumps(value)
278+
type_str = type(value).__name__
279+
variables.append({
280+
"name": name,
281+
"value": value_str,
282+
"type": type_str,
283+
"variablesReference": 0
284+
})
285+
except Exception:
286+
variables.append(self._var_error(name))
287+
return variables
288+
289+
def _process_regular_variables(self, var_dict):
290+
"""Process regular variables (excluding special ones)."""
271291
variables = []
272-
273-
if scope_type == 1: # Locals
274-
var_dict = frame.f_locals if hasattr(frame, 'f_locals') else {}
275-
elif scope_type == 2: # Globals
276-
var_dict = frame.f_globals if hasattr(frame, 'f_globals') else {}
277-
else:
278-
return []
279-
280292
for name, value in var_dict.items():
281293
# Skip private/internal variables
282294
if name.startswith('__') and name.endswith('__'):
283295
continue
284-
285296
try:
286-
value_str = repr(value)
297+
value_str = json.dumps(value)
287298
type_str = type(value).__name__
288-
289299
variables.append({
290300
"name": name,
291301
"value": value_str,
292302
"type": type_str,
293-
"variablesReference": 0 # Simple implementation - no nested objects
294-
})
295-
except Exception:
296-
variables.append({
297-
"name": name,
298-
"value": "<error>",
299-
"type": "unknown",
300303
"variablesReference": 0
301304
})
302-
305+
except Exception:
306+
variables.append(self._var_error(name))
303307
return variables
308+
309+
@staticmethod
310+
def _var_error(name:str):
311+
return {"name": name, "value": "<error>", "type": "unknown", "variablesReference": 0 }
312+
313+
@staticmethod
314+
def _special_vars(varref:int):
315+
return {"name": "Special", "value": "", "variablesReference": varref}
316+
317+
def get_variables(self, variables_ref):
318+
"""Get variables for a scope."""
319+
frame_id = variables_ref // 1000
320+
scope_type = variables_ref % 1000
321+
322+
if frame_id not in self.variables_cache:
323+
return []
324+
325+
frame = self.variables_cache[frame_id]
304326

327+
# Handle special scope types first
328+
if scope_type == VARREF_LOCALS_SPECIAL:
329+
var_dict = frame.f_locals if hasattr(frame, 'f_locals') else {}
330+
return self._process_special_variables(var_dict)
331+
elif scope_type == VARREF_GLOBALS_SPECIAL:
332+
var_dict = frame.f_globals if hasattr(frame, 'f_globals') else {}
333+
return self._process_special_variables(var_dict)
334+
335+
# Handle regular scope types with special folder
336+
variables = []
337+
if scope_type == VARREF_LOCALS:
338+
var_dict = frame.f_locals if hasattr(frame, 'f_locals') else {}
339+
variables.append(self._special_vars( VARREF_LOCALS_SPECIAL))
340+
elif scope_type == VARREF_GLOBALS:
341+
var_dict = frame.f_globals if hasattr(frame, 'f_globals') else {}
342+
variables.append(self._special_vars( VARREF_GLOBALS_SPECIAL))
343+
else:
344+
# Invalid reference, return empty
345+
return []
346+
347+
# Add regular variables
348+
variables.extend(self._process_regular_variables(var_dict))
349+
return variables
350+
305351
def evaluate_expression(self, expression, frame_id=None):
306352
"""Evaluate an expression in the context of a frame."""
307353
if frame_id is not None and frame_id in self.variables_cache:
@@ -317,14 +363,13 @@ def evaluate_expression(self, expression, frame_id=None):
317363
else:
318364
globals_dict = globals()
319365
locals_dict = {}
320-
321366
try:
322367
# Evaluate the expression
323368
result = eval(expression, globals_dict, locals_dict)
324369
return result
325370
except Exception as e:
326371
raise Exception(f"Evaluation error: {e}")
327-
372+
328373
def cleanup(self):
329374
"""Clean up resources."""
330375
self.variables_cache.clear()

0 commit comments

Comments
 (0)