textual-debugger 0.2.4__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/PKG-INFO +82 -11
- {textual_debugger-0.2.4/src/tdb → textual_debugger-0.2.6}/README.md +81 -10
- {textual_debugger-0.2.4 → textual_debugger-0.2.6/src/tdb}/README.md +81 -10
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/__init__.py +1 -1
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/bash/server.py +114 -2
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/bash/session.py +139 -35
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/bash/tdb_harness.sh +15 -5
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/helpers.pl +22 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/server.py +214 -5
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/session.py +91 -18
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/breakpoints.py +8 -2
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/guardian.py +210 -40
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/inspect.py +9 -3
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/instrumenter.py +20 -6
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/models.py +1 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/runtime.py +15 -5
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/server.py +275 -15
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/session.py +442 -87
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/transport.py +107 -21
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app.py +103 -2
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/cli.py +70 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/dap/client.py +13 -1
- textual_debugger-0.2.6/src/tdb/dap/reverse.py +54 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/base.py +6 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/bash.py +2 -1
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/cpp.py +12 -1
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/perl.py +2 -1
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/python.py +1 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/tcsh.py +2 -1
- textual_debugger-0.2.6/src/tdb/run_mode.py +271 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/controller.py +36 -4
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/event_bus.py +49 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/modals.py +44 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/textual_debugger.egg-info/PKG-INFO +82 -11
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/textual_debugger.egg-info/SOURCES.txt +2 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/LICENSE +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/pyproject.toml +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/setup.cfg +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/__main__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/_timeouts.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/bash/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/bash/__main__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/bash/declares.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/Devel/TdbCompile.pm +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/Devel/TdbRemote.pm +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/__main__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/protocol.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/perl/refs.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/__main__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/cli.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/protocol.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/adapters/tcsh/scanner.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app_handlers/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app_handlers/dap_events.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app_handlers/inspection.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app_handlers/routing.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app_handlers/ui_panels.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/app_helpers.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/breakpoint_hook.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/dap/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/dap/messages.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/dap/protocol.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/dap/types.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/inspection.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/inspection_full.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/keybindings/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/errors.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/languages/registry.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/mcp/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/mcp/__main__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/mcp/server.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/mcp/session.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/persist.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/post_mortem.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/processes_cache.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/replay.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/server/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/server/app.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/server/event_handler.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/server/handlers.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/server/rpc_types.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/server/runner.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/child_processes.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/inspect_service.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/messages.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/post_mortem_loader.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/recorder.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/state.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/statement_stepper.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/terminal.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/session/textual_handler.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/source_analysis.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/__init__.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/_inspection_modal.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/async_tasks_modal.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/breakpoint_view.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/code_view.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/console_view.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/evaluate_console.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/full_contents_modal.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/menu_bar.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/processes_modal.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/stack_view.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/status_bar.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/threads_modal.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/tdb/widgets/variable_view.py +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/textual_debugger.egg-info/entry_points.txt +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/src/textual_debugger.egg-info/requires.txt +0 -0
- {textual_debugger-0.2.4 → textual_debugger-0.2.6}/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.
|
|
3
|
+
Version: 0.2.6
|
|
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:
|
|
@@ -142,6 +142,8 @@ as open source.
|
|
|
142
142
|
- OpenAI, for providing access to Codex through the
|
|
143
143
|
[Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
|
|
144
144
|
|
|
145
|
+
This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/) debugger.
|
|
146
|
+
|
|
145
147
|
## Gallery
|
|
146
148
|
<p align="center">
|
|
147
149
|
<img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
|
|
@@ -303,8 +305,9 @@ process inspectors and wait graph, the evaluate console's trailing-`?` help,
|
|
|
303
305
|
the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
|
|
304
306
|
programs by nature). Remote attach (`-r`) also works for Perl (see
|
|
305
307
|
[Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), but not
|
|
306
|
-
for C/C++, Bash, or Tcsh. `--terminal`
|
|
307
|
-
|
|
308
|
+
for C/C++, Bash, or Tcsh. `--terminal` works for every launch-mode
|
|
309
|
+
language — Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap` —
|
|
310
|
+
see [External Terminal Support](#external-terminal-support).
|
|
308
311
|
|
|
309
312
|
**Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
|
|
310
313
|
has a smaller feature envelope than Python/Perl:
|
|
@@ -366,6 +369,9 @@ See [Tcsh](#tcsh) below for launch details.
|
|
|
366
369
|
commands, so evaluate expressions with an explicit `print`, e.g.
|
|
367
370
|
`print x` rather than bare `x` (bare `x` collides with GDB's
|
|
368
371
|
examine-memory command). `lldb-dap` evaluates bare expressions directly.
|
|
372
|
+
- `--terminal` (see [External Terminal Support](#external-terminal-support))
|
|
373
|
+
requires `--adapter lldb-dap`; GDB's DAP mode has no terminal integration
|
|
374
|
+
and `tdb` refuses `--terminal` with the default `gdb` adapter.
|
|
369
375
|
|
|
370
376
|
### Perl
|
|
371
377
|
|
|
@@ -379,6 +385,9 @@ hood, so it works with any Perl already on the system.
|
|
|
379
385
|
tdb script.pl
|
|
380
386
|
```
|
|
381
387
|
|
|
388
|
+
`--terminal` is supported (see
|
|
389
|
+
[External Terminal Support](#external-terminal-support)).
|
|
390
|
+
|
|
382
391
|
**Compile-time code (`BEGIN` blocks):** Perl runs `BEGIN` blocks and the
|
|
383
392
|
`use` statements that are themselves `BEGIN` blocks while it is still
|
|
384
393
|
*compiling* your program, before stock `perl5db` ever stops. `tdb` arms the
|
|
@@ -500,7 +509,8 @@ Most notably, a breakpoint can't be set on a `func() {` line itself (the
|
|
|
500
509
|
`DEBUG` trap never fires there), stops never land inside subshells or
|
|
501
510
|
pipeline segments, child bash processes run uninstrumented, and only the
|
|
502
511
|
innermost frame's locals are inspectable. There is no remote-attach mode for
|
|
503
|
-
Bash.
|
|
512
|
+
Bash. `--terminal` is supported (see
|
|
513
|
+
[External Terminal Support](#external-terminal-support)).
|
|
504
514
|
|
|
505
515
|
The Variables view shows three scopes for bash: Locals (innermost frame
|
|
506
516
|
only), Globals (unexported shell variables), and Environment (exported
|
|
@@ -529,7 +539,8 @@ stack navigation across `source` nesting, variable inspection, and the
|
|
|
529
539
|
evaluate console with the v1 caveats listed in
|
|
530
540
|
[What works for non-Python languages](#what-works-for-non-python-languages),
|
|
531
541
|
most notably: no conditional breakpoints, no pause of a free-running
|
|
532
|
-
script, and stepping is per logical source line.
|
|
542
|
+
script, and stepping is per logical source line. `--terminal` is supported
|
|
543
|
+
(see [External Terminal Support](#external-terminal-support)).
|
|
533
544
|
|
|
534
545
|
The Variables view shows four scopes for tcsh: Shell Variables (`set`),
|
|
535
546
|
Environment (`setenv`), Aliases, and Arguments (`argv`). All of them show
|
|
@@ -1007,22 +1018,82 @@ application directory and a shared library directory) in one invocation.
|
|
|
1007
1018
|
|
|
1008
1019
|
### External Terminal Support
|
|
1009
1020
|
|
|
1010
|
-
Some
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1021
|
+
Some programs, notably text user interfaces, use terminal control codes and
|
|
1022
|
+
require direct access to the terminal to function properly. Such programs
|
|
1023
|
+
can be debugged with `tdb` by having it launch the debuggee in a separate
|
|
1024
|
+
terminal:
|
|
1014
1025
|
|
|
1015
1026
|
```bash
|
|
1016
1027
|
tdb --terminal xterm my_tui_app.py
|
|
1017
1028
|
```
|
|
1018
1029
|
|
|
1019
|
-
|
|
1030
|
+
`--terminal` works for every language `tdb` can *launch* (as opposed to
|
|
1031
|
+
attach to): Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap`.
|
|
1032
|
+
The default C/C++ adapter, `gdb -i dap`, has no terminal integration --
|
|
1033
|
+
`tdb` rejects `--terminal` up front with an error pointing at
|
|
1034
|
+
`--adapter lldb-dap` instead. `--terminal` also only applies when `tdb`
|
|
1035
|
+
launches the program itself; it is rejected for remote-attach (`-r`), since
|
|
1036
|
+
there's no program for `tdb` to spawn a terminal around.
|
|
1037
|
+
|
|
1038
|
+
The debuggee runs in a separate window of the specified terminal, including
|
|
1039
|
+
all of its stdin/stdout/stderr -- keyboard input, program output, and
|
|
1040
|
+
anything the program itself draws to the terminal all happen in that
|
|
1041
|
+
window, not in `tdb`'s Console View. Supported choices:
|
|
1020
1042
|
`xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
|
|
1021
1043
|
`wezterm`, `terminator`. The selected terminal must be on `PATH`. Debugging
|
|
1022
|
-
|
|
1044
|
+
(breakpoints, stepping, variable inspection, the evaluate console) proceeds
|
|
1045
|
+
as usual in the terminal where `tdb` was invoked.
|
|
1023
1046
|
|
|
1024
1047
|
This feature only works in graphical environments where external terminals are available.
|
|
1025
1048
|
|
|
1049
|
+
### Run Mode (`--run`)
|
|
1050
|
+
|
|
1051
|
+
For inspecting a program that appears hung: run it headless, at full speed, with no
|
|
1052
|
+
breakpoints installed and no stop-on-entry, then drop into the full TUI the moment you
|
|
1053
|
+
actually need it -- without paying for either up front.
|
|
1054
|
+
|
|
1055
|
+
```bash
|
|
1056
|
+
tdb --run my_program.py args...
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
The debuggee's stdout/stderr stream straight to the terminal. If the program exits on
|
|
1060
|
+
its own, `tdb` exits with the same code:
|
|
1061
|
+
|
|
1062
|
+
```bash
|
|
1063
|
+
tdb --run my_program.py; echo $? # my_program.py's own exit code
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
**Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
|
|
1067
|
+
`SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
|
|
1068
|
+
the debuggee at its currently executing line and opens the TUI with full debugging
|
|
1069
|
+
available -- breakpoints, stepping, variable inspection, the evaluate console. The
|
|
1070
|
+
debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
|
|
1071
|
+
group), so its own `SIGINT` handling is undisturbed.
|
|
1072
|
+
|
|
1073
|
+
**Quitting an adopted session** adds a third choice to the usual quit dialog:
|
|
1074
|
+
- `d` (or `q`) -- detach and resume: the program keeps running headlessly; interrupt it
|
|
1075
|
+
again later the same way.
|
|
1076
|
+
- `t` -- terminate the program and quit `tdb`.
|
|
1077
|
+
- `Esc` -- cancel, stay in the TUI.
|
|
1078
|
+
|
|
1079
|
+
A breakpoint set during one of these episodes stays live after you detach; if it's hit,
|
|
1080
|
+
`tdb` reopens the TUI there, just like an interrupt.
|
|
1081
|
+
|
|
1082
|
+
If the program instead dies on an unhandled exception -- in Python this includes a
|
|
1083
|
+
nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb` opens
|
|
1084
|
+
the TUI at the point of failure instead of exiting silently, so you can inspect the
|
|
1085
|
+
crash. Exit-code passthrough applies only to a clean exit during the headless phase.
|
|
1086
|
+
|
|
1087
|
+
Supported languages: Python, Perl, Bash, Tcsh, and C/C++ (both `gdb` and `lldb-dap`).
|
|
1088
|
+
`--run` is rejected up front for any other language.
|
|
1089
|
+
|
|
1090
|
+
**Limitation:** pausing is cooperative. If the debuggee is blocked inside a single
|
|
1091
|
+
blocking external call or syscall, the pause can't land until that call returns --
|
|
1092
|
+
`tdb` prints a notice and opens the TUI once the stop actually arrives.
|
|
1093
|
+
|
|
1094
|
+
**Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--server`,
|
|
1095
|
+
`--headless`, `--mcp`, `--terminal`.
|
|
1096
|
+
|
|
1026
1097
|
### Keybinding Schemes
|
|
1027
1098
|
|
|
1028
1099
|
```bash
|
|
@@ -74,6 +74,8 @@ as open source.
|
|
|
74
74
|
- OpenAI, for providing access to Codex through the
|
|
75
75
|
[Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
|
|
76
76
|
|
|
77
|
+
This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/) debugger.
|
|
78
|
+
|
|
77
79
|
## Gallery
|
|
78
80
|
<p align="center">
|
|
79
81
|
<img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
|
|
@@ -235,8 +237,9 @@ process inspectors and wait graph, the evaluate console's trailing-`?` help,
|
|
|
235
237
|
the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
|
|
236
238
|
programs by nature). Remote attach (`-r`) also works for Perl (see
|
|
237
239
|
[Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), but not
|
|
238
|
-
for C/C++, Bash, or Tcsh. `--terminal`
|
|
239
|
-
|
|
240
|
+
for C/C++, Bash, or Tcsh. `--terminal` works for every launch-mode
|
|
241
|
+
language — Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap` —
|
|
242
|
+
see [External Terminal Support](#external-terminal-support).
|
|
240
243
|
|
|
241
244
|
**Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
|
|
242
245
|
has a smaller feature envelope than Python/Perl:
|
|
@@ -298,6 +301,9 @@ See [Tcsh](#tcsh) below for launch details.
|
|
|
298
301
|
commands, so evaluate expressions with an explicit `print`, e.g.
|
|
299
302
|
`print x` rather than bare `x` (bare `x` collides with GDB's
|
|
300
303
|
examine-memory command). `lldb-dap` evaluates bare expressions directly.
|
|
304
|
+
- `--terminal` (see [External Terminal Support](#external-terminal-support))
|
|
305
|
+
requires `--adapter lldb-dap`; GDB's DAP mode has no terminal integration
|
|
306
|
+
and `tdb` refuses `--terminal` with the default `gdb` adapter.
|
|
301
307
|
|
|
302
308
|
### Perl
|
|
303
309
|
|
|
@@ -311,6 +317,9 @@ hood, so it works with any Perl already on the system.
|
|
|
311
317
|
tdb script.pl
|
|
312
318
|
```
|
|
313
319
|
|
|
320
|
+
`--terminal` is supported (see
|
|
321
|
+
[External Terminal Support](#external-terminal-support)).
|
|
322
|
+
|
|
314
323
|
**Compile-time code (`BEGIN` blocks):** Perl runs `BEGIN` blocks and the
|
|
315
324
|
`use` statements that are themselves `BEGIN` blocks while it is still
|
|
316
325
|
*compiling* your program, before stock `perl5db` ever stops. `tdb` arms the
|
|
@@ -432,7 +441,8 @@ Most notably, a breakpoint can't be set on a `func() {` line itself (the
|
|
|
432
441
|
`DEBUG` trap never fires there), stops never land inside subshells or
|
|
433
442
|
pipeline segments, child bash processes run uninstrumented, and only the
|
|
434
443
|
innermost frame's locals are inspectable. There is no remote-attach mode for
|
|
435
|
-
Bash.
|
|
444
|
+
Bash. `--terminal` is supported (see
|
|
445
|
+
[External Terminal Support](#external-terminal-support)).
|
|
436
446
|
|
|
437
447
|
The Variables view shows three scopes for bash: Locals (innermost frame
|
|
438
448
|
only), Globals (unexported shell variables), and Environment (exported
|
|
@@ -461,7 +471,8 @@ stack navigation across `source` nesting, variable inspection, and the
|
|
|
461
471
|
evaluate console with the v1 caveats listed in
|
|
462
472
|
[What works for non-Python languages](#what-works-for-non-python-languages),
|
|
463
473
|
most notably: no conditional breakpoints, no pause of a free-running
|
|
464
|
-
script, and stepping is per logical source line.
|
|
474
|
+
script, and stepping is per logical source line. `--terminal` is supported
|
|
475
|
+
(see [External Terminal Support](#external-terminal-support)).
|
|
465
476
|
|
|
466
477
|
The Variables view shows four scopes for tcsh: Shell Variables (`set`),
|
|
467
478
|
Environment (`setenv`), Aliases, and Arguments (`argv`). All of them show
|
|
@@ -939,22 +950,82 @@ application directory and a shared library directory) in one invocation.
|
|
|
939
950
|
|
|
940
951
|
### External Terminal Support
|
|
941
952
|
|
|
942
|
-
Some
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
953
|
+
Some programs, notably text user interfaces, use terminal control codes and
|
|
954
|
+
require direct access to the terminal to function properly. Such programs
|
|
955
|
+
can be debugged with `tdb` by having it launch the debuggee in a separate
|
|
956
|
+
terminal:
|
|
946
957
|
|
|
947
958
|
```bash
|
|
948
959
|
tdb --terminal xterm my_tui_app.py
|
|
949
960
|
```
|
|
950
961
|
|
|
951
|
-
|
|
962
|
+
`--terminal` works for every language `tdb` can *launch* (as opposed to
|
|
963
|
+
attach to): Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap`.
|
|
964
|
+
The default C/C++ adapter, `gdb -i dap`, has no terminal integration --
|
|
965
|
+
`tdb` rejects `--terminal` up front with an error pointing at
|
|
966
|
+
`--adapter lldb-dap` instead. `--terminal` also only applies when `tdb`
|
|
967
|
+
launches the program itself; it is rejected for remote-attach (`-r`), since
|
|
968
|
+
there's no program for `tdb` to spawn a terminal around.
|
|
969
|
+
|
|
970
|
+
The debuggee runs in a separate window of the specified terminal, including
|
|
971
|
+
all of its stdin/stdout/stderr -- keyboard input, program output, and
|
|
972
|
+
anything the program itself draws to the terminal all happen in that
|
|
973
|
+
window, not in `tdb`'s Console View. Supported choices:
|
|
952
974
|
`xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
|
|
953
975
|
`wezterm`, `terminator`. The selected terminal must be on `PATH`. Debugging
|
|
954
|
-
|
|
976
|
+
(breakpoints, stepping, variable inspection, the evaluate console) proceeds
|
|
977
|
+
as usual in the terminal where `tdb` was invoked.
|
|
955
978
|
|
|
956
979
|
This feature only works in graphical environments where external terminals are available.
|
|
957
980
|
|
|
981
|
+
### Run Mode (`--run`)
|
|
982
|
+
|
|
983
|
+
For inspecting a program that appears hung: run it headless, at full speed, with no
|
|
984
|
+
breakpoints installed and no stop-on-entry, then drop into the full TUI the moment you
|
|
985
|
+
actually need it -- without paying for either up front.
|
|
986
|
+
|
|
987
|
+
```bash
|
|
988
|
+
tdb --run my_program.py args...
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
The debuggee's stdout/stderr stream straight to the terminal. If the program exits on
|
|
992
|
+
its own, `tdb` exits with the same code:
|
|
993
|
+
|
|
994
|
+
```bash
|
|
995
|
+
tdb --run my_program.py; echo $? # my_program.py's own exit code
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
**Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
|
|
999
|
+
`SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
|
|
1000
|
+
the debuggee at its currently executing line and opens the TUI with full debugging
|
|
1001
|
+
available -- breakpoints, stepping, variable inspection, the evaluate console. The
|
|
1002
|
+
debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
|
|
1003
|
+
group), so its own `SIGINT` handling is undisturbed.
|
|
1004
|
+
|
|
1005
|
+
**Quitting an adopted session** adds a third choice to the usual quit dialog:
|
|
1006
|
+
- `d` (or `q`) -- detach and resume: the program keeps running headlessly; interrupt it
|
|
1007
|
+
again later the same way.
|
|
1008
|
+
- `t` -- terminate the program and quit `tdb`.
|
|
1009
|
+
- `Esc` -- cancel, stay in the TUI.
|
|
1010
|
+
|
|
1011
|
+
A breakpoint set during one of these episodes stays live after you detach; if it's hit,
|
|
1012
|
+
`tdb` reopens the TUI there, just like an interrupt.
|
|
1013
|
+
|
|
1014
|
+
If the program instead dies on an unhandled exception -- in Python this includes a
|
|
1015
|
+
nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb` opens
|
|
1016
|
+
the TUI at the point of failure instead of exiting silently, so you can inspect the
|
|
1017
|
+
crash. Exit-code passthrough applies only to a clean exit during the headless phase.
|
|
1018
|
+
|
|
1019
|
+
Supported languages: Python, Perl, Bash, Tcsh, and C/C++ (both `gdb` and `lldb-dap`).
|
|
1020
|
+
`--run` is rejected up front for any other language.
|
|
1021
|
+
|
|
1022
|
+
**Limitation:** pausing is cooperative. If the debuggee is blocked inside a single
|
|
1023
|
+
blocking external call or syscall, the pause can't land until that call returns --
|
|
1024
|
+
`tdb` prints a notice and opens the TUI once the stop actually arrives.
|
|
1025
|
+
|
|
1026
|
+
**Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--server`,
|
|
1027
|
+
`--headless`, `--mcp`, `--terminal`.
|
|
1028
|
+
|
|
958
1029
|
### Keybinding Schemes
|
|
959
1030
|
|
|
960
1031
|
```bash
|
|
@@ -74,6 +74,8 @@ as open source.
|
|
|
74
74
|
- OpenAI, for providing access to Codex through the
|
|
75
75
|
[Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
|
|
76
76
|
|
|
77
|
+
This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/) debugger.
|
|
78
|
+
|
|
77
79
|
## Gallery
|
|
78
80
|
<p align="center">
|
|
79
81
|
<img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
|
|
@@ -235,8 +237,9 @@ process inspectors and wait graph, the evaluate console's trailing-`?` help,
|
|
|
235
237
|
the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
|
|
236
238
|
programs by nature). Remote attach (`-r`) also works for Perl (see
|
|
237
239
|
[Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), but not
|
|
238
|
-
for C/C++, Bash, or Tcsh. `--terminal`
|
|
239
|
-
|
|
240
|
+
for C/C++, Bash, or Tcsh. `--terminal` works for every launch-mode
|
|
241
|
+
language — Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap` —
|
|
242
|
+
see [External Terminal Support](#external-terminal-support).
|
|
240
243
|
|
|
241
244
|
**Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
|
|
242
245
|
has a smaller feature envelope than Python/Perl:
|
|
@@ -298,6 +301,9 @@ See [Tcsh](#tcsh) below for launch details.
|
|
|
298
301
|
commands, so evaluate expressions with an explicit `print`, e.g.
|
|
299
302
|
`print x` rather than bare `x` (bare `x` collides with GDB's
|
|
300
303
|
examine-memory command). `lldb-dap` evaluates bare expressions directly.
|
|
304
|
+
- `--terminal` (see [External Terminal Support](#external-terminal-support))
|
|
305
|
+
requires `--adapter lldb-dap`; GDB's DAP mode has no terminal integration
|
|
306
|
+
and `tdb` refuses `--terminal` with the default `gdb` adapter.
|
|
301
307
|
|
|
302
308
|
### Perl
|
|
303
309
|
|
|
@@ -311,6 +317,9 @@ hood, so it works with any Perl already on the system.
|
|
|
311
317
|
tdb script.pl
|
|
312
318
|
```
|
|
313
319
|
|
|
320
|
+
`--terminal` is supported (see
|
|
321
|
+
[External Terminal Support](#external-terminal-support)).
|
|
322
|
+
|
|
314
323
|
**Compile-time code (`BEGIN` blocks):** Perl runs `BEGIN` blocks and the
|
|
315
324
|
`use` statements that are themselves `BEGIN` blocks while it is still
|
|
316
325
|
*compiling* your program, before stock `perl5db` ever stops. `tdb` arms the
|
|
@@ -432,7 +441,8 @@ Most notably, a breakpoint can't be set on a `func() {` line itself (the
|
|
|
432
441
|
`DEBUG` trap never fires there), stops never land inside subshells or
|
|
433
442
|
pipeline segments, child bash processes run uninstrumented, and only the
|
|
434
443
|
innermost frame's locals are inspectable. There is no remote-attach mode for
|
|
435
|
-
Bash.
|
|
444
|
+
Bash. `--terminal` is supported (see
|
|
445
|
+
[External Terminal Support](#external-terminal-support)).
|
|
436
446
|
|
|
437
447
|
The Variables view shows three scopes for bash: Locals (innermost frame
|
|
438
448
|
only), Globals (unexported shell variables), and Environment (exported
|
|
@@ -461,7 +471,8 @@ stack navigation across `source` nesting, variable inspection, and the
|
|
|
461
471
|
evaluate console with the v1 caveats listed in
|
|
462
472
|
[What works for non-Python languages](#what-works-for-non-python-languages),
|
|
463
473
|
most notably: no conditional breakpoints, no pause of a free-running
|
|
464
|
-
script, and stepping is per logical source line.
|
|
474
|
+
script, and stepping is per logical source line. `--terminal` is supported
|
|
475
|
+
(see [External Terminal Support](#external-terminal-support)).
|
|
465
476
|
|
|
466
477
|
The Variables view shows four scopes for tcsh: Shell Variables (`set`),
|
|
467
478
|
Environment (`setenv`), Aliases, and Arguments (`argv`). All of them show
|
|
@@ -939,22 +950,82 @@ application directory and a shared library directory) in one invocation.
|
|
|
939
950
|
|
|
940
951
|
### External Terminal Support
|
|
941
952
|
|
|
942
|
-
Some
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
953
|
+
Some programs, notably text user interfaces, use terminal control codes and
|
|
954
|
+
require direct access to the terminal to function properly. Such programs
|
|
955
|
+
can be debugged with `tdb` by having it launch the debuggee in a separate
|
|
956
|
+
terminal:
|
|
946
957
|
|
|
947
958
|
```bash
|
|
948
959
|
tdb --terminal xterm my_tui_app.py
|
|
949
960
|
```
|
|
950
961
|
|
|
951
|
-
|
|
962
|
+
`--terminal` works for every language `tdb` can *launch* (as opposed to
|
|
963
|
+
attach to): Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap`.
|
|
964
|
+
The default C/C++ adapter, `gdb -i dap`, has no terminal integration --
|
|
965
|
+
`tdb` rejects `--terminal` up front with an error pointing at
|
|
966
|
+
`--adapter lldb-dap` instead. `--terminal` also only applies when `tdb`
|
|
967
|
+
launches the program itself; it is rejected for remote-attach (`-r`), since
|
|
968
|
+
there's no program for `tdb` to spawn a terminal around.
|
|
969
|
+
|
|
970
|
+
The debuggee runs in a separate window of the specified terminal, including
|
|
971
|
+
all of its stdin/stdout/stderr -- keyboard input, program output, and
|
|
972
|
+
anything the program itself draws to the terminal all happen in that
|
|
973
|
+
window, not in `tdb`'s Console View. Supported choices:
|
|
952
974
|
`xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
|
|
953
975
|
`wezterm`, `terminator`. The selected terminal must be on `PATH`. Debugging
|
|
954
|
-
|
|
976
|
+
(breakpoints, stepping, variable inspection, the evaluate console) proceeds
|
|
977
|
+
as usual in the terminal where `tdb` was invoked.
|
|
955
978
|
|
|
956
979
|
This feature only works in graphical environments where external terminals are available.
|
|
957
980
|
|
|
981
|
+
### Run Mode (`--run`)
|
|
982
|
+
|
|
983
|
+
For inspecting a program that appears hung: run it headless, at full speed, with no
|
|
984
|
+
breakpoints installed and no stop-on-entry, then drop into the full TUI the moment you
|
|
985
|
+
actually need it -- without paying for either up front.
|
|
986
|
+
|
|
987
|
+
```bash
|
|
988
|
+
tdb --run my_program.py args...
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
The debuggee's stdout/stderr stream straight to the terminal. If the program exits on
|
|
992
|
+
its own, `tdb` exits with the same code:
|
|
993
|
+
|
|
994
|
+
```bash
|
|
995
|
+
tdb --run my_program.py; echo $? # my_program.py's own exit code
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
**Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
|
|
999
|
+
`SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
|
|
1000
|
+
the debuggee at its currently executing line and opens the TUI with full debugging
|
|
1001
|
+
available -- breakpoints, stepping, variable inspection, the evaluate console. The
|
|
1002
|
+
debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
|
|
1003
|
+
group), so its own `SIGINT` handling is undisturbed.
|
|
1004
|
+
|
|
1005
|
+
**Quitting an adopted session** adds a third choice to the usual quit dialog:
|
|
1006
|
+
- `d` (or `q`) -- detach and resume: the program keeps running headlessly; interrupt it
|
|
1007
|
+
again later the same way.
|
|
1008
|
+
- `t` -- terminate the program and quit `tdb`.
|
|
1009
|
+
- `Esc` -- cancel, stay in the TUI.
|
|
1010
|
+
|
|
1011
|
+
A breakpoint set during one of these episodes stays live after you detach; if it's hit,
|
|
1012
|
+
`tdb` reopens the TUI there, just like an interrupt.
|
|
1013
|
+
|
|
1014
|
+
If the program instead dies on an unhandled exception -- in Python this includes a
|
|
1015
|
+
nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb` opens
|
|
1016
|
+
the TUI at the point of failure instead of exiting silently, so you can inspect the
|
|
1017
|
+
crash. Exit-code passthrough applies only to a clean exit during the headless phase.
|
|
1018
|
+
|
|
1019
|
+
Supported languages: Python, Perl, Bash, Tcsh, and C/C++ (both `gdb` and `lldb-dap`).
|
|
1020
|
+
`--run` is rejected up front for any other language.
|
|
1021
|
+
|
|
1022
|
+
**Limitation:** pausing is cooperative. If the debuggee is blocked inside a single
|
|
1023
|
+
blocking external call or syscall, the pause can't land until that call returns --
|
|
1024
|
+
`tdb` prints a notice and opens the TUI once the stop actually arrives.
|
|
1025
|
+
|
|
1026
|
+
**Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--server`,
|
|
1027
|
+
`--headless`, `--mcp`, `--terminal`.
|
|
1028
|
+
|
|
958
1029
|
### Keybinding Schemes
|
|
959
1030
|
|
|
960
1031
|
```bash
|
|
@@ -15,6 +15,7 @@ from typing import Any, Awaitable, Callable
|
|
|
15
15
|
|
|
16
16
|
from tdb.dap.messages import Event, Request, Response, parse_message
|
|
17
17
|
from tdb.dap.protocol import encode_message, read_message
|
|
18
|
+
from tdb.dap.reverse import ReverseRequester
|
|
18
19
|
|
|
19
20
|
from .declares import BashVar
|
|
20
21
|
from .session import BashProtocolError, BashSession
|
|
@@ -47,6 +48,14 @@ class BashDapServer:
|
|
|
47
48
|
self._refs: dict[int, tuple] = {}
|
|
48
49
|
self._next_ref = 1
|
|
49
50
|
self._stack_cache: list[dict] | None = None
|
|
51
|
+
self._reverse = ReverseRequester(self._write, self._next_seq)
|
|
52
|
+
self._client_supports_run_in_terminal = False
|
|
53
|
+
# Strong ref: asyncio only holds a weak reference to a bare
|
|
54
|
+
# ensure_future() task, so it can be garbage-collected mid-flight
|
|
55
|
+
# if nothing else keeps it alive. An externalTerminal launch runs
|
|
56
|
+
# in the background so run()'s read loop can service the
|
|
57
|
+
# runInTerminal reverse-request reply (see _on_launch).
|
|
58
|
+
self._launch_task: asyncio.Future | None = None
|
|
50
59
|
self.handlers: dict[str, Callable[[Request], Awaitable[None]]] = {}
|
|
51
60
|
for name in dir(self):
|
|
52
61
|
if name.startswith("_on_"):
|
|
@@ -92,6 +101,9 @@ class BashDapServer:
|
|
|
92
101
|
except (ConnectionError, asyncio.IncompleteReadError, EOFError):
|
|
93
102
|
break
|
|
94
103
|
msg = parse_message(raw)
|
|
104
|
+
if self._reverse.route(msg):
|
|
105
|
+
await self._writer.drain()
|
|
106
|
+
continue
|
|
95
107
|
if not isinstance(msg, Request):
|
|
96
108
|
continue
|
|
97
109
|
handler = self.handlers.get(msg.command)
|
|
@@ -131,15 +143,90 @@ class BashDapServer:
|
|
|
131
143
|
|
|
132
144
|
# ---- lifecycle ----
|
|
133
145
|
async def _on_initialize(self, request: Request) -> None:
|
|
146
|
+
self._client_supports_run_in_terminal = bool(
|
|
147
|
+
request.arguments.get("supportsRunInTerminalRequest")
|
|
148
|
+
)
|
|
134
149
|
self.send_response(request, CAPABILITIES)
|
|
135
150
|
|
|
151
|
+
async def _cancel_launch_task(self) -> None:
|
|
152
|
+
"""Cancel and await an in-flight externalTerminal `_finish_launch`
|
|
153
|
+
background task before disconnect/terminate tear down the session.
|
|
154
|
+
|
|
155
|
+
Without this, a still-running launch continuation can assign
|
|
156
|
+
`self.session = BashSession(...)` (the first thing `_finish_launch`
|
|
157
|
+
does, synchronously, before any await) AFTER disconnect/terminate's
|
|
158
|
+
own `if self.session is not None` check already ran and found
|
|
159
|
+
nothing to stop -- leaking a live (or about-to-be-live) debuggee
|
|
160
|
+
with nothing left to ever call session.stop() on it. Cancelling and
|
|
161
|
+
awaiting first makes the eventual `self.session is not None` check
|
|
162
|
+
below see any session the task managed to create before it was
|
|
163
|
+
cancelled, so it still gets torn down.
|
|
164
|
+
"""
|
|
165
|
+
task, self._launch_task = self._launch_task, None
|
|
166
|
+
if task is None or task.done():
|
|
167
|
+
return
|
|
168
|
+
task.cancel()
|
|
169
|
+
try:
|
|
170
|
+
await task
|
|
171
|
+
except asyncio.CancelledError:
|
|
172
|
+
pass
|
|
173
|
+
except Exception:
|
|
174
|
+
log.exception("launch task raised during cancellation")
|
|
175
|
+
|
|
136
176
|
async def _on_launch(self, request: Request) -> None:
|
|
137
177
|
args = request.arguments
|
|
138
178
|
program = args.get("program", "")
|
|
139
179
|
if not os.path.isfile(program):
|
|
140
180
|
self.send_error(request, f"program not found: {program}")
|
|
141
181
|
return
|
|
182
|
+
run_in_terminal = None
|
|
183
|
+
if args.get("console") == "externalTerminal":
|
|
184
|
+
if not self._client_supports_run_in_terminal:
|
|
185
|
+
self.send_error(
|
|
186
|
+
request,
|
|
187
|
+
"externalTerminal launch requires a client that "
|
|
188
|
+
"supports the runInTerminal reverse request",
|
|
189
|
+
)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
async def run_in_terminal(cmd, cwd, env):
|
|
193
|
+
await self._reverse.request(
|
|
194
|
+
"runInTerminal",
|
|
195
|
+
{
|
|
196
|
+
"kind": "external",
|
|
197
|
+
"title": "tdb bash debuggee",
|
|
198
|
+
"cwd": cwd,
|
|
199
|
+
"args": cmd,
|
|
200
|
+
"env": env,
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
142
204
|
self._stop_on_entry = bool(args.get("stopOnEntry", True))
|
|
205
|
+
if run_in_terminal is not None:
|
|
206
|
+
# session.launch() awaits self._reverse.request("runInTerminal",
|
|
207
|
+
# ...), whose reply can only be read/routed by THIS coroutine's
|
|
208
|
+
# own run() loop -- but run() is what's calling _on_launch, and
|
|
209
|
+
# it awaits handler(msg) synchronously before reading the next
|
|
210
|
+
# message. Awaiting inline here would deadlock: the reply can
|
|
211
|
+
# never arrive because nothing is reading stdin anymore. Run
|
|
212
|
+
# the rest of launch as a background task (strong ref, per the
|
|
213
|
+
# repo's task-GC pitfall) so _on_launch returns immediately,
|
|
214
|
+
# letting run() go back to reading -- including the eventual
|
|
215
|
+
# runInTerminal response, which self._reverse.route(msg)
|
|
216
|
+
# handles directly in run() without going through a handler.
|
|
217
|
+
self._launch_task = asyncio.ensure_future(
|
|
218
|
+
self._finish_launch(request, program, args, run_in_terminal)
|
|
219
|
+
)
|
|
220
|
+
return
|
|
221
|
+
await self._finish_launch(request, program, args, run_in_terminal)
|
|
222
|
+
|
|
223
|
+
async def _finish_launch(
|
|
224
|
+
self,
|
|
225
|
+
request: Request,
|
|
226
|
+
program: str,
|
|
227
|
+
args: dict,
|
|
228
|
+
run_in_terminal,
|
|
229
|
+
) -> None:
|
|
143
230
|
self.session = BashSession(
|
|
144
231
|
self._forward_output, self._on_session_stop, self._on_session_exit
|
|
145
232
|
)
|
|
@@ -150,15 +237,39 @@ class BashDapServer:
|
|
|
150
237
|
cwd=args.get("cwd") or os.getcwd(),
|
|
151
238
|
env=args.get("env"),
|
|
152
239
|
bash=args.get("bash") or "bash",
|
|
240
|
+
run_in_terminal=run_in_terminal,
|
|
153
241
|
)
|
|
154
|
-
except
|
|
155
|
-
|
|
242
|
+
except Exception as e:
|
|
243
|
+
# Deliberately broad (mirrors run()'s own blanket `except
|
|
244
|
+
# Exception` net around handler dispatch): BashProtocolError
|
|
245
|
+
# and ReverseRequestError are the expected failures, but
|
|
246
|
+
# session.launch() can also raise e.g. bare
|
|
247
|
+
# asyncio.TimeoutError (ReverseRequester.request()'s own
|
|
248
|
+
# wait_for) or OSError. In the terminal-mode (background-task)
|
|
249
|
+
# case this coroutine is NOT awaited inline by run() -- there
|
|
250
|
+
# is no other except-Exception net standing behind this one --
|
|
251
|
+
# so anything narrower here would leave the client's launch
|
|
252
|
+
# request unanswered forever and silently strand the exception
|
|
253
|
+
# on self._launch_task.
|
|
254
|
+
#
|
|
255
|
+
# asyncio.CancelledError deliberately propagates past this
|
|
256
|
+
# (BaseException, not Exception, on py>=3.8): that's
|
|
257
|
+
# _on_disconnect/_on_terminate cancelling an in-flight launch,
|
|
258
|
+
# not a launch failure, and they do their own cleanup.
|
|
259
|
+
try:
|
|
260
|
+
await self.session.stop()
|
|
261
|
+
except Exception:
|
|
262
|
+
log.exception("session.stop() failed while tearing down failed launch")
|
|
156
263
|
self.session = None
|
|
264
|
+
if not isinstance(e, BashProtocolError):
|
|
265
|
+
log.exception("launch failed unexpectedly")
|
|
157
266
|
self.send_error(request, str(e))
|
|
267
|
+
await self._writer.drain()
|
|
158
268
|
return
|
|
159
269
|
self._launched = True
|
|
160
270
|
self._start_request = request
|
|
161
271
|
self.send_event("initialized")
|
|
272
|
+
await self._writer.drain()
|
|
162
273
|
# response is sent by _on_configurationDone (DAP ordering)
|
|
163
274
|
|
|
164
275
|
async def _on_configurationDone(self, request: Request) -> None:
|
|
@@ -171,6 +282,7 @@ class BashDapServer:
|
|
|
171
282
|
self.session.resume("step" if self._stop_on_entry else "continue")
|
|
172
283
|
|
|
173
284
|
async def _on_disconnect(self, request: Request) -> None:
|
|
285
|
+
await self._cancel_launch_task()
|
|
174
286
|
if self.session is not None:
|
|
175
287
|
await self.session.stop()
|
|
176
288
|
self.session = None
|