tgdb is a terminal-based front-end for GDB, reimplemented in Python using
Textual for the TUI and
Pygments for syntax highlighting.
It aims for 100% compatibility with cgdb in
terms of keybindings, configuration, and behaviour.
- Split-screen TUI: syntax-highlighted source window above, GDB console below
- Vi-like keybindings in the source window
- Syntax highlighting for C, C++, Python, Rust, Go, Ada, Fortran, and more (via Pygments)
- Visual breakpoint management: set/delete/enable/disable with
Space - Executing line indicator:
shortarrow,longarrow,highlight, orblockstyles - Regex search in source window (
/,?,n,N) with optionalhlsearch - Marks (
m[a-z/A-Z]/'[a-z/A-Z]) — local and global - File dialog (
o) to browse and select source files - GDB scrollback buffer with configurable size and vi-style scroll mode
- ANSI colour support in GDB output window
~/.config/tgdb/tgdbrcconfig file (XDG_CONFIG_HOME/tgdb/tgdbrc)- Numeric prefix for movement:
[N]j,[N]k,[N]G(e.g.20j= down 20 lines) - Horizontal vim navigation:
0= beginning of line,^= first char,$= end of line - Goto line:
:Njumps to line N in the source window (:+N/:-Nfor relative) - Command history: persistent across sessions,
!!reruns last command,!Nreruns entry N - Comments in command mode: lines starting with
#are no-ops (useful in rc files and history) - Async Python scripting:
:python await foo(), heredoc:python << EOF, top-levelawait
pip install -e .
# or
pip install textual pygments ptyprocess pyte
python -m tgdbtgdb [OPTIONS] [PROGRAM [CORE|PID]]
Options:
-d, --debugger DEBUGGER Path to GDB executable (default: gdb)
-r, --rcfile FILE Configuration file (default: ~/.config/tgdb/tgdbrc)
Use --rcfile NONE (uppercase) to skip loading
--args Pass remaining args as program + arguments to GDB
--cd DIR Change to DIR before starting GDB
Examples:
tgdb myprogram
tgdb -d /usr/bin/gdb myprogram
tgdb --args myprogram arg1 arg2
tgdb myprogram core| Key | Action |
|---|---|
ESC |
Stay in / return to CGDB mode |
i |
Switch to GDB mode |
s |
Switch to GDB scroll mode |
[N]j / [N]↓ |
Move cursor down N lines (default 1) |
[N]k / [N]↑ |
Move cursor up N lines (default 1) |
h / ← |
Move left (horizontal scroll) |
l / → |
Move right (horizontal scroll) |
0 |
Go to beginning of line (reset horizontal scroll) |
^ |
Go to first visible char (same as 0) |
$ |
Go to end of current line |
Ctrl-f / PageDn |
Page down |
Ctrl-b / PageUp |
Page up |
Ctrl-d |
Half page down |
Ctrl-u |
Half page up |
gg |
Go to top |
[N]G |
Go to line N (or bottom if no prefix) |
H / M / L |
Screen top / middle / bottom |
/ |
Forward search |
? |
Backward search |
n / N |
Next / previous search match |
Space |
Toggle breakpoint at current line |
t |
Set temporary breakpoint |
o |
Open file dialog |
m[a-z/A-Z] |
Set mark |
'[a-z/A-Z] |
Jump to mark |
'' |
Jump to last jump location |
'. |
Jump to executing line |
Ctrl-W |
Toggle split orientation |
- / = |
Shrink / grow source window by 1 |
_ / + |
Shrink / grow source window by 25% |
Ctrl-l |
Redraw screen |
F5 |
run |
F6 |
continue |
F7 |
finish |
F8 |
next |
F10 |
step |
: |
Enter command mode |
| Key | Action |
|---|---|
ESC |
Switch to CGDB mode |
PageUp |
Enter scroll mode |
↑ / ↓ |
Command history |
Ctrl-C |
Interrupt GDB |
Enter |
Send command to GDB |
| Key | Action |
|---|---|
ESC / q / i / Enter |
Exit scroll mode |
j / ↓ |
Scroll down |
k / ↑ |
Scroll up |
PageUp / PageDn |
Page up / down |
Ctrl-u / Ctrl-d |
Half page |
gg / G |
Beginning / end of buffer |
/ / ? / n / N |
Search |
| Key | Action |
|---|---|
q / ESC |
Close dialog |
j / k |
Move down / up |
Enter |
Open selected file |
/ / ? / n / N |
Search files |
tgdb reads ~/.config/tgdb/tgdbrc (or $XDG_CONFIG_HOME/tgdb/tgdbrc) on
startup. Commands can also be typed interactively via :.
Use --rcfile NONE (uppercase) to skip loading the rc file.
Any line beginning with optional whitespace then # is a comment and is ignored:
# This line is a comment
set hlsearch # NOT a comment — '#' only counts at line start
Comments are valid in rc files, in :source-d files, and in interactive
command mode. They are also recorded in history (the session-delimiter line
# tgdb begins ... uses this mechanism).
| Option | Abbrev | Default | Description |
|---|---|---|---|
autosourcereload |
asr |
on | Auto-reload changed source files |
cgdbmodekey=KEY |
— | ESC |
Key to enter CGDB mode |
color |
— | on | Enable colour support |
debugwincolor |
dwc |
on | Show ANSI colours in GDB window |
disasm |
dis |
off | Show disassembly |
executinglinedisplay=STYLE |
eld |
longarrow |
shortarrow|longarrow|highlight|block |
history=N |
— | 1024 | Command history buffer size (0 = disabled) |
hlsearch |
hls |
off | Highlight all search matches |
ignorecase |
ic |
off | Case-insensitive search |
scrollbackbuffersize=N |
sbbs |
10000 | GDB scrollback lines |
selectedlinedisplay=STYLE |
sld |
block |
Same styles as above |
showmarks |
— | on | Show marks in gutter |
showdebugcommands |
sdc |
off | Show GDB commands sent |
tabstop=N |
ts |
8 | Tab width |
timeout |
to |
on | Timeout on key maps |
timeoutlen=N |
tm |
1000 | Map timeout (ms) |
ttimeout |
— | on | Timeout on key codes |
ttimeoutlen=N |
ttm |
100 | Key code timeout (ms) |
winminheight=N |
wmh |
0 | Minimum window height |
winminwidth=N |
wmw |
0 | Minimum window width |
winsplit=STYLE |
— | even |
src_full|src_big|even|gdb_big|gdb_full |
winsplitorientation=STYLE |
wso |
vertical |
horizontal|vertical |
wrapscan |
ws |
on | Search wraps around end of file |
Setting history=0 disables history (buffer cleared immediately). Use a very
large number (e.g. set history=9999999) for effectively unlimited history.
| Command | Description |
|---|---|
:history |
List all recorded commands with index numbers |
:save history |
Save history buffer to disk immediately |
:save history FILE |
Save history buffer to FILE |
!! |
Re-run the last non-comment history entry |
!N |
Re-run history entry number N |
History is loaded from ~/.local/state/tgdb/history at startup.
A session delimiter comment (# tgdb begins YYYY-MM-DD HH:MM:SS) is
automatically prepended. Commands from the rc file are not recorded;
only interactive commands are saved. History is written to disk on exit.
:highlight GROUP ctermfg=COLOR ctermbg=COLOR cterm=ATTRS
Groups: Statement, Type, Constant, Comment, PreProc, Normal,
StatusLine, Search, IncSearch, SelectedLineArrow, ExecutingLineArrow,
SelectedLineHighlight, ExecutingLineHighlight, SelectedLineBlock,
ExecutingLineBlock, Breakpoint, DisabledBreakpoint, Logo, Mark, etc.
Colours: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta,
Brown/DarkYellow, LightGray/Gray, DarkGray, Blue/LightBlue,
Green/LightGreen, Cyan/LightCyan, Red/LightRed,
Magenta/LightMagenta, Yellow/LightYellow, White.
Attributes: bold, underline, reverse/inverse, standout, blink, dim, normal/NONE.
:map <F7> :step<Enter>
:map <F8> :next<Enter>
:imap <C-x> :quit<Enter>
:unmap <F7>
:iunmap <C-x>
set tabstop=4
set hlsearch
set autosourcereload
set executinglinedisplay=longarrow
set selectedlinedisplay=block
set scrollbackbuffersize=50000
set winsplit=even
highlight Statement ctermfg=Yellow cterm=bold
highlight Comment ctermfg=DarkGray
highlight Breakpoint ctermfg=Red cterm=bold
map <F5> :run<Enter>
map <F6> :continue<Enter>
map <F7> :finish<Enter>
map <F8> :next<Enter>
map <F10> :step<Enter>
tgdb/
├── __main__.py CLI entry point (argparse, launches app)
├── app/ Main application package — TGDBApp + workspace tree
├── gdb_controller/ GDB bridge package — controller, MI parser, callbacks
├── source_widget/ Source code viewer package (SourceView + internal helpers)
├── gdb_widget/ GDB console package — pane, screen, scroll mode
├── command_line_bar/ Command/status bar package
├── context_menu/ Cascading workspace context-menu package
├── status_bar.py Status bar widget
├── file_dialog/ File picker dialog package
├── config/ Configuration package (tgdbrc parser + helpers)
├── highlight_groups.py Highlight group table and colour management
└── key_mapper.py Key mapping with trie + timeout (KUI)
- Startup:
TGDBAppreads$XDG_CONFIG_HOME/tgdb/tgdbrc(default~/.config/tgdb/tgdbrc), spawns GDB viaptyprocesswith dual PTYs: primary (console), secondary (MI channel). - GDB I/O: Two asyncio readers:
- Primary PTY: raw console bytes → GDB widget (with ANSI colours)
- Secondary PTY: MI stream → parser for async records (
*stopped,=thread-selected, etc.)
- Event-driven source updates: An AF_UNIX socketpair lets GDB's embedded Python
(via
gdb.events.*hooks intgdb_pysetup.py) notify tgdb of frame changes, register writes, inferior calls, and library loads without relying on MI async records alone (GDB does not broadcast per-UI frame changes). Events are coalesced and dispatched on the asyncio loop; see Event Hooks below. - Source view: When the selected frame changes (via CLI
up/down, library load, or stop event), the source widget loads the file (if needed) and positions the executing-line arrow. - Breakpoints: The space bar sends
-break-insert file:linevia MI. Breakpoint events update the gutter markers. - Key mapping: All keys pass through
KeyMapperwhich resolves user maps (:map/:imap) before dispatching.
tgdb wires several GDB Python events via an AF_UNIX socketpair (created at startup
and inherited into the GDB child process). Each event is encoded as a tagged line,
coalesced on the asyncio loop, and dispatched to callbacks in core.py and
callbacks.py. This mechanism captures frame and state changes that GDB does not
broadcast over the MI channel.
| Event | GDB Python hook | Purpose |
|---|---|---|
P |
gdb.events.before_prompt |
CLI prompt about to display — refresh selected frame (catch up/down/frame N typed directly) |
R<n> |
gdb.events.register_changed |
User set a register (e.g. set $rax=0x1234) — refresh register pane immediately |
O/F/C |
gdb.events.new_objfile / free_objfile / clear_objfiles |
Shared libraries loaded/unloaded; invalidate any cached objfile state (currently unused; file list is lazily fetched by file dialog) |
Ipre / Ipost |
gdb.events.inferior_call |
Expression trigger inferior execution (e.g. print foo()) — post refreshes locals/registers/memory since inferior ran |
X |
gdb.events.gdb_exiting |
GDB's main loop shutting down — start tgdb exit promptly (avoids lag waiting for PTY EOF) |
All handlers in tgdb_pysetup.py swallow write errors so a dead reader cannot
stall GDB. Per-tag coalescing means a burst of N events produces a single dispatch
per asyncio cycle, keeping MI load low.
- Uses Textual (Python TUI framework) instead of ncurses directly
- Uses Pygments for syntax highlighting (supports many more languages than cgdb's built-in flex lexers)
- GDB/MI parsing is done in pure Python (no C++ gdbwire library)
- Readline integration: basic history via up/down arrows (no libreadline)
- The
--tty/Ctrl-Tseparate TTY feature is not yet implemented - Event-driven frame/state updates: Embeds Python in GDB to hook
gdb.events.*(before_prompt, register_changed, inferior_call, gdb_exiting, etc.) via an inherited AF_UNIX socket, ensuring frame changes from CLI commands (up/down/frame N) update the source pane immediately, without relying on MI async records alone