textual-debugger 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/PKG-INFO +91 -26
- {textual_debugger-0.1.2/src/tdb → textual_debugger-0.1.3}/README.md +90 -25
- {textual_debugger-0.1.2 → textual_debugger-0.1.3/src/tdb}/README.md +90 -25
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/__init__.py +1 -1
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app.py +143 -5
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/dap_events.py +12 -3
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/inspection.py +43 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/ui_panels.py +10 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/cli.py +139 -9
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/client.py +55 -0
- textual_debugger-0.1.3/src/tdb/inspection_full.py +230 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/keybindings/__init__.py +2 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/runner.py +51 -14
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/controller.py +51 -2
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/code_view.py +27 -3
- textual_debugger-0.1.3/src/tdb/widgets/full_contents_modal.py +223 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/variable_view.py +85 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/PKG-INFO +91 -26
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/SOURCES.txt +2 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/LICENSE +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/pyproject.toml +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/setup.cfg +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/__main__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/_timeouts.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/routing.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_helpers.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/breakpoint_hook.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/messages.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/protocol.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/types.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/inspection.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/persist.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/post_mortem.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/processes_cache.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/app.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/event_handler.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/handlers.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/rpc_types.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/child_processes.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/event_bus.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/messages.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/post_mortem_loader.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/state.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/statement_stepper.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/terminal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/textual_handler.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/source_analysis.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/_inspection_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/async_tasks_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/breakpoint_view.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/console_view.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/evaluate_console.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/menu_bar.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/modals.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/processes_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/stack_view.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/status_bar.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/threads_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/entry_points.txt +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/requires.txt +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: textual-debugger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A TUI Python debugger based on textual and debugpy
|
|
5
5
|
Author-email: Al Danial <al.danial@gmail.com>
|
|
6
6
|
License: textual-debugger is licensed to you under the MIT/X Consortium license:
|
|
@@ -88,9 +88,9 @@ It specifically supports modules
|
|
|
88
88
|
- `threading` (with a thread inspector)
|
|
89
89
|
- `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
|
|
90
90
|
|
|
91
|
-
- supports remote attachment to
|
|
91
|
+
- supports remote attachment to debugpy-enabled Python programs
|
|
92
92
|
|
|
93
|
-
- includes a JSON-RPC server mode
|
|
93
|
+
- includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
|
|
94
94
|
automated, headless debugging workflows and AI-assisted debugging
|
|
95
95
|
|
|
96
96
|
- can spawn the debuggee in an external terminal to enable debugging TUI applications
|
|
@@ -182,10 +182,20 @@ tdb --no-stop-on-entry my_program.py
|
|
|
182
182
|
tdb --terminal xterm my_program.py
|
|
183
183
|
|
|
184
184
|
# attach to a remote Python program that has a debugpy server on port 5678
|
|
185
|
-
|
|
185
|
+
# (source code is automatically downloaded from the remote host)
|
|
186
|
+
tdb -r remotehost:5678
|
|
186
187
|
|
|
187
|
-
#
|
|
188
|
-
#
|
|
188
|
+
# attach to a remote Python program that has a debugpy server on port 5678
|
|
189
|
+
# and set a breakpoint where tdb and the remote program have the same
|
|
190
|
+
# source code layout
|
|
191
|
+
tdb -r remotehost:5678 -k my_program.py:42
|
|
192
|
+
|
|
193
|
+
# attach to a remote Python program that has a debugpy server on port 5678
|
|
194
|
+
# and set a breakpoint where code on the local host is at a different location
|
|
195
|
+
# than code on the remote host
|
|
196
|
+
tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_program.py:42
|
|
197
|
+
|
|
198
|
+
# separate tdb arguments from debuggee arguments with `--`
|
|
189
199
|
tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
|
|
190
200
|
```
|
|
191
201
|
|
|
@@ -199,7 +209,7 @@ python -m tdb my_program.py
|
|
|
199
209
|
|
|
200
210
|
```
|
|
201
211
|
┌─ Header ──────────────────────────────────────────────┐
|
|
202
|
-
├─ Menu Bar (File
|
|
212
|
+
├─ Menu Bar (File / Configure / Help)───────────────────┤
|
|
203
213
|
│ │ │
|
|
204
214
|
│ Code View │ Console View (stdout) │
|
|
205
215
|
│ (source + breakpoints) ├───────────────────────────┤
|
|
@@ -270,7 +280,7 @@ Switch from Navigate back to Debug mode with `Escape`.
|
|
|
270
280
|
|
|
271
281
|
> **Note:** Many terminals send the byte sequence `ESC+f` for `Alt+F`, which Textual's
|
|
272
282
|
ANSI parser rewrites to `Ctrl+Right` (the readline "forward-word" convention).
|
|
273
|
-
`tdb` binds both so `Alt+F` works as expected
|
|
283
|
+
`tdb` binds both so `Alt+F` works as expected.
|
|
274
284
|
|
|
275
285
|
### Debugging Controls
|
|
276
286
|
|
|
@@ -288,8 +298,10 @@ those for gdb/pdb, with some aliases and extras thrown in for convenience.
|
|
|
288
298
|
| `u` / `d` | Navigate stack up (caller) / down (callee) |
|
|
289
299
|
| `j` / `k` | Move cursor down / up (with count: `5j`, `10k`) |
|
|
290
300
|
| `G` | Go to last line (with count: `42G` jumps to line 42) |
|
|
301
|
+
| `e` | Re-display the last error (traceback) |
|
|
291
302
|
| `R` | Restart the debug session |
|
|
292
|
-
| `
|
|
303
|
+
| `q q` | Quit |
|
|
304
|
+
| `Ctrl+q` | Quit |
|
|
293
305
|
|
|
294
306
|
> **Note:** `f` ("finish") and `r` ("return") are both aliases for step-out. DAP's only
|
|
295
307
|
"exit-a-function" primitive is `stepOut`, which runs the rest of the current function
|
|
@@ -310,7 +322,7 @@ print(results) # next stop lands here, not on each sub-line above
|
|
|
310
322
|
|
|
311
323
|
lands on `print(results)`, not on each interior sub-line of the `gather` call. Switch to
|
|
312
324
|
**Line** mode (Configure > Step Mode) to get debugpy's native per-line behavior, which
|
|
313
|
-
stops on each physical line
|
|
325
|
+
stops on each physical line--useful for inspecting how a complex expression is built up.
|
|
314
326
|
The choice is saved to `~/.config/tdb/config.json`.
|
|
315
327
|
|
|
316
328
|
### Breakpoints
|
|
@@ -337,6 +349,11 @@ The Variable View shows a tree of scopes (Locals, Globals) with all variables in
|
|
|
337
349
|
frame. Expand nodes to drill into complex objects. Children are loaded lazily on demand.
|
|
338
350
|
Variable values can be changed in the Evaluate Console.
|
|
339
351
|
|
|
352
|
+
Double-click a variable, or highlight the variable with the text cursor in
|
|
353
|
+
the Variables View and press `Enter`
|
|
354
|
+
to display that variable in a modal. This simplifies inspection of
|
|
355
|
+
large or deeply nested data structures.
|
|
356
|
+
|
|
340
357
|
### Call Stack
|
|
341
358
|
|
|
342
359
|
The Stack View shows the full call stack. Click a frame to navigate to its source
|
|
@@ -344,7 +361,8 @@ location and inspect its variables.
|
|
|
344
361
|
|
|
345
362
|
### Evaluate Console
|
|
346
363
|
|
|
347
|
-
A REPL at the bottom
|
|
364
|
+
A read-evaluate-print loop (REPL) at the bottom left permits
|
|
365
|
+
interactive evaluation of expressions in the current scope:
|
|
348
366
|
|
|
349
367
|
```
|
|
350
368
|
>>> len(items)
|
|
@@ -370,8 +388,11 @@ The Console View captures stdout (normal text) and stderr (red text) from the de
|
|
|
370
388
|
in real time.
|
|
371
389
|
|
|
372
390
|
If your program prints a lot, or prompts for input, or uses colors or
|
|
373
|
-
terminal control codes,
|
|
374
|
-
with `--terminal` for
|
|
391
|
+
terminal control codes, run the program in an external terminal
|
|
392
|
+
with `--terminal` for a better experience.
|
|
393
|
+
The `--terminal` switch requires a graphical environment and a compatible
|
|
394
|
+
terminal emulator.
|
|
395
|
+
|
|
375
396
|
|
|
376
397
|
### Crash Detection
|
|
377
398
|
|
|
@@ -381,6 +402,9 @@ When the debuggee raises an unhandled exception, `tdb`:
|
|
|
381
402
|
3. Populates the Stack View with the exception's call stack
|
|
382
403
|
4. Lets you press `R` to restart or `Escape` to dismiss
|
|
383
404
|
|
|
405
|
+
> Note: after dismissing the traceback modal, you
|
|
406
|
+
> can display it again by hitting `e` when focus is in the Code View.
|
|
407
|
+
|
|
384
408
|
### Post-Mortem Exception Hook
|
|
385
409
|
|
|
386
410
|
You can have `tdb` pop open automatically when *any* Python program crashes without the
|
|
@@ -389,7 +413,6 @@ need to launch through `tdb` up front. Install the hook once at the top of your
|
|
|
389
413
|
```python
|
|
390
414
|
import sys
|
|
391
415
|
import tdb
|
|
392
|
-
|
|
393
416
|
sys.excepthook = tdb.exception_hook
|
|
394
417
|
```
|
|
395
418
|
|
|
@@ -407,11 +430,11 @@ In post-mortem mode you can:
|
|
|
407
430
|
- Read the full traceback (including chained `cause`/`context` exceptions) in the Console View
|
|
408
431
|
- Jump around the source with the full Code View (syntax highlighting, goto-line, etc.)
|
|
409
432
|
|
|
410
|
-
Stepping,
|
|
411
|
-
interpreter is gone
|
|
433
|
+
Stepping, continue, breakpoints, restart, and the Evaluate View are disabled. The original
|
|
434
|
+
interpreter is gone since the view is a frozen snapshot. Press `q` to exit.
|
|
412
435
|
|
|
413
436
|
The hook is a no-op when stdin/stdout aren't a tty (e.g. when your program is piped or
|
|
414
|
-
run from cron), so it
|
|
437
|
+
run from cron), so it is safe to leave installed in production code. Snapshots are
|
|
415
438
|
written to a temp file that is deleted as soon as `tdb` exits.
|
|
416
439
|
|
|
417
440
|
Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep the
|
|
@@ -422,7 +445,6 @@ memoization.
|
|
|
422
445
|
|
|
423
446
|
`tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
|
|
424
447
|
`pdb.set_trace()`) used to pause at a specific line to inspect, then
|
|
425
|
-
here=
|
|
426
448
|
continuing--use `tdb.breakpoint()`:
|
|
427
449
|
|
|
428
450
|
```python
|
|
@@ -445,7 +467,7 @@ spawns `python -m tdb -r <port>` as a subprocess so the TUI takes over the termi
|
|
|
445
467
|
and pauses the calling thread at the line that called `tdb.breakpoint()` (the hook
|
|
446
468
|
auto-steps out of its own helper so you land in your own frame, not inside
|
|
447
469
|
`breakpoint_hook.py`). Stepping (`n`/`s`/`o`), `continue`, and setting/removing breakpoints
|
|
448
|
-
all work normally; quitting `tdb` (`Ctrl+
|
|
470
|
+
all work normally; quitting `tdb` (`Ctrl+q`) detaches without killing the program, and
|
|
449
471
|
debugpy auto-resumes any threads still paused.
|
|
450
472
|
|
|
451
473
|
This differs from `tdb.exception_hook` in one way:
|
|
@@ -458,6 +480,12 @@ drive the user's program forward.
|
|
|
458
480
|
As with `exception_hook`, the call is a no-op when stdin/stdout aren't a tty, so it's
|
|
459
481
|
safe to leave in code paths that sometimes run headless.
|
|
460
482
|
|
|
483
|
+
Quitting `tdb` while paused in a `tdb.breakpoint()` session detaches the debugger and
|
|
484
|
+
lets the program continue running normally.
|
|
485
|
+
This behavior matches hitting `c` while in a conventional (that is, the Python
|
|
486
|
+
standard library's) `breakpoint()` session.
|
|
487
|
+
If you want to kill the program instead, use `Ctrl+c` in the terminal running the debuggee.
|
|
488
|
+
|
|
461
489
|
### Async Task Inspector
|
|
462
490
|
|
|
463
491
|
For programs using `asyncio`, the menu bar shows an **Async Tasks (N)** label with the count of
|
|
@@ -467,13 +495,17 @@ active tasks (updated each time the program stops). Click it to open a full-scre
|
|
|
467
495
|
primitive (`Lock.acquire`, `Queue.get`, `asyncio.sleep`, …), and coroutine
|
|
468
496
|
- **Right pane**: detail view with full stack trace and an expandable variable tree (same
|
|
469
497
|
as the main Variables View) for the selected task
|
|
470
|
-
- Press `g` to switch the right pane to the **wait graph**
|
|
498
|
+
- Press `g` to switch the right pane to the **wait graph** which is a tree showing
|
|
499
|
+
each blocked
|
|
471
500
|
task, the asyncio primitive it's parked on, and the task(s) holding that primitive.
|
|
472
501
|
Cycles (deadlocks) are highlighted in red both in the task table and as a "Deadlock
|
|
473
502
|
cycles" section at the top of the graph. Selecting a node in the tree highlights the
|
|
474
503
|
corresponding task in the table.
|
|
475
504
|
- Navigate with arrow keys; press `r` to refresh, `Escape` to close
|
|
476
505
|
|
|
506
|
+
> Note: Async task relationships may be directed acyclic graphs (DAGs) rather than trees
|
|
507
|
+
> but I don't know of a way to visualize DAGs in textual.
|
|
508
|
+
|
|
477
509
|
RPC equivalents:
|
|
478
510
|
|
|
479
511
|
```bash
|
|
@@ -585,6 +617,36 @@ All debugging features (breakpoints, stepping, variable inspection, threads, pro
|
|
|
585
617
|
async tasks) work in remote attach mode. The Code View automatically navigates to the
|
|
586
618
|
source file when the program stops.
|
|
587
619
|
|
|
620
|
+
**Mapping remote paths to local copies (`--local-root` / `--remote-root`):** when the
|
|
621
|
+
debuggee lives on another machine, or in a container, or simply in a different directory
|
|
622
|
+
on the same machine, the source paths it reports (and the paths it expects breakpoints
|
|
623
|
+
to refer to) won't match anything on the `tdb` host. To bridge that gap, give `tdb` one
|
|
624
|
+
or more `--local-root` / `--remote-root` pairs. Each `--local-root` points at a local
|
|
625
|
+
directory containing a copy of the code; each `--remote-root` is the corresponding path
|
|
626
|
+
on the debuggee. The two flags must be supplied in equal numbers and are paired in CLI
|
|
627
|
+
order via `zip()`, so the first `--local-root` matches the first `--remote-root`, the
|
|
628
|
+
second matches the second, and so on. `debugpy` then translates paths bidirectionally:
|
|
629
|
+
breakpoints set on a local file land on the matching remote file, and source paths
|
|
630
|
+
returned in stopped-events / stack-traces are rewritten back to the local copy so the
|
|
631
|
+
Code View loads directly from disk (no DAP `source` round-trip needed).
|
|
632
|
+
|
|
633
|
+
These flags are required whenever you want to set a `-k` breakpoint against a remote
|
|
634
|
+
debuggee whose code lives at a different path than your local copy. For example, if the
|
|
635
|
+
remote runs `program.py` at `/path/to/code/program.py` and your local copy is at
|
|
636
|
+
`/local/project/code/program.py`, set a breakpoint at line 321 with:
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
tdb -r RHOST:15678 \
|
|
640
|
+
--local-root /local/project/code \
|
|
641
|
+
--remote-root /path/to/code \
|
|
642
|
+
-k program.py:321
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
With `--local-root` set, a relative `-k FILE:LINE` is resolved by searching each
|
|
646
|
+
`--local-root` directory in CLI order (first match wins); absolute paths still work as
|
|
647
|
+
before. Multiple pairs can be supplied to mirror multiple source trees (e.g. an
|
|
648
|
+
application directory and a shared library directory) in one invocation.
|
|
649
|
+
|
|
588
650
|
### External Terminal Support
|
|
589
651
|
|
|
590
652
|
Some Python programs, notably text user interfaces, use terminal control
|
|
@@ -712,22 +774,25 @@ Each is JSON with `event`, `data`, and `timestamp` fields.
|
|
|
712
774
|
## CLI Reference
|
|
713
775
|
|
|
714
776
|
```
|
|
715
|
-
usage: tdb [-h] [-v
|
|
716
|
-
[--no-just-my-code] [--no-subprocess] [--python PYTHON]
|
|
717
|
-
[
|
|
777
|
+
usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
|
|
778
|
+
[--no-just-my-code] [--no-subprocess] [--python PYTHON] [--pv]
|
|
779
|
+
[--keybindings {default,vim,emacs}]
|
|
718
780
|
[--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
|
|
719
|
-
[--
|
|
781
|
+
[--local-root PATH] [--remote-root PATH]
|
|
782
|
+
[--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text]
|
|
720
783
|
[program] [args ...]
|
|
721
|
-
|
|
722
784
|
```
|
|
723
785
|
|
|
724
786
|
| Flag | Description |
|
|
725
787
|
|------|-------------|
|
|
726
788
|
| `-r HOST:PORT` | Attach to a remote debugpy server |
|
|
789
|
+
| `--local-root PATH` | Local directory containing a copy of remote code (repeat to mirror multiple trees). Pair with `--remote-root`; counts must match. Required when `-k` sets a breakpoint against a remote debuggee whose code lives at a different path. |
|
|
790
|
+
| `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
|
|
727
791
|
| `-k`, `--breakpoint FILE:LINE|LINE` | Set a breakpoint (may be repeated). Passing `-k` implies `--no-stop-on-entry` so the program runs straight to the first breakpoint. |
|
|
728
792
|
| `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
|
|
729
793
|
| `--cwd DIR` | Working directory for the debuggee |
|
|
730
794
|
| `--python PATH` | Python interpreter for the debuggee |
|
|
795
|
+
| `--pv` | Shorthand for --python .venv/bin/python |
|
|
731
796
|
| `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
|
|
732
797
|
(default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
|
|
733
798
|
including library frames, regardless of this flag. |
|
|
@@ -759,7 +824,7 @@ into) handle multi-line source statements:
|
|
|
759
824
|
| Value | Behavior |
|
|
760
825
|
|-------|----------|
|
|
761
826
|
| `"statement"` (default) | A multi-line statement (e.g. a `gather(...)` call spanning five lines) is one step. The debugger keeps issuing DAP steps until execution leaves the statement, then stops on the next logical line. |
|
|
762
|
-
| `"line"` | debugpy's native per-line behavior
|
|
827
|
+
| `"line"` | debugpy's native per-line behavior (stops on every physical line, including each interior sub-line of a multi-line expression)|
|
|
763
828
|
|
|
764
829
|
Change it from the menu (**Configure > Step Mode**); the choice is saved immediately and
|
|
765
830
|
applies to all future sessions. Breakpoint hits, exceptions, and pauses always interrupt
|
|
@@ -23,9 +23,9 @@ It specifically supports modules
|
|
|
23
23
|
- `threading` (with a thread inspector)
|
|
24
24
|
- `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
|
|
25
25
|
|
|
26
|
-
- supports remote attachment to
|
|
26
|
+
- supports remote attachment to debugpy-enabled Python programs
|
|
27
27
|
|
|
28
|
-
- includes a JSON-RPC server mode
|
|
28
|
+
- includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
|
|
29
29
|
automated, headless debugging workflows and AI-assisted debugging
|
|
30
30
|
|
|
31
31
|
- can spawn the debuggee in an external terminal to enable debugging TUI applications
|
|
@@ -117,10 +117,20 @@ tdb --no-stop-on-entry my_program.py
|
|
|
117
117
|
tdb --terminal xterm my_program.py
|
|
118
118
|
|
|
119
119
|
# attach to a remote Python program that has a debugpy server on port 5678
|
|
120
|
-
|
|
120
|
+
# (source code is automatically downloaded from the remote host)
|
|
121
|
+
tdb -r remotehost:5678
|
|
121
122
|
|
|
122
|
-
#
|
|
123
|
-
#
|
|
123
|
+
# attach to a remote Python program that has a debugpy server on port 5678
|
|
124
|
+
# and set a breakpoint where tdb and the remote program have the same
|
|
125
|
+
# source code layout
|
|
126
|
+
tdb -r remotehost:5678 -k my_program.py:42
|
|
127
|
+
|
|
128
|
+
# attach to a remote Python program that has a debugpy server on port 5678
|
|
129
|
+
# and set a breakpoint where code on the local host is at a different location
|
|
130
|
+
# than code on the remote host
|
|
131
|
+
tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_program.py:42
|
|
132
|
+
|
|
133
|
+
# separate tdb arguments from debuggee arguments with `--`
|
|
124
134
|
tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
|
|
125
135
|
```
|
|
126
136
|
|
|
@@ -134,7 +144,7 @@ python -m tdb my_program.py
|
|
|
134
144
|
|
|
135
145
|
```
|
|
136
146
|
┌─ Header ──────────────────────────────────────────────┐
|
|
137
|
-
├─ Menu Bar (File
|
|
147
|
+
├─ Menu Bar (File / Configure / Help)───────────────────┤
|
|
138
148
|
│ │ │
|
|
139
149
|
│ Code View │ Console View (stdout) │
|
|
140
150
|
│ (source + breakpoints) ├───────────────────────────┤
|
|
@@ -205,7 +215,7 @@ Switch from Navigate back to Debug mode with `Escape`.
|
|
|
205
215
|
|
|
206
216
|
> **Note:** Many terminals send the byte sequence `ESC+f` for `Alt+F`, which Textual's
|
|
207
217
|
ANSI parser rewrites to `Ctrl+Right` (the readline "forward-word" convention).
|
|
208
|
-
`tdb` binds both so `Alt+F` works as expected
|
|
218
|
+
`tdb` binds both so `Alt+F` works as expected.
|
|
209
219
|
|
|
210
220
|
### Debugging Controls
|
|
211
221
|
|
|
@@ -223,8 +233,10 @@ those for gdb/pdb, with some aliases and extras thrown in for convenience.
|
|
|
223
233
|
| `u` / `d` | Navigate stack up (caller) / down (callee) |
|
|
224
234
|
| `j` / `k` | Move cursor down / up (with count: `5j`, `10k`) |
|
|
225
235
|
| `G` | Go to last line (with count: `42G` jumps to line 42) |
|
|
236
|
+
| `e` | Re-display the last error (traceback) |
|
|
226
237
|
| `R` | Restart the debug session |
|
|
227
|
-
| `
|
|
238
|
+
| `q q` | Quit |
|
|
239
|
+
| `Ctrl+q` | Quit |
|
|
228
240
|
|
|
229
241
|
> **Note:** `f` ("finish") and `r` ("return") are both aliases for step-out. DAP's only
|
|
230
242
|
"exit-a-function" primitive is `stepOut`, which runs the rest of the current function
|
|
@@ -245,7 +257,7 @@ print(results) # next stop lands here, not on each sub-line above
|
|
|
245
257
|
|
|
246
258
|
lands on `print(results)`, not on each interior sub-line of the `gather` call. Switch to
|
|
247
259
|
**Line** mode (Configure > Step Mode) to get debugpy's native per-line behavior, which
|
|
248
|
-
stops on each physical line
|
|
260
|
+
stops on each physical line--useful for inspecting how a complex expression is built up.
|
|
249
261
|
The choice is saved to `~/.config/tdb/config.json`.
|
|
250
262
|
|
|
251
263
|
### Breakpoints
|
|
@@ -272,6 +284,11 @@ The Variable View shows a tree of scopes (Locals, Globals) with all variables in
|
|
|
272
284
|
frame. Expand nodes to drill into complex objects. Children are loaded lazily on demand.
|
|
273
285
|
Variable values can be changed in the Evaluate Console.
|
|
274
286
|
|
|
287
|
+
Double-click a variable, or highlight the variable with the text cursor in
|
|
288
|
+
the Variables View and press `Enter`
|
|
289
|
+
to display that variable in a modal. This simplifies inspection of
|
|
290
|
+
large or deeply nested data structures.
|
|
291
|
+
|
|
275
292
|
### Call Stack
|
|
276
293
|
|
|
277
294
|
The Stack View shows the full call stack. Click a frame to navigate to its source
|
|
@@ -279,7 +296,8 @@ location and inspect its variables.
|
|
|
279
296
|
|
|
280
297
|
### Evaluate Console
|
|
281
298
|
|
|
282
|
-
A REPL at the bottom
|
|
299
|
+
A read-evaluate-print loop (REPL) at the bottom left permits
|
|
300
|
+
interactive evaluation of expressions in the current scope:
|
|
283
301
|
|
|
284
302
|
```
|
|
285
303
|
>>> len(items)
|
|
@@ -305,8 +323,11 @@ The Console View captures stdout (normal text) and stderr (red text) from the de
|
|
|
305
323
|
in real time.
|
|
306
324
|
|
|
307
325
|
If your program prints a lot, or prompts for input, or uses colors or
|
|
308
|
-
terminal control codes,
|
|
309
|
-
with `--terminal` for
|
|
326
|
+
terminal control codes, run the program in an external terminal
|
|
327
|
+
with `--terminal` for a better experience.
|
|
328
|
+
The `--terminal` switch requires a graphical environment and a compatible
|
|
329
|
+
terminal emulator.
|
|
330
|
+
|
|
310
331
|
|
|
311
332
|
### Crash Detection
|
|
312
333
|
|
|
@@ -316,6 +337,9 @@ When the debuggee raises an unhandled exception, `tdb`:
|
|
|
316
337
|
3. Populates the Stack View with the exception's call stack
|
|
317
338
|
4. Lets you press `R` to restart or `Escape` to dismiss
|
|
318
339
|
|
|
340
|
+
> Note: after dismissing the traceback modal, you
|
|
341
|
+
> can display it again by hitting `e` when focus is in the Code View.
|
|
342
|
+
|
|
319
343
|
### Post-Mortem Exception Hook
|
|
320
344
|
|
|
321
345
|
You can have `tdb` pop open automatically when *any* Python program crashes without the
|
|
@@ -324,7 +348,6 @@ need to launch through `tdb` up front. Install the hook once at the top of your
|
|
|
324
348
|
```python
|
|
325
349
|
import sys
|
|
326
350
|
import tdb
|
|
327
|
-
|
|
328
351
|
sys.excepthook = tdb.exception_hook
|
|
329
352
|
```
|
|
330
353
|
|
|
@@ -342,11 +365,11 @@ In post-mortem mode you can:
|
|
|
342
365
|
- Read the full traceback (including chained `cause`/`context` exceptions) in the Console View
|
|
343
366
|
- Jump around the source with the full Code View (syntax highlighting, goto-line, etc.)
|
|
344
367
|
|
|
345
|
-
Stepping,
|
|
346
|
-
interpreter is gone
|
|
368
|
+
Stepping, continue, breakpoints, restart, and the Evaluate View are disabled. The original
|
|
369
|
+
interpreter is gone since the view is a frozen snapshot. Press `q` to exit.
|
|
347
370
|
|
|
348
371
|
The hook is a no-op when stdin/stdout aren't a tty (e.g. when your program is piped or
|
|
349
|
-
run from cron), so it
|
|
372
|
+
run from cron), so it is safe to leave installed in production code. Snapshots are
|
|
350
373
|
written to a temp file that is deleted as soon as `tdb` exits.
|
|
351
374
|
|
|
352
375
|
Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep the
|
|
@@ -357,7 +380,6 @@ memoization.
|
|
|
357
380
|
|
|
358
381
|
`tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
|
|
359
382
|
`pdb.set_trace()`) used to pause at a specific line to inspect, then
|
|
360
|
-
here=
|
|
361
383
|
continuing--use `tdb.breakpoint()`:
|
|
362
384
|
|
|
363
385
|
```python
|
|
@@ -380,7 +402,7 @@ spawns `python -m tdb -r <port>` as a subprocess so the TUI takes over the termi
|
|
|
380
402
|
and pauses the calling thread at the line that called `tdb.breakpoint()` (the hook
|
|
381
403
|
auto-steps out of its own helper so you land in your own frame, not inside
|
|
382
404
|
`breakpoint_hook.py`). Stepping (`n`/`s`/`o`), `continue`, and setting/removing breakpoints
|
|
383
|
-
all work normally; quitting `tdb` (`Ctrl+
|
|
405
|
+
all work normally; quitting `tdb` (`Ctrl+q`) detaches without killing the program, and
|
|
384
406
|
debugpy auto-resumes any threads still paused.
|
|
385
407
|
|
|
386
408
|
This differs from `tdb.exception_hook` in one way:
|
|
@@ -393,6 +415,12 @@ drive the user's program forward.
|
|
|
393
415
|
As with `exception_hook`, the call is a no-op when stdin/stdout aren't a tty, so it's
|
|
394
416
|
safe to leave in code paths that sometimes run headless.
|
|
395
417
|
|
|
418
|
+
Quitting `tdb` while paused in a `tdb.breakpoint()` session detaches the debugger and
|
|
419
|
+
lets the program continue running normally.
|
|
420
|
+
This behavior matches hitting `c` while in a conventional (that is, the Python
|
|
421
|
+
standard library's) `breakpoint()` session.
|
|
422
|
+
If you want to kill the program instead, use `Ctrl+c` in the terminal running the debuggee.
|
|
423
|
+
|
|
396
424
|
### Async Task Inspector
|
|
397
425
|
|
|
398
426
|
For programs using `asyncio`, the menu bar shows an **Async Tasks (N)** label with the count of
|
|
@@ -402,13 +430,17 @@ active tasks (updated each time the program stops). Click it to open a full-scre
|
|
|
402
430
|
primitive (`Lock.acquire`, `Queue.get`, `asyncio.sleep`, …), and coroutine
|
|
403
431
|
- **Right pane**: detail view with full stack trace and an expandable variable tree (same
|
|
404
432
|
as the main Variables View) for the selected task
|
|
405
|
-
- Press `g` to switch the right pane to the **wait graph**
|
|
433
|
+
- Press `g` to switch the right pane to the **wait graph** which is a tree showing
|
|
434
|
+
each blocked
|
|
406
435
|
task, the asyncio primitive it's parked on, and the task(s) holding that primitive.
|
|
407
436
|
Cycles (deadlocks) are highlighted in red both in the task table and as a "Deadlock
|
|
408
437
|
cycles" section at the top of the graph. Selecting a node in the tree highlights the
|
|
409
438
|
corresponding task in the table.
|
|
410
439
|
- Navigate with arrow keys; press `r` to refresh, `Escape` to close
|
|
411
440
|
|
|
441
|
+
> Note: Async task relationships may be directed acyclic graphs (DAGs) rather than trees
|
|
442
|
+
> but I don't know of a way to visualize DAGs in textual.
|
|
443
|
+
|
|
412
444
|
RPC equivalents:
|
|
413
445
|
|
|
414
446
|
```bash
|
|
@@ -520,6 +552,36 @@ All debugging features (breakpoints, stepping, variable inspection, threads, pro
|
|
|
520
552
|
async tasks) work in remote attach mode. The Code View automatically navigates to the
|
|
521
553
|
source file when the program stops.
|
|
522
554
|
|
|
555
|
+
**Mapping remote paths to local copies (`--local-root` / `--remote-root`):** when the
|
|
556
|
+
debuggee lives on another machine, or in a container, or simply in a different directory
|
|
557
|
+
on the same machine, the source paths it reports (and the paths it expects breakpoints
|
|
558
|
+
to refer to) won't match anything on the `tdb` host. To bridge that gap, give `tdb` one
|
|
559
|
+
or more `--local-root` / `--remote-root` pairs. Each `--local-root` points at a local
|
|
560
|
+
directory containing a copy of the code; each `--remote-root` is the corresponding path
|
|
561
|
+
on the debuggee. The two flags must be supplied in equal numbers and are paired in CLI
|
|
562
|
+
order via `zip()`, so the first `--local-root` matches the first `--remote-root`, the
|
|
563
|
+
second matches the second, and so on. `debugpy` then translates paths bidirectionally:
|
|
564
|
+
breakpoints set on a local file land on the matching remote file, and source paths
|
|
565
|
+
returned in stopped-events / stack-traces are rewritten back to the local copy so the
|
|
566
|
+
Code View loads directly from disk (no DAP `source` round-trip needed).
|
|
567
|
+
|
|
568
|
+
These flags are required whenever you want to set a `-k` breakpoint against a remote
|
|
569
|
+
debuggee whose code lives at a different path than your local copy. For example, if the
|
|
570
|
+
remote runs `program.py` at `/path/to/code/program.py` and your local copy is at
|
|
571
|
+
`/local/project/code/program.py`, set a breakpoint at line 321 with:
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
tdb -r RHOST:15678 \
|
|
575
|
+
--local-root /local/project/code \
|
|
576
|
+
--remote-root /path/to/code \
|
|
577
|
+
-k program.py:321
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
With `--local-root` set, a relative `-k FILE:LINE` is resolved by searching each
|
|
581
|
+
`--local-root` directory in CLI order (first match wins); absolute paths still work as
|
|
582
|
+
before. Multiple pairs can be supplied to mirror multiple source trees (e.g. an
|
|
583
|
+
application directory and a shared library directory) in one invocation.
|
|
584
|
+
|
|
523
585
|
### External Terminal Support
|
|
524
586
|
|
|
525
587
|
Some Python programs, notably text user interfaces, use terminal control
|
|
@@ -647,22 +709,25 @@ Each is JSON with `event`, `data`, and `timestamp` fields.
|
|
|
647
709
|
## CLI Reference
|
|
648
710
|
|
|
649
711
|
```
|
|
650
|
-
usage: tdb [-h] [-v
|
|
651
|
-
[--no-just-my-code] [--no-subprocess] [--python PYTHON]
|
|
652
|
-
[
|
|
712
|
+
usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
|
|
713
|
+
[--no-just-my-code] [--no-subprocess] [--python PYTHON] [--pv]
|
|
714
|
+
[--keybindings {default,vim,emacs}]
|
|
653
715
|
[--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
|
|
654
|
-
[--
|
|
716
|
+
[--local-root PATH] [--remote-root PATH]
|
|
717
|
+
[--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text]
|
|
655
718
|
[program] [args ...]
|
|
656
|
-
|
|
657
719
|
```
|
|
658
720
|
|
|
659
721
|
| Flag | Description |
|
|
660
722
|
|------|-------------|
|
|
661
723
|
| `-r HOST:PORT` | Attach to a remote debugpy server |
|
|
724
|
+
| `--local-root PATH` | Local directory containing a copy of remote code (repeat to mirror multiple trees). Pair with `--remote-root`; counts must match. Required when `-k` sets a breakpoint against a remote debuggee whose code lives at a different path. |
|
|
725
|
+
| `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
|
|
662
726
|
| `-k`, `--breakpoint FILE:LINE|LINE` | Set a breakpoint (may be repeated). Passing `-k` implies `--no-stop-on-entry` so the program runs straight to the first breakpoint. |
|
|
663
727
|
| `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
|
|
664
728
|
| `--cwd DIR` | Working directory for the debuggee |
|
|
665
729
|
| `--python PATH` | Python interpreter for the debuggee |
|
|
730
|
+
| `--pv` | Shorthand for --python .venv/bin/python |
|
|
666
731
|
| `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
|
|
667
732
|
(default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
|
|
668
733
|
including library frames, regardless of this flag. |
|
|
@@ -694,7 +759,7 @@ into) handle multi-line source statements:
|
|
|
694
759
|
| Value | Behavior |
|
|
695
760
|
|-------|----------|
|
|
696
761
|
| `"statement"` (default) | A multi-line statement (e.g. a `gather(...)` call spanning five lines) is one step. The debugger keeps issuing DAP steps until execution leaves the statement, then stops on the next logical line. |
|
|
697
|
-
| `"line"` | debugpy's native per-line behavior
|
|
762
|
+
| `"line"` | debugpy's native per-line behavior (stops on every physical line, including each interior sub-line of a multi-line expression)|
|
|
698
763
|
|
|
699
764
|
Change it from the menu (**Configure > Step Mode**); the choice is saved immediately and
|
|
700
765
|
applies to all future sessions. Breakpoint hits, exceptions, and pauses always interrupt
|