textual-debugger 0.1.5__tar.gz → 0.2.0__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 (77) hide show
  1. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/PKG-INFO +141 -20
  2. {textual_debugger-0.1.5/src/tdb → textual_debugger-0.2.0}/README.md +140 -19
  3. {textual_debugger-0.1.5 → textual_debugger-0.2.0/src/tdb}/README.md +140 -19
  4. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/__init__.py +1 -1
  5. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app.py +63 -13
  6. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app_handlers/dap_events.py +2 -0
  7. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app_handlers/inspection.py +56 -8
  8. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/cli.py +61 -2
  9. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/dap/client.py +45 -75
  10. textual_debugger-0.2.0/src/tdb/languages/__init__.py +21 -0
  11. textual_debugger-0.2.0/src/tdb/languages/base.py +139 -0
  12. textual_debugger-0.2.0/src/tdb/languages/cpp.py +150 -0
  13. textual_debugger-0.2.0/src/tdb/languages/python.py +116 -0
  14. textual_debugger-0.2.0/src/tdb/languages/registry.py +106 -0
  15. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/mcp/server.py +12 -2
  16. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/mcp/session.py +48 -4
  17. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/persist.py +15 -1
  18. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/server/handlers.py +9 -4
  19. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/server/runner.py +23 -9
  20. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/child_processes.py +7 -0
  21. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/controller.py +71 -19
  22. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/inspect_service.py +14 -2
  23. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/statement_stepper.py +16 -3
  24. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/code_view.py +6 -3
  25. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/textual_debugger.egg-info/PKG-INFO +141 -20
  26. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/textual_debugger.egg-info/SOURCES.txt +5 -0
  27. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/LICENSE +0 -0
  28. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/pyproject.toml +0 -0
  29. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/setup.cfg +0 -0
  30. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/__main__.py +0 -0
  31. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/_timeouts.py +0 -0
  32. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app_handlers/__init__.py +0 -0
  33. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app_handlers/routing.py +0 -0
  34. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app_handlers/ui_panels.py +0 -0
  35. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/app_helpers.py +0 -0
  36. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/breakpoint_hook.py +0 -0
  37. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/dap/__init__.py +0 -0
  38. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/dap/messages.py +0 -0
  39. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/dap/protocol.py +0 -0
  40. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/dap/types.py +0 -0
  41. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/inspection.py +0 -0
  42. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/inspection_full.py +0 -0
  43. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/keybindings/__init__.py +0 -0
  44. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/mcp/__init__.py +0 -0
  45. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/mcp/__main__.py +0 -0
  46. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/post_mortem.py +0 -0
  47. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/processes_cache.py +0 -0
  48. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/server/__init__.py +0 -0
  49. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/server/app.py +0 -0
  50. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/server/event_handler.py +0 -0
  51. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/server/rpc_types.py +0 -0
  52. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/__init__.py +0 -0
  53. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/event_bus.py +0 -0
  54. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/messages.py +0 -0
  55. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/post_mortem_loader.py +0 -0
  56. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/state.py +0 -0
  57. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/terminal.py +0 -0
  58. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/session/textual_handler.py +0 -0
  59. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/source_analysis.py +0 -0
  60. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/__init__.py +0 -0
  61. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/_inspection_modal.py +0 -0
  62. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/async_tasks_modal.py +0 -0
  63. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/breakpoint_view.py +0 -0
  64. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/console_view.py +0 -0
  65. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/evaluate_console.py +0 -0
  66. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/full_contents_modal.py +0 -0
  67. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/menu_bar.py +0 -0
  68. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/modals.py +0 -0
  69. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/processes_modal.py +0 -0
  70. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/stack_view.py +0 -0
  71. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/status_bar.py +0 -0
  72. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/threads_modal.py +0 -0
  73. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/tdb/widgets/variable_view.py +0 -0
  74. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  75. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  76. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/src/textual_debugger.egg-info/requires.txt +0 -0
  77. {textual_debugger-0.1.5 → textual_debugger-0.2.0}/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.5
3
+ Version: 0.2.0
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:
@@ -67,12 +67,18 @@ Dynamic: license-file
67
67
  # `textual-debugger`
68
68
 
69
69
  `textual-debugger` (the package) provides `tdb` (the command-line tool and module),
