Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d3c968f

Browse files
committed
dynamic scopes
1 parent a21fc87 commit d3c968f

File tree

2 files changed

+118
-76
lines changed

2 files changed

+118
-76
lines changed

dap_client.py

Lines changed: 81 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
#!/usr/bin/env python3
2+
13
import socket
24
import json
35

46
HOST = "127.0.0.1"
57
PORT = 5678
6-
DEPTH_LIMIT = 3 # 2 # How many levels deep to fetch variables
8+
DEPTH_LIMIT = 3 # How many levels deep to fetch variables
79

810

911
def read_line(sock):
@@ -36,7 +38,6 @@ def read_dap_message(sock):
3638
Reads and returns one DAP message from the socket as a Python dict.
3739
Raises ConnectionError if the socket is closed or data is invalid.
3840
"""
39-
# Read headers until blank line
4041
headers = {}
4142
while True:
4243
line = read_line(sock)
@@ -85,7 +86,6 @@ def fetch_variables(sock, seq, var_ref):
8586
if msg.get("type") == "response" and msg.get("command") == "variables":
8687
variables_response = msg
8788
else:
88-
# Just log and continue
8989
print("Got message (waiting for variables):", msg)
9090

9191
vars_body = variables_response.get("body", {})
@@ -95,71 +95,83 @@ def fetch_variables(sock, seq, var_ref):
9595

9696
def fetch_variable_tree(sock, seq, var_ref, depth=DEPTH_LIMIT, visited=None):
9797
"""
98-
Recursively fetches a tree of variables up to a certain depth.
99-
- var_ref: The DAP variablesReference to expand.
100-
- depth: How many levels deep to recurse.
101-
- visited: A set of references we've already expanded, to avoid cycles.
102-
103-
Returns (updated_seq, list_of_trees).
98+
Recursively fetches a tree of variables up to 'depth' levels.
10499
105-
Each item in list_of_trees is a dict:
100+
Each returned item is a dict:
106101
{
107102
"name": str,
108103
"value": str,
109104
"type": str,
110105
"evaluateName": str or None,
111106
"variablesReference": int,
112-
"children": [ ... nested items ... ]
107+
"children": [...]
113108
}
114109
"""
115110
if visited is None:
116111
visited = set()
117112

118-
# If we've already visited this reference, skip to avoid infinite loop
113+
# Prevent infinite recursion on cyclical references
119114
if var_ref in visited:
120115
return seq, [
121-
{"name": "<recursive>", "value": "...", "type": "recursive", "children": []}
116+
{
117+
"name": "<recursive>",
118+
"value": "...",
119+
"type": "recursive",
120+
"evaluateName": None,
121+
"variablesReference": 0,
122+
"children": [],
123+
}
122124
]
123125

124126
visited.add(var_ref)
125127

126-
# Always do a single-level "variables" fetch
128+
# 1) Fetch immediate child variables at this level
127129
seq, vars_list = fetch_variables(sock, seq, var_ref)
128130

129131
result = []
130132
for v in vars_list:
131-
child = {
133+
child_ref = v.get("variablesReference", 0)
134+
item = {
132135
"name": v["name"],
133136
"value": v.get("value", ""),
134137
"type": v.get("type", ""),
135138
"evaluateName": v.get("evaluateName"),
136-
"variablesReference": v.get("variablesReference", 0),
139+
"variablesReference": child_ref,
137140
"children": [],
138141
}
139142

140-
# If this variable has nested children, and we still have depth left
141-
child_ref = child["variablesReference"]
142-
if child_ref and child_ref > 0 and depth > 0:
143-
# Recursively fetch child variables
143+
# If this variable itself has children, recurse (within depth)
144+
if child_ref > 0 and depth > 0:
144145
seq, child_vars = fetch_variable_tree(
145146
sock, seq, child_ref, depth=depth - 1, visited=visited
146147
)
147-
child["children"] = child_vars
148+
item["children"] = child_vars
148149

149-
result.append(child)
150+
result.append(item)
150151

151152
return seq, result
152153

153154

154155
def dap_client():
155-
"""Example DAP client that pauses the main thread and fetches local/global variables with children."""
156+
"""
157+
Example DAP client that:
158+
1. Connects to debugpy,
159+
2. Attaches to a running Python script,
160+
3. Sends configurationDone,
161+
4. Pauses the first thread,
162+
5. Reads the stack trace,
163+
6. For each frame, fetches all scopes (locals, globals, closures, etc.),
164+
7. Recursively expands variables up to DEPTH_LIMIT,
165+
8. Returns a structure with all frames and scopes.
166+
"""
167+
156168
print(f"Connecting to {HOST}:{PORT}...")
157169
sock = socket.create_connection((HOST, PORT))
158170
sock.settimeout(10.0)
159171

160172
seq = 1
161173

162-
# 1) initialize
174+
# 1) "initialize"
163175
seq = send_dap_request(
164176
sock,
165177
seq,
@@ -185,11 +197,11 @@ def dap_client():
185197
else:
186198
print("Got message (before initialize response):", msg)
187199

188-
# 2) attach
200+
# 2) "attach"
189201
seq = send_dap_request(sock, seq, "attach", {"subProcess": False})
190202
print("Sent 'attach' request.")
191203

192-
# 3) configurationDone
204+
# 3) "configurationDone"
193205
print("Sending 'configurationDone' request...")
194206
seq = send_dap_request(sock, seq, "configurationDone")
195207
print("Sent 'configurationDone' request.")
@@ -203,7 +215,7 @@ def dap_client():
203215
else:
204216
print("Got message (waiting for configurationDone):", msg)
205217

206-
# 4) threads
218+
# 4) "threads"
207219
seq = send_dap_request(sock, seq, "threads")
208220
print("Sent 'threads' request.")
209221

@@ -222,9 +234,9 @@ def dap_client():
222234
if not threads_list:
223235
print("No threads. Exiting.")
224236
sock.close()
225-
return {}
237+
return {"frames": []}
226238

227-
# Pause the first thread so we can inspect variables
239+
# Pause the first thread to ensure we can see meaningful variable data
228240
thread_id = threads_list[0]["id"]
229241
print(f"Pausing thread {thread_id}...")
230242

@@ -241,9 +253,16 @@ def dap_client():
241253
else:
242254
print("Got message while waiting to pause:", msg)
243255

244-
# Now that thread is paused, ask for "stackTrace"
256+
# 5) "stackTrace"
245257
seq = send_dap_request(
246-
sock, seq, "stackTrace", {"threadId": thread_id, "startFrame": 0, "levels": 20}
258+
sock,
259+
seq,
260+
"stackTrace",
261+
{
262+
"threadId": thread_id,
263+
"startFrame": 0,
264+
"levels": 50, # raise if you suspect more frames
265+
},
247266
)
248267
stack_trace_response = None
249268
while not stack_trace_response:
@@ -253,20 +272,17 @@ def dap_client():
253272
else:
254273
print("Got message (waiting for stackTrace):", msg)
255274

275+
frames_data = []
256276
frames = stack_trace_response["body"].get("stackFrames", [])
257-
print(f"Found {len(frames)} frames.")
277+
print(f"Found {len(frames)} frames in stackTrace.")
258278

259-
globals_result = []
260-
locals_result = []
279+
for frame_info in frames:
280+
frame_id = frame_info["id"]
281+
fn_name = frame_info["name"]
282+
source_path = frame_info.get("source", {}).get("path", "no_source")
283+
print(f"Frame {frame_id}: {fn_name} @ {source_path}")
261284

262-
# Inspect the top frame's scopes, or all frames if you like
263-
for f in frames:
264-
frame_id = f["id"]
265-
print(
266-
f"Frame ID {frame_id}: {f['name']} @ {f.get('source',{}).get('path','no_source')}"
267-
)
268-
269-
# 1) get scopes
285+
# 6) "scopes" for each frame
270286
seq = send_dap_request(sock, seq, "scopes", {"frameId": frame_id})
271287
scopes_response = None
272288
while not scopes_response:
@@ -277,31 +293,37 @@ def dap_client():
277293
print("Got message (waiting for scopes):", msg)
278294

279295
scope_list = scopes_response["body"].get("scopes", [])
296+
297+
# We'll store all scopes in a dict keyed by scope name
298+
scope_dict = {}
280299
for scope_info in scope_list:
281-
scope_name = scope_info["name"]
300+
scope_name_original = scope_info["name"]
301+
scope_name_lower = scope_name_original.lower()
282302
scope_ref = scope_info["variablesReference"]
283-
print(f" Scope: {scope_name} (ref={scope_ref})")
284-
285-
# 2) Recursively expand the variables in this scope
286-
seq, var_tree = fetch_variable_tree(sock, seq, scope_ref, depth=2)
287-
288-
if scope_name.lower() == "locals":
289-
locals_result.extend(var_tree)
290-
elif scope_name.lower() == "globals":
291-
globals_result.extend(var_tree)
292-
else:
293-
print(f" (Scope '{scope_name}' not recognized as locals/globals)")
303+
print(f" Scope: {scope_name_original} (ref={scope_ref})")
304+
305+
# Recursively expand variables in this scope
306+
seq, var_tree = fetch_variable_tree(sock, seq, scope_ref, depth=DEPTH_LIMIT)
307+
# Store them under the scope name (lowercased or original, your choice)
308+
scope_dict[scope_name_lower] = var_tree
309+
310+
frames_data.append(
311+
{
312+
"id": frame_id,
313+
"functionName": fn_name,
314+
"sourcePath": source_path,
315+
"scopes": scope_dict,
316+
}
317+
)
294318

295319
print("Done collecting variables. Closing socket.")
296320
sock.close()
297321

298-
return {
299-
"globals": globals_result,
300-
"locals": locals_result,
301-
}
322+
# Return everything
323+
return {"frames": frames_data}
302324

303325

304326
if __name__ == "__main__":
305327
result = dap_client()
306-
print("\n=== Final Expanded Variables ===\n")
328+
print("\n=== Final Expanded Frames ===\n")
307329
print(json.dumps(result, indent=2))

pov.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def render_variable_tree(variables):
9999
Renders a list of variables (each may have 'children') in a nested tree format.
100100
"""
101101
for v in variables:
102+
print(f"Rendering variable tree for: {v}")
102103
name = v["name"]
103104
value = v.get("value", "unknown")
104105
var_type = v.get("type", "unknown")
@@ -156,25 +157,44 @@ def pov():
156157
if dap_task.done:
157158
print("dap_task is done")
158159
results = dap_task.result # This is the dict returned by dap_client()
159-
# print(f"Results: {results}")
160-
print(f"Results: {results.keys()}")
161-
# print(f"Got {len(results)} variables")
162-
print(f"Got {len(results['locals'])} local variables")
163-
print(f"Got {len(results['globals'])} global variables")
164-
165-
globals_scope = results.get("globals", [])
166-
locals_scope = results.get("locals", [])
167-
168-
# Tree method
169-
with hd.tab_group() as tabs:
170-
t1 = hd.tab("Locals")
171-
t2 = hd.tab("Globals")
172160

