textual-debugger 0.2.4__tar.gz → 0.2.8__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 (136) hide show
  1. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/PKG-INFO +360 -38
  2. {textual_debugger-0.2.4/src/tdb → textual_debugger-0.2.8}/README.md +359 -37
  3. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/pyproject.toml +2 -1
  4. {textual_debugger-0.2.4 → textual_debugger-0.2.8/src/tdb}/README.md +359 -37
  5. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/__init__.py +1 -1
  6. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/bash/server.py +114 -2
  7. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/bash/session.py +139 -35
  8. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/bash/tdb_harness.sh +15 -5
  9. textual_debugger-0.2.8/src/tdb/adapters/ocaml/lldb_formatters.py +153 -0
  10. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/helpers.pl +22 -0
  11. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/server.py +235 -15
  12. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/session.py +100 -19
  13. textual_debugger-0.2.8/src/tdb/adapters/ruby/__init__.py +1 -0
  14. textual_debugger-0.2.8/src/tdb/adapters/ruby/__main__.py +23 -0
  15. textual_debugger-0.2.8/src/tdb/adapters/ruby/server.py +1120 -0
  16. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/breakpoints.py +8 -2
  17. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/guardian.py +210 -40
  18. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/inspect.py +9 -3
  19. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/instrumenter.py +20 -6
  20. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/models.py +1 -0
  21. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/runtime.py +15 -5
  22. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/server.py +275 -15
  23. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/session.py +442 -87
  24. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/transport.py +107 -21
  25. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app.py +131 -19
  26. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app_handlers/dap_events.py +3 -0
  27. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app_handlers/inspection.py +101 -1
  28. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app_handlers/routing.py +36 -0
  29. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app_handlers/ui_panels.py +39 -0
  30. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/cli.py +106 -5
  31. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/dap/client.py +16 -1
  32. textual_debugger-0.2.8/src/tdb/dap/reverse.py +54 -0
  33. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/base.py +69 -1
  34. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/bash.py +5 -2
  35. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/cpp.py +61 -7
  36. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/errors.py +216 -0
  37. textual_debugger-0.2.8/src/tdb/languages/ocaml.py +324 -0
  38. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/perl.py +5 -2
  39. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/python.py +7 -4
  40. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/registry.py +71 -9
  41. textual_debugger-0.2.8/src/tdb/languages/ruby.py +117 -0
  42. textual_debugger-0.2.8/src/tdb/languages/rust.py +158 -0
  43. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/tcsh.py +5 -2
  44. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/mcp/server.py +41 -9
  45. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/mcp/session.py +29 -5
  46. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/replay.py +12 -2
  47. textual_debugger-0.2.8/src/tdb/run_mode.py +271 -0
  48. textual_debugger-0.2.8/src/tdb/rust_concurrency/__init__.py +41 -0
  49. textual_debugger-0.2.8/src/tdb/rust_concurrency/analyzer.py +505 -0
  50. textual_debugger-0.2.8/src/tdb/rust_concurrency/classifier.py +255 -0
  51. textual_debugger-0.2.8/src/tdb/rust_concurrency/collector.py +337 -0
  52. textual_debugger-0.2.8/src/tdb/rust_concurrency/models.py +269 -0
  53. textual_debugger-0.2.8/src/tdb/rust_concurrency/probes/__init__.py +18 -0
  54. textual_debugger-0.2.8/src/tdb/rust_concurrency/probes/base.py +166 -0
  55. textual_debugger-0.2.8/src/tdb/rust_concurrency/probes/gdb.py +27 -0
  56. textual_debugger-0.2.8/src/tdb/rust_concurrency/probes/gdb_script.py +238 -0
  57. textual_debugger-0.2.8/src/tdb/rust_concurrency/probes/lldb.py +35 -0
  58. textual_debugger-0.2.8/src/tdb/rust_concurrency/probes/lldb_script.py +276 -0
  59. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/server/app.py +1 -1
  60. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/server/handlers.py +21 -0
  61. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/server/rpc_types.py +17 -0
  62. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/server/runner.py +15 -3
  63. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/controller.py +143 -13
  64. textual_debugger-0.2.8/src/tdb/session/errors.py +23 -0
  65. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/event_bus.py +49 -0
  66. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/inspect_service.py +24 -15
  67. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/recorder.py +3 -0
  68. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/state.py +2 -0
  69. textual_debugger-0.2.8/src/tdb/widgets/__init__.py +0 -0
  70. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/_inspection_modal.py +11 -2
  71. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/modals.py +65 -10
  72. textual_debugger-0.2.8/src/tdb/widgets/rust_concurrency_modal.py +397 -0
  73. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/stack_view.py +6 -2
  74. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/threads_modal.py +55 -8
  75. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/textual_debugger.egg-info/PKG-INFO +360 -38
  76. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/textual_debugger.egg-info/SOURCES.txt +23 -0
  77. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/LICENSE +0 -0
  78. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/setup.cfg +0 -0
  79. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/__main__.py +0 -0
  80. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/_timeouts.py +0 -0
  81. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/__init__.py +0 -0
  82. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/bash/__init__.py +0 -0
  83. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/bash/__main__.py +0 -0
  84. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/bash/declares.py +0 -0
  85. {textual_debugger-0.2.4/src/tdb/adapters/perl → textual_debugger-0.2.8/src/tdb/adapters/ocaml}/__init__.py +0 -0
  86. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/Devel/TdbCompile.pm +0 -0
  87. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/Devel/TdbRemote.pm +0 -0
  88. {textual_debugger-0.2.4/src/tdb/dap → textual_debugger-0.2.8/src/tdb/adapters/perl}/__init__.py +0 -0
  89. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/__main__.py +0 -0
  90. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/protocol.py +0 -0
  91. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/perl/refs.py +0 -0
  92. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/__init__.py +0 -0
  93. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/__main__.py +0 -0
  94. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/cli.py +0 -0
  95. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/protocol.py +0 -0
  96. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/adapters/tcsh/scanner.py +0 -0
  97. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app_handlers/__init__.py +0 -0
  98. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/app_helpers.py +0 -0
  99. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/breakpoint_hook.py +0 -0
  100. {textual_debugger-0.2.4/src/tdb/server → textual_debugger-0.2.8/src/tdb/dap}/__init__.py +0 -0
  101. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/dap/messages.py +0 -0
  102. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/dap/protocol.py +0 -0
  103. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/dap/types.py +0 -0
  104. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/inspection.py +0 -0
  105. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/inspection_full.py +0 -0
  106. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/keybindings/__init__.py +0 -0
  107. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/languages/__init__.py +0 -0
  108. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/mcp/__init__.py +0 -0
  109. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/mcp/__main__.py +0 -0
  110. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/persist.py +0 -0
  111. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/post_mortem.py +0 -0
  112. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/processes_cache.py +0 -0
  113. {textual_debugger-0.2.4/src/tdb/session → textual_debugger-0.2.8/src/tdb/server}/__init__.py +0 -0
  114. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/server/event_handler.py +0 -0
  115. {textual_debugger-0.2.4/src/tdb/widgets → textual_debugger-0.2.8/src/tdb/session}/__init__.py +0 -0
  116. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/child_processes.py +0 -0
  117. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/messages.py +0 -0
  118. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/post_mortem_loader.py +0 -0
  119. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/statement_stepper.py +0 -0
  120. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/terminal.py +0 -0
  121. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/session/textual_handler.py +0 -0
  122. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/source_analysis.py +0 -0
  123. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/async_tasks_modal.py +0 -0
  124. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/breakpoint_view.py +0 -0
  125. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/code_view.py +0 -0
  126. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/console_view.py +0 -0
  127. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/evaluate_console.py +0 -0
  128. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/full_contents_modal.py +0 -0
  129. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/menu_bar.py +0 -0
  130. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/processes_modal.py +0 -0
  131. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/status_bar.py +0 -0
  132. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/tdb/widgets/variable_view.py +0 -0
  133. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  134. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  135. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/src/textual_debugger.egg-info/requires.txt +0 -0
  136. {textual_debugger-0.2.4 → textual_debugger-0.2.8}/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.2.4
