textual-debugger 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/PKG-INFO +1 -1
  2. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/pyproject.toml +4 -1
  3. textual_debugger-0.0.3/src/tdb/README.md +641 -0
  4. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/__init__.py +1 -1
  5. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/textual_debugger.egg-info/PKG-INFO +1 -1
  6. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/textual_debugger.egg-info/SOURCES.txt +1 -0
  7. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/LICENSE +0 -0
  8. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/README.md +0 -0
  9. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/setup.cfg +0 -0
  10. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/__main__.py +0 -0
  11. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/app.py +0 -0
  12. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/app_handlers/__init__.py +0 -0
  13. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/app_handlers/dap_events.py +0 -0
  14. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/app_handlers/inspection.py +0 -0
  15. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/app_helpers.py +0 -0
  16. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/breakpoint_hook.py +0 -0
  17. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/cli.py +0 -0
  18. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/dap/__init__.py +0 -0
  19. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/dap/client.py +0 -0
  20. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/dap/messages.py +0 -0
  21. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/dap/protocol.py +0 -0
  22. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/dap/types.py +0 -0
  23. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/inspection.py +0 -0
  24. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/keybindings/__init__.py +0 -0
  25. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/persist.py +0 -0
  26. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/post_mortem.py +0 -0
  27. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/server/__init__.py +0 -0
  28. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/server/app.py +0 -0
  29. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/server/event_handler.py +0 -0
  30. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/server/handlers.py +0 -0
  31. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/server/rpc_types.py +0 -0
  32. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/server/runner.py +0 -0
  33. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/session/__init__.py +0 -0
  34. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/session/controller.py +0 -0
  35. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/session/event_bus.py +0 -0
  36. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/session/messages.py +0 -0
  37. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/session/state.py +0 -0
  38. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/session/textual_handler.py +0 -0
  39. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/__init__.py +0 -0
  40. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/async_tasks_modal.py +0 -0
  41. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/breakpoint_view.py +0 -0
  42. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/code_view.py +0 -0
  43. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/console_view.py +0 -0
  44. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/evaluate_console.py +0 -0
  45. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/menu_bar.py +0 -0
  46. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/modals.py +0 -0
  47. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/processes_modal.py +0 -0
  48. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/stack_view.py +0 -0
  49. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/status_bar.py +0 -0
  50. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/threads_modal.py +0 -0
  51. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/tdb/widgets/variable_view.py +0 -0
  52. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  53. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  54. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/textual_debugger.egg-info/requires.txt +0 -0
  55. {textual_debugger-0.0.2 → textual_debugger-0.0.3}/src/textual_debugger.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: textual-debugger
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A TUI Python debugger based on textual and debugpy
5
5
  Author-email: Al Danial <al.danial@gmail.com>
6
6
  License: textual-debugger is licensed to you under the MIT/X Consortium license:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "textual-debugger"
3
- version = "0.0.2"
3
+ version = "0.0.3"
4
4
  description = "A TUI Python debugger based on textual and debugpy"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -51,6 +51,9 @@
51
51
  [tool.setuptools.packages.find]
52
52
  where = ["src"]
53
53
 
54
+ [tool.setuptools.package-data]
55
+ tdb = ["README.md"]
56
+
54
57
  [tool.pytest.ini_options]
55
58
  testpaths = ["tests"]
56
59
  asyncio_mode = "auto"
