textual-debugger 0.2.12__tar.gz → 0.2.16__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 (196) hide show
  1. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/PKG-INFO +770 -83
  2. {textual_debugger-0.2.12/src/tdb → textual_debugger-0.2.16}/README.md +764 -81
  3. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/pyproject.toml +21 -4
  4. {textual_debugger-0.2.12 → textual_debugger-0.2.16/src/tdb}/README.md +764 -81
  5. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/__init__.py +1 -1
  6. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/_timeouts.py +7 -0
  7. textual_debugger-0.2.16/src/tdb/adapters/bash/arith_for.py +326 -0
  8. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/bash/server.py +20 -0
  9. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/bash/session.py +97 -6
  10. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/bash/tdb_harness.sh +29 -3
  11. textual_debugger-0.2.16/src/tdb/adapters/native/__init__.py +1 -0
  12. textual_debugger-0.2.16/src/tdb/adapters/native/gdb_stl_printers.py +740 -0
  13. textual_debugger-0.2.16/src/tdb/adapters/native/tdb.h +269 -0
  14. textual_debugger-0.2.16/src/tdb/adapters/perl/Devel/TdbRemote.pm +363 -0
  15. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/padwalker.py +63 -4
  16. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/server.py +109 -10
  17. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/session.py +111 -8
  18. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/powershell/server.py +65 -2
  19. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/ruby/server.py +210 -16
  20. textual_debugger-0.2.16/src/tdb/adapters/ruby/tdb.rb +216 -0
  21. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/inspect.py +38 -11
  22. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/server.py +21 -0
  23. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/session.py +86 -2
  24. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app.py +427 -42
  25. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app_handlers/dap_events.py +47 -13
  26. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app_helpers.py +9 -21
  27. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/breakpoint_hook.py +55 -10
  28. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/cli.py +182 -44
  29. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/dap/client.py +106 -5
  30. textual_debugger-0.2.16/src/tdb/examples/C/breakpoint_hook_demo.c +26 -0
  31. textual_debugger-0.2.16/src/tdb/examples/C/pi_digits.c +102 -0
  32. textual_debugger-0.2.16/src/tdb/examples/C++/breakpoint_hook_demo.cpp +26 -0
  33. textual_debugger-0.2.16/src/tdb/examples/C++/pi_digits.cpp +77 -0
  34. textual_debugger-0.2.16/src/tdb/examples/Fortran/breakpoint_hook_demo.f90 +38 -0
  35. textual_debugger-0.2.16/src/tdb/examples/Fortran/pi_digits.f90 +165 -0
  36. textual_debugger-0.2.16/src/tdb/examples/Go/breakpoint_hook_demo/go.mod +7 -0
  37. textual_debugger-0.2.16/src/tdb/examples/Go/breakpoint_hook_demo/main.go +32 -0
  38. textual_debugger-0.2.16/src/tdb/examples/Go/pi_digits.go +77 -0
  39. textual_debugger-0.2.16/src/tdb/examples/OCaml/breakpoint_hook_demo.ml +26 -0
  40. textual_debugger-0.2.16/src/tdb/examples/OCaml/pi_digits.ml +66 -0
  41. textual_debugger-0.2.16/src/tdb/examples/Perl/breakpoint_hook_demo.pl +20 -0
  42. textual_debugger-0.2.16/src/tdb/examples/Perl/pi_digits.pl +42 -0
  43. textual_debugger-0.2.16/src/tdb/examples/PowerShell/pi_digits.ps1 +50 -0
  44. textual_debugger-0.2.16/src/tdb/examples/Python/pi_digits.py +138 -0
  45. textual_debugger-0.2.16/src/tdb/examples/Ruby/breakpoint_hook_demo.rb +17 -0
  46. textual_debugger-0.2.16/src/tdb/examples/Ruby/pi_digits.rb +61 -0
  47. textual_debugger-0.2.16/src/tdb/examples/Rust/breakpoint_hook_demo/Cargo.toml +9 -0
  48. textual_debugger-0.2.16/src/tdb/examples/Rust/breakpoint_hook_demo/src/main.rs +22 -0
  49. textual_debugger-0.2.16/src/tdb/examples/Rust/pi_digits.rs +69 -0
  50. textual_debugger-0.2.16/src/tdb/examples/bash/pi_digits.sh +60 -0
  51. textual_debugger-0.2.16/src/tdb/examples/tcsh/pi_digits.tcsh +95 -0
  52. textual_debugger-0.2.16/src/tdb/info.py +278 -0
  53. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/keybindings/__init__.py +86 -3
  54. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/base.py +133 -0
  55. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/bash.py +11 -1
  56. textual_debugger-0.2.16/src/tdb/languages/cpp.py +478 -0
  57. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/go.py +25 -2
  58. textual_debugger-0.2.16/src/tdb/languages/native_tools.py +223 -0
  59. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/ocaml.py +76 -6
  60. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/perl.py +11 -2
  61. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/powershell.py +8 -2
  62. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/python.py +25 -1
  63. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/registry.py +68 -2
  64. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/ruby.py +8 -1
  65. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/rust.py +55 -15
  66. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/tcsh.py +10 -1
  67. textual_debugger-0.2.16/src/tdb/mcp/__init__.py +45 -0
  68. textual_debugger-0.2.16/src/tdb/mcp/__main__.py +6 -0
  69. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/persist.py +75 -9
  70. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/post_mortem.py +42 -10
  71. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/replay_tui.py +21 -0
  72. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/run_mode.py +30 -4
  73. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/probes/gdb_script.py +9 -1
  74. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/server/handlers.py +26 -0
  75. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/server/runner.py +10 -0
  76. textual_debugger-0.2.16/src/tdb/session/breakpoint_sync.py +158 -0
  77. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/controller.py +384 -18
  78. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/state.py +27 -0
  79. textual_debugger-0.2.16/src/tdb/session/terminal.py +268 -0
  80. textual_debugger-0.2.16/src/tdb/source_edit.py +141 -0
  81. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/breakpoint_view.py +11 -0
  82. textual_debugger-0.2.16/src/tdb/widgets/code_editor.py +738 -0
  83. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/code_view.py +422 -20
  84. textual_debugger-0.2.16/src/tdb/widgets/console_view.py +128 -0
  85. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/evaluate_console.py +7 -1
  86. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/modals.py +13 -4
  87. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/variable_view.py +63 -7
  88. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/textual_debugger.egg-info/PKG-INFO +770 -83
  89. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/textual_debugger.egg-info/SOURCES.txt +32 -0
  90. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/textual_debugger.egg-info/entry_points.txt +1 -1
  91. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/textual_debugger.egg-info/requires.txt +7 -1
  92. textual_debugger-0.2.12/src/tdb/adapters/perl/Devel/TdbRemote.pm +0 -93
  93. textual_debugger-0.2.12/src/tdb/languages/cpp.py +0 -204
  94. textual_debugger-0.2.12/src/tdb/mcp/__init__.py +0 -10
  95. textual_debugger-0.2.12/src/tdb/mcp/__main__.py +0 -6
  96. textual_debugger-0.2.12/src/tdb/session/terminal.py +0 -121
  97. textual_debugger-0.2.12/src/tdb/widgets/console_view.py +0 -35
  98. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/LICENSE +0 -0
  99. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/setup.cfg +0 -0
  100. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/__main__.py +0 -0
  101. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/__init__.py +0 -0
  102. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/bash/__init__.py +0 -0
  103. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/bash/__main__.py +0 -0
  104. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/bash/declares.py +0 -0
  105. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/ocaml/__init__.py +0 -0
  106. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/ocaml/lldb_formatters.py +0 -0
  107. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/Devel/TdbCompile.pm +0 -0
  108. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/__init__.py +0 -0
  109. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/__main__.py +0 -0
  110. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/helpers.pl +0 -0
  111. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/padwalker_src/Makefile.PL +0 -0
  112. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/padwalker_src/PadWalker.pm +0 -0
  113. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/padwalker_src/PadWalker.xs +0 -0
  114. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/protocol.py +0 -0
  115. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/perl/refs.py +0 -0
  116. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/powershell/__init__.py +0 -0
  117. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/powershell/__main__.py +0 -0
  118. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/powershell/locate.py +0 -0
  119. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/powershell/output.py +0 -0
  120. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/powershell/tdb_launch.ps1 +0 -0
  121. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/ruby/__init__.py +0 -0
  122. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/ruby/__main__.py +0 -0
  123. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/seqs.py +0 -0
  124. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/__init__.py +0 -0
  125. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/__main__.py +0 -0
  126. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/breakpoints.py +0 -0
  127. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/cli.py +0 -0
  128. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/guardian.py +0 -0
  129. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/instrumenter.py +0 -0
  130. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/models.py +0 -0
  131. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/protocol.py +0 -0
  132. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/runtime.py +0 -0
  133. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/scanner.py +0 -0
  134. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/adapters/tcsh/transport.py +0 -0
  135. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app_handlers/__init__.py +0 -0
  136. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app_handlers/inspection.py +0 -0
  137. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app_handlers/routing.py +0 -0
  138. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/app_handlers/ui_panels.py +0 -0
  139. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/dap/__init__.py +0 -0
  140. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/dap/messages.py +0 -0
  141. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/dap/protocol.py +0 -0
  142. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/dap/reverse.py +0 -0
  143. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/dap/types.py +0 -0
  144. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/eval_mode.py +0 -0
  145. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/examine.py +0 -0
  146. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/go_concurrency/__init__.py +0 -0
  147. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/go_concurrency/analyzer.py +0 -0
  148. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/go_concurrency/classifier.py +0 -0
  149. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/go_concurrency/collector.py +0 -0
  150. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/go_concurrency/models.py +0 -0
  151. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/inspection.py +0 -0
  152. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/inspection_full.py +0 -0
  153. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/__init__.py +0 -0
  154. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/languages/errors.py +0 -0
  155. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/mcp/server.py +0 -0
  156. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/mcp/session.py +0 -0
  157. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/processes_cache.py +0 -0
  158. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/replay.py +0 -0
  159. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/__init__.py +0 -0
  160. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/analyzer.py +0 -0
  161. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/classifier.py +0 -0
  162. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/collector.py +0 -0
  163. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/models.py +0 -0
  164. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/probes/__init__.py +0 -0
  165. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/probes/base.py +0 -0
  166. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/probes/gdb.py +0 -0
  167. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/probes/lldb.py +0 -0
  168. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/rust_concurrency/probes/lldb_script.py +0 -0
  169. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/server/__init__.py +0 -0
  170. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/server/app.py +0 -0
  171. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/server/event_handler.py +0 -0
  172. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/server/rpc_types.py +0 -0
  173. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/__init__.py +0 -0
  174. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/child_processes.py +0 -0
  175. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/errors.py +0 -0
  176. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/event_bus.py +0 -0
  177. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/inspect_service.py +0 -0
  178. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/messages.py +0 -0
  179. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/post_mortem_loader.py +0 -0
  180. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/recorder.py +0 -0
  181. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/statement_stepper.py +0 -0
  182. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/session/textual_handler.py +0 -0
  183. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/source_analysis.py +0 -0
  184. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/__init__.py +0 -0
  185. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/_inspection_modal.py +0 -0
  186. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/async_tasks_modal.py +0 -0
  187. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/full_contents_modal.py +0 -0
  188. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/goroutines_modal.py +0 -0
  189. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/menu_bar.py +0 -0
  190. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/processes_modal.py +0 -0
  191. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/rust_concurrency_modal.py +0 -0
  192. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/stack_view.py +0 -0
  193. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/status_bar.py +0 -0
  194. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/tdb/widgets/threads_modal.py +0 -0
  195. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  196. {textual_debugger-0.2.12 → textual_debugger-0.2.16}/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.12
3
+ Version: 0.2.16
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:
@@ -53,8 +53,12 @@ Requires-Dist: debugpy>=1.8.12
53
53
  Requires-Dist: pygments>=2.17.0
