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.
Files changed (67) hide show
  1. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/PKG-INFO +91 -26
  2. {textual_debugger-0.1.2/src/tdb → textual_debugger-0.1.3}/README.md +90 -25
  3. {textual_debugger-0.1.2 → textual_debugger-0.1.3/src/tdb}/README.md +90 -25
  4. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/__init__.py +1 -1
  5. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app.py +143 -5
  6. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/dap_events.py +12 -3
  7. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/inspection.py +43 -0
  8. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/ui_panels.py +10 -0
  9. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/cli.py +139 -9
  10. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/client.py +55 -0
  11. textual_debugger-0.1.3/src/tdb/inspection_full.py +230 -0
  12. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/keybindings/__init__.py +2 -0
  13. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/runner.py +51 -14
  14. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/controller.py +51 -2
  15. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/code_view.py +27 -3
  16. textual_debugger-0.1.3/src/tdb/widgets/full_contents_modal.py +223 -0
  17. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/variable_view.py +85 -0
  18. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/PKG-INFO +91 -26
  19. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/SOURCES.txt +2 -0
  20. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/LICENSE +0 -0
  21. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/pyproject.toml +0 -0
  22. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/setup.cfg +0 -0
  23. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/__main__.py +0 -0
  24. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/_timeouts.py +0 -0
  25. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/__init__.py +0 -0
  26. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_handlers/routing.py +0 -0
  27. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/app_helpers.py +0 -0
  28. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/breakpoint_hook.py +0 -0
  29. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/__init__.py +0 -0
  30. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/messages.py +0 -0
  31. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/protocol.py +0 -0
  32. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/dap/types.py +0 -0
  33. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/inspection.py +0 -0
  34. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/persist.py +0 -0
  35. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/post_mortem.py +0 -0
  36. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/processes_cache.py +0 -0
  37. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/__init__.py +0 -0
  38. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/app.py +0 -0
  39. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/event_handler.py +0 -0
  40. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/handlers.py +0 -0
  41. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/server/rpc_types.py +0 -0
  42. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/__init__.py +0 -0
  43. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/child_processes.py +0 -0
  44. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/event_bus.py +0 -0
  45. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/messages.py +0 -0
  46. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/post_mortem_loader.py +0 -0
  47. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/state.py +0 -0
  48. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/statement_stepper.py +0 -0
  49. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/terminal.py +0 -0
  50. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/session/textual_handler.py +0 -0
  51. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/source_analysis.py +0 -0
  52. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/__init__.py +0 -0
  53. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/_inspection_modal.py +0 -0
  54. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/async_tasks_modal.py +0 -0
  55. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/breakpoint_view.py +0 -0
  56. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/console_view.py +0 -0
  57. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/evaluate_console.py +0 -0
  58. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/menu_bar.py +0 -0
  59. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/modals.py +0 -0
  60. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/processes_modal.py +0 -0
  61. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/stack_view.py +0 -0
  62. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/status_bar.py +0 -0
  63. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/tdb/widgets/threads_modal.py +0 -0
  64. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  65. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  66. {textual_debugger-0.1.2 → textual_debugger-0.1.3}/src/textual_debugger.egg-info/requires.txt +0 -0
  67. {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.2
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 any debugpy-enabled Python program
91
+ - supports remote attachment to debugpy-enabled Python programs
92
92
 
93
- - includes a JSON-RPC server mode for programmatic debug control, making it suitable for
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
- tdb -r remotehost:5678 my_program.py
185
+ # (source code is automatically downloaded from the remote host)
186
+ tdb -r remotehost:5678
186
187
 
187
- # prevent `argparse` confusion by separating `tdb` switches from
188
- # the debuggee switches by prefixing the debuggee with `--`
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 | Configure | Help) ──────────────────┤
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 regardless.
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
- | `Ctrl+Q` | Quit |
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 lineuseful for inspecting how a complex expression is built up.
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-left evaluates expressions in the current scope:
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, consider running it in an external terminal
374
- with `--terminal` for the best experience.
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, `continue`, breakpoints, restart, and Evaluate are disabled. The original
411
- interpreter is gone, the view is a frozen snapshot. Press `q` to exit.
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's safe to leave installed in production-style code. Snapshots are
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+Q`) detaches without killing the program, and
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** a tree showing each blocked
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/--version] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
716
- [--no-just-my-code] [--no-subprocess] [--python PYTHON]
717
- [-d/--doc] [--doc-text] [--keybindings {default,vim,emacs}]
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
- [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT]
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 stops on every physical line, including each interior sub-line of a multi-line expression. |
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 any debugpy-enabled Python program
26
+ - supports remote attachment to debugpy-enabled Python programs
27
27
 
28
- - includes a JSON-RPC server mode for programmatic debug control, making it suitable for
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
- tdb -r remotehost:5678 my_program.py
120
+ # (source code is automatically downloaded from the remote host)
121
+ tdb -r remotehost:5678
121
122
 
122
- # prevent `argparse` confusion by separating `tdb` switches from
123
- # the debuggee switches by prefixing the debuggee with `--`
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 | Configure | Help) ──────────────────┤
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 regardless.
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
- | `Ctrl+Q` | Quit |
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 lineuseful for inspecting how a complex expression is built up.
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-left evaluates expressions in the current scope:
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, consider running it in an external terminal
309
- with `--terminal` for the best experience.
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, `continue`, breakpoints, restart, and Evaluate are disabled. The original
346
- interpreter is gone, the view is a frozen snapshot. Press `q` to exit.
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's safe to leave installed in production-style code. Snapshots are
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+Q`) detaches without killing the program, and
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** a tree showing each blocked
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/--version] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
651
- [--no-just-my-code] [--no-subprocess] [--python PYTHON]
652
- [-d/--doc] [--doc-text] [--keybindings {default,vim,emacs}]
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
- [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT]
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 stops on every physical line, including each interior sub-line of a multi-line expression. |
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