3
+ Version: 0.2.8
4
4
  Summary: A TUI debugger for Python, Perl, and other languages. Based on textual.
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:
@@ -73,16 +73,16 @@ a full-featured terminal-based debugger for Python and other languages
73
73
  with a Debug Adapter Protocol (DAP) implementation. In addition to Python,
74
74
  `tdb` comes with built-in support for
75
75
  - C and C++ (via `gdb` or `lldb-dap`)
76
+ - Rust (via `gdb` or `lldb-dap`, selected explicitly)
76
77
  - Perl (via `perl -d`)
77
78
  - Bash (via bash's own `DEBUG` trap; bash ≥ 4.4)
78
79
  - Tcsh (via source instrumentation of a stock `tcsh`)
80
+ - Ruby (via the [debug gem](https://github.com/ruby/debug)'s `rdbg`; debug ≥ 1.9)
81
+ - OCaml (native via `lldb-dap`/`gdb`, bytecode via [ocamlearlybird](https://github.com/hackwaly/ocamlearlybird))
79
82
 
80
83
  `tdb` is built with [textual](https://github.com/Textualize/textual) and speaks
81
- DAP to a pluggable debug adapter: [debugpy](https://github.com/microsoft/debugpy)
82
- (the engine behind VS Code's Python debugger) for Python,
83
- [GDB's DAP mode](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html) or
84
- [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html)
85
- for compiled code. It provides a rich interactive interface for stepping through
84
+ DAP to a pluggable debug adapter.
85
+ It provides a rich interactive interface for stepping through
86
86
  code, inspecting variables, managing breakpoints, and evaluating expressions in
87
87
  complex programs.
88
88
 
@@ -96,10 +96,13 @@ MIT License. Copyright 2026 by Al Danial.
96
96
  `tdb`:
97
97
 
98
98
  - debugs multiple languages through the Debug Adapter Protocol: Python (via `debugpy`,
99
- the richest feature set), C/C++ (via `gdb -i dap` or `lldb-dap`), Perl (via `perl -d`),
100
- Bash (via bash's own `DEBUG` trap), and Tcsh (via source instrumentation of a
101
- stock `tcsh`), with the language
102
- auto-detected from the target (ref. [Multi-Language Debugging](#multi-language-debugging)).
99
+ the richest feature set), C/C++ (via `gdb -i dap` or `lldb-dap`), Rust (via `gdb` or
100
+ `lldb-dap`), Perl (via `perl -d`),
101
+ Bash (via bash's own `DEBUG` trap), Tcsh (via source instrumentation of a
102
+ stock `tcsh`), Ruby (via the debug gem's `rdbg`), and OCaml (native via
103
+ `lldb-dap`/`gdb`, bytecode via ocamlearlybird). Most languages are
104
+ auto-detected from the target; Rust intentionally requires `--lang rust`
105
+ (ref. [Multi-Language Debugging](#multi-language-debugging)).
103
106
 
104
107
  - supports debugging of synchronous, asynchronous, multi-threaded, and multi-process Python code.
105
108
  It specifically supports modules
@@ -107,7 +110,7 @@ It specifically supports modules
107
110
  - `threading` (with a thread inspector)
108
111
  - `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
109
112
 
110
- - supports remote attachment to debugpy-enabled Python programs
113
+ - supports remote attachment to Python, Perl, Ruby, Rust, and C/C++ programs
111
114
 
112
115
  - includes a JSON-RPC server mode, an MCP mode, and a `SKILL.md` file that enable
113
116
  programmatic debug control, making it suitable for
@@ -142,6 +145,9 @@ as open source.
142
145
  - OpenAI, for providing access to Codex through the
143
146
  [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
144
147
 
148
+ This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/)
149
+ Python debugger.
150
+
145
151
  ## Gallery
146
152
  <p align="center">
147
153
  <img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
@@ -244,32 +250,49 @@ python -m tdb my_program.py
244
250
 
245
251
  ## Multi-Language Debugging
246
252
 
247
- `tdb` debugs any language that has a Debug Adapter Protocol backend. Five
253
+ `tdb` debugs any language that has a Debug Adapter Protocol backend. Eight
248
254
  languages are supported out of the box:
249
255
 
250
256
  | Language | Adapter(s) | Dependencies | Feature level |
251
257
  |----------|------------|------------------------|---------------|
252
258
  | Python | `debugpy` (default) | Python ≥ 3.11 | everything in this README |
253
- | 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 |
259
+ | 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 + remote attach (gdbserver/lldb-server, local symbol-bearing executable required) |
260
+ | Rust (explicit `--lang rust`) | `gdb` (Linux default), `lldb-dap` (macOS default) | any rustc building with debug info; GDB ≥ 14 or LLVM `lldb-dap` ≥ 17 (concurrency ownership evidence additionally requires stable Rust 1.98) | core debugging + best-effort Rust concurrency inspection + remote attach |
254
261
  | Perl | perl-tdb (bundled) | perl ≥ 5.18 on PATH | core debugging + remote attach |
255
262
  | Bash | bash-tdb (bundled) | bash ≥ 4.4 on PATH | core debugging (no remote attach) |
256
263
  | Tcsh | tcsh-tdb (bundled) | tcsh on PATH | core debugging (no remote attach, no conditional breakpoints, no pause) |
264
+ | Ruby | `rdbg` (the [debug gem](https://github.com/ruby/debug)) | `rdbg` ≥ 1.9 on PATH | core debugging + remote attach |
265
+ | OCaml (native executable) | `lldb-dap` (default), `gdb` (alternate) | OCaml ≥ 4.12; `lldb-dap` ships with LLVM ≥ 17; `gdb -i dap` requires GDB ≥ 14 | core debugging + domains-as-threads; Variables view shows no OCaml locals (upstream DWARF limitation); evaluate console is lldb/C-level, not OCaml |
266
+ | OCaml (bytecode executable) | `ocamlearlybird` (default) | OCaml ≥ 4.12; `opam install earlybird` | core debugging + rich OCaml locals; no pause/`--run`, no evaluate responses, no fatal-error modal (ocamlearlybird 1.3.6 limitations); single-domain only |
257
267
 
258
268
  ### Language detection and selection
259
269
 
260
270
  The language is auto-detected from the debug target:
261
271
 
262
272
  1. File extension: `.py` → Python; `.pl` / `.pm` / `.t` → Perl; `.sh` / `.bash`
263
- → Bash; `.csh` / `.tcsh` → Tcsh.
264
- 2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
265
- 3. A `#!...python`, `#!...perl`, `#!...bash`, or `#!...csh`/`#!...tcsh`
266
- shebang → Python / Perl / Bash / Tcsh respectively.
273
+ → Bash; `.csh` / `.tcsh` → Tcsh; `.rb` → Ruby. (`.ml` / `.mli` do *not*
274
+ auto-select OCaml, see point 5.)
275
+ 2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++, unless
276
+ byte-sniffing finds an OCaml marker, either a native binary's
277
+ `caml_program`/`caml_startup` runtime symbols, a bytecode file's
278
+ trailing `Caml1999` marker, or a `#!...ocamlrun` shebang, in which case
279
+ → OCaml (native or bytecode respectively). A stripped native OCaml
280
+ binary that byte-sniffing can't identify falls back to C/C++; force it
281
+ with `--lang ocaml`. Rust is never inferred from an executable; select
282
+ it explicitly with `--lang rust`.
283
+ 3. A `#!...python`, `#!...perl`, `#!...bash`, `#!...csh`/`#!...tcsh`, or
284
+ `#!...ruby` shebang → Python / Perl / Bash / Tcsh / Ruby respectively.
267
285
  4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
268
286
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
269
- 5. Anything else produces an error naming the `--lang` override.
287
+ 5. `.ml` / `.mli` (OCaml source) produce an error too: build the project
288
+ first (dune's dev profile keeps debug info) and pass the **built
289
+ executable** to `tdb` (`tdb ./_build/default/bin/main.exe`), never the
290
+ `.ml` file.
291
+ 6. Anything else produces an error naming the `--lang` override.
270
292
 
271
293
  `--lang` forces the language; `--adapter` picks a non-default adapter within
272
- it (`tdb --lang cpp --adapter lldb-dap ./myprog`).
294
+ it (`tdb --lang cpp --adapter lldb-dap ./myprog`). Rust always requires the
295
+ explicit language selection (`tdb --lang rust target/debug/app`).
273
296
 
274
297
  > **Migration note:** extensionless Python scripts without a `python` shebang
275
298
  > were previously assumed to be Python; they now require `--lang python`.
@@ -302,12 +325,17 @@ process inspectors and wait graph, the evaluate console's trailing-`?` help,
302
325
  `--python`/`--pv`, `--no-subprocess`, automatic child-process attachment, and
303
326
  the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
304
327
  programs by nature). Remote attach (`-r`) also works for Perl (see
305
- [Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), but not
306
- for C/C++, Bash, or Tcsh. `--terminal` is
307
- currently ignored for non-Python targets.
328
+ [Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), Ruby
329
+ (see [Ruby](#ruby), `rdbg --open` in place of `debugpy.listen()`), and for
330
+ Rust and C/C++ native binaries (against a gdbserver/lldb-server stub, with
331
+ a local symbol-bearing executable; see [Rust](#rust) — the same flags work
332
+ for a C/C++ target without `--lang`), but not for Bash, Tcsh, or OCaml. `--terminal` works for every launch-mode
333
+ language — Python, Perl, Bash, Tcsh, Ruby, and C/C++, OCaml native, or Rust
334
+ sessions via `--adapter lldb-dap` — see
335
+ [External Terminal Support](#external-terminal-support).
308
336
 
309
337
  **Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
310
- has a smaller feature envelope than Python/Perl:
338
+ has a smaller feature set than Python:
311
339
 
312
340
  - Debuggee code that installs its own `DEBUG` trap clobbers the harness;
313
341
  debugging silently degrades to free-running.
@@ -351,9 +379,30 @@ paths and line numbers are preserved in everything tdb displays):
351
379
 
352
380
  See [Tcsh](#tcsh) below for launch details.
353
381
 
382
+ **OCaml limitations (v1):** OCaml has two independent adapters with
383
+ different, non-overlapping gaps; see [OCaml](#ocaml) below for the full
384
+ picture:
385
+
386
+ - **Native (`lldb-dap`/`gdb`):** the Variables view shows no named OCaml
387
+ locals in an OCaml frame (only a `Registers` scope). This is an
388
+ upstream OCaml/DWARF limitation on stock OCaml 5.4, not a `tdb` bug; use
389
+ the bytecode adapter when you need to inspect locals. The evaluate
390
+ console evaluates lldb/C-level expressions (runtime spelunking), not
391
+ OCaml expressions.
392
+ - **Bytecode (`ocamlearlybird`):** single-domain only (no multicore); no
393
+ `pause` while running (so `--run` is unavailable); the evaluate console
394
+ never gets a response for any expression (an `ocamlearlybird` 1.3.6
395
+ limitation); and the fatal-error modal doesn't appear for an uncaught
396
+ exception (`ocamlearlybird` swallows the real exception text before it
397
+ reaches `tdb`). Run the program outside the debugger to see the
398
+ backtrace instead.
399
+ - No Windows support, no remote attach, and a stripped native binary that
400
+ byte-sniffing can't identify needs `--lang ocaml`.
401
+
354
402
  ### C/C++ tips
355
403
 
356
- - Compile with `-g` (ideally `-g -O0`). If no breakpoint in a file can be
404
+ - Compile with `-g` (ideally `-g -O0` or `-g3 -O0`).
405
+ If no breakpoint in a file can be
357
406
  bound, `tdb` prints a console warning suggesting the program may lack
358
407
  debug info.
359
408
  - Stack frames pointing into system libraries often have no source on disk;
@@ -366,6 +415,75 @@ See [Tcsh](#tcsh) below for launch details.
366
415
  commands, so evaluate expressions with an explicit `print`, e.g.
367
416
  `print x` rather than bare `x` (bare `x` collides with GDB's
368
417
  examine-memory command). `lldb-dap` evaluates bare expressions directly.
418
+ - `--terminal` (see [External Terminal Support](#external-terminal-support))
419
+ requires `--adapter lldb-dap`; GDB's DAP mode has no terminal integration
420
+ and `tdb` refuses `--terminal` with the default `gdb` adapter.
421
+
422
+ ### Rust
423
+
424
+ Rust debugging is intentionally explicit: build a normal debug executable,
425
+ leave it unmodified, and pass that executable with `--lang rust`. Linux and
426
+ macOS are supported; Windows is not yet (MSVC-toolchain binaries carry PDB
427
+ debug info that neither `gdb -i dap` nor this release's probes read). `tdb` does
428
+ not compile, instrument, or auto-detect Rust programs. Core debugging
429
+ (breakpoints, stepping, stacks, variables, remote attach) works with any
430
+ rustc that emits debug info; the concurrency inspector's layout-specific
431
+ *ownership evidence* additionally requires the current stable Rust
432
+ **1.98** standard-library layout — on other versions the workspace still
433
+ opens with stack-based wait classification and a compatibility warning.
434
+ Build with debug information and no optimization for the most useful
435
+ source locations:
436
+
437
+ ```bash
438
+ cargo rustc -- -C debuginfo=2 -C opt-level=0
439
+ # Equivalent direct rustc settings: rustc -C debuginfo=2 -C opt-level=0 src/main.rs
440
+ ```
441
+
442
+ A panic that terminates the debuggee opens the error modal with the parsed
443
+ panic message and backtrace; `tdb` injects `RUST_BACKTRACE=1` into launched
444
+ programs (your own value wins if you set one) so the frames are present.
445
+
446
+ The common commands are:
447
+
448
+ ```bash
449
+ cargo build
450
+ tdb --lang rust target/debug/app
451
+ tdb --lang rust --adapter lldb-dap --run target/debug/app
452
+ tdb --lang rust --adapter lldb-dap --terminal xterm target/debug/app
453
+ tdb --lang rust --adapter gdb --remote-attach host:2345 target/debug/app
454
+ ```
455
+
456
+ `--run` and normal stopped-on-entry launch work with both adapters. On Linux,
457
+ use GDB (the default) or `lldb-dap`; on macOS, use `lldb-dap` (the default).
458
+ External terminals are LLDB-only: GDB's DAP mode cannot provide terminal
459
+ integration. The mode/platform matrix is:
460
+
461
+ | Platform | Normal / `--run` | `--terminal` | `--remote-attach` |
462
+ |----------|------------------|--------------|-------------------|
463
+ | Linux | GDB or `lldb-dap` | `lldb-dap` only | GDB or `lldb-dap` |
464
+ | macOS | `lldb-dap` | `lldb-dap` only | `lldb-dap` |
465
+
466
+ Remote attach connects to a GDB-remote server already running on the target.
467
+ Pass the matching **local, unmodified executable with debug symbols** as the
468
+ program argument; `tdb` requires it for symbols and Rust concurrency evidence.
469
+ When source paths differ, pair `--local-root` with `--remote-root`. Do not
470
+ expose the remote debug port to an untrusted network: prefer an SSH tunnel,
471
+ for example `ssh -L 2345:127.0.0.1:2345 host`, then attach to `127.0.0.1:2345`.
472
+
473
+ In a Rust session the Threads action (`Alt+T`, or the Threads menu entry)
474
+ opens the Rust Concurrency workspace while the debuggee is stopped — a
475
+ fresh, bounded snapshot of threads, wait edges, and findings. The graph is
476
+ best effort: `confirmed` evidence is directly observed by the debugger,
477
+ `probable` is a supported inference, and `unknown` means ownership could not
478
+ be established. A deadlock is confirmed only when every edge in its cycle is
479
+ confirmed. A suspected cycle or whole-program stall is a diagnostic lead, not
480
+ a proof: it means a cycle has incomplete evidence, or all observed application
481
+ threads are blocked while the graph cannot safely invent an owner. Refresh
482
+ after the next stop to collect a new snapshot.
483
+
484
+ The built-in evidence recognizes current `std` synchronization layouts. A
485
+ future helper crate may expose stable, application-provided synchronization
486
+ metadata for broader primitives and stronger ownership evidence.
369
487
 
370
488
  ### Perl
371
489
 
@@ -379,6 +497,9 @@ hood, so it works with any Perl already on the system.
379
497
  tdb script.pl
380
498
  ```
381
499
 
500
+ `--terminal` is supported (see
501
+ [External Terminal Support](#external-terminal-support)).
502
+
382
503
  **Compile-time code (`BEGIN` blocks):** Perl runs `BEGIN` blocks and the
383
504
  `use` statements that are themselves `BEGIN` blocks while it is still
384
505
  *compiling* your program, before stock `perl5db` ever stops. `tdb` arms the
@@ -500,7 +621,8 @@ Most notably, a breakpoint can't be set on a `func() {` line itself (the
500
621
  `DEBUG` trap never fires there), stops never land inside subshells or
501
622
  pipeline segments, child bash processes run uninstrumented, and only the
502
623
  innermost frame's locals are inspectable. There is no remote-attach mode for
503
- Bash.
624
+ Bash. `--terminal` is supported (see
625
+ [External Terminal Support](#external-terminal-support)).
504
626
 
505
627
  The Variables view shows three scopes for bash: Locals (innermost frame
506
628
  only), Globals (unexported shell variables), and Environment (exported
@@ -529,7 +651,8 @@ stack navigation across `source` nesting, variable inspection, and the
529
651
  evaluate console with the v1 caveats listed in
530
652
  [What works for non-Python languages](#what-works-for-non-python-languages),
531
653
  most notably: no conditional breakpoints, no pause of a free-running
532
- script, and stepping is per logical source line.
654
+ script, and stepping is per logical source line. `--terminal` is supported
655
+ (see [External Terminal Support](#external-terminal-support)).
533
656
 
534
657
  The Variables view shows four scopes for tcsh: Shell Variables (`set`),
535
658
  Environment (`setenv`), Aliases, and Arguments (`argv`). All of them show
@@ -538,6 +661,119 @@ text directly in the paused shell. It can inspect *and mutate* state
538
661
  (`set name = value` takes effect immediately), and a syntax error in
539
662
  evaluated text can terminate the debuggee; there is no isolation.
540
663
 
664
+ ### Ruby
665
+
666
+ tdb debugs Ruby via the [debug gem](https://github.com/ruby/debug)'s
667
+ `rdbg` (Ruby >= 3.1 ships it; otherwise `gem install debug`; tdb needs
668
+ debug >= 1.9). `rdbg` must be on PATH, or point tdb at it with
669
+ `{"adapters": {"rdbg": "/path/to/rdbg"}}` in config.json.
670
+
671
+ ```bash
672
+ tdb script.rb # launch, stop at first line
673
+ tdb --run script.rb # run immediately, debug on demand
674
+ tdb --terminal script.rb # program I/O in its own terminal
675
+ ```
676
+
677
+ Remote attach: start the program with
678
+ `rdbg --open --port 5678 --host 0.0.0.0 script.rb` (add `--nonstop` to
679
+ let it run before you attach), then `tdb -r HOST:5678 --lang ruby`.
680
+ Note: rdbg's `--cookie` authentication is not part of DAP and is not
681
+ supported; bind to localhost and tunnel over SSH instead.
682
+ `--local-root`/`--remote-root` path mappings are not supported for Ruby
683
+ yet. Bundler projects work when your environment resolves `rdbg`
684
+ (`gem install debug` into the project's Ruby); there is no `bundle
685
+ exec` integration yet.
686
+
687
+ > **Startup note:** the debug gem's `rdbg` has an occasional handshake
688
+ > race on launch; tdb detects a stalled/corrupted handshake and retries
689
+ > once automatically with a fresh `rdbg`, so this is usually invisible.
690
+
691
+ ### OCaml
692
+
693
+ `tdb` debugs OCaml through two independent adapters, chosen automatically
694
+ from how the executable was built:
695
+
696
+ - **Native** (`ocamlopt`/dune's default build) → **`lldb-dap`** (default;
697
+ `--adapter gdb` also works). OCaml 5's domains show up as threads.
698
+ - **Bytecode** (`ocamlc -g`) → **`ocamlearlybird`** (`opam install
699
+ earlybird`). Single-domain only, but with rich, real OCaml locals.
700
+
701
+ **Build requirement:** compile with `-g` (dune's dev profile already
702
+ does). Always pass the **built executable** to `tdb`, never the `.ml`
703
+ source, as in `tdb ./_build/default/bin/main.exe`, not `tdb main.ml` (which
704
+ errors with this exact guidance).
705
+
706
+ **OCaml version:** tdb requires **OCaml ≥ 4.12**. Older runtimes print
707
+ `Printexc` backtraces without function names, which the fatal-error
708
+ modal's parser does not understand (frames would be dropped and the
709
+ modal would wrongly suggest recompiling with `-g`).
710
+
711
+ ```bash
712
+ tdb ./_build/default/bin/main.exe # native, lldb-dap
713
+ tdb --adapter gdb ./_build/default/bin/main.exe # native, gdb
714
+ tdb ./_build/default/bin/main.byte # bytecode, ocamlearlybird
715
+ ```
716
+
717
+ A stripped native binary that byte-sniffing can't identify as OCaml falls
718
+ back to C/C++; force it with `--lang ocaml`. Override the adapter or point
719
+ at a non-`PATH` install in `config.json` (see
720
+ [Configuration](#configuration)):
721
+
722
+ ```json
723
+ {
724
+ "adapters": {"ocamlearlybird": "/path/to/ocamlearlybird"},
725
+ "default_adapters": {"ocaml": "gdb"}
726
+ }
727
+ ```
728
+
729
+ **Domains as threads (native only):** OCaml 5's domains are presented in
730
+ the Threads modal (`Alt+T` or the **Threads (N)** menu label) as `Domain 0
731
+ (main)`, `Domain 1`, and so on, in creation order. The runtime's internal
732
+ "backup thread" (one per domain, used for I/O blocking) is hidden by
733
+ default since it's never running user code; press `a` in the Threads
734
+ modal to reveal it alongside the domains. Breakpoints, stepping, continue,
735
+ and pause all work normally against any domain. In both the Stack view
736
+ and the Threads modal, OCaml frame names are demangled from the
737
+ runtime's raw `camlModule__name_NNN` convention into readable
738
+ `Module.name` form (e.g. `camlOcaml_domains.worker_297` displays as
739
+ `Ocaml_domains.worker`); runtime C frames (`caml_start_program`,
740
+ `caml_callback_exn`, and the like) are shown as-is, unchanged.
741
+
742
+ **Variables view (native):** stock OCaml 5.4 native DWARF shows **no
743
+ named locals**. The `scopes`/`variables` round trip succeeds, but every
744
+ OCaml frame's `Locals` and `Globals` scopes come back empty; only a
745
+ `Registers` scope has data (the raw register file, not decoded OCaml
746
+ values). This is an upstream OCaml compiler/DWARF limitation on the
747
+ verified toolchain (OCaml 5.4.0 / lldb 21), not a `tdb` bug. If you need
748
+ to inspect local variables, use the bytecode adapter instead.
749
+
750
+ **Variables view (bytecode):** `ocamlearlybird` reports real local names
751
+ and values, exactly like Python locals.
752
+
753
+ **Evaluate console:** the two adapters give fundamentally different
754
+ consoles. Native (`lldb-dap`) evaluates **lldb/C-level expressions**.
755
+ This is runtime spelunking (registers, raw memory, C symbol names), not
756
+ OCaml expression evaluation. Bytecode (`ocamlearlybird`) is meant to
757
+ evaluate real OCaml expressions in scope, but in the verified
758
+ `ocamlearlybird` 1.3.6 release its `evaluate` request never responds (a
759
+ confirmed upstream limitation, not specific to `tdb`); the console
760
+ silently returns nothing for any expression.
761
+
762
+ **Uncaught exceptions:** native sessions set a breakpoint on
763
+ `caml_fatal_uncaught_exception` so the debugger stops there, and `tdb`
764
+ also parses the `OCAMLRUNPARAM=b` backtrace it injects into the debuggee's
765
+ environment once the process exits, opening the same error modal Python
766
+ tracebacks get. Bytecode sessions do **not** get this: `ocamlearlybird`
767
+ intercepts the uncaught exception itself and only reports a generic
768
+ "Program exited due to Uncaught_exc" message, so the real exception text
769
+ never reaches `tdb`. Run the program outside the debugger
770
+ (`OCAMLRUNPARAM=b ./main.byte`) to see its backtrace.
771
+
772
+ **Limitations (v1):** no Windows support; no remote attach; bytecode
773
+ sessions can't `pause` a running program (so `--run` is unavailable for
774
+ bytecode. Native sessions support `--run` normally); `Unix.fork` and Eio
775
+ fibers are out of scope (only OCaml 5 domains are presented as threads).
776
+
541
777
  ## Layout
542
778
 
543
779
  ```
@@ -593,6 +829,11 @@ A cursor line (blue) tracks your position; the current execution line is highlig
593
829
  | `Alt+A` | Async Tasks |
594
830
  | `Alt+H` | Help (Documentation, About) |
595
831
 
832
+ File > Open works for every supported language: the file picker filters to
833
+ the current session's language (by extension) and refuses a pick that
834
+ detects as a different language, so you can only switch to another script
835
+ written in the same language as the one currently being debugged.
836
+
596
837
  **Navigation (vim-style by default):**
597
838
 
598
839
  By default the Code View is in Debug mode. Hit `Escape` to switch to Navigate mode
@@ -650,7 +891,7 @@ results = await asyncio.gather(
650
891
  fetch(2, 1),
651
892
  fetch(3, 3),
652
893
  )
653
- print(results) # next stop lands here, not on each sub-line above
894
+ print(results) # next stop lands here, not on each sub-line above
654
895
  ```
655
896
 
656
897
  lands on `print(results)`, not on each interior sub-line of the `gather` call. Switch to
@@ -760,6 +1001,7 @@ need to launch through `tdb` up front. Install the hook once at the top of your
760
1001
  ```python
761
1002
  import sys
762
1003
  import tdb
1004
+
763
1005
  sys.excepthook = tdb.exception_hook
764
1006
  ```
765
1007
 
@@ -808,6 +1050,7 @@ continuing--use `tdb.breakpoint()`:
808
1050
  ```python
809
1051
  import tdb
810
1052
 
1053
+
811
1054
  def compute(n):
812
1055
  total = sum(range(n))
813
1056
  tdb.breakpoint() # pause here and drop into tdb
@@ -950,6 +1193,7 @@ where you want to attach the debugger:
950
1193
  ```python
951
1194
  # In the target program:
952
1195
  import debugpy
1196
+
953
1197
  debugpy.listen(("0.0.0.0", 5678))
954
1198
  print("Waiting for tdb to attach on port 5678...")
955
1199
  debugpy.wait_for_client() # optional: pause until debugger connects
@@ -1007,22 +1251,94 @@ application directory and a shared library directory) in one invocation.
1007
1251
 
1008
1252
  ### External Terminal Support
1009
1253
 
1010
- Some Python programs, notably text user interfaces, use terminal control
1011
- codes and require direct access to the terminal to function properly.
1012
- Such programs can be debugged with `tdb` by having it launch the debuggee in
1013
- a separate terminal:
1254
+ Some programs, notably text user interfaces, use terminal control codes and
1255
+ require direct access to the terminal to function properly. Such programs
1256
+ can be debugged with `tdb` by having it launch the debuggee in a separate
1257
+ terminal:
1014
1258
 
1015
1259
  ```bash
1016
1260
  tdb --terminal xterm my_tui_app.py
1017
1261
  ```
1018
1262
 
1019
- The debuggee runs in a separate window of the specified terminal. Supported choices:
1263
+ `--terminal` works for every language `tdb` can *launch* (as opposed to
1264
+ attach to): Python, Perl, Bash, Tcsh, Ruby, and C/C++ or native OCaml
1265
+ sessions via `--adapter lldb-dap`. Neither `gdb -i dap` (the default C/C++
1266
+ adapter, and an alternate for native OCaml) nor `ocamlearlybird` (the
1267
+ bytecode OCaml adapter) has any terminal integration -- `tdb` rejects
1268
+ `--terminal` up front for both, with an error pointing at `--adapter
1269
+ lldb-dap` instead. `--terminal` also only applies when `tdb` launches the
1270
+ program itself; it is rejected for remote-attach (`-r`), since there's no
1271
+ program for `tdb` to spawn a terminal around.
1272
+
1273
+ The debuggee runs in a separate window of the specified terminal, including
1274
+ all of its stdin/stdout/stderr -- keyboard input, program output, and
1275
+ anything the program itself draws to the terminal all happen in that
1276
+ window, not in `tdb`'s Console View. Supported choices:
1020
1277
  `xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
1021
1278
  `wezterm`, `terminator`. The selected terminal must be on `PATH`. Debugging
1022
- proceeds as usual in the terminal where `tdb` was invoked.
1279
+ (breakpoints, stepping, variable inspection, the evaluate console) proceeds
1280
+ as usual in the terminal where `tdb` was invoked.
1023
1281
 
1024
1282
  This feature only works in graphical environments where external terminals are available.
1025
1283
 
1284
+ ### Run Mode (`--run`)
1285
+
1286
+ `tdb`'s run mode invokes an unmodified program nearly at full speed, without bringing up the TUI.
1287
+ At any point afterwards, hitting `Ctrl-C` in that terminal pauses the program
1288
+ and drops you into the full TUI.
1289
+ From there you can inspect the program's state, navigate the stack, set and continue
1290
+ to breakpoints, and so on. After you finish inspecting, hitting `q` to quit brings up
1291
+ a modal that lets you either detach from the TUI and let the program resume running,
1292
+ or lets you terminate it.
1293
+
1294
+ The primary use cases for run mode are: (1) allowing inspection of a program that
1295
+ appears hung, and (2) finding where a program execution path lands after it
1296
+ receives a problematic input.
1297
+
1298
+ ```bash
1299
+ tdb --run my_program.py args...
1300
+ ```
1301
+
1302
+ The debuggee's stdout/stderr stream straight to the terminal. If the program exits on
1303
+ its own, `tdb` exits with the same code:
1304
+
1305
+ ```bash
1306
+ tdb --run my_program.py; echo $? # my_program.py's own exit code
1307
+ ```
1308
+
1309
+ **Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
1310
+ `SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
1311
+ the debuggee at its currently executing line and opens the TUI with full debugging
1312
+ available -- breakpoints, stepping, variable inspection, the evaluate console. The
1313
+ debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
1314
+ group), so its own `SIGINT` handling is undisturbed.
1315
+
1316
+ **Quitting an adopted session** adds a third choice to the usual quit dialog:
1317
+ - `d` (or `q`) -- detach and resume: the program keeps running headlessly; interrupt it
1318
+ again later the same way.
1319
+ - `t` -- terminate the program and quit `tdb`.
1320
+ - `Esc` -- cancel, stay in the TUI.
1321
+
1322
+ A breakpoint set during one of these episodes stays live after you detach; if it's hit,
1323
+ `tdb` reopens the TUI there, just like an interrupt.
1324
+
1325
+ If the program instead dies on an unhandled exception -- in Python this includes a
1326
+ nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb` opens
1327
+ the TUI at the point of failure instead of exiting silently, so you can inspect the
1328
+ crash. Exit-code passthrough applies only to a clean exit during the headless phase.
1329
+
1330
+ Supported languages: Python, Perl, Bash, Tcsh, Ruby, C/C++ (both `gdb` and
1331
+ `lldb-dap`), and native OCaml (both `gdb` and `lldb-dap`; bytecode/
1332
+ `ocamlearlybird` sessions don't support pause, so `--run` is unavailable
1333
+ for those). `--run` is rejected up front for any other language.
1334
+
1335
+ **Limitation:** pausing is cooperative. If the debuggee is blocked inside a single
1336
+ blocking external call or syscall, the pause can't land until that call returns --
1337
+ `tdb` prints a notice and opens the TUI once the stop actually arrives.
1338
+
1339
+ **Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--server`,
1340
+ `--headless`, `--mcp`, `--terminal`.
1341
+
1026
1342
  ### Keybinding Schemes
1027
1343
 
1028
1344
  ```bash
@@ -1058,7 +1374,9 @@ Both the interactive TUI and the JSON-RPC server run simultaneously.
1058
1374
  ### RPC Protocol
1059
1375
 
1060
1376
  Send POST requests with `{"action": "...", "params": [...]}`. Responses return
1061
- `{"timestamp": "...", "success": true/false, "value": "..."}`.
1377
+ `{"timestamp": "...", "success": true/false, "value": "..."}`. Actions with a
1378
+ structured result (today only `rust_concurrency`) additionally carry a `data`
1379
+ object holding the full JSON payload; `value` then holds a one-line summary.
1062
1380
 
1063
1381
  ```bash
1064
1382
  # Check status
@@ -1116,6 +1434,7 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
1116
1434
  | `list_tasks` | `[]` | List all asyncio tasks |
1117
1435
  | `inspect_task` | `["task_name"]` | Inspect a specific asyncio task |
1118
1436
  | `wait_graph` | `[]` | Show wait graph + any deadlock cycles |
1437
+ | `rust_concurrency` | `[]` | Structured Rust thread/wait/deadlock snapshot in `data` (Rust sessions, stopped) |
1119
1438
  | `restart` | `[]` | Restart session (preserves breakpoints) |
1120
1439
  | `quit` | `[]` | Shut down |
1121
1440
 
@@ -1198,7 +1517,7 @@ point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
1198
1517
  `python -m tdb.mcp` (module form). Pick whichever matches how your MCP
1199
1518
  client expects to launch servers.
1200
1519
 
1201
- ### Tool surface (16 tools, curated)
1520
+ ### Tool surface (17 tools, curated)
1202
1521
 
1203
1522
  | Cluster | Tools |
1204
1523
  |---------|-------|
@@ -1206,7 +1525,7 @@ client expects to launch servers.
1206
1525
  | Control | `control(action, timeout_s=30)` where `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
1207
1526
  | Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
1208
1527
  | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
1209
- | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
1528
+ | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()`, `rust_concurrency()` |
1210
1529
 
1211
1530
  `control` is intentionally one tool that takes an action enum. The six
1212
1531
  underlying RPC actions share a return shape, and agents perform
@@ -1217,9 +1536,10 @@ single optional argument for the same reason.
1217
1536
  `debug_launch` accepts optional `lang` and `adapter` parameters mirroring the
1218
1537
  CLI's `--lang`/`--adapter`; when omitted, the language is auto-detected from
1219
1538
  `program`, so an agent can hand it a compiled binary directly. The
1220
- `tasks`/`processes`/`wait_graph` tools stay registered for every language but
1539
+ exception is Rust: compiled native binaries require explicit `lang="rust"`.
1540
+ The `tasks`/`processes`/`wait_graph` tools stay registered for every language but
1221
1541
  return a structured "not supported when debugging C/C++"-style error for
1222
- non-Python debuggees.
1542
+ non-Python debuggees; `rust_concurrency` does the same for non-Rust debuggees.
1223
1543
 
1224
1544
  ### Agent flow for a long-running step
1225
1545
 
@@ -1337,6 +1657,8 @@ fires as expected.
1337
1657
  - [textual](https://github.com/Textualize/textual) : TUI framework
1338
1658
  - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation for Python
1339
1659
  - [gdb](https://sourceware.org/gdb/) / [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html) : optional, user-installed DAP adapters for C/C++
1660
+ - [debug gem](https://github.com/ruby/debug) (`rdbg`) : optional, user-installed DAP adapter for Ruby
1661
+ - [ocamlearlybird](https://github.com/hackwaly/ocamlearlybird) : optional, user-installed DAP adapter for OCaml bytecode (native OCaml reuses `gdb`/`lldb-dap`)
1340
1662
  - [pygments](https://pygments.org/) : Syntax highlighting
1341
1663
  - [FastAPI](https://fastapi.tiangolo.com/) + [uvicorn](https://www.uvicorn.org/) : JSON-RPC server
1342
1664