54
54
  Requires-Dist: fastapi>=0.115.0
55
55
  Requires-Dist: uvicorn>=0.30.0
56
- Requires-Dist: mcp<3.0.0,>=2.0.0
56
+ Provides-Extra: edit
57
+ Requires-Dist: textual[syntax]>=8.0.0; extra == "edit"
58
+ Provides-Extra: mcp
59
+ Requires-Dist: mcp<3.0.0,>=2.0.0; extra == "mcp"
57
60
  Provides-Extra: dev
61
+ Requires-Dist: textual-debugger[mcp]; extra == "dev"
58
62
  Requires-Dist: pytest; extra == "dev"
59
63
  Requires-Dist: pytest-cov; extra == "dev"
60
64
  Requires-Dist: pytest-asyncio; extra == "dev"
@@ -72,7 +76,7 @@ Dynamic: license-file
72
76
  a full-featured terminal-based debugger for Python and other languages
73
77
  with a Debug Adapter Protocol (DAP) implementation. In addition to Python,
74
78
  `tdb` comes with built-in support for
75
- - C and C++ (via `gdb` or `lldb-dap`)
79
+ - C, C++, Fortran (via `gdb` or `lldb-dap`)
76
80
  - Rust (via `gdb` or `lldb-dap`, selected explicitly)
77
81
  - Perl (via `perl -d`)
78
82
  - Bash (via bash's own `DEBUG` trap; bash ≥ 4.4)
@@ -98,12 +102,12 @@ MIT License. Copyright 2026 by Al Danial.
98
102
  `tdb`:
99
103
 
100
104
  - debugs multiple languages through the Debug Adapter Protocol: Python (via `debugpy`,
101
- the richest feature set), C/C++ (via `gdb -i dap` or `lldb-dap`), Rust (via `gdb` or
102
- `lldb-dap`), Perl (via `perl -d`),
103
- Bash (via bash's own `DEBUG` trap), Tcsh (via source instrumentation of a
104
- stock `tcsh`), Ruby (via the debug gem's `rdbg`), OCaml (native via
105
- `lldb-dap`/`gdb`, bytecode via ocamlearlybird), Go (via Delve's `dlv
106
- dap`), and PowerShell 7 (via PowerShell Editor Services). Most languages are
105
+ the richest feature set); C/C++ and Fortran (via `gdb -i dap` or `lldb-dap`); Rust (via `gdb` or
106
+ `lldb-dap`); Perl (via `perl -d`);
107
+ Bash (via bash's own `DEBUG` trap); Tcsh (via source instrumentation of a
108
+ stock `tcsh`); Ruby (via the debug gem's `rdbg`); OCaml (native via
109
+ `lldb-dap`/`gdb`, bytecode via ocamlearlybird); Go (via Delve's `dlv
110
+ dap`); and PowerShell 7 (via PowerShell Editor Services). Most languages are
107
111
  auto-detected from the target; Rust intentionally requires `--lang rust`
108
112
  (ref. [Multi-Language Debugging](#multi-language-debugging)).
109
113
 
@@ -123,7 +127,11 @@ automated, headless debugging workflows and AI-assisted debugging
123
127
  built with `textual`, `prompt-toolkit`, `urwid`, `curses`, `rich`, and so on
124
128
 
125
129
  - comes with a post-mortem exception hook that can be installed in Python programs
126
- to have `tdb` pop open automatically at the first uncaught exception
130
+ to have `tdb` pop open automatically at the first uncaught exception, and a live
131
+ breakpoint hook (`tdb.breakpoint()` in Python, `Devel::TdbRemote::breakpoint()`
132
+ in Perl, `Tdb.breakpoint` in Ruby, `tdb.Breakpoint()` in Go, `tdb_breakpoint()`
133
+ in C/C++ and Fortran, `tdb::breakpoint()` in Rust, `Tdb.breakpoint ()` in OCaml) that opens
134
+ `tdb` paused at a line of your choosing
127
135
 
128
136
  - can be entirely keyboard-driven
129
137
  making it suitable for operation in non-graphical environments (mouse support is
@@ -161,6 +169,14 @@ or run it without installing:
161
169
  uvx --from textual-debugger tdb my_program.py
162
170
  ```
163
171
 
172
+ The MCP server (`tdb-mcp`, `tdb --mcp`) is an optional extra because its
173
+ `mcp` dependency requires the `cryptography` package, which cannot be
174
+ installed on every platform. Add it with:
175
+
176
+ ```bash
177
+ pip install "textual-debugger[mcp]"
178
+ ```
179
+
164
180
 
165
181
  ## Quick Start
166
182
 
@@ -168,11 +184,25 @@ uvx --from textual-debugger tdb my_program.py
168
184
  # show comprehensive documentation in a terminal-based Markdown viewer
169
185
  tdb --doc
170
186
 
171
- # show where tdb is installed, where its bundled Perl PadWalker module
172
- # lives, and the Help > About text
187
+ # show the Help > About text, where tdb and its config, breakpoints and
188
+ # log files live, and the path + version of each interpreter and native
189
+ # debugger it would use (python, gdb, lldb-dap, perl, ruby, dlv,
190
+ # ocamlearlybird, bash, tcsh, pwsh), whether gdb has its DAP interpreter
191
+ # (`gdb -i dap`), whether perl has ExtUtils::MakeMaker (needed to
192
+ # build the bundled PadWalker), and the `examples dir` holding the
193
+ # per-language examples (C, C++, Fortran, Go, OCaml, Perl, PowerShell,
194
+ # Python, Ruby, Rust, bash, tcsh) installed with the package
173
195
  tdb --info
174
196
 
175
- # debug a script (stops at first line by default)
197
+ # reset config.json to defaults (the old file is kept as
198
+ # config.json-YYYY-mm-DD-HH:MM:SS; HH.MM.SS on Windows)
199
+ tdb --config write-default
200
+
201
+ # build and cache the bundled PadWalker Perl module now rather than
202
+ # on the first Perl launch
203
+ tdb --config install-padwalker
204
+
205
+ # debug a Python program (stops at first line by default)
176
206
  tdb my_program.py
177
207
 
178
208
  # debug with arguments
@@ -207,7 +237,7 @@ tdb --python /path/to/venv/bin/python my_program.py
207
237
  # step into, or stop at tracebacks in library code
208
238
  tdb --no-just-my-code --python /path/to/venv/bin/python my_program.py
209
239
 
210
- # run until first breakpoint or exit
240
+ # run until first breakpoint (persisted during a prior run) or exit
211
241
  tdb --no-stop-on-entry my_program.py
212
242
 
213
243
  # run the debuggee in an external terminal
@@ -232,7 +262,7 @@ tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_progra
232
262
  # attach, which can leave it suspended after tdb quits
233
263
  tdb -r remotehost:5678 --no-pause-on-attach
234
264
 
235
- # separate tdb arguments from debuggee arguments with `--`
265
+ # separate tdb arguments from debuggee arguments with `--`
236
266
  tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
237
267
  ```
238
268
 
@@ -250,7 +280,7 @@ languages are supported out of the box:
250
280
  | Language | Adapter(s) | Dependencies | Feature level |
251
281
  |----------|------------|------------------------|---------------|
252
282
  | 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 + remote attach (gdbserver/lldb-server, local symbol-bearing executable required) |
283
+ | C / C++ / Fortran (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14 built with Python (RHEL 8/9: `dnf install gcc-toolset-14-gdb`, see [C/C++/Fortran tips](#cc-tips)); `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) |
254
284
  | 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 |
255
285
  | Perl | perl-tdb (bundled) | perl ≥ 5.18 on PATH | core debugging + remote attach |
256
286
  | Bash | bash-tdb (bundled) | bash ≥ 4.4 on PATH | core debugging (no remote attach) |
@@ -271,7 +301,7 @@ The language is auto-detected from the debug target:
271
301
  `.sh` / `.bash` → Bash; `.csh` / `.tcsh` → Tcsh; `.rb` → Ruby; `.ps1` /
272
302
  `.psm1` → PowerShell. (`.ml` / `.mli` do *not* auto-select OCaml, see
273
303
  point 6.)
274
- 3. Native executables (ELF, Mach-O, PE magic bytes) → C/C++, unless
304
+ 3. Native executables (ELF, Mach-O, PE magic bytes) → C/C++/Fortran, unless
275
305
  byte-checking finds an OCaml marker (a native binary's
276
306
  `caml_program`/`caml_startup` runtime symbols, a bytecode file's
277
307
  trailing `Caml1999` marker, or a `#!...ocamlrun` shebang) → OCaml
@@ -279,13 +309,13 @@ The language is auto-detected from the debug target:
279
309
  `go version` itself locates, scanned in the first 16MB of the file) →
280
310
  Go. A stripped native OCaml binary, or a Go binary whose buildinfo
281
311
  blob sits beyond the 16MB scan window, that byte-checking can't
282
- identify falls back to C/C++; force it with `--lang ocaml` or `--lang
312
+ identify falls back to C/C++/Fortran; force it with `--lang ocaml` or `--lang
283
313
  go` respectively. Rust is never inferred from an executable; select it
284
314
  explicitly with `--lang rust`.
285
315
  4. A `#!...python`, `#!...perl`, `#!...bash`, `#!...csh`/`#!...tcsh`,
286
316
  `#!...ruby`, or `#!...pwsh` shebang → Python / Perl / Bash / Tcsh / Ruby /
287
317
  PowerShell respectively.
288
- 5. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
318
+ 5. C/C++/Fortran/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
289
319
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
290
320
  6. `.ml` / `.mli` (OCaml source) produce an error too: build the project