70
- a full-featured terminal-based Python debugger.
71
-
72
- `tdb` is built with [textual](https://github.com/Textualize/textual)
73
- and [debugpy](https://github.com/microsoft/debugpy) (the Debug Adapter Protocol engine behind
74
- VS Code's Python debugger). It provides a rich interactive interface for stepping through code,
75
- inspecting variables, managing breakpoints, and evaluating expressions in complex Python programs.
70
+ a full-featured terminal-based debugger for Python and other languages
71
+ with a Debug Adapter Protocol (DAP) implementation. C and C++ support (via
72
+ `gdb` or `lldb-dap`) is built in.
73
+
74
+ `tdb` is built with [textual](https://github.com/Textualize/textual) and speaks
75
+ DAP to a pluggable debug adapter: [debugpy](https://github.com/microsoft/debugpy)
76
+ (the engine behind VS Code's Python debugger) for Python,
77
+ [GDB's DAP mode](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html) or
78
+ [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html)
79
+ for compiled code. It provides a rich interactive interface for stepping through
80
+ code, inspecting variables, managing breakpoints, and evaluating expressions in
81
+ complex programs.
76
82
 
77
83
  - PyPI: https://pypi.org/project/textual-debugger/
78
84
  - GitHub: https://github.com/AlDanial/tdb
@@ -83,6 +89,10 @@ MIT License. Copyright 2026 by Al Danial.
83
89
 
84
90
  `tdb`:
85
91
 
92
+ - debugs multiple languages through the Debug Adapter Protocol: Python (via `debugpy`,
93
+ the richest feature set) and C/C++ (via `gdb -i dap` or `lldb-dap`), with the language
94
+ auto-detected from the target (ref. [Multi-Language Debugging](#multi-language-debugging)).
95
+
86
96
  - supports debugging of synchronous, asynchronous, multi-threaded, and multi-process Python code.
87
97
  It specifically supports modules
88
98
  - `asyncio` (with a built-in async task inspector and task wait graph)
@@ -169,6 +179,16 @@ tdb my_program.py
169
179
  # debug with arguments
170
180
  tdb my_program.py arg1 arg2
171
181
 
182
+ # debug a C/C++ (or other native) executable built with -g. The ELF/Mach-O/PE
183
+ # binary is auto-detected and debugged through GDB's DAP mode (GDB >= 14)
184
+ tdb ./myprog arg1 arg2
185
+
186
+ # same, but using lldb-dap (LLVM >= 17) instead of gdb
187
+ tdb --adapter lldb-dap ./myprog
188
+
189
+ # force the language when auto-detection can't tell (e.g. an extensionless script)
190
+ tdb --lang python ./mytool
191
+
172
192
  # add breakpoints at lines 20 and 35 of `my_program.py` and line 14
173
193
  # of `module.py` (when -k is given, --no-stop-on-entry is set and the
174
194
  # program runs to the first breakpoint)
@@ -210,6 +230,79 @@ Alternatively, use the module entry point:
210
230
  python -m tdb my_program.py
211
231
  ```
212
232
 
233
+ ## Multi-Language Debugging
234
+
235
+ `tdb` debugs any language that has a Debug Adapter Protocol backend. Two
236
+ languages are supported out of the box:
237
+
238
+ | Language | Adapter(s) | How to get the adapter | Feature level |
239
+ |----------|------------|------------------------|---------------|
240
+ | Python | `debugpy` (default) | installed with `textual-debugger` | everything in this README |
241
+ | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console |
242
+
243
+ ### Language detection and selection
244
+
245
+ The language is auto-detected from the debug target:
246
+
247
+ 1. File extension: `.py` → Python.
248
+ 2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
249
+ 3. A `#!...python` shebang → Python.
250
+ 4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
251
+ hint: compile with debug info (`g++ -g -O0`) and debug the binary.
252
+ 5. Anything else produces an error naming the `--lang` override.
253
+
254
+ `--lang` forces the language; `--adapter` picks a non-default adapter within
255
+ it (`tdb --lang cpp --adapter lldb-dap ./myprog`).
256
+
257
+ > **Migration note:** extensionless Python scripts without a `python` shebang
258
+ > were previously assumed to be Python; they now require `--lang python`.
259
+
260
+ ### Adapters are found on `PATH`
261
+
262
+ `tdb` does not download or bundle adapters. If the adapter executable isn't
263
+ found, the error names the package to install. To use an adapter from a
264
+ non-standard location, or change a language's default adapter, add to
265
+ `config.json` (see [Configuration](#configuration)):
266
+
267
+ ```json
268
+ {
269
+ "adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"},
270
+ "default_adapters": {"cpp": "lldb-dap"}
271
+ }
272
+ ```
273
+
274
+ ### What works for non-Python languages
275
+
276
+ Core debugging works identically for every language: breakpoints (incl.
277
+ conditions and persistence), stepping, continue/pause, run-to-cursor, stack
278
+ navigation, variable inspection, the evaluate console, syntax highlighting,
279
+ and the JSON-RPC / MCP programmatic modes.
280
+
281
+ Python-specific features are hidden or return a clear "not supported for this
282
+ language" message when debugging other languages: statement-granularity
283
+ stepping (non-Python languages always step per line), the async task /
284
+ process inspectors and wait graph, the evaluate console's trailing-`?` help,
285
+ remote attach (`-r`), `--python`/`--pv`, `--no-subprocess`,
286
+ automatic child-process attachment, and the post-mortem / `tdb.breakpoint()`
287
+ hooks (those hooks live inside Python programs by nature). `--terminal` is
288
+ currently ignored for non-Python targets.
289
+
290
+ ### C/C++ tips
291
+
292
+ - Compile with `-g` (ideally `-g -O0`). If no breakpoint in a file can be
293
+ bound, `tdb` prints a console warning suggesting the program may lack
294
+ debug info.
295
+ - Stack frames pointing into system libraries often have no source on disk;
296
+ the Code View shows a `<Could not read …>` placeholder while the stack,
297
+ variables, and evaluate console remain fully usable.
298
+ - GDB (the default adapter) has the most complete libstdc++
299
+ pretty-printing. `lldb-dap` (via `--adapter lldb-dap`) also debugs
300
+ GCC-built binaries fine — DWARF is compiler-neutral.
301
+ - **GDB evaluate-console quirk:** GDB's DAP treats REPL input as CLI
302
+ commands, so evaluate expressions with an explicit `print`, e.g.
303
+ `print x` rather than bare `x` (bare `x` collides with GDB's
304
+ examine-memory command). `lldb-dap` evaluates bare expressions directly.
305
+
213
306
  ## Layout
214
307
 
215
308
  ```
@@ -238,7 +331,7 @@ The footer shows the most relevant keybindings for the current mode.
238
331
  ### Navigation and Keybindings
239
332
 
240
333
 
241
- The Code View shows syntax-highlighted Python source with line numbers.
334
+ The Code View shows syntax-highlighted source (lexer chosen per language) with line numbers.
242
335
  A cursor line (blue) tracks your position; the current execution line is highlighted in gold.
243
336
 
244
337
  **View focus shortcuts (global):**
@@ -330,6 +423,9 @@ lands on `print(results)`, not on each interior sub-line of the `gather` call. S
330
423
  stops on each physical line--useful for inspecting how a complex expression is built up.
331
424
  The choice is saved to `~/.config/tdb/config.json`.
332
425
 
426
+ Statement mode requires a source-language model and is currently Python-only;
427
+ other languages always step per line (the Step Mode menu says so if you try).
428
+
333
429
  ### Breakpoints
334
430
 
335
431
  Click the gutter in the Code View to toggle a breakpoint, or press `b` in Debug mode.
@@ -387,6 +483,15 @@ interactive evaluation of expressions in the current scope:
387
483
 
388
484
  Variable values set here are reflected in the running code.
389
485
 
486
+ ### Cut / Paste
487
+
488
+ Expression for the Evaluate Console are often copied from the Code View.
489
+ Doing this in `tdb` differs from traditional terminal behavior, because `textual` applications
490
+ capture mouse events for their own use.
491
+
492
+ Instead, hold the `Shift` key while performing your conventional cut/paste keystrokes or mouse
493
+ operation to get the expected behavior.
494
+
390
495
  ### Console Output
391
496
 
392
497
  The Console View captures stdout (normal text) and stderr (red text) from the debuggee
@@ -754,7 +859,7 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
754
859
  | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
755
860
  | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
756
861
  | `list_breakpoints` | `[]` | Show all breakpoints |
757
- | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still runningcall pause or wait again"` (success) |
862
+ | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running--call pause or wait again"` (success) |
758
863
  | `next` | `[]` or `[timeout_s]` | Step over |
759
864
  | `step_in` | `[]` or `[timeout_s]` | Step into |
760
865
  | `step_out` | `[]` or `[timeout_s]` | Step out |
@@ -793,7 +898,7 @@ Each is JSON with `event`, `data`, and `timestamp` fields.
793
898
  tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
794
899
  the debugger as a curated set of tools an AI agent can call. The MCP
795
900
  server is a third in-process consumer of the same dispatch handlers the
796
- TUI and the HTTP server use so an agent gets the same lock semantics,
901
+ TUI and the HTTP server use so an agent gets the same lock semantics,
797
902
  including the pause-during-continue bypass, and the same DAP-backed
798
903
  inspection surface.
799
904
 
@@ -832,17 +937,24 @@ client expects to launch servers.
832
937
  | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
833
938
  | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
834
939
 
835
- `control` is intentionally one tool that takes an action enum the six
940
+ `control` is intentionally one tool that takes an action enum. The six
836
941
  underlying RPC actions share a return shape, and agents perform
837
942
  measurably better with a small surface than with one tool per action.
838
943
  `threads` / `tasks` / `processes` overload list-vs-inspect via a
839
944
  single optional argument for the same reason.
840
945
 
946
+ `debug_launch` accepts optional `lang` and `adapter` parameters mirroring the
947
+ CLI's `--lang`/`--adapter`; when omitted, the language is auto-detected from
948
+ `program`, so an agent can hand it a compiled binary directly. The
949
+ `tasks`/`processes`/`wait_graph` tools stay registered for every language but
950
+ return a structured "not supported when debugging C/C++"-style error for
951
+ non-Python debuggees.
952
+
841
953
  ### Agent flow for a long-running step
842
954
 
843
955
  ```
844
956
  agent → control(action="continue", timeout_s=30)
845
- mcp → "still running call pause or wait again"
957
+ mcp → "still running, call pause or wait again"
846
958
  agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
847
959
  mcp → "<file>:<line>"
848
960
  agent → inspect(["x", "len(buf)"])
@@ -851,7 +963,7 @@ mcp → "x = 7\nlen(buf) = 1024"
851
963
 
852
964
  `pause` bypasses the dispatch lock so it can interrupt a `continue`
853
965
  that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
854
- policy see `tdb/server/app.py`).
966
+ policy; see `tdb/server/app.py`).
855
967
 
856
968
  ### Security caveat
857
969
 
@@ -860,22 +972,23 @@ execution in the debuggee process**. This is inherent to a debugger and
860
972
  not a tdb-specific concern, but MCP clients (and the humans running
861
973
  them) should apply appropriate permission models: don't auto-approve
862
974
  `inspect` against untrusted expressions, and don't expose `tdb-mcp` on
863
- a network stdio transport only by design.
975
+ a network (stdio transport only by design).
864
976
 
865
977
  ### Deferred / out of scope (v1)
866
978
 
867
- - SSE-style event push `control` and `wait_for_stop` make polling
979
+ - SSE-style event push: `control` and `wait_for_stop` make polling
868
980
  efficient enough; events would also need uneven MCP-client support.
869
- - HTTP / streamable-HTTP transports would require auth (which the
981
+ - HTTP / streamable-HTTP transports: would require auth (which the
870
982
  HTTP RPC server also currently lacks); stdio inherits the trust of
871
983
  the process that spawned it.
872
- - Multi-session one debug session per MCP process.
984
+ - Multi-session: one debug session per MCP process.
873
985
 
874
986
  ## CLI Reference
875
987
 
876
988
  ```
877
989
  usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
878
990
  [--no-just-my-code] [--no-subprocess] [--python PYTHON] [--pv]
991
+ [--lang LANGUAGE] [--adapter ADAPTER]
879
992
  [--keybindings {default,vim,emacs}]
880
993
  [--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
881
994
  [--local-root PATH] [--remote-root PATH]
@@ -891,8 +1004,10 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
891
1004
  | `-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. |
892
1005
  | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
893
1006
  | `--cwd DIR` | Working directory for the debuggee |
894
- | `--python PATH` | Python interpreter for the debuggee |
1007
+ | `--python PATH` | Python interpreter for the debuggee (Python targets only) |
895
1008
  | `--pv` | Shorthand for --python .venv/bin/python |
1009
+ | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`); default: auto-detect from the target |
1010
+ | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`); default: the language's standard adapter |
896
1011
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
897
1012
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
898
1013
  including library frames, regardless of this flag. |
@@ -912,9 +1027,14 @@ On Windows, it uses `%APPDATA%\tdb\`.
912
1027
 
913
1028
  | File | Contents |
914
1029
  |------|----------|
915
- | `config.json` | User preferences (keybinding scheme, color theme, step mode) |
1030
+ | `config.json` | User preferences (keybinding scheme, color theme, step mode, adapter overrides) |
916
1031
  | `breakpoints.json` | Breakpoints from previous sessions, keyed by project directory |
917
1032
 
1033
+ Adapter-related keys in `config.json`: `adapters` maps an adapter id to an
1034
+ executable path (`{"adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"}}`), and
1035
+ `default_adapters` picks a language's default adapter
1036
+ (`{"default_adapters": {"cpp": "lldb-dap"}}`).
1037
+
918
1038
  Breakpoints are saved on exit and restored when debugging a program in the same
919
1039
  directory. Each project's breakpoints are independent.
920
1040
 
@@ -934,7 +1054,8 @@ fires as expected.
934
1054
  ## Tech Stack
935
1055
 
936
1056
  - [textual](https://github.com/Textualize/textual) : TUI framework
937
- - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation
1057
+ - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation for Python
1058
+ - [gdb](https://sourceware.org/gdb/) / [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html) : optional, user-installed DAP adapters for C/C++
938
1059
  - [pygments](https://pygments.org/) : Syntax highlighting
939
1060
  - [FastAPI](https://fastapi.tiangolo.com/) + [uvicorn](https://www.uvicorn.org/) : JSON-RPC server
940
1061
 
@@ -1,12 +1,18 @@
1
1
  # `textual-debugger`
2
2
 
3
3
  `textual-debugger` (the package) provides `tdb` (the command-line tool and module),
4
- a full-featured terminal-based Python debugger.
5
-
6
- `tdb` is built with [textual](https://github.com/Textualize/textual)
7
- and [debugpy](https://github.com/microsoft/debugpy) (the Debug Adapter Protocol engine behind
8
- VS Code's Python debugger). It provides a rich interactive interface for stepping through code,
9
- inspecting variables, managing breakpoints, and evaluating expressions in complex Python programs.
4
+ a full-featured terminal-based debugger for Python and other languages
5
+ with a Debug Adapter Protocol (DAP) implementation. C and C++ support (via
6
+ `gdb` or `lldb-dap`) is built in.
7
+
8
+ `tdb` is built with [textual](https://github.com/Textualize/textual) and speaks
9
+ DAP to a pluggable debug adapter: [debugpy](https://github.com/microsoft/debugpy)
10
+ (the engine behind VS Code's Python debugger) for Python,
11
+ [GDB's DAP mode](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html) or
12
+ [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html)
13
+ for compiled code. It provides a rich interactive interface for stepping through
14
+ code, inspecting variables, managing breakpoints, and evaluating expressions in
15
+ complex programs.
10
16
 
11
17
  - PyPI: https://pypi.org/project/textual-debugger/
12
18
  - GitHub: https://github.com/AlDanial/tdb
@@ -17,6 +23,10 @@ MIT License. Copyright 2026 by Al Danial.
17
23
 
18
24
  `tdb`:
19
25
 
26
+ - debugs multiple languages through the Debug Adapter Protocol: Python (via `debugpy`,
27
+ the richest feature set) and C/C++ (via `gdb -i dap` or `lldb-dap`), with the language
28
+ auto-detected from the target (ref. [Multi-Language Debugging](#multi-language-debugging)).
29
+
20
30
  - supports debugging of synchronous, asynchronous, multi-threaded, and multi-process Python code.
21
31
  It specifically supports modules
22
32
  - `asyncio` (with a built-in async task inspector and task wait graph)
@@ -103,6 +113,16 @@ tdb my_program.py
103
113
  # debug with arguments
104
114
  tdb my_program.py arg1 arg2
105
115
 
116
+ # debug a C/C++ (or other native) executable built with -g. The ELF/Mach-O/PE
117
+ # binary is auto-detected and debugged through GDB's DAP mode (GDB >= 14)
118
+ tdb ./myprog arg1 arg2
119
+
120
+ # same, but using lldb-dap (LLVM >= 17) instead of gdb
121
+ tdb --adapter lldb-dap ./myprog
122
+
123
+ # force the language when auto-detection can't tell (e.g. an extensionless script)
124
+ tdb --lang python ./mytool
125
+
106
126
  # add breakpoints at lines 20 and 35 of `my_program.py` and line 14
107
127
  # of `module.py` (when -k is given, --no-stop-on-entry is set and the
108
128
  # program runs to the first breakpoint)
@@ -144,6 +164,79 @@ Alternatively, use the module entry point:
144
164
  python -m tdb my_program.py
145
165
  ```
146
166
 
167
+ ## Multi-Language Debugging
168
+
169
+ `tdb` debugs any language that has a Debug Adapter Protocol backend. Two
170
+ languages are supported out of the box:
171
+
172
+ | Language | Adapter(s) | How to get the adapter | Feature level |
173
+ |----------|------------|------------------------|---------------|
174
+ | Python | `debugpy` (default) | installed with `textual-debugger` | everything in this README |
175
+ | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console |
176
+
177
+ ### Language detection and selection
178
+
179
+ The language is auto-detected from the debug target:
180
+
181
+ 1. File extension: `.py` → Python.
182
+ 2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
183
+ 3. A `#!...python` shebang → Python.
184
+ 4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
185
+ hint: compile with debug info (`g++ -g -O0`) and debug the binary.
186
+ 5. Anything else produces an error naming the `--lang` override.
187
+
188
+ `--lang` forces the language; `--adapter` picks a non-default adapter within
189
+ it (`tdb --lang cpp --adapter lldb-dap ./myprog`).
190
+
191
+ > **Migration note:** extensionless Python scripts without a `python` shebang
192
+ > were previously assumed to be Python; they now require `--lang python`.
193
+
194
+ ### Adapters are found on `PATH`
195
+
196
+ `tdb` does not download or bundle adapters. If the adapter executable isn't
197
+ found, the error names the package to install. To use an adapter from a
198
+ non-standard location, or change a language's default adapter, add to
199
+ `config.json` (see [Configuration](#configuration)):
200
+
201
+ ```json
202
+ {
203
+ "adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"},
204
+ "default_adapters": {"cpp": "lldb-dap"}
205
+ }
206
+ ```
207
+
208
+ ### What works for non-Python languages
209
+
210
+ Core debugging works identically for every language: breakpoints (incl.
211
+ conditions and persistence), stepping, continue/pause, run-to-cursor, stack
212
+ navigation, variable inspection, the evaluate console, syntax highlighting,
213
+ and the JSON-RPC / MCP programmatic modes.
214
+
215
+ Python-specific features are hidden or return a clear "not supported for this
216
+ language" message when debugging other languages: statement-granularity
217
+ stepping (non-Python languages always step per line), the async task /
218
+ process inspectors and wait graph, the evaluate console's trailing-`?` help,
219
+ remote attach (`-r`), `--python`/`--pv`, `--no-subprocess`,
220
+ automatic child-process attachment, and the post-mortem / `tdb.breakpoint()`
221
+ hooks (those hooks live inside Python programs by nature). `--terminal` is
222
+ currently ignored for non-Python targets.
223
+
224
+ ### C/C++ tips
225
+
226
+ - Compile with `-g` (ideally `-g -O0`). If no breakpoint in a file can be
227
+ bound, `tdb` prints a console warning suggesting the program may lack
228
+ debug info.
229
+ - Stack frames pointing into system libraries often have no source on disk;
230
+ the Code View shows a `<Could not read …>` placeholder while the stack,
231
+ variables, and evaluate console remain fully usable.
232
+ - GDB (the default adapter) has the most complete libstdc++
233
+ pretty-printing. `lldb-dap` (via `--adapter lldb-dap`) also debugs
234
+ GCC-built binaries fine — DWARF is compiler-neutral.
235
+ - **GDB evaluate-console quirk:** GDB's DAP treats REPL input as CLI
236
+ commands, so evaluate expressions with an explicit `print`, e.g.
237
+ `print x` rather than bare `x` (bare `x` collides with GDB's
238
+ examine-memory command). `lldb-dap` evaluates bare expressions directly.
239
+
147
240
  ## Layout
148
241
 
149
242
  ```
@@ -172,7 +265,7 @@ The footer shows the most relevant keybindings for the current mode.
172
265
  ### Navigation and Keybindings
173
266
 
174
267
 
175
- The Code View shows syntax-highlighted Python source with line numbers.
268
+ The Code View shows syntax-highlighted source (lexer chosen per language) with line numbers.
176
269
  A cursor line (blue) tracks your position; the current execution line is highlighted in gold.
177
270
 
178
271
  **View focus shortcuts (global):**
@@ -264,6 +357,9 @@ lands on `print(results)`, not on each interior sub-line of the `gather` call. S
264
357
  stops on each physical line--useful for inspecting how a complex expression is built up.
265
358
  The choice is saved to `~/.config/tdb/config.json`.
266
359
 
360
+ Statement mode requires a source-language model and is currently Python-only;
361
+ other languages always step per line (the Step Mode menu says so if you try).
362
+
267
363
  ### Breakpoints
268
364
 
269
365
  Click the gutter in the Code View to toggle a breakpoint, or press `b` in Debug mode.
@@ -321,6 +417,15 @@ interactive evaluation of expressions in the current scope:
321
417
 
322
418
  Variable values set here are reflected in the running code.
323
419
 
420
+ ### Cut / Paste
421
+
422
+ Expression for the Evaluate Console are often copied from the Code View.
423
+ Doing this in `tdb` differs from traditional terminal behavior, because `textual` applications
424
+ capture mouse events for their own use.
425
+
426
+ Instead, hold the `Shift` key while performing your conventional cut/paste keystrokes or mouse
427
+ operation to get the expected behavior.
428
+
324
429
  ### Console Output
325
430
 
326
431
  The Console View captures stdout (normal text) and stderr (red text) from the debuggee
@@ -688,7 +793,7 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
688
793
  | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
689
794
  | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
690
795
  | `list_breakpoints` | `[]` | Show all breakpoints |
691
- | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still runningcall pause or wait again"` (success) |
796
+ | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running--call pause or wait again"` (success) |
692
797
  | `next` | `[]` or `[timeout_s]` | Step over |
693
798
  | `step_in` | `[]` or `[timeout_s]` | Step into |
694
799
  | `step_out` | `[]` or `[timeout_s]` | Step out |
@@ -727,7 +832,7 @@ Each is JSON with `event`, `data`, and `timestamp` fields.
727
832
  tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
728
833
  the debugger as a curated set of tools an AI agent can call. The MCP
729
834
  server is a third in-process consumer of the same dispatch handlers the
730
- TUI and the HTTP server use so an agent gets the same lock semantics,
835
+ TUI and the HTTP server use so an agent gets the same lock semantics,
731
836
  including the pause-during-continue bypass, and the same DAP-backed
732
837
  inspection surface.
733
838
 
@@ -766,17 +871,24 @@ client expects to launch servers.
766
871
  | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
767
872
  | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
768
873
 
769
- `control` is intentionally one tool that takes an action enum the six
874
+ `control` is intentionally one tool that takes an action enum. The six
770
875
  underlying RPC actions share a return shape, and agents perform
771
876
  measurably better with a small surface than with one tool per action.
772
877
  `threads` / `tasks` / `processes` overload list-vs-inspect via a
773
878
  single optional argument for the same reason.
774
879
 
880
+ `debug_launch` accepts optional `lang` and `adapter` parameters mirroring the
881
+ CLI's `--lang`/`--adapter`; when omitted, the language is auto-detected from
882
+ `program`, so an agent can hand it a compiled binary directly. The
883
+ `tasks`/`processes`/`wait_graph` tools stay registered for every language but
884
+ return a structured "not supported when debugging C/C++"-style error for
885
+ non-Python debuggees.
886
+
775
887
  ### Agent flow for a long-running step
776
888
 
777
889
  ```
778
890
  agent → control(action="continue", timeout_s=30)
779
- mcp → "still running call pause or wait again"
891
+ mcp → "still running, call pause or wait again"
780
892
  agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
781
893
  mcp → "<file>:<line>"
782
894
  agent → inspect(["x", "len(buf)"])
@@ -785,7 +897,7 @@ mcp → "x = 7\nlen(buf) = 1024"
785
897
 
786
898
  `pause` bypasses the dispatch lock so it can interrupt a `continue`
787
899
  that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
788
- policy see `tdb/server/app.py`).
900
+ policy; see `tdb/server/app.py`).
789
901
 
790
902
  ### Security caveat
791
903
 
@@ -794,22 +906,23 @@ execution in the debuggee process**. This is inherent to a debugger and
794
906
  not a tdb-specific concern, but MCP clients (and the humans running
795
907
  them) should apply appropriate permission models: don't auto-approve
796
908
  `inspect` against untrusted expressions, and don't expose `tdb-mcp` on
797
- a network stdio transport only by design.
909
+ a network (stdio transport only by design).
798
910
 
799
911
  ### Deferred / out of scope (v1)
800
912
 
801
- - SSE-style event push `control` and `wait_for_stop` make polling
913
+ - SSE-style event push: `control` and `wait_for_stop` make polling
802
914
  efficient enough; events would also need uneven MCP-client support.
803
- - HTTP / streamable-HTTP transports would require auth (which the
915
+ - HTTP / streamable-HTTP transports: would require auth (which the
804
916
  HTTP RPC server also currently lacks); stdio inherits the trust of
805
917
  the process that spawned it.
806
- - Multi-session one debug session per MCP process.
918
+ - Multi-session: one debug session per MCP process.
807
919
 
808
920
  ## CLI Reference
809
921
 
810
922
  ```
811
923
  usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
812
924
  [--no-just-my-code] [--no-subprocess] [--python PYTHON] [--pv]
925
+ [--lang LANGUAGE] [--adapter ADAPTER]
813
926
  [--keybindings {default,vim,emacs}]
814
927
  [--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
815
928
  [--local-root PATH] [--remote-root PATH]
@@ -825,8 +938,10 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
825
938
  | `-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. |
826
939
  | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
827
940
  | `--cwd DIR` | Working directory for the debuggee |
828
- | `--python PATH` | Python interpreter for the debuggee |
941
+ | `--python PATH` | Python interpreter for the debuggee (Python targets only) |
829
942
  | `--pv` | Shorthand for --python .venv/bin/python |
943
+ | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`); default: auto-detect from the target |
944
+ | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`); default: the language's standard adapter |
830
945
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
831
946
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
832
947
  including library frames, regardless of this flag. |
@@ -846,9 +961,14 @@ On Windows, it uses `%APPDATA%\tdb\`.
846
961
 
847
962
  | File | Contents |
848
963
  |------|----------|
849
- | `config.json` | User preferences (keybinding scheme, color theme, step mode) |
964
+ | `config.json` | User preferences (keybinding scheme, color theme, step mode, adapter overrides) |
850
965
  | `breakpoints.json` | Breakpoints from previous sessions, keyed by project directory |
851
966
 
967
+ Adapter-related keys in `config.json`: `adapters` maps an adapter id to an
968
+ executable path (`{"adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"}}`), and
969
+ `default_adapters` picks a language's default adapter
970
+ (`{"default_adapters": {"cpp": "lldb-dap"}}`).
971
+
852
972
  Breakpoints are saved on exit and restored when debugging a program in the same
853
973
  directory. Each project's breakpoints are independent.
854
974
 
@@ -868,7 +988,8 @@ fires as expected.
868
988
  ## Tech Stack
869
989
 
870
990
  - [textual](https://github.com/Textualize/textual) : TUI framework
871
- - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation
991
+ - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation for Python
992
+ - [gdb](https://sourceware.org/gdb/) / [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html) : optional, user-installed DAP adapters for C/C++
872
993
  - [pygments](https://pygments.org/) : Syntax highlighting
873
994
  - [FastAPI](https://fastapi.tiangolo.com/) + [uvicorn](https://www.uvicorn.org/) : JSON-RPC server
874
995