161+
# If no frames, nothing to display
162+
frames = results.get("frames", [])
163+
if not frames:
164+
hd.markdown("No frames returned from dap_client.")
165+
return
166+
167+
# Right now we only get one frame, so we'll just use that
168+
first_frame = frames[0]
169+
dap_scopes = first_frame.get("scopes", {})
170+
print(f"Scopes available: {list(dap_scopes.keys())}")
171+
172+
# Count variables in each scope
173+
for scope_list in dap_scopes.keys():
174+
print(
175+
f"Scope: {scope_list} has {len(dap_scopes[scope_list])} variables"
176+
)
177+
178+
# Create a tab group for all scope names
179+
tabs_dict = {}
180+
with hd.tab_group():
181+
for scope_name in dap_scopes.keys():
182+
with hd.scope(scope_name):
183+
# Create a tab with the title = scope_name.title()
184+
tab_obj = hd.tab(scope_name.title())
185+
# Store the tab object in a dict so we can check if it's active later
186+
tabs_dict[scope_name] = tab_obj
187+
188+
# Now show the variables for whichever tab is active
173189
with hd.hbox(gap=1):
174-
if t1.active:
175-
render_tree(locals_scope, "Locals")
176-
elif t2.active:
177-
render_tree(globals_scope, "Globals")
190+
# We'll iterate again to find the active tab
191+
for scope_name, tab_obj in tabs_dict.items():
192+
with hd.scope(tab_obj):
193+
if tab_obj.active:
194+
scope_vars = dap_scopes[scope_name]
195+
render_tree(
196+
scope_vars, title=f"{scope_name.title()} Scope"
197+
)
178198

179199

180200
hd.run(

0 commit comments

Comments
 (0)