@@ -0,0 +1,641 @@
1
+ # `textual-debugger`
2
+
3
+ `textual-debugger` (the package) provides `tdb` (the command-line tool and module),
4
+ a full-featured terminal-based Python debugger.
5
+
6
+ `tdb` is built with [textual](https://github.com/Textualize/textual)
7
+ and [debugpy](https://github.com/microsoft/debugpy) (the Debug Adapter Protocol engine behind
8
+ VS Code's Python debugger). It provides a rich interactive interface for stepping through code,
9
+ inspecting variables, managing breakpoints, and evaluating expressions in complex Python programs.
10
+
11
+ - PyPI: https://pypi.org/project/textual-debugger/
12
+ - GitHub: https://github.com/AlDanial/tdb
13
+
14
+ MIT License. Copyright 2026 by Al Danial.
15
+
16
+ ## Feature Overview
17
+
18
+ `tdb`:
19
+
20
+ - supports debugging of synchronous, asynchronous, multi-threaded, and multi-process Python code.
21
+ It specifically supports modules
22
+ - `asyncio` (with a built-in async task inspector)
23
+ - `threading` (with a thread inspector)
24
+ - `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
25
+
26
+ - supports remote attachment to any debugpy-enabled Python program
27
+
28
+ - includes a JSON-RPC server mode for programmatic debug control, making it suitable for
29
+ automated, headless debugging workflows and AI-assisted debugging
30
+
31
+ - can spawn the debuggee in an external terminal to enable debugging TUI applications
32
+ built with `textual`, `prompt-toolkit`, `urwid`, `curses`, `rich`, and so on
33
+
34
+ - comes with a post-mortem exception hook that can be installed in Python programs
35
+ to have the debugger pop open automatically at the first uncaught exception
36
+
37
+ - can be entirely keyboard-driven
38
+ making it suitable for operation in non-graphical environments (mouse support is
39
+ available in graphical environments)
40
+
41
+ ## Acknowledgments
42
+
43
+ Thank you:
44
+
45
+ - Will McGugan for the amazing `textual` module.
46
+ `tdb` would be a pale shadow of itself had I used any other TUI framework.
47
+ Fantastic work, Will.
48
+
49
+ - Microsoft for the Debug Adapter Protocol (DAP) and releasing
50
+ its implementation in `debugpy` and the Python Debugger extension for Visual Studio Code
51
+ as open source.
52
+
53
+ - Anthropic, for providing access to Claude Code through the
54
+ [Claude for Open Source](https://claude.com/contact-sales/claude-for-oss) program.
55
+ `tdb` was made almost entirely with Claude Code.
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install textual-debugger
61
+ ```
62
+
63
+ or (better):
64
+
65
+ ```bash
66
+ uv pip install textual-debugger
67
+ ```
68
+
69
+
70
+ ## Quick Start
71
+
72
+ ```bash
73
+ # Debug a script (stops at first line by default)
74
+ tdb my_script.py
75
+
76
+ # Debug with arguments
77
+ tdb my_script.py arg1 arg2
78
+
79
+ # Use a specific virtualenv
80
+ tdb --python /path/to/venv/bin/python my_script.py
81
+
82
+ # Don't stop on entry; run until first breakpoint or exit
83
+ tdb --no-stop-on-entry my_script.py
84
+
85
+ > **Note:** Avoid `argparse` confusion by separating `tdb` switches from
86
+ > the debuggee's switches by prefixing the debuggee with `--`.
87
+
88
+ # `--` separates tdb's switches from the debuggee's switches
89
+ tdb --python /path/to/venv/bin/python -- my_script.py -f 17 --max 23.3
90
+ ```
91
+
92
+ Or use the module entry point:
93
+
94
+ ```bash
95
+ python -m tdb my_script.py
96
+ ```
97
+
98
+ ## Layout
99
+
100
+ ```
101
+ ┌─ Header ──────────────────────────────────────────────┐
102
+ ├─ Menu Bar (File | Configure | Help) ──────────────────┤
103
+ │ │ │
104
+ │ Code View │ Console View (stdout) │
105
+ │ (source + breakpoints) ├───────────────────────────┤
106
+ │ │ Variable View (tree) │
107
+ │ ├───────────────────────────┤
108
+ │ │ Stack View (call stack) │
109
+ ├─ Status Bar ──────────────────────────────────────────┤
110
+ │ │ │
111
+ │ Evaluate Console (REPL) │ Breakpoint View (table) │
112
+ │ │ │
113
+ ├─ Footer (keybindings) ────────────────────────────────┤
114
+ └───────────────────────────────────────────────────────┘
115
+ ```
116
+
117
+ ## Features
118
+
119
+ ### Navigation and Keybindings
120
+
121
+ The Code View shows syntax-highlighted Python source with line numbers.
122
+ A cursor line (blue) tracks your position; the current execution line is highlighted in gold.
123
+
124
+ **Navigation (vim-style by default):**
125
+
126
+ | Key | Action |
127
+ |-----|--------|
128
+ | `j` / `k` | Move cursor down / up |
129
+ | `5j`, `10k` | Move N lines with count prefix |
130
+ | `g` | Go to line (with count: `42g` jumps to line 42) |
131
+ | `G` | Go to end of file |
132
+ | `[` / `]` | Jump to previous / next paragraph boundary |
133
+ | `/` | Search forward |
134
+ | `?` | Search backward |
135
+ | `n` / `N` | Next / previous search result |
136
+ | `PageUp` / `PageDown` | Scroll by page |
137
+
138
+ Switch between Navigation and Debug modes with `Escape`.
139
+
140
+ **View focus shortcuts (global):**
141
+
142
+ | Key | View |
143
+ |-----|------|
144
+ | `Ctrl+C` | Code View |
145
+ | `Ctrl+O` | Console View |
146
+ | `Ctrl+E` | Evaluate Console |
147
+ | `Ctrl+V` | Variable View |
148
+ | `Ctrl+S` | Stack View |
149
+ | `Ctrl+B` | Breakpoint View |
150
+
151
+ **Menu-bar shortcuts (global):**
152
+
153
+ `Alt+<first-letter>` opens the corresponding tab in the menu bar.
154
+
155
+ | Key | Menu |
156
+ |-----|------|
157
+ | `Alt+F` | File (open a different script to debug) |
158
+ | `Alt+C` | Configure (Color Theme, Keybindings) |
159
+ | `Alt+T` | Threads |
160
+ | `Alt+P` | Processes |
161
+ | `Alt+A` | Async Tasks |
162
+ | `Alt+H` | Help (Documentation, About) |
163
+
164
+ > **Note:** Many terminals send the byte sequence `ESC+f` for `Alt+F`, which Textual's
165
+ ANSI parser rewrites to `Ctrl+Right` (the readline "forward-word" convention).
166
+ `tdb` binds both so `Alt+F` works as expected regardless.
167
+
168
+ ### Debugging Controls
169
+
170
+ Keybindings for stepping, continuing, pausing, and stack navigation match
171
+ those for gdb/pdb, with some aliases and extras thrown in for convenience.
172
+
173
+ | Key | Action |
174
+ |-----|--------|
175
+ | `n` | Step over (next line) |
176
+ | `s` | Step into function call |
177
+ | `o` / `f` / `r` | Step out of current function (also aliased as "finish" and "return") |
178
+ | `c` | Continue execution |
179
+ | `p` | Pause a running program |
180
+ | `t` | Run to cursor position |
181
+ | `u` / `d` | Navigate stack up (caller) / down (callee) |
182
+ | `R` | Restart the debug session |
183
+ | `Ctrl+Q` | Quit |
184
+
185
+ > **Note:** `f` ("finish") and `r` ("return") are both aliases for step-out. DAP's only
186
+ "exit-a-function" primitive is `stepOut`, which runs the rest of the current function
187
+ normally and stops at the return point. A true gdb-style immediate-return (skipping
188
+ remaining code in the function without executing side effects) is not supported by DAP/debugpy.
189
+
190
+ ### Breakpoints
191
+
192
+ Click the gutter in the Code View to toggle a breakpoint, or press `b` in Debug mode.
193
+
194
+ **Breakpoint indicators:**
195
+ - Red dot: active breakpoint
196
+ - Yellow dot: conditional breakpoint
197
+ - Blue dot: disabled breakpoint
198
+
199
+ **Conditional breakpoints:** Double-click a breakpoint to open the condition editor.
200
+ Set a Python expression (e.g., `x > 10`) and/or a hit count (pause on the Nth hit).
201
+
202
+ **Breakpoint View actions:**
203
+ - `D` : Disable / enable all breakpoints
204
+ - `C` : Clear all breakpoints
205
+
206
+ Breakpoints persist across session restarts.
207
+
208
+ ### Variable Inspection
209
+
210
+ The Variable View shows a tree of scopes (Locals, Globals) with all variables in the current
211
+ frame. Expand nodes to drill into complex objects. Children are loaded lazily on demand.
212
+
213
+ Format: `name (type) = value`
214
+
215
+ ### Call Stack
216
+
217
+ The Stack View shows the full call stack. Click a frame to navigate to its source
218
+ location and inspect its variables.
219
+
220
+ ### Evaluate Console
221
+
222
+ A REPL at the bottom-left evaluates expressions in the current scope:
223
+
224
+ ```
225
+ >>> len(items)
226
+ 42
227
+ >>> sorted(data, key=lambda x: x.priority)[:3]
228
+ [Item(priority=1), Item(priority=2), Item(priority=3)]
229
+ ```
230
+
231
+ - **Up/Down arrows** cycle through expression history
232
+ - **Tab** triggers DAP-based completion
233
+ - **Trailing `?`** shows help (signature + docstring):
234
+
235
+ ```
236
+ >>> os.path.join?
237
+ (a, *p) : Join two or more pathname components...
238
+ ```
239
+
240
+ ### Console Output
241
+
242
+ The Console View captures stdout (normal text) and stderr (red text) from the debuggee
243
+ in real time.
244
+
245
+ ### Crash Detection
246
+
247
+ When the debuggee raises an unhandled exception, `tdb`:
248
+ 1. Shows a modal with the full traceback
249
+ 2. Navigates the Code View to the crash line
250
+ 3. Populates the Stack View with the exception's call stack
251
+ 4. Lets you press `R` to restart or `Escape` to dismiss
252
+
253
+ ### Post-Mortem Exception Hook
254
+
255
+ You can have `tdb` pop open automatically when *any* Python program crashes without the
256
+ need to launch through `tdb` up front. Install the hook once at the top of your program:
257
+
258
+ ```python
259
+ import sys
260
+ import tdb
261
+
262
+ sys.excepthook = tdb.exception_hook
263
+ ```
264
+
265
+ When an uncaught exception reaches the hook, `tdb`:
266
+
267
+ 1. Prints the standard Python traceback to stderr (so your scrollback still has a record)
268
+ 2. Snapshots every frame in the traceback. This includes locals, plus one level of
269
+ recursion into containers (`dict`, `list`, `tuple`, `set`) and objects with `__dict__`
270
+ 3. Launches the TUI in **post-mortem mode**, inheriting the current terminal
271
+
272
+ In post-mortem mode you can:
273
+
274
+ - Navigate the call stack (`u` / `d` or the Stack View) and see each frame's locals
275
+ - Expand nested containers and object attributes in the Variables View
276
+ - Read the full traceback (including chained `cause`/`context` exceptions) in the Console View
277
+ - Jump around the source with the full Code View (syntax highlighting, goto-line, etc.)
278
+
279
+ Stepping, `continue`, breakpoints, restart, and Evaluate are disabled. The original
280
+ interpreter is gone, the view is a frozen snapshot. Press `q` to exit.
281
+
282
+ The hook is a no-op when stdin/stdout aren't a tty (e.g. when your program is piped or
283
+ run from cron), so it's safe to leave installed in production-style code. Snapshots are
284
+ written to a temp file that is deleted as soon as `tdb` exits.
285
+
286
+ Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep the
287
+ capture cheap even for pathological object graphs; cycles are handled via identity
288
+ memoization.
289
+
290
+ ### Live Breakpoint Hook
291
+
292
+ For the `pdb.set_trace()` use case--pausing at a specific line to inspect, then
293
+ continuing--use `tdb.breakpoint()`:
294
+
295
+ ```python
296
+ import tdb
297
+
298
+ def compute(n):
299
+ total = sum(range(n))
300
+ tdb.breakpoint() # pause here and drop into tdb
301
+ return total
302
+ ```
303
+
304
+ Or hook it into the builtin `breakpoint()` function for the whole program:
305
+
306
+ ```bash
307
+ PYTHONBREAKPOINT=tdb.breakpoint python myscript.py
308
+ ```
309
+
310
+ When the call is reached, `tdb` starts an in-process `debugpy` server on a loopback port,
311
+ spawns `python -m tdb -r <port>` as a subprocess so the TUI takes over the terminal,
312
+ and pauses the calling thread at the line that called `tdb.breakpoint()` (the hook
313
+ auto-steps out of its own helper so you land in your own frame, not inside
314
+ `breakpoint_hook.py`). Stepping (`n`/`s`/`o`), `continue`, and setting/removing breakpoints
315
+ all work normally; quitting `tdb` (`Ctrl+Q`) detaches without killing the program, and
316
+ debugpy auto-resumes any threads still paused.
317
+
318
+ This differs from `tdb.exception_hook` in one way:
319
+ - **Requires `debugpy`** as a runtime dependency for the debuggee (only imported when the hook actually fires).
320
+
321
+ Unlike the exception hook (which works on a frozen snapshot), the breakpoint hook leaves
322
+ the interpreter live: variable inspection reads real objects, and stepping/`continue`
323
+ drive the user's program forward.
324
+
325
+ As with `exception_hook`, the call is a no-op when stdin/stdout aren't a tty, so it's
326
+ safe to leave in code paths that sometimes run headless.
327
+
328
+ ### Async Task Inspector
329
+
330
+ For programs using `asyncio`, the menu bar shows an **Async Tasks (N)** label with the count of
331
+ active tasks (updated each time the program stops). Click it to open a full-screen modal:
332
+
333
+ - **Left pane**: list of all tasks with name, state (pending/done/cancelled), awaiting
334
+ primitive (`Lock.acquire`, `Queue.get`, `asyncio.sleep`, …), and coroutine
335
+ - **Right pane**: detail view with full stack trace and an expandable variable tree (same
336
+ as the main Variables View) for the selected task
337
+ - Press `g` to switch the right pane to the **wait graph** — a tree showing each blocked
338
+ task, the asyncio primitive it's parked on, and the task(s) holding that primitive.
339
+ Cycles (deadlocks) are highlighted in red both in the task table and as a "Deadlock
340
+ cycles" section at the top of the graph. Selecting a node in the tree highlights the
341
+ corresponding task in the table.
342
+ - Navigate with arrow keys; press `r` to refresh, `Escape` to close
343
+
344
+ RPC equivalents:
345
+
346
+ ```bash
347
+ # List all tasks
348
+ curl -s -X POST http://127.0.0.1:8150/rpc \
349
+ -H 'Content-Type: application/json' \
350
+ -d '{"action":"list_tasks","params":[]}'
351
+
352
+ # Inspect a specific task by name
353
+ curl -s -X POST http://127.0.0.1:8150/rpc \
354
+ -H 'Content-Type: application/json' \
355
+ -d '{"action":"inspect_task","params":["Task-1"]}'
356
+
357
+ # Show wait graph and any deadlock cycles
358
+ curl -s -X POST http://127.0.0.1:8150/rpc \
359
+ -H 'Content-Type: application/json' \
360
+ -d '{"action":"wait_graph","params":[]}'
361
+ ```
362
+
363
+ ### Thread Inspector
364
+
365
+ The menu bar shows a **Threads (N)** label when the program has 2 or more threads. Click it to open a modal with:
366
+
367
+ - **Left pane**: list of threads with ID and name (current thread shown in bold)
368
+ - **Right pane**: full stack trace and expandable variable tree for the selected thread's top frame
369
+ - Navigate with arrow keys; press `r` to refresh, `Escape` to close
370
+
371
+ RPC equivalents:
372
+
373
+ ```bash
374
+ # List all threads (* marks current)
375
+ curl -s -X POST http://127.0.0.1:8150/rpc \
376
+ -H 'Content-Type: application/json' \
377
+ -d '{"action":"list_threads","params":[]}'
378
+
379
+ # Inspect a specific thread by ID
380
+ curl -s -X POST http://127.0.0.1:8150/rpc \
381
+ -H 'Content-Type: application/json' \
382
+ -d '{"action":"inspect_thread","params":[1]}'
383
+ ```
384
+
385
+ ### Process Inspector
386
+
387
+ For programs using `multiprocessing`, the menu bar shows a **Processes (N)** label when there
388
+ are 2 or more child processes. Click it to open a modal with:
389
+
390
+ - **Left pane**: list of child processes with PID, name, and status (alive/exited)
391
+ - **Right pane**: process details, full stack trace, and expandable variable tree for the selected process
392
+
393
+ `tdb` automatically attaches to child processes spawned via `multiprocessing.Process`, `multiprocessing.Pool`,
394
+ or `concurrent.futures.ProcessPoolExecutor`. Breakpoints set in the parent are propagated to all
395
+ child processes. When any process hits a breakpoint, all other processes are paused. Pressing `p`
396
+ pauses all processes; `c` continues all.
397
+
398
+ **Stepping in multi-process programs:** step commands (`n`, `s`, `o`, `f`, `r`) apply only to the
399
+ process whose stack is currently shown in the Code View (the one that hit the breakpoint).
400
+ Other processes remain paused throughout the step. To step in a different process, open
401
+ the Processes tab and select it first. The Code View then switches focus to that process,
402
+ and subsequent step commands operate on it.
403
+
404
+ RPC equivalents:
405
+
406
+ ```bash
407
+ # List all child processes
408
+ curl -s -X POST http://127.0.0.1:8150/rpc \
409
+ -H 'Content-Type: application/json' \
410
+ -d '{"action":"list_processes","params":[]}'
411
+
412
+ # Inspect a specific process by name or PID
413
+ curl -s -X POST http://127.0.0.1:8150/rpc \
414
+ -H 'Content-Type: application/json' \
415
+ -d '{"action":"inspect_process","params":["ForkPoolWorker-1"]}'
416
+ ```
417
+
418
+ ### Remote Attach
419
+
420
+ Remote attachment is useful in situations where you can't launch the debuggee directly
421
+ with `tdb`, for example, if it is launched from another program or runs in an environment
422
+ where you can't install `tdb`. Two requirements must still be met though:
423
+ 1. the `debugpy` package must be installed in the debuggee's Python environment
424
+ 2. you need write access to the debuggee's code to add the following code at the point
425
+ where you want to attach the debugger:
426
+
427
+ ```python
428
+ # In the target program:
429
+ import debugpy
430
+ debugpy.listen(("0.0.0.0", 5678))
431
+ print("Waiting for tdb to attach on port 5678...")
432
+ debugpy.wait_for_client() # optional: pause until debugger connects
433
+ print("tdb is attached!")
434
+ ```
435
+
436
+ When the debuggee runs and hits the `debugpy.wait_for_client()` line, it starts a
437
+ debugpy server listening on port 5678.
438
+ Attach `tdb` to it with the `-r` / `--remote-attach` switch, specifying the host and port.
439
+ If the debuggee is on the same machine, you can omit the host or use `localhost`.
440
+ This example assumes the debuggee runs on 192.168.1.10 and listens on port 5678:
441
+
442
+ ```bash
443
+ # Attach from tdb:
444
+ tdb -r 5678
445
+ tdb -r 192.168.1.10:5678
446
+
447
+ # With breakpoints:
448
+ tdb -r 5678 -k my_script.py:42
449
+ ```
450
+
451
+ All debugging features (breakpoints, stepping, variable inspection, threads, processes,
452
+ async tasks) work in remote attach mode. The Code View automatically navigates to the
453
+ source file when the program stops.
454
+
455
+ ### External Terminal Support
456
+
457
+ Some Python programs, notably text user interfaces, make heavy use of terminal control
458
+ codes and require direct access to the terminal to function properly.
459
+ Such programs can be debugged with `tdb` by having it launch the debuggee in
460
+ a separate terminal:
461
+
462
+ ```bash
463
+ tdb --terminal xterm my_tui_app.py
464
+ ```
465
+
466
+ The debuggee runs in a separate window of the specified terminal. Supported choices:
467
+ `xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
468
+ `wezterm`, `terminator`. The selected terminal must be on `PATH`. Debugging
469
+ proceeds as usual in the terminal where `tdb` was invoked.
470
+
471
+ This feature only works in graphical environments where external terminals are available.
472
+
473
+ ### Keybinding Schemes
474
+
475
+ ```bash
476
+ tdb --keybindings vim my_script.py # default
477
+ tdb --keybindings emacs my_script.py
478
+ tdb --keybindings default my_script.py
479
+ ```
480
+
481
+ The keybinding choice is saved to `~/.config/tdb/config.json` and remembered for subsequent
482
+ runs. View the full keybinding reference from the menu: **Configure > Keybindings**.
483
+
484
+ ## JSON-RPC Server Mode
485
+
486
+ `tdb` includes a built-in debug server for programmatic control which is useful for scripted
487
+ debugging, CI pipelines, or AI-assisted debugging workflows.
488
+
489
+ ### Headless Mode (no TUI)
490
+
491
+ ```bash
492
+ python -m tdb --headless my_script.py &
493
+ ```
494
+
495
+ The server listens on `http://127.0.0.1:8150/rpc` (change with `--server-port`).
496
+
497
+ ### Dual Mode (TUI + server)
498
+
499
+ ```bash
500
+ tdb --server my_script.py
501
+ ```
502
+
503
+ Both the interactive TUI and the JSON-RPC server run simultaneously.
504
+
505
+ ### RPC Protocol
506
+
507
+ Send POST requests with `{"action": "...", "params": [...]}`. Responses return
508
+ `{"timestamp": "...", "success": true/false, "value": "..."}`.
509
+
510
+ ```bash
511
+ # Check status
512
+ curl -s -X POST http://127.0.0.1:8150/rpc \
513
+ -H 'Content-Type: application/json' \
514
+ -d '{"action":"status","params":[]}'
515
+
516
+ # Set a breakpoint
517
+ curl -s -X POST http://127.0.0.1:8150/rpc \
518
+ -H 'Content-Type: application/json' \
519
+ -d '{"action":"set_breakpoint","params":["/abs/path/to/file.py:42"]}'
520
+
521
+ # Continue execution
522
+ curl -s -X POST http://127.0.0.1:8150/rpc \
523
+ -H 'Content-Type: application/json' \
524
+ -d '{"action":"continue","params":[]}'
525
+
526
+ # Inspect variables
527
+ curl -s -X POST http://127.0.0.1:8150/rpc \
528
+ -H 'Content-Type: application/json' \
529
+ -d '{"action":"inspect","params":["x", "len(items)", "type(result)"]}'
530
+
531
+ # Shut down
532
+ curl -s -X POST http://127.0.0.1:8150/rpc \
533
+ -H 'Content-Type: application/json' \
534
+ -d '{"action":"quit","params":[]}'
535
+ ```
536
+
537
+ ### All RPC Actions
538
+
539
+ | Action | Params | Description |
540
+ |--------|--------|-------------|
541
+ | `help` | `[]` | List all actions |
542
+ | `status` | `[]` | Current state with location |
543
+ | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
544
+ | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
545
+ | `list_breakpoints` | `[]` | Show all breakpoints |
546
+ | `continue` | `[]` | Resume execution |
547
+ | `next` | `[]` | Step over |
548
+ | `step_in` | `[]` | Step into |
549
+ | `step_out` | `[]` | Step out |
550
+ | `pause` | `[]` | Pause execution |
551
+ | `inspect` | `["expr1", "expr2", ...]` | Evaluate multiple expressions |
552
+ | `evaluate` | `["expression"]` | Evaluate a single expression |
553
+ | `stack_up` | `[]` | Move up the call stack |
554
+ | `stack_down` | `[]` | Move down the call stack |
555
+ | `get_stack_trace` | `[]` | Full call stack |
556
+ | `get_output` | `[]` | Drain buffered stdout/stderr |
557
+ | `get_source` | `["file_path"]` | Read a source file |
558
+ | `list_threads` | `[]` | List all threads |
559
+ | `inspect_thread` | `[thread_id]` | Inspect a specific thread |
560
+ | `list_processes` | `[]` | List child processes (multiprocessing) |
561
+ | `inspect_process` | `["name_or_pid"]` | Inspect a specific child process |
562
+ | `list_tasks` | `[]` | List all asyncio tasks |
563
+ | `inspect_task` | `["task_name"]` | Inspect a specific asyncio task |
564
+ | `wait_graph` | `[]` | Show wait graph + any deadlock cycles |
565
+ | `restart` | `[]` | Restart session (preserves breakpoints) |
566
+ | `quit` | `[]` | Shut down |
567
+
568
+ ### SSE Event Stream
569
+
570
+ Subscribe to real-time debug events:
571
+
572
+ ```bash
573
+ curl -N http://127.0.0.1:8150/events
574
+ ```
575
+
576
+ Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
577
+ Each is JSON with `event`, `data`, and `timestamp` fields.
578
+
579
+ ## CLI Reference
580
+
581
+ ```
582
+ usage: tdb [-h] [-r [HOST:]PORT] [-k FILE:LINE] [--cwd CWD]
583
+ [--no-stop-on-entry] [--no-just-my-code] [--no-subprocess]
584
+ [--python PYTHON] [--keybindings {default,vim,emacs}]
585
+ [--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
586
+ [--server] [--headless] [--server-port PORT]
587
+ [program] [args ...]
588
+ ```
589
+
590
+ | Flag | Description |
591
+ |------|-------------|
592
+ | `-r`, `--remote-attach HOST:PORT` | Attach to a remote debugpy server |
593
+ | `-k`, `--breakpoint FILE:LINE` | Set a breakpoint (may be repeated) |
594
+ | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry) |
595
+ | `--cwd DIR` | Working directory for the debuggee |
596
+ | `--python PATH` | Python interpreter for the debuggee |
597
+ | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
598
+ (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
599
+ including library frames, regardless of this flag. |
600
+ | `--no-subprocess` | Disable debugpy's subprocess tracking (use when debugging `tdb` itself) |
601
+ | `--terminal TERM` | Run debuggee in the named external terminal: `xterm`, `konsole`,
602
+ `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`, `wezterm`, or `terminator` |
603
+ | `--keybindings SCHEME` | `default`, `vim`, or `emacs` (saved to config) |
604
+ | `--server` | Enable JSON-RPC server alongside TUI |
605
+ | `--headless` | JSON-RPC server only, no TUI |
606
+ | `--server-port PORT` | Server port (default: 8150) |
607
+
608
+ ## Configuration
609
+
610
+ On UNIX-like systems (Linux, macOS, FreeBSD, etc.),
611
+ `tdb` stores configuration and breakpoints in `~/.config/tdb/`.
612
+ On Windows, it uses `%APPDATA%\tdb\`.
613
+
614
+ | File | Contents |
615
+ |------|----------|
616
+ | `config.json` | User preferences (keybinding scheme) |
617
+ | `breakpoints.json` | Breakpoints from previous sessions, keyed by project directory |
618
+
619
+ Breakpoints are saved on exit and restored when debugging a program in the same
620
+ directory. Each project's breakpoints are independent.
621
+
622
+ ## Tech Stack
623
+
624
+ - [textual](https://github.com/Textualize/textual) : TUI framework
625
+ - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation
626
+ - [pygments](https://pygments.org/) : Syntax highlighting
627
+ - [FastAPI](https://fastapi.tiangolo.com/) + [uvicorn](https://www.uvicorn.org/) : JSON-RPC server
628
+
629
+ ## License
630
+
631
+ MIT
632
+
633
+
634
+ ## Known Problems
635
+
636
+ This command
637
+ ```
638
+ tdb --terminal gnome-terminal --python /path/to/venv/matplotlib/bin/python3 examples/double_pendulum.py
639
+ ```
640
+ either ignores breakpoints or crashes after showing the first frame.
641
+ The `--python` argument must point to an installation with `matplotlib`.
@@ -1,6 +1,6 @@
1
1
  from tdb.breakpoint_hook import breakpoint
2
2
  from tdb.post_mortem import exception_hook
3
3
 
4
- __version__ = "0.0.2"
4
+ __version__ = "0.0.3"
5
5
 
6
6
  __all__ = ["breakpoint", "exception_hook"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: textual-debugger
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A TUI Python debugger based on textual and debugpy
5
5
  Author-email: Al Danial <al.danial@gmail.com>
6
6
  License: textual-debugger is licensed to you under the MIT/X Consortium license:
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
+ src/tdb/README.md
4
5
  src/tdb/__init__.py
5
6
  src/tdb/__main__.py
6
7
  src/tdb/app.py