291
321
  first (dune's dev profile keeps debug info) and pass the **built
@@ -297,6 +327,23 @@ The language is auto-detected from the debug target:
297
327
  it (`tdb --lang cpp --adapter lldb-dap ./myprog`). Rust always requires the
298
328
  explicit language selection (`tdb --lang rust target/debug/app`).
299
329
 
330
+ `--adapter` also accepts a full path to a `gdb`, `lldb-dap`, or `dlv`
331
+ executable (`tdb --adapter /opt/llvm/bin/lldb-dap ./myprog`,
332
+ `tdb --adapter /usr/bin/gdb-multiarch ./myprog`,
333
+ `tdb --adapter ~/go/bin/dlv ./main.go`). The adapter id is taken from the
334
+ file's basename — versioned and variant names such as `lldb-dap-21` and
335
+ `gdb-multiarch` work — and that exact executable is used for the session,
336
+ ahead of anything on `$PATH` or in `config.json`'s `adapters` map.
337
+ `tdb --info` shows which `gdb` and `lldb-dap` would be used otherwise.
338
+
339
+ `gdb`'s DAP mode needs GDB ≥ 14, so `tdb` checks `gdb --version` before
340
+ starting a session. A `gdb` that is provably too old is refused with a
341
+ message naming its path and version; when that `gdb` came from `PATH`
342
+ (not from `--adapter` or `config.json`), `tdb` first looks for a newer one
343
+ installed by Red Hat's Software Collections
344
+ (`/opt/rh/gcc-toolset-*/root/usr/bin/gdb`, `/opt/rh/devtoolset-*/...`) and
345
+ uses the newest that qualifies. See [C/C++/Fortran tips](#cc-tips) for RHEL 8.
346
+
300
347
  > **Migration note:** extensionless Python scripts without a `python` shebang
301
348
  > were previously assumed to be Python; they now require `--lang python`.
302
349
 
@@ -333,15 +380,18 @@ programs by nature). Remote attach (`-r`) also works for Perl (see
333
380
  [Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), Ruby
334
381
  (see [Ruby](#ruby), `rdbg --open` in place of `debugpy.listen()`), Go (see
335
382
  [Go](#go), `dlv dap --listen` in place of `debugpy.listen()`), and for
336
- Rust and C/C++ native binaries (against a gdbserver/lldb-server stub, with
383
+ Rust and C/C++/Fortran native binaries (against a gdbserver/lldb-server stub, with
337
384
  a local symbol-bearing executable; see [Rust](#rust) — the same flags work
338
- for a C/C++ target without `--lang`), but not for Bash, Tcsh, OCaml, or
385
+ for a C/C++/Fortran target without `--lang`), but not for Bash, Tcsh, OCaml, or
339
386
  PowerShell. `--terminal` works for every launch-mode
340
- language — Python, Perl, Bash, Tcsh, Ruby, and C/C++, OCaml native, or Rust
387
+ language — Python, Perl, Bash, Tcsh, Ruby, and C/C++/Fortran, OCaml native, or Rust
341
388
  sessions via `--adapter lldb-dap` — see
342
389
  [External Terminal Support](#external-terminal-support). Go and PowerShell do
343
390
  not support `--terminal` yet (`dlv dap` and PSES have no terminal-routing
344
- mode).
391
+ mode). Typing into the Console view to feed the program's stdin works for
392
+ Python, Perl, Bash, Tcsh, Ruby, and PowerShell, but not for C/C++/Fortran, Rust,
393
+ OCaml, or Go, whose adapters spawn the debuggee themselves — see
394
+ [Console Output and Input](#console-output-and-input).
345
395
 
346
396
  **Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
347
397
  has a smaller feature set than Python:
@@ -407,10 +457,16 @@ picture:
407
457
  backtrace instead.
408
458
  - No Windows support, no remote attach, and a stripped native binary that
409
459
  byte-checking can't identify needs `--lang ocaml`.
460
+ - No stdin forwarding from the Console view (both adapters spawn the
461
+ program themselves); a program that reads stdin needs `--terminal`
462
+ (native, `--adapter lldb-dap`) or input from a file.
410
463
 
411
464
  **Go limitations (v1):**
412
465
 
413
466
  - `--terminal` is not supported (`dlv dap` has no terminal-routing mode).
467
+ - No stdin forwarding from the Console view: `dlv dap` spawns the program
468
+ itself, so a program that reads stdin must get it from a file or pipe on
469
+ its command line.
414
470
  - The Goroutines workspace's wait graph shows channel-send/-receive,
415
471
  mutex, and `WaitGroup` edges; a goroutine parked in a `select` is
416
472
  classified but contributes no wait edge (Delve can't tell which of the
@@ -424,25 +480,56 @@ picture:
424
480
  multiprocessing child attach.
425
481
  - Detection of a Go binary via buildinfo check scans only the first
426
482
  16MB of the file; a huge binary whose buildinfo blob sits beyond that
427
- falls back to C/C++ — force it with `--lang go`.
483
+ falls back to C/C++/Fortran — force it with `--lang go`.
428
484
  - `-a`/`--attach`'s language auto-detection (reading the target pid's
429
485
  buildinfo from `/proc/PID/exe`) is Linux-only; on macOS/Windows pass
430
486
  `--lang go` alongside `-a`.
431
487
 
432
488
  See [Go](#go) below for launch details.
433
489
 
434
- ### C/C++ tips
490
+ ### C/C++/Fortran tips
435
491
 
436
492
  - Compile with `-g` (ideally `-g -O0` or `-g3 -O0`).
437
493
  If no breakpoint in a file can be
438
494
  bound, `tdb` prints a console warning suggesting the program may lack
439
495
  debug info.
496
+ - The Console view's stdin line is not available: `gdb -i dap` and
497
+ `lldb-dap` spawn the program themselves, so `tdb` has no handle on its
498
+ stdin. A program that reads stdin needs `--terminal` (with
499
+ `--adapter lldb-dap`) or its input from a file.
440
500
  - Stack frames pointing into system libraries often have no source on disk;
441
501
  the Code View shows a `<Could not read …>` placeholder while the stack,
442
502
  variables, and evaluate console remain fully usable.
503
+ - For compiled executables, the Code View shows shows a brief note while
504
+ it attempts to load the associated source file. The note will identify
505
+ the failure reason if the source cannot be loaded (executable was built
506
+ without `-g`, or the adapter is not working, e.g. a GDB too old for DAP).
443
507
  - GDB (the default adapter) has the most complete libstdc++
444
508
  pretty-printing. `lldb-dap` (via `--adapter lldb-dap`) also debugs
445
509
  GCC-built binaries fine. DWARF is compiler-neutral.
510
+ - **STL containers in the Variables view.** A distro `gdb` shows
511
+ `std::vector<Result>` as `std::vector of length 2, capacity 2` with
512
+ `[0]`, `[1]` children because gcc installs libstdc++'s pretty-printers
513
+ next to it and `gdb` auto-loads them. A `gdb` built from source into
514
+ its own prefix never finds that auto-load script and shows the same
515
+ variable as `_M_impl` / `_M_start` pointer soup. `tdb` sources a small
516
+ helper into every `gdb` session that, on the first `std::` value it
517
+ sees, looks for gcc's printers (the distro auto-load script for the
518
+ loaded `libstdc++.so`, `share/gcc*/python` near that library or near
519
+ gdb's data directory, then the usual distro, Red Hat toolset, Homebrew
520
+ and MacPorts locations) and uses them; when none are installed it
521
+ falls back to its own bundled printers for `std::string`, `vector`,
522
+ `deque`, `list`, `map`/`set` (and multi-), `unordered_map`/`set`,
523
+ `unique_ptr`, `shared_ptr`/`weak_ptr` and `optional`. Printers gdb
524
+ auto-loads itself or that your `.gdbinit` registers still take
525
+ precedence. The helper also shields gdb's DAP layer from gcc 15's map
526
+ and set printers, whose child count is broken there (expanding a
527
+ `std::map` fails with "name 'self' is not defined" on an unpatched
528
+ distro gdb). `TDB_GDB_STL_PRINTERS` tunes it: `off` registers nothing,
529
+ `bundled` forces the bundled printers (and drops gdb's auto-loaded
530
+ ones), a directory holding `libstdcxx/` names gcc's printers
531
+ explicitly. Type `tdb-stl-printers` in the evaluate console to see
532
+ which printers are in use.
446
533
  - **GDB evaluate-console quirk:** GDB's DAP treats REPL input as CLI
447
534
  commands, so evaluate expressions with an explicit `print`, e.g.
448
535
  `print x` rather than bare `x` (bare `x` collides with GDB's
@@ -450,6 +537,107 @@ See [Go](#go) below for launch details.
450
537
  - `--terminal` (see [External Terminal Support](#external-terminal-support))
451
538
  requires `--adapter lldb-dap`; GDB's DAP mode has no terminal integration
452
539
  and `tdb` refuses `--terminal` with the default `gdb` adapter.
540
+ - **GDB source-location quirk:** some GDB builds (RHEL 8 among them)
541
+ report no source file for a stopped frame -- `info source` answers
542
+ "No current source file" -- until a `list` command has selected the
543
+ default source file. `tdb` therefore sends `list` once, before its
544
+ first stack query, so the Code View can find the source. This is why a
545
+ fresh gdb session shows `Current source file is …` in `info source`
546
+ from the evaluate console without you having typed `list`.
547
+ - **RHEL 8 (and 9) gdb is too old for DAP:** the stock `/usr/bin/gdb` is
548
+ 8.2 on RHEL 8 (10.2 on RHEL 9) and answers `gdb -i dap` with
549
+ ``Interpreter `dap' unrecognized``. Install a newer one
550
+ (minimum GCC Toolset version is 14) from the
551
+ GCC Toolset, which leaves `/usr/bin/gdb` alone:
552
+
553
+ ```bash
554
+ sudo dnf install gcc-toolset-14-gdb # gdb 14.2 under /opt/rh/gcc-toolset-14
555
+ ```
556
+
557
+ `tdb` then finds `/opt/rh/gcc-toolset-14/root/usr/bin/gdb` by itself
558
+ whenever the `gdb` on `PATH` is missing or too old — no `scl enable`
559
+ needed (`tdb --info` shows it as `gdb used instead`). Two things pin
560
+ the old one and must be changed by hand: `--adapter /usr/bin/gdb`, and
561
+ an `"adapters": {"gdb": "/usr/bin/gdb"}` entry in `config.json` (`tdb`
562
+ seeds that entry with whatever `gdb` was on `PATH` the first time it
563
+ wrote the file). Either delete the entry or point it at the toolset
564
+ gdb. A GDB ≥ 14 that was built without Python support gives the same
565
+ ``Interpreter `dap' unrecognized`` error (`gdb --configuration` shows
566
+ `--without-python`); `tdb --info` probes for this and reports
567
+ `GDB LACKS DAP CAPABILITY AND CANNOT BE USED` in its `DAP interpreter`
568
+ row when the gdb it found rejects `-i dap`. Use `--adapter lldb-dap`
569
+ or a gdb built with
570
+ Python.
571
+
572
+ **Attach to a running process:** `tdb -a PID` attaches gdb (or `--adapter
573
+ lldb-dap`) to a live native process and stops it. On Linux tdb reads
574
+ `/proc/PID/exe` to identify the language (Go, OCaml, Rust, otherwise
575
+ C/C++/Fortran) and to load symbols; pass the executable path as well to use a
576
+ different symbol file, and pass `--lang` off Linux. `--no-pause-on-attach`
577
+ leaves the process running, for a program about to stop itself. An
578
+ interpreter process (python3, perl, ruby, bash) is attached as C/C++/Fortran; for
579
+ Python, Perl, and Ruby programs use `--remote-attach` instead.
580
+
581
+ **Live breakpoint hook:** the C/C++/Fortran counterpart of Python's
582
+ [`tdb.breakpoint()`](#live-breakpoint-hook). Include `tdb.h` (its directory
583
+ is the `tdb.h dir` row of `tdb --info`) and call `tdb_breakpoint()`:
584
+
585
+ ```c
586
+ #include "tdb.h"
587
+
588
+ int compute(int n) {
589
+ int total = 0;
590
+ for (int i = 0; i < n; i++) total += i;
591
+ tdb_breakpoint(); /* tdb opens here, paused on the next line */
592
+ return total;
593
+ }
594
+ ```
595
+
596
+ ```bash
597
+ gcc -g -O0 -I "$(tdb --info | awk -F': ' '/tdb.h dir/ {print $2}')" -o prog prog.c
598
+ ./prog # run it directly, not under tdb
599
+ ```
600
+
601
+ When the call is reached, the program starts `tdb --lang cpp -a <its pid>
602
+ --no-pause-on-attach` on its own terminal (the `tdb` on `PATH`, or the one
603
+ named by `$TDB`), waits for tdb's gdb or lldb to attach, and stops in the
604
+ hook; tdb steps out so the stop lands on the line after the call, in your
605
+ own frame. Later calls reuse the running tdb. Quitting tdb (`Ctrl+q`)
606
+ detaches and the program runs on; the next call opens a fresh tdb. The
607
+ call is a no-op when stdin or stdout is not a terminal, and it warns and
608
+ continues when `tdb` cannot be found, exits before attaching, or cannot
609
+ attach within 60 s (Yama `ptrace_scope` 2 or 3, or a container without
610
+ `SYS_PTRACE`, blocks attaching). If a tdb whose attach failed is still
611
+ running, later calls warn once and return rather than open another tdb
612
+ over it. Build with `-g -O0` so locals stay
613
+ inspectable; a stripped binary attaches but never stops, because the hook's
614
+ stop symbol cannot be resolved. The header is C99 and C++ compatible and
615
+ header-only. Linux only: the hook grants ptrace access to tdb's debugger
616
+ with `PR_SET_PTRACER`; elsewhere it warns once and returns. See
617
+ `examples/C/breakpoint_hook_demo.c` and `examples/C++/breakpoint_hook_demo.cpp`.
618
+
619
+ Fortran (or any language that can call C) uses the same header: compile
620
+ `tdb.h` as C into an object that defines `tdb_breakpoint()`, declare it with
621
+ `bind(C)`, and link the object in:
622
+
623
+ ```fortran
624
+ interface
625
+ subroutine tdb_breakpoint() bind(C, name="tdb_breakpoint")
626
+ end subroutine tdb_breakpoint
627
+ end interface
628
+ ...
629
+ call tdb_breakpoint() ! tdb opens here, paused on the next line
630
+ ```
631
+
632
+ ```bash
633
+ gcc -g -O0 -c -x c "$(tdb --info | awk -F': ' '/tdb.h dir/ {print $2}')/tdb.h" -o tdb.o
634
+ gfortran -g -O0 -o prog prog.f90 tdb.o
635
+ ./prog # run it directly, not under tdb
636
+ ```
637
+
638
+ Use gdb (the default) for Fortran: lldb-dap stops on the right line but,
639
+ lacking Fortran support, shows no Fortran locals. See
640
+ `examples/Fortran/breakpoint_hook_demo.f90`.
453
641
 
454
642
  ### Rust
455
643
 
@@ -471,7 +659,9 @@ cargo rustc -- -C debuginfo=2 -C opt-level=0
471
659
  # Equivalent direct rustc settings: rustc -C debuginfo=2 -C opt-level=0 src/main.rs
472
660
  ```
473
661
 
474
- A panic that terminates the debuggee opens the error modal with the parsed
662
+ As for C/C++/Fortran, the Console view cannot feed the program's stdin (the gdb and
663
+ lldb adapters spawn it themselves); use `--terminal` with `--adapter
664
+ lldb-dap` for interactive programs. A panic that terminates the debuggee opens the error modal with the parsed
475
665
  panic message and backtrace; `tdb` injects `RUST_BACKTRACE=1` into launched
476
666
  programs (your own value wins if you set one) so the frames are present.
477
667
 
@@ -502,6 +692,50 @@ When source paths differ, pair `--local-root` with `--remote-root`. Do not
502
692
  expose the remote debug port to an untrusted network: prefer an SSH tunnel,
503
693
  for example `ssh -L 2345:127.0.0.1:2345 host`, then attach to `127.0.0.1:2345`.
504
694
 
695
+ **Attach to a running process:** `tdb --lang rust -a PID` attaches gdb (or
696
+ `--adapter lldb-dap`) to a live Rust process and stops it; tdb loads
697
+ symbols from `/proc/PID/exe`, or from an executable path you pass as well.
698
+ Without `--lang`, tdb recognizes a non-stripped Rust binary by its runtime
699
+ symbols; a stripped one is debugged as C/C++/Fortran.
700
+
701
+ **Live breakpoint hook:** the Rust counterpart of Python's
702
+ [`tdb.breakpoint()`](#live-breakpoint-hook). Depend on the `tdb` crate from
703
+ this repository (it has no dependencies) and call `tdb::breakpoint()`:
704
+
705
+ ```toml
706
+ [dependencies]
707
+ tdb = { git = "https://github.com/AlDanial/tdb" } # or path = ".../rust/tdb"
708
+ ```
709
+
710
+ ```rust
711
+ fn compute(n: u64) -> u64 {
712
+ let total: u64 = (0..n).sum();
713
+ tdb::breakpoint(); // tdb opens here
714
+ total
715
+ }
716
+ ```
717
+
718
+ ```bash
719
+ cargo build && ./target/debug/prog # dev profile; run it directly, not under tdb
720
+ ```
721
+
722
+ When the call is reached, the program starts `tdb --lang rust -a <its pid>
723
+ --no-pause-on-attach` on its own terminal (the `tdb` on `PATH`, or the one
724
+ named by `$TDB`), waits for tdb's gdb or lldb to attach, and stops in the
725
+ hook; tdb steps out so the stop lands in your own frame on, or just after,
726
+ the call line, as the compiler's line table attributes the return address.
727
+ Later calls reuse the running tdb. Quitting tdb (`Ctrl+q`)
728
+ detaches and the program runs on; the next call opens a fresh tdb. The
729
+ call is a no-op when stdin or stdout is not a terminal, and it warns and
730
+ continues when `tdb` cannot be found, exits before attaching, or cannot
731
+ attach within 60 s (Yama `ptrace_scope` 2 or 3, or a container without
732
+ `SYS_PTRACE`, blocks attaching). If a tdb whose attach failed is still
733
+ running, later calls warn once and return rather than open another tdb
734
+ over it. Use the dev profile so locals stay
735
+ inspectable. Linux only: the hook grants ptrace access to tdb's debugger
736
+ with `PR_SET_PTRACER`; elsewhere it warns once and returns. See
737
+ `examples/Rust/breakpoint_hook_demo/`.
738
+
505
739
  In a Rust session the Threads action (`Alt+T`, or the Threads menu entry)
506
740
  opens the Rust Concurrency workspace while the debuggee is stopped — a
507
741
  fresh, bounded snapshot of threads, wait edges, and findings. The graph is
@@ -534,12 +768,20 @@ per interpreter under its config directory (`~/.config/tdb/padwalker/` on
534
768
  Linux/macOS, `%APPDATA%\\tdb\\padwalker\\` on Windows), and prepends that
535
769
  directory to the debuggee's `PERL5LIB`. The build takes about a second and
536
770
  needs perl's headers (`libperl-dev` on Debian/Ubuntu, `perl-devel` on
537
- Fedora/RHEL) plus a C compiler; Strawberry Perl on Windows ships both. When
538
- the build is not possible, `tdb` prints a one-line notice on the console and
539
- falls back to a read-only pad walk, so outer-frame lexicals degrade but
540
- debugging otherwise proceeds. `tdb --info` reports the source directory, the
541
- cache directory, and whether a build exists for the `perl` on your `PATH`.
771
+ Fedora/RHEL), a C compiler, and the core `ExtUtils::MakeMaker` module (some
772
+ minimal distro perls omit it; `perl-ExtUtils-MakeMaker` on Fedora/RHEL);
773
+ Strawberry Perl on Windows ships all three. When the build is not possible,
774
+ `tdb` prints a one-line notice on the console and falls back to a read-only
775
+ pad walk, so outer-frame lexicals degrade but debugging otherwise proceeds.
776
+ `tdb --info` reports the source directory, the cache directory, and the
777
+ `ExtUtils::MakeMaker` version of the `perl` it would use, with a warning when
778
+ that module is missing and PadWalker therefore cannot be built.
542
779
  `TDB_PADWALKER_CACHE` overrides the cache location.
780
+ To build it ahead of time (for example when provisioning a machine, or to
781
+ see the compiler's error output without starting a debug session), run
782
+ `tdb --config install-padwalker`; it exits 0 when PadWalker loads
783
+ (natively, from the cache, or freshly built) and 1 with the reason when it
784
+ cannot be built.
543
785
 
544
786
  **Launching a script:**
545
787
 
@@ -637,11 +879,48 @@ export PERL5LIB=/opt/tdb-perl:$PERL5LIB
637
879
  the two files in the same relative layout shown above; `helpers.pl` is a
638
880
  sibling of the `Devel/` directory, not inside it.)
639
881
 
640
- **Pause is unavailable in attach mode.** Launch-mode sessions (`tdb
641
- script.pl`) support pausing a running program at any time. Remote-attach
642
- sessions don't. Asynchronous pause (as Python gets via `debugpy`) needs a control channel
643
- `Devel::TdbRemote` doesn't implement yet; `pause` in attach mode returns a
644
- "not available" error instead of hanging.
882
+ **Pause in attach mode** uses a control channel: after the debug connection
883
+ is up, `tdb` opens a second connection to the same port and asks
884
+ `Devel::TdbRemote` to accept it. From then on `pause` (or the `p` key) writes a
885
+ byte on that connection, the kernel delivers `SIGIO` to the debuggee, and the
886
+ program stops at its next statement -- the same mechanism `perl5db` uses for
887
+ Ctrl-C, so a program blocked in `sleep` or `select` is interrupted too. The
888
+ second connection reuses the debug port, so an SSH tunnel that forwards that
889
+ one port carries both. Requirements: the `Devel/TdbRemote.pm` copied to the
890
+ remote host must be at least the version shipped with this feature (an older
891
+ copy still attaches fine, but `pause` returns a "not available" error and the
892
+ Console view says why), and the remote perl must support `O_ASYNC` (Linux,
893
+ macOS, BSD; not Windows).
894
+
895
+ **Live breakpoint hook:** the Perl counterpart of Python's
896
+ [`tdb.breakpoint()`](#live-breakpoint-hook). Run your program normally (not
897
+ under `tdb`) and have it open `tdb` on itself when it reaches a line of
898
+ interest:
899
+
900
+ ```perl
901
+ use Devel::TdbRemote; # first line of your program
902
+ ...
903
+ Devel::TdbRemote::breakpoint(); # tdb opens here, paused on the next line
904
+ ```
905
+
906
+ On the first call the program listens on an ephemeral loopback port, spawns
907
+ `tdb --lang perl -r 127.0.0.1:PORT` on the same terminal, waits for it to
908
+ attach, and stops on the statement after the call, in your own frame. Later
909
+ calls reuse the running `tdb`, which simply receives another stop. Quitting
910
+ `tdb` (`Ctrl+q`) detaches and lets the program run on; a `breakpoint()` reached
911
+ after that spawns a fresh `tdb`. The call is a no-op when stdin/stdout are not
912
+ a tty, so it is safe in code that sometimes runs headless, and if `tdb` cannot
913
+ be started (not on `PATH`, or it exits before attaching) it warns and the
914
+ program continues. `tdb` is found on `PATH`, or via the `TDB` environment
915
+ variable. The same arming caveat as above applies: `use Devel::TdbRemote;`
916
+ must be the first line (or use `PERL5OPT=-d:TdbRemote`), and `Devel::TdbRemote`
917
+ has to be on `@INC`, e.g.
918
+
919
+ ```bash
920
+ PERL5LIB=$(tdb --info | sed -n 's/.*Devel::TdbRemote dir *: *//p') perl prog.pl
921
+ ```
922
+
923
+ See [`examples/Perl/breakpoint_hook_demo.pl`](https://github.com/AlDanial/tdb/blob/main/examples/Perl/breakpoint_hook_demo.pl).
645
924
 
646
925
  ### Bash
647
926
 
@@ -731,6 +1010,33 @@ exec` integration yet.
731
1010
  > race on launch; tdb detects a stalled/corrupted handshake and retries
732
1011
  > once automatically with a fresh `rdbg`, so this is usually invisible.
733
1012
 
1013
+ **Live breakpoint hook:** the Ruby counterpart of Python's
1014
+ [`tdb.breakpoint()`](#live-breakpoint-hook). Run your program normally (not
1015
+ under `tdb`) and have it open `tdb` on itself when it reaches a line of
1016
+ interest:
1017
+
1018
+ ```ruby
1019
+ require 'tdb'
1020
+ ...
1021
+ Tdb.breakpoint # tdb opens here, paused on the next line
1022
+ ```
1023
+
1024
+ The first call starts the debug gem's own debug server on an ephemeral
1025
+ loopback port and plants the gem's usual one-shot breakpoint on the next
1026
+ line. When that fires, the program parks and `tdb --lang ruby -r 127.0.0.1:PORT`
1027
+ is spawned on the same terminal, attaches, and shows the stop in your own
1028
+ frame. Later calls reuse the running `tdb`. Quitting `tdb` (`Ctrl+q`) detaches
1029
+ and lets the program run on; a `Tdb.breakpoint` reached after that spawns a
1030
+ fresh `tdb`. The call is a no-op when stdin/stdout are not a tty, and if `tdb`
1031
+ is not on `PATH` (or in the `TDB` environment variable) it warns and the
1032
+ program continues. Put `tdb.rb` on the load path, e.g.
1033
+
1034
+ ```bash
1035
+ RUBYLIB=$(tdb --info | sed -n 's/.*tdb.rb dir *: *//p') ruby prog.rb
1036
+ ```
1037
+
1038
+ See [`examples/Ruby/breakpoint_hook_demo.rb`](https://github.com/AlDanial/tdb/blob/main/examples/Ruby/breakpoint_hook_demo.rb).
1039
+
734
1040
  ### OCaml
735
1041
 
736
1042
  `tdb` debugs OCaml through two independent adapters, chosen automatically
@@ -754,7 +1060,7 @@ ocamlc -g -o my_program.byte "$PWD/my_program.ml"
754
1060
  ```
755
1061
 
756
1062
  Then, to ensure transition from bytecode to OCaml source in the
757
- Code view, provide an explicit temporary breakpoint (`-t`)
1063
+ Code view, provide an explicit temporary breakpoint (`-t`)
758
1064
  at the first line of execution:
759
1065
 
760
1066
  ```
@@ -775,7 +1081,7 @@ tihs line correctly.
775
1081
  usual behavior.
776
1082
 
777
1083
  A stripped native binary that byte-checking can't identify as OCaml falls
778
- back to C/C++; force it with `--lang ocaml`. Override the adapter or point
1084
+ back to C/C++/Fortran; force it with `--lang ocaml`. Override the adapter or point
779
1085
  at a non-`PATH` install in `config.json` (see
780
1086
  [Configuration](#configuration)):
781
1087
 
@@ -834,6 +1140,52 @@ sessions can't `pause` a running program (so `--run` is unavailable for
834
1140
  bytecode. Native sessions support `--run` normally); `Unix.fork` and Eio
835
1141
  fibers are out of scope (only OCaml 5 domains are presented as threads).
836
1142
 
1143
+ **Attach to a running process:** `tdb -a PID` attaches lldb-dap (or
1144
+ `--adapter gdb`) to a live native OCaml process and stops it; tdb
1145
+ recognizes the OCaml runtime in `/proc/PID/exe` and always picks a native
1146
+ adapter (bytecode programs cannot be attached to). `--no-pause-on-attach`
1147
+ leaves the process running, for a program about to stop itself.
1148
+
1149
+ **Live breakpoint hook:** the OCaml counterpart of Python's
1150
+ [`tdb.breakpoint()`](#live-breakpoint-hook), for native code. Vendor the
1151
+ `ocaml/tdb` directory from this repository (or `opam pin` it), add
1152
+ `(libraries tdb)` to your executable's dune stanza, and call
1153
+ `Tdb.breakpoint ()`:
1154
+
1155
+ ```ocaml
1156
+ let compute n =
1157
+ let total = ref 0 in
1158
+ for i = 0 to n - 1 do total := !total + i done;
1159
+ Tdb.breakpoint (); (* tdb opens paused on this line *)
1160
+ !total
1161
+ ```
1162
+
1163
+ ```bash
1164
+ dune build && ./_build/default/prog.exe # dev profile; run it directly, not under tdb
1165
+ ```
1166
+
1167
+ Without dune: copy `tdb.ml`, `tdb.mli`, `tdb_stubs.c`, and `tdb.h` from
1168
+ `ocaml/tdb` next to your source and build with
1169
+ `ocamlopt -g -o prog tdb_stubs.c tdb.mli tdb.ml prog.ml`.
1170
+
1171
+ When the call is reached, the program starts `tdb --lang ocaml -a <its pid>
1172
+ --no-pause-on-attach` on its own terminal (the `tdb` on `PATH`, or the one
1173
+ named by `$TDB`), waits for tdb's lldb or gdb to attach, and stops in the
1174
+ hook, and tdb steps out of the hook's frames into your own. Unlike the
1175
+ other languages' hooks, the stop lands on the `Tdb.breakpoint ()` call
1176
+ line itself rather than the line after: both lldb and gdb map the hook's
1177
+ return address back to the call. ocamlopt also emits no debug info
1178
+ for locals, so the Variables view shows none for OCaml frames (globals
1179
+ and the stack are still available). Later calls reuse the running tdb.
1180
+ Quitting tdb (`Ctrl+q`) detaches and the program runs on; the next call
1181
+ opens a fresh tdb. The call is a no-op when stdin or stdout is not a
1182
+ terminal, and it warns and continues when `tdb` cannot be found, exits
1183
+ before attaching, or cannot attach within 60 s (Yama `ptrace_scope` 2 or
1184
+ 3, or a container without `SYS_PTRACE`, blocks attaching). Keep debug
1185
+ info (`-g`, dune's dev profile). Linux only: the hook grants ptrace
1186
+ access to tdb's debugger with `PR_SET_PTRACER`; elsewhere it warns once
1187
+ and returns. See `examples/OCaml/breakpoint_hook_demo.ml`.
1188
+
837
1189
  ### Go
838
1190
 
839
1191
  `tdb` debugs Go through [Delve](https://github.com/go-delve/delve)'s own
@@ -859,10 +1211,53 @@ tdb -r host:port --lang go # attach to a `dlv dap --listen` already running remo
859
1211
 
860
1212
  `tdb --test ./pkg` debugs the package's tests under Delve's `test` mode;
861
1213
  pass test-binary flags after `--`, e.g. `tdb --test ./pkg -- -run TestFoo`.
862
- `-a`/`--attach` currently supports Go only; on Linux, and only on Linux,
863
- `tdb` can identify the target as Go from `/proc/PID/exe`'s buildinfo
1214
+ `-a`/`--attach` works for Go and, through gdb/lldb-dap, for C/C++/Fortran, Rust,
1215
+ and OCaml; on Linux `tdb` identifies the language from `/proc/PID/exe`
864
1216
  without `--lang` (elsewhere pass `--lang go` alongside `-a`). Attaching
865
- stops the process immediately so you get control right away.
1217
+ stops the process immediately so you get control right away;
1218
+ `--no-pause-on-attach` leaves it running instead, for a program that is
1219
+ about to stop itself (see the live breakpoint hook below).
1220
+
1221
+ **Entry stop:** Delve's own `stopOnEntry` halts at the process entry point
1222
+ before any goroutine exists (no stack, no source). `tdb` instead runs to
1223
+ `main.main`, so the default entry stop lands on your program's first line
1224
+ with its source on screen; `--no-stop-on-entry` skips it as usual.
1225
+
1226
+ **Live breakpoint hook:** the Go counterpart of Python's
1227
+ `tdb.breakpoint()`. Import the `tdb` module from this repository and call
1228
+ `tdb.Breakpoint()` where you want the debugger to open:
1229
+
1230
+ ```go
1231
+ import "github.com/AlDanial/tdb/go/tdb"
1232
+
1233
+ func compute(n int) int {
1234
+ total := 0
1235
+ for i := 0; i < n; i++ {
1236
+ total += i
1237
+ }
1238
+ tdb.Breakpoint() // tdb opens here, paused on the next line
1239
+ return total
1240
+ }
1241
+ ```
1242
+
1243
+ ```bash
1244
+ go get github.com/AlDanial/tdb/go/tdb
1245
+ go build -gcflags=all=-N -l -o prog . && ./prog # run it directly, not under tdb
1246
+ ```
1247
+
1248
+ When the call is reached, the program starts `tdb --lang go -a <its pid>
1249
+ --no-pause-on-attach` on its own terminal (the `tdb` on `PATH`, or the one
1250
+ named by `$TDB`), waits for tdb's Delve to attach, and traps with
1251
+ `runtime.Breakpoint()`; tdb steps out of the trap so the stop lands on the
1252
+ line after the call, in your own frame. Later `tdb.Breakpoint()` calls
1253
+ reuse the running tdb. Quitting tdb (`Ctrl+q`) detaches and the program
1254
+ runs on; the next call opens a fresh tdb. The call is a no-op when stdin
1255
+ or stdout is not a terminal, and it warns and continues when `tdb`
1256
+ cannot be found or exits before attaching. Build with
1257
+ `-gcflags=all=-N -l` so locals stay inspectable. Linux only: the hook
1258
+ grants ptrace access to tdb's Delve with `PR_SET_PTRACER` (Yama
1259
+ `ptrace_scope=1`); elsewhere it warns once and returns. See
1260
+ `examples/Go/breakpoint_hook_demo/`.
866
1261
 
867
1262
  **Remote attach:** start Delve's own DAP server against your program
868
1263
  first —
@@ -964,6 +1359,12 @@ Notes:
964
1359
  blocks attributed to tdb's own `tdb_launch.ps1` are skipped, so a bare
965
1360
  `Write-Error` before the `exit` opens no modal at all.
966
1361
  - `--terminal` and remote attach are not supported for PowerShell yet.
1362
+ - `Read-Host`, `$Host.UI.ReadLine()`, and `[Console]::ReadLine()` read
1363
+ from the Console view's stdin line (pwsh is no longer started with
1364
+ `-NonInteractive`, so mandatory-parameter and `-Confirm` prompts now wait
1365
+ for your input there instead of failing). pwsh echoes the prompt and the
1366
+ answer together once the line completes, so a bare `Read-Host` prompt
1367
+ appears only after you answer it; a preceding `Write-Host` shows at once.
967
1368
  - Windows PowerShell 5.1 is not supported (pwsh 7 only). Running tdb *on*
968
1369
  Windows against pwsh is designed for but not yet verified (experimental).
969
1370
 
@@ -973,7 +1374,7 @@ Notes:
973
1374
  ┌─ Header ──────────────────────────────────────────────┐
974
1375
  ├─ Menu Bar (File / Configure / Help)───────────────────┤
975
1376
  │ │ │
976
- │ Code View │ Console View (stdout) │
1377
+ │ Code View │ Console View (stdio) │
977
1378
  │ (source + breakpoints) ├───────────────────────────┤
978
1379
  │ │ Variable View (tree) │
979
1380
  │ ├───────────────────────────┤
@@ -1009,6 +1410,13 @@ A cursor line (blue) tracks your position; the current execution line is highlig
1009
1410
  | `Ctrl+S` | Stack View |
1010
1411
  | `Ctrl+B` | Breakpoint View |
1011
1412
 
1413
+ Pressing the shortcut of the view that already has focus returns focus to
1414
+ the previous view, so e.g. `Ctrl+E` jumps from the Code View to the
1415
+ Evaluate Console and a second `Ctrl+E` jumps back.
1416
+ The shortcuts also work while typing in the Evaluate Console and the
1417
+ Console View's stdin line, where `Ctrl+E` and `Ctrl+V` take precedence over
1418
+ end-of-line and paste (`End` and the terminal's own paste still work).
1419
+
1012
1420
  **Menu-bar shortcuts (global):**
1013
1421
 
1014
1422
  `Alt+<first-letter>` opens the corresponding tab in the menu bar.
@@ -1016,6 +1424,7 @@ A cursor line (blue) tracks your position; the current execution line is highlig
1016
1424
  | Key | Menu |
1017
1425
  |-----|------|
1018
1426
  | `Alt+F` | File (open a different script to debug) |
1427
+ | `Alt+E` | Edit (Save, Revert to Disk, Discard and Exit Edit Mode, Open in $EDITOR) |
1019
1428
  | `Alt+C` | Configure (Color Theme, Keybindings, Step Mode) |
1020
1429
  | `Alt+T` | Threads |
1021
1430
  | `Alt+P` | Processes |
@@ -1029,7 +1438,7 @@ written in the same language as the one currently being debugged.
1029
1438
 
1030
1439
  **Navigation (vim-style by default):**
1031
1440
 
1032
- By default the Code View is in Debug mode. Hit `Escape` to switch to Navigate mode
1441
+ By default the Code View is in Debug mode. `Escape` cycles Debug → Navigate → Edit → Debug.
1033
1442
  In Navigate mode, you can move around the file with the following keys:
1034
1443
 
1035
1444
  | Key | Action |
@@ -1043,7 +1452,43 @@ In Navigate mode, you can move around the file with the following keys:
1043
1452
  | `n` / `N` | Next / previous search result |
1044
1453
  | `PageUp` / `PageDown` | Scroll by page |
1045
1454
 
1046
- Switch from Navigate back to Debug mode with `Escape`.
1455
+ Press `Escape` again to enter Edit mode (below), and once more to return to Debug mode.
1456
+
1457
+ **Edit mode:**
1458
+
1459
+ Press `Escape` twice from Debug mode to open the current file in an editor
1460
+ inside the Code View. The pane title shows `[Edit]`, with `*` while there
1461
+ are unsaved changes. Editing keys follow the keybinding scheme:
1462
+
1463
+ | Scheme | Editing style | Save | Leave Edit mode |
1464
+ |--------|---------------|------|-----------------|
1465
+ | `vim` | vim-lite: normal / insert modes, counts, `h j k l w b e 0 ^ $ gg G`, `x dd dw D yy p P u Ctrl+R J`, `i a I A o O`, `/ ? n N` | `:w` or `Ctrl+S` | `Esc` (from normal mode), `:q`, `:wq`, `:q!` |
1466
+ | `emacs` | `Ctrl+N/P/F/B`, `Alt+F/B`, `Ctrl+A/E`, `Ctrl+K` / `Ctrl+Y`, `Ctrl+_` undo, `Ctrl+S` / `Ctrl+R` search | `Ctrl+X Ctrl+S` | `Esc` or `Ctrl+X Ctrl+C` |
1467
+ | `default` (Notepad-style) | arrows, Home/End, PgUp/PgDn, Delete/Backspace, Shift+arrows to select, `Ctrl+Z`/`Ctrl+Y`, `Ctrl+X`/`Ctrl+C`/`Ctrl+V` | `Ctrl+S` | `Esc` |
1468
+
1469
+ Leaving Edit mode, quitting, restarting, or opening another file with
1470
+ unsaved changes prompts: `s` save, `d` discard, `Esc` keep editing.
1471
+
1472
+ Saving does not change the running program. tdb shifts your breakpoints
1473
+ to their new lines, clears the current-line marker, and (when restart is
1474
+ available) reminds you to press `R` to restart with the new code. In
1475
+ `tdb --run` sessions, which cannot restart, you will see only "Saved
1476
+ `<file>`." Edit mode is unavailable when no file is loaded, for sources
1477
+ that are not on this machine (remote attach), during replay, and in
1478
+ post-mortem mode.
1479
+
1480
+ The `Edit` menu (`Alt+E`) offers Save, Revert to Disk, Discard and Exit
1481
+ Edit Mode, and Open in $EDITOR, which suspends tdb, runs `$VISUAL` /
1482
+ `$EDITOR` (`notepad` on Windows, `vi` otherwise) on the file, and reloads
1483
+ it when the editor exits.
1484
+
1485
+ Syntax highlighting inside the editor needs tree-sitter, an optional
1486
+ extra: `uv pip install "textual-debugger[edit]"` (highlights Python, Bash,
1487
+ Go, and Rust; other languages edit as plain text). Highlighting in the
1488
+ normal Code View is unaffected.
1489
+
1490
+ CRLF source files are written back with LF line endings after a save in
1491
+ Edit mode (a v1 limitation — no CRLF round-trip yet).
1047
1492
 
1048
1493
  > **Note:** Many terminals send the byte sequence `ESC+f` for `Alt+F`, which Textual's
1049
1494
  ANSI parser rewrites to `Ctrl+Right` (the readline "forward-word" convention).
@@ -1115,6 +1560,15 @@ Set a Python expression (e.g., `x > 10`) and/or a hit count (pause on the Nth hi
1115
1560
 
1116
1561
  Breakpoints persist across session restarts.
1117
1562
 
1563
+ **Breakpoints set at the debugger's own prompt (gdb):** when debugging native code
1564
+ with `gdb`, commands typed in the Evaluate Console such as `b 83` or `delete 2`
1565
+ go straight to gdb. Switching to the Breakpoint View (`Ctrl+B`, or clicking it)
1566
+ synchronizes tdb's table with gdb's list: breakpoints created at the gdb prompt
1567
+ are adopted (with their conditions and enabled state) and become ordinary tdb
1568
+ breakpoints, and breakpoints deleted at the prompt are dropped. Watchpoints,
1569
+ catchpoints and `tbreak` temporaries are left to gdb. `lldb-dap` has no such
1570
+ prompt-level breakpoint channel, so nothing to sync there.
1571
+
1118
1572
  ### Variable Inspection
1119
1573
 
1120
1574
  The Variable View shows a tree of scopes with all variables in the current frame. The scopes
@@ -1128,6 +1582,41 @@ the Variables View and press `Enter`
1128
1582
  to display that variable in a modal. This simplifies inspection of
1129
1583
  large or deeply nested data structures.
1130
1584
 
1585
+ #### Display list
1586
+
1587
+ Like `gdb`'s `display` command, a variable (or any expression) can be
1588
+ pinned so its value is re-read at every stop. Pinned entries appear
1589
+ under a **Display** scope at the top of the Variables View, but only
1590
+ while they are defined in the current frame: step out of the function
1591
+ that owns `b` and the entry vanishes, step back into it and the entry
1592
+ returns. Two ways to pin, two ways to unpin:
1593
+
1594
+ | | Evaluate Console | Variables View |
1595
+ |---|---|---|
1596
+ | pin | `display b` | right-click the variable's row |
1597
+ | unpin | `undisplay b` | right-click its row under **Display** |
1598
+
1599
+ ```
1600
+ >>> display b
1601
+ ```
1602
+
1603
+ ```
1604
+ ▼ Display
1605
+ b = 6
1606
+ ▼ Locals
1607
+ a = 3
1608
+ b = 6
1609
+ ```
1610
+
1611
+ Right-clicking a child of an expanded object pins the full path
1612
+ (`obj.count`, `items[2]`), and a bare `display` lists everything pinned.
1613
+ Left-click works the same under **Display** as in every other scope:
1614
+ it expands or collapses an object, and a double-click opens it in a modal.
1615
+ A click in the Variables View is recorded exactly as the typed command,
1616
+ so `--record`/`--replay` sessions reproduce it. Some terminals keep
1617
+ right-click for their own paste or context menu (Windows Terminal and
1618
+ the VS Code terminal do by default); use the `display` command there.
1619
+
1131
1620
  ### Call Stack
1132
1621
 
1133
1622
  The Stack View shows the full call stack. Click a frame to navigate to its source
@@ -1155,6 +1644,45 @@ interactive evaluation of expressions in the current scope:
1155
1644
  ```
1156
1645
 
1157
1646
  Variable values set here are reflected in the running code.
1647
+ The Variables View redraws after every entry, so an assignment typed at
1648
+ the prompt shows up immediately rather than at the next stop.
1649
+
1650
+ #### Interactive variables
1651
+
1652
+ A variable *created* at the prompt is tracked and listed under an extra
1653
+ **Interactive** scope in the Variables View, alongside Locals, Globals and
1654
+ the other language-specific scopes. The value is re-read at every stop,
1655
+ so it works like a watch list of the names you introduced:
1656
+
1657
+ ```
1658
+ >>> newvar = 42
1659
+ ```
1660
+
1661
+ ```
1662
+ ▼ Locals
1663
+ a = 3
1664
+ b = 6
1665
+ ▼ Interactive
1666
+ newvar = 42
1667
+ ```
1668
+
1669
+ What counts as "created" depends on the language, and so does how long the
1670
+ variable lives:
1671
+
1672
+ | Language | Creates an interactive variable | Lifetime |
1673
+ |---|---|---|
1674
+ | Python | `name = …` | the current frame (gone once it returns) |
1675
+ | Perl | `$name = …`, `@name = …`, `%name = …`, `our …` | the whole run (`my` lexicals vanish with the eval and are not tracked) |
1676
+ | Bash | `name=…`, `local`/`declare`/`export …` | the whole run; `local` follows the function |
1677
+ | tcsh | `set name …`, `setenv NAME …` | the whole run |
1678
+ | Ruby | `name = …`, `$name = …`, `@name = …` | globals and instance variables persist; plain locals live only in the eval's binding and read back as unavailable after the next step |
1679
+ | PowerShell | `$name = …`, `$global:name = …` | the function scope, or the whole run for `$global:` |
1680
+ | C/C++/Fortran/Rust/OCaml under gdb | `set $name = …` (a gdb convenience variable) | the whole run |
1681
+ | C/C++/Fortran/Rust/OCaml under lldb-dap | `int $name = …` (an lldb persistent variable) | the whole run |
1682
+ | Go (dlv), OCaml under ocamlearlybird | not possible: their evaluate cannot create variables | — |
1683
+
1684
+ A tracked name that no longer resolves (a Python local after its frame
1685
+ returned, for example) stays in the list and shows as `<unavailable>`.
1158
1686
 
1159
1687
  ### Cut / Paste
1160
1688
 
@@ -1165,12 +1693,35 @@ capture mouse events for their own use.
1165
1693
  Instead, hold the `Shift` key while performing your conventional cut/paste keystrokes or mouse
1166
1694
  operation to get the expected behavior.
1167
1695
 
1168
- ### Console Output
1696
+ ### Console Output and Input
1169
1697
 
1170
1698
  The Console View captures stdout (normal text) and stderr (red text) from the debuggee
1171
1699
  in real time.
1172
1700
 
1173
- If your program prints a lot, or prompts for input, or uses colors or
1701
+ Programs that read from stdin (`input()`, `<STDIN>`, `read`, `gets`, …) get
1702
+ their input from the same view: while the program runs, an input line sits
1703
+ under the output log. `Ctrl+O` focuses it, `Enter` sends the line (plus a
1704
+ newline) to the program and echoes it in cyan next to the program's prompt,
1705
+ and `Ctrl+D` sends end-of-file. A bare `Enter` sends an empty line, which is
1706
+ a real answer to `input()`. The line disappears when the program exits or
1707
+ when the session has no stdin to feed.
1708
+
1709
+ Stdin forwarding works for the languages whose program `tdb` launches on a
1710
+ pipe it owns: Python, Perl, Bash, Tcsh, Ruby, and PowerShell. It is not
1711
+ available for the compiled languages — C/C++/Fortran, Rust, OCaml, and Go — because
1712
+ their adapters (`gdb -i dap`, `lldb-dap`, `dlv dap`) spawn the debuggee
1713
+ themselves and give `tdb` no handle on its stdin; the input line stays
1714
+ hidden there. It is also unavailable for remote attach (`-r`), where the
1715
+ program's stdin belongs to whoever started it, and under `--terminal`,
1716
+ where the program reads from its own terminal window. For those cases run
1717
+ the program in an external terminal with `--terminal` (launch mode only),
1718
+ or give it its input another way (a file, a pipe on the command line).
1719
+
1720
+ Every line typed here is captured by `--record` as a `stdin` gesture and
1721
+ replayed by `--replay`/`--replay-tui`, and the JSON-RPC server exposes the
1722
+ same thing as the `stdin` / `stdin_eof` actions.
1723
+
1724
+ If your program prints a lot, or uses colors or
1174
1725
  terminal control codes, run the program in an external terminal
1175
1726
  with `--terminal` for a better experience.
1176
1727
  The `--terminal` switch requires a graphical environment and a compatible
@@ -1282,6 +1833,57 @@ This behavior matches hitting `c` while in a conventional (that is, the Python
1282
1833
  standard library's) `breakpoint()` session.
1283
1834
  If you want to kill the program instead, use `Ctrl+c` in the terminal running the debuggee.
1284
1835
 
1836
+ Perl, Ruby, Go, C/C++/Fortran, Rust, and OCaml programs get the same hook as
1837
+ `Devel::TdbRemote::breakpoint()`, `Tdb.breakpoint`, `tdb.Breakpoint()`,
1838
+ `tdb_breakpoint()`, `tdb::breakpoint()`, and `Tdb.breakpoint ()`; see
1839
+ [Perl](#perl), [Ruby](#ruby), [Go](#go), [C/C++/Fortran tips](#cc-tips),
1840
+ [Rust](#rust), and [OCaml](#ocaml).
1841
+
1842
+ ### pytest Integration (`--pdbcls`)
1843
+
1844
+ `tdb` can stand in for `pdb` when `pytest` stops on a failing test, the
1845
+ same way IPython (`--pdbcls=IPython.terminal.debugger:Pdb`) and pudb
1846
+ (`--pdbcls=pudb.debugger:Debugger`) do:
1847
+
1848
+ ```bash
1849
+ pytest --pdb --pdbcls=tdb.pytest_debugger:Debugger
1850
+ ```
1851
+
1852
+ `-s` / `--capture=no` is not required; pytest suspends output capturing
1853
+ before handing control to the debugger. To make it the default, add the
1854
+ options to your pytest configuration:
1855
+
1856
+ ```toml
1857
+ [tool.pytest.ini_options]
1858
+ addopts = "--pdbcls=tdb.pytest_debugger:Debugger"
1859
+ ```
1860
+
1861
+ (`tdb.pytest_debugger:Pdb` is an alias for the same class.)
1862
+
1863
+ What you get depends on how pytest enters the debugger:
1864
+
1865
+ | pytest trigger | `tdb` behavior |
1866
+ |---|---|
1867
+ | `--pdb`, test fails or errors | Post-mortem TUI on the failing test, as with [`tdb.exception_hook`](#post-mortem-exception-hook). Navigate the stack and inspect every frame's variables at the point of failure. pytest's own runner frames are trimmed from the Stack View. Quit `tdb` (`q`, then `q` again to confirm) and pytest moves on to the next test. |
1868
+ | `breakpoint()` or `pytest.set_trace()` in a test | Live `tdb` attached to the pytest process, paused on the line after the call, as with [`tdb.breakpoint()`](#live-breakpoint-hook). Step, evaluate, set breakpoints, continue. |
1869
+ | `--trace` | Live `tdb` paused on the first line of each test. |
1870
+
1871
+ Notes:
1872
+
1873
+ - Post-mortem mode is a frozen snapshot: no Evaluate View, no stepping.
1874
+ For a live view of a failure, put a `breakpoint()` in the test (or use
1875
+ `--trace`) and `continue`. `tdb` stops on the failing line with the
1876
+ interpreter still alive.
1877
+ - One live `tdb` serves the whole pytest run. After `continue` it stays
1878
+ attached, showing **Running**, and the next `breakpoint()` or `--trace`
1879
+ stop appears in it. If a test fails under `--pdb` while it is still
1880
+ attached, the post-mortem view opens once you quit the live `tdb`.
1881
+ - Every failure opens its own post-mortem session. Combine with `-x` or
1882
+ `--maxfail=N` to limit how many you step through.
1883
+ - Live stops need `debugpy` installed in the environment running pytest.
1884
+ - When stdin/stdout aren't a terminal (CI, `pytest-xdist` workers), the
1885
+ class falls back to plain `pdb` behavior.
1886
+
1285
1887
  ### Async Task Inspector
1286
1888
 
1287
1889
  For programs using `asyncio`, the menu bar shows an **Async Tasks (N)** label with the count of
@@ -1456,8 +2058,8 @@ tdb --terminal xterm my_tui_app.py
1456
2058
  ```
1457
2059
 
1458
2060
  `--terminal` works for every language `tdb` can *launch* (as opposed to
1459
- attach to): Python, Perl, Bash, Tcsh, Ruby, and C/C++ or native OCaml
1460
- sessions via `--adapter lldb-dap`. Neither `gdb -i dap` (the default C/C++
2061
+ attach to): Python, Perl, Bash, Tcsh, Ruby, and C/C++/Fortran or native OCaml
2062
+ sessions via `--adapter lldb-dap`. Neither `gdb -i dap` (the default C/C++/Fortran
1461
2063
  adapter, and an alternate for native OCaml) nor `ocamlearlybird` (the
1462
2064
  bytecode OCaml adapter) has any terminal integration -- `tdb` rejects
1463
2065
  `--terminal` up front for both, with an error pointing at `--adapter
@@ -1470,7 +2072,8 @@ terminal around.
1470
2072
  The debuggee runs in a separate window of the specified terminal, including
1471
2073
  all of its stdin/stdout/stderr. Keyboard input, program output, and
1472
2074
  anything the program itself draws to the terminal all happen in that
1473
- window, not in `tdb`'s Console View. Supported choices:
2075
+ window, not in `tdb`'s Console View (whose stdin line is hidden in this
2076
+ mode). Supported choices:
1474
2077
  `xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
1475
2078
  `wezterm`, `terminator`. The selected terminal must be on `PATH`. Debugging
1476
2079
  (breakpoints, stepping, variable inspection, the evaluate console) proceeds
@@ -1480,39 +2083,31 @@ This feature only works in graphical environments where external terminals are a
1480
2083
 
1481
2084
  ### Run Mode (`--run`)
1482
2085
 
1483
- `tdb`'s run mode invokes an unmodified program nearly at full speed, without bringing up the TUI.
1484
- At any point afterwards, hitting `Ctrl-C` in that terminal pauses the program
1485
- and drops you into the full TUI.
1486
- From there you can inspect the program's state, navigate the stack, set and continue
1487
- to breakpoints, and so on. After you finish inspecting, hitting `q` to quit brings up
1488
- a modal that lets you either detach from the TUI and let the program resume running,
1489
- or lets you terminate it.
2086
+ `tdb`'s run mode acts as a job shepherd.
2087
+ It runs your unmodified program at nearly full speed, without bringing up the TUI,
2088
+ and listens for two interrupt signals:
1490
2089
 
1491
- The primary use cases for run mode are: (1) allowing inspection of a program that
1492
- appears hung, and (2) finding where a program execution path lands after it
1493
- receives a problematic input.
2090
+ - `Ctrl-C` pauses the program and brings up TUI, placing you at the currently
2091
+ active line of code. From there you can debug interactively as usual. When
2092
+ you quit the TUI, you can either detach and let the program continue running, or terminate it.
2093
+
2094
+ - `Ctrl-\` temporarily pauses the program and prints a JSON-formatted stack trace to stdout
2095
+ or a log file, then resumes the program.
1494
2096
 
1495
2097
  ```bash
1496
2098
  tdb --run my_program.py args...
1497
2099
  ```
1498
2100
 
1499
- The debuggee's stdout/stderr stream straight to the terminal. If the program exits on
2101
+ The debuggee's stdout/stderr stream straight to the terminal, and for the
2102
+ languages with stdin forwarding (see
2103
+ [Console Output and Input](#console-output-and-input)) the program reads
2104
+ its stdin from that terminal too. If the program exits on
1500
2105
  its own, `tdb` exits with the same code:
1501
2106
 
1502
2107
  ```bash
1503
2108
  tdb --run my_program.py; echo $? # my_program.py's own exit code
1504
2109
  ```
1505
2110
 
1506
- **Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
1507
- `SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
1508
- the debuggee at its currently executing line and opens the TUI with full debugging
1509
- available (breakpoints, stepping, variable inspection, the evaluate console). The
1510
- debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
1511
- group), so its own `SIGINT` handling is undisturbed.
1512
-
1513
- **Examining without opening the TUI:** press `Ctrl-\` in the terminal (`Ctrl-Break` on
1514
- Windows), or, on Unix, send `SIGUSR2` to `tdb`'s pid: `kill -USR2 <tdb pid>`.
1515
-
1516
2111
  **Windows caveat:** the Ctrl-Break trigger is untested. Unlike Ctrl-C, a console
1517
2112
  Ctrl-Break is delivered to every process attached to the console, including the debug
1518
2113
  adapter and the program itself, so it may terminate the program instead of snapshotting
@@ -1572,7 +2167,7 @@ nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb
1572
2167
  the TUI at the point of failure instead of exiting silently, so you can inspect the
1573
2168
  crash. Exit-code passthrough applies only to a clean exit during the headless phase.
1574
2169
 
1575
- Supported languages: Python, Perl, Bash, Tcsh, Ruby, PowerShell 7, C/C++
2170
+ Supported languages: Python, Perl, Bash, Tcsh, Ruby, PowerShell 7, C/C++/Fortran
1576
2171
  (both `gdb` and `lldb-dap`), and native OCaml (both `gdb` and `lldb-dap`;
1577
2172
  bytecode/`ocamlearlybird` sessions don't support pause, so `--run` is
1578
2173
  unavailable for those). `--run` is rejected up front for any other
@@ -1582,14 +2177,30 @@ language.
1582
2177
  blocking external call or syscall, the pause can't land until that call returns --
1583
2178
  `tdb` prints a notice and opens the TUI once the stop actually arrives.
1584
2179
 
1585
- **Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--replay-tui`, `--server`,
2180
+ **Breakpoints:** run mode never uses breakpoints saved in `breakpoints.json`, and
2181
+ never writes to it either, so the breakpoints you saved for normal sessions are left
2182
+ untouched. Breakpoints given on the command line with `-k`/`-t` are accepted, but they
2183
+ stay inactive while the program runs headlessly. They are installed the first time the
2184
+ TUI opens (normally when you press Ctrl-C), and stay live after you detach, so the
2185
+ program then stops at them:
2186
+
2187
+ ```bash
2188
+ tdb --run -k 120 -k utils.py:45 my_program.py # breakpoints arm on the first Ctrl-C
2189
+ ```
2190
+
2191
+ **Incompatible flags:** `-r`, `--record`, `--replay`, `--replay-tui`, `--server`,
1586
2192
  `--headless`, `--mcp`, `--terminal`.
1587
2193
 
1588
2194
  ### Eval Mode (`-e`/`--eval`)
1589
2195
 
1590
- Eval mode automates a common debugging loop--set a breakpoint, run to it,
1591
- evaluate an expression in the Evaluate console, continue--without
1592
- opening the TUI. `-e` takes two arguments, a location and an expression:
2196
+ Eval mode is essentially a code injection mechanism. Rather than injecting
2197
+ native code though, only expressions that are valid in the Evaluate View
2198
+ can be injected (in Python, native expressions are processed correctly
2199
+ in the Evaluate View).
2200
+
2201
+ In this way, Eval mode automates the common debugging action of setting a
2202
+ breakpoint, running to it, evaluating (and printing the result of) an expression, then
2203
+ resuming execution, all without opening the TUI. `-e` takes two arguments, a location and an expression:
1593
2204
 
1594
2205
  ```bash
1595
2206
  tdb --eval examples/digits_of_pi.py:25 "print(f'{nr=}')" examples/digits_of_pi.py
@@ -1609,7 +2220,7 @@ evaluated in each child process that reaches it.
1609
2220
  - `-e` may be repeated to plant several eval points in one run.
1610
2221
  - The expression is evaluated through the debug adapter (DAP `evaluate`,
1611
2222
  context `repl`), just like the Evaluate console: side effects are real
1612
- and persist in the running program, for example
2223
+ and persist in the running program, for example
1613
2224
  `-e my_program.py:25 "limit = 10"` changes `limit` from line 25 onward.
1614
2225
  - A bare expression's result is printed to stdout; statement-style
1615
2226
  expressions (`print(...)`, assignments) print nothing beyond their own
@@ -1619,7 +2230,7 @@ evaluated in each child process that reaches it.
1619
2230
  uncaught-exception stop) is continued untouched, so the program runs to
1620
2231
  completion unattended.
1621
2232
  - For non-Python debuggees the expression must be in the debug adapter's
1622
- own syntax, for example gdb's `print x` / `set var x=3` for C/C++, and not
2233
+ own syntax, for example gdb's `print x` / `set var x=3` for C/C++/Fortran, and not
1623
2234
  necessarily the debuggee's language. The adapter may bind the breakpoint
1624
2235
  to the next executable line (gdb moves one off a declaration or brace);
1625
2236
  the eval point matches that bound line, so the expression still runs.
@@ -1632,7 +2243,7 @@ evaluated in each child process that reaches it.
1632
2243
  ```bash
1633
2244
  tdb --keybindings vim my_program.py # default
1634
2245
  tdb --keybindings emacs my_program.py
1635
- tdb --keybindings default my_program.py
2246
+ tdb --keybindings default my_program.py # Notepad-style editing
1636
2247
  ```
1637
2248
 
1638
2249
  The keybinding choice is saved to `~/.config/tdb/config.json` and remembered for subsequent
@@ -1714,6 +2325,8 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
1714
2325
  | `stack_down` | `[]` | Move down the call stack |
1715
2326
  | `get_stack_trace` | `[]` | Full call stack |
1716
2327
  | `get_output` | `[]` | Drain buffered stdout/stderr |
2328
+ | `stdin` | `["line"]` | Send a line (newline appended) to the program's stdin; errors when the session has no stdin (compiled languages, `--terminal`, remote attach) |
2329
+ | `stdin_eof` | `[]` | Close the program's stdin (Ctrl+D) |
1717
2330
  | `get_source` | `["file_path"]` | Read a source file |
1718
2331
  | `list_threads` | `[]` | List all threads |
1719
2332
  | `inspect_thread` | `[thread_id]` | Inspect a specific thread |
@@ -1741,7 +2354,8 @@ Each is JSON with `event`, `data`, and `timestamp` fields.
1741
2354
 
1742
2355
  `tdb --record session.jsonl prog.py` runs a normal TUI session and captures
1743
2356
  your debugging actions including breakpoints (including `-k`/`-t` and persisted
1744
- ones), stepping, continue/pause, Evaluate-console entries, stack-frame
2357
+ ones), stepping, continue/pause, Evaluate-console entries, lines typed into
2358
+ the Console view for the program's stdin (and Ctrl+D), stack-frame
1745
2359
  navigation, variable expansion, restart, quit. The session is
1746
2360
  written to `session.jsonl` as
1747
2361
  JSON-RPC commands. Works with launch mode (any language) and `-r`
@@ -1803,6 +2417,16 @@ its companion `examples/sales_report_buggy.py`.
1803
2417
 
1804
2418
  ### Running the MCP server
1805
2419
 
2420
+ The MCP server needs the optional `mcp` extra:
2421
+
2422
+ ```bash
2423
+ pip install "textual-debugger[mcp]"
2424
+ ```
2425
+
2426
+ Without it, `tdb-mcp` and `tdb --mcp` exit with that install hint, and
2427
+ `tdb --info` reports `MCP server : not installed`. Everything else in
2428
+ tdb works without the extra.
2429
+
1806
2430
  Configure your MCP client (Claude Desktop, an IDE extension, etc.) to
1807
2431
  launch `tdb-mcp` over stdio. Example `claude_desktop_config.json`:
1808
2432
 
@@ -1888,12 +2512,14 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
1888
2512
  [--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
1889
2513
  [--local-root PATH] [--remote-root PATH]
1890
2514
  [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text] [--info]
2515
+ [--config {write-default,install-padwalker}]
1891
2516
  [program] [args ...]
1892
2517
  ```
1893
2518
 
1894
2519
  | Flag | Description |
1895
2520
  |------|-------------|
1896
2521
  | `-r HOST:PORT` | Attach to a remote debugpy server |
2522
+ | `-a`, `--attach PID` | Attach to a running local process by pid: Go (Delve) or a native C/C++, Fortran, Rust, or OCaml program (gdb or lldb-dap). Linux identifies the language from `/proc/PID/exe`; elsewhere pass `--lang` and, for native programs, the executable path. |
1897
2523
  | `--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. |
1898
2524
  | `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
1899
2525
  | `-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. |
@@ -1903,14 +2529,14 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
1903
2529
  | `--python PATH` | Python interpreter for the debuggee (Python targets only) |
1904
2530
  | `--pv` | Shorthand for --python .venv/bin/python |
1905
2531
  | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`, `perl`); default: auto-detect from the target |
1906
- | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`); default: the language's standard adapter |
2532
+ | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`), or a full path to a `gdb`/`lldb-dap`/`dlv` executable to use instead of the one on `PATH`; default: the language's standard adapter |
1907
2533
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
1908
2534
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
1909
2535
  including library frames, regardless of this flag. |
1910
2536
  | `--no-subprocess` | Disable debugpy's subprocess tracking (use when debugging `tdb` itself) |
1911
2537
  | `--terminal TERM` | Run debuggee in the named external terminal: `xterm`, `konsole`,
1912
2538
  `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`, `wezterm`, or `terminator` |
1913
- | `--keybindings SCHEME` | `default`, `vim`, or `emacs` (saved to config) |
2539
+ | `--keybindings SCHEME` | `vim`, `emacs`, or `default` (Notepad-style editing); saved to config |
1914
2540
  | `--server` | Enable JSON-RPC server alongside TUI |
1915
2541
  | `--headless` | JSON-RPC server only, no TUI |
1916
2542
  | `--server-port PORT` | Server port (default: 8150) |
@@ -1922,6 +2548,11 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
1922
2548
  | `--replay-interval S` | With `--replay`/`--replay-tui`: fixed S-second delay before each action instead of the recorded gaps |
1923
2549
  | `--replay-timeout S` | With `--replay`/`--replay-tui`: per-action stop-wait timeout (default 30) |
1924
2550
  | `--examine-log DEST` | With --run: write each Ctrl-\ / SIGUSR2 stack snapshot to DEST (- = stdout; repeatable) |
2551
+ | `--doc` | Display this README in a terminal Markdown viewer and exit |
2552
+ | `--doc-text` | Print this README to stdout as wrapped plain text and exit |
2553
+ | `--info` | Print the About text, file locations, and the path and version of each interpreter and debugger `tdb` would use, then exit |
2554
+ | `--config write-default` | Rename the existing `config.json` to `config.json-YYYY-mm-DD-HH:MM:SS` (`HH.MM.SS` on Windows, which forbids colons in filenames) and write a new one holding every default value, then exit |
2555
+ | `--config install-padwalker` | Build and cache the bundled PadWalker Perl module now, for the perl `tdb` would debug with (`adapters.perl` in `config.json`, else `perl` on `PATH`), instead of waiting for the first Perl launch; exits 1 if it cannot be built |
1925
2556
 
1926
2557
  ## Configuration
1927
2558
 
@@ -1934,10 +2565,65 @@ On Windows, it uses `%APPDATA%\tdb\`.
1934
2565
  | `config.json` | User preferences (keybinding scheme, color theme, step mode, adapter overrides) |
1935
2566
  | `breakpoints.json` | Breakpoints from previous sessions, keyed by project directory |
1936
2567
 
2568
+ ### Sample `config.json`
2569
+
2570
+ Every key `tdb` reads, with its default or a representative value.
2571
+ `tdb --config write-default` writes a fresh file with these defaults
2572
+ (moving the old one aside as `config.json-YYYY-mm-DD-HH:MM:SS`), or copy
2573
+ this to restore a deleted or corrupted file, then delete the entries you
2574
+ don't need: a missing key takes its default, an unknown key is ignored,
2575
+ and an invalid value (say a misspelled `step_mode`) falls back to the
2576
+ default instead of stopping `tdb`.
2577
+
2578
+ ```json
2579
+ {
2580
+ "keybindings": "vim",
2581
+ "theme": null,
2582
+ "step_mode": "statement",
2583
+ "adapters": {
2584
+ "gdb": "/usr/bin/gdb",
2585
+ "lldb-dap": null,
2586
+ "dlv": "/home/me/go/bin/dlv",
2587
+ "perl": "/usr/bin/perl",
2588
+ "rdbg": "/usr/local/bin/rdbg",
2589
+ "bash": "/usr/bin/bash",
2590
+ "tcsh": "/usr/bin/tcsh",
2591
+ "pwsh": "/usr/bin/pwsh",
2592
+ "pses": "/home/me/.local/share/PowerShellEditorServices",
2593
+ "ocamlearlybird": "/home/me/.opam/default/bin/ocamlearlybird"
2594
+ },
2595
+ "default_adapters": {
2596
+ "cpp": "gdb",
2597
+ "rust": "gdb",
2598
+ "ocaml": "lldb-dap"
2599
+ }
2600
+ }
2601
+ ```
2602
+
2603
+ | Key | Values | Meaning |
2604
+ |-----|--------|---------|
2605
+ | `keybindings` | `"vim"` (default), `"emacs"`, `"default"` | Code View keybinding scheme; also set by `--keybindings` |
2606
+ | `theme` | `null` (default) or a Textual theme name such as `"textual-dark"`, `"textual-light"`, `"nord"`, `"gruvbox"`, `"dracula"`, `"monokai"`, `"tokyo-night"`, `"catppuccin-mocha"` | Color theme; the names offered under **Configure > Theme** are the valid ones, and an unknown name is ignored |
2607
+ | `step_mode` | `"statement"` (default), `"line"` | How `n`/`s` treat multi-line statements (see **Step mode** below) |
2608
+ | `adapters` | adapter id → executable path, or `null` | Where to find a debugger or interpreter instead of searching `PATH`. Ids: `gdb`, `lldb-dap`, `dlv` (debuggers); `perl`, `rdbg`, `bash`, `tcsh`, `pwsh` (interpreters `tdb` spawns); `pses` (the PowerShell Editor Services module directory); `ocamlearlybird`. Only list the ones you need |
2609
+ | `default_adapters` | language id → adapter id | Which adapter a language uses when `--adapter` isn't given. Languages with a choice: `cpp` and `rust` (`gdb` or `lldb-dap`), `ocaml` (`lldb-dap`, `gdb`, or `ocamlearlybird`) |
2610
+
2611
+ The file is plain JSON: no comments, no trailing commas. `tdb` rewrites it
2612
+ when you change a setting from the **Configure** menu, so hand edits made
2613
+ while `tdb` is running may be overwritten.
2614
+
1937
2615
  Adapter-related keys in `config.json`: `adapters` maps an adapter id to an
1938
2616
  executable path (`{"adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"}}`), and
1939
2617
  `default_adapters` picks a language's default adapter
1940
- (`{"default_adapters": {"cpp": "lldb-dap"}}`).
2618
+ (`{"default_adapters": {"cpp": "lldb-dap"}}`). For a one-off run,
2619
+ `--adapter /path/to/gdb` (or `lldb-dap`, `dlv`) overrides the `adapters` entry
2620
+ without editing the file; `tdb --info` reports the `gdb` and `lldb-dap`
2621
+ currently resolved (config override first, then `PATH`) with their versions.
2622
+
2623
+ When `tdb` creates `config.json` for the first time it seeds `adapters` with
2624
+ `gdb` and `lldb-dap` entries pointing at the executables found on `PATH`
2625
+ (`null` when one isn't installed) so the keys are there to edit. A `null`
2626
+ entry behaves like a missing one: `tdb` falls back to `PATH`.
1941
2627
 
1942
2628
  **Perl is a special case:** `perl-tdb` is tdb's own bundled adapter (always
1943
2629
  found; it's Python code, not an external executable), so
@@ -1972,7 +2658,7 @@ fires as expected.
1972
2658
 
1973
2659
  - [textual](https://github.com/Textualize/textual) : TUI framework
1974
2660
  - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation for Python
1975
- - [gdb](https://sourceware.org/gdb/) / [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html) : optional, user-installed DAP adapters for C/C++
2661
+ - [gdb](https://sourceware.org/gdb/) / [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html) : optional, user-installed DAP adapters for C/C++/Fortran
1976
2662
  - [debug gem](https://github.com/ruby/debug) (`rdbg`) : optional, user-installed DAP adapter for Ruby
1977
2663
  - [ocamlearlybird](https://github.com/hackwaly/ocamlearlybird) : optional, user-installed DAP adapter for OCaml bytecode (native OCaml reuses `gdb`/`lldb-dap`)
1978
2664
  - [pygments](https://pygments.org/) : Syntax highlighting
@@ -2000,6 +2686,7 @@ as open source.
2000
2686
 
2001
2687
  - OpenAI, for providing access to Codex through the
2002
2688
  [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
2689
+ Codex was used to implement tcsh and Rust support.
2003
2690
 
2004
2691
  This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/)
2005
2692
  Python debugger.