Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31daa1582c
|
||
|
|
f8cce8da37
|
||
|
|
d062ac27ff
|
||
|
|
85ff3b1688
|
||
|
|
7a21d372ff
|
||
|
|
98c943885a
|
||
|
|
acc698ad80
|
||
|
|
8fbccb9391
|
||
|
|
1e19db2101
|
||
|
|
c1c3143532
|
||
|
|
53cc0802c3
|
||
|
|
c02021ad5e
|
||
|
|
1cdde2a177
|
||
|
|
ffe561402f
|
||
|
|
c8f37bcb7e
|
||
|
|
c420acd599
|
||
|
|
33d9675318
|
||
|
|
77be1ac578
|
||
|
|
475e14bfa7
|
||
|
|
8dc460c0aa
|
||
|
|
372afcdd4f
|
||
|
|
c202038455
|
||
|
|
1758fad8ca
|
||
|
|
c143525f30
|
||
|
|
f5647e53f6
|
||
|
|
33f1e6ab73
|
||
|
|
4f5fecfb34
|
||
|
|
32c7e465c8
|
||
|
|
c7e9fb799d
|
||
|
|
988bd06d54
|
||
|
|
fb695ce359
|
||
|
|
869a4a36d9
|
||
|
|
5477df4578
|
||
|
|
be8a3ac93f
|
||
|
|
4cec465a16
|
||
|
|
95cfcf915b
|
||
|
|
b4644cfc9f
|
||
|
|
8d1ddc9c78
|
||
|
|
a728450607
|
||
|
|
8d8dd23b3a
|
||
|
|
be628194ec
|
||
|
|
31f9ae1733
|
||
|
|
151ff538db
|
No files matched your search
+142
@@ -1,5 +1,147 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.1 — 2026-04-12
|
||||
|
||||
Language coverage release. 10 new DAP adapters bring built-in support from
|
||||
12 to 22 languages, prioritized by language popularity and DAP adapter
|
||||
maturity.
|
||||
|
||||
### Added
|
||||
|
||||
- **Dart** (with optional **Flutter**) — auto-detects `flutter` on PATH and
|
||||
adds a "Launch Flutter app" configuration alongside the plain Dart file
|
||||
launch. Uses `dart debug_adapter` / `flutter debug_adapter`.
|
||||
- **Bash / Sh** — `bash-debug-adapter` with automatic `bashdb_dir` probing
|
||||
from the mason package path.
|
||||
- **R** — `vscDebugger` R-package integration. Preflights the package via
|
||||
`Rscript` and silently skips registration if it's missing.
|
||||
- **Elixir** — `elixir-ls-debugger` with `mix_task` launch (default task
|
||||
`test`). Falls back to the mason package `debugger.sh` if the wrapper
|
||||
binary isn't on PATH.
|
||||
- **Haskell** — `haskell-debug-adapter`.
|
||||
- **OCaml** — `ocamlearlybird`.
|
||||
- **Zig / Nim / Crystal** — routed through the existing `codelldb` adapter
|
||||
via filetype extension (no new adapter; just added filetypes to the
|
||||
existing codelldb configuration).
|
||||
- **F#** — routed through the existing `netcoredbg` adapter (same
|
||||
`.dll`-launch flow as C#).
|
||||
|
||||
### Internal
|
||||
|
||||
- Extended `tests/test_adapters.lua` to cover the three new modular adapter
|
||||
modules (`dart`, `bash`, `r`).
|
||||
- All new registrations follow the existing `find_executable` +
|
||||
`loader.register` pattern and silently skip when the required binary
|
||||
isn't found — matches the "no noise when prereqs missing" invariant of
|
||||
the previously-shipped adapters.
|
||||
|
||||
## 0.2.0 — 2026-04-12
|
||||
|
||||
Major UI rework and stability release. Two floating chrome bars replace the
|
||||
old single toolbar, every resize/tabline-toggle edge case is handled, and
|
||||
every highlight now derives from the user's colorscheme.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **`quit_exits_mode` option removed.** `q` is now context-aware: if a DAP
|
||||
session is running, `q` terminates the session but keeps debug mode
|
||||
active (so you can read the Console / inspect post-mortem state). Press
|
||||
`q` again to exit debug mode.
|
||||
- **`help_on_enter` default is now `false`** (was `true`). The control bar
|
||||
always shows `(?) help`, so the auto-popup is no longer needed. Set to
|
||||
`true` to restore the splash behavior.
|
||||
- **Default layout changed.** Sidebar panes are now Call Stack → Scopes →
|
||||
Watches → Breakpoints (all 25%). REPL is no longer in the default
|
||||
layout — press `E` during debug mode to open it as a floating window.
|
||||
- **Console pane sizing.** Bottom Console pane is `max(8 rows, 20% of
|
||||
editor lines)` (previously hardcoded). Configurable via `console_height`.
|
||||
|
||||
### Added
|
||||
|
||||
- **Two-bar floating chrome**, independent of any statusline plugin:
|
||||
- Status bar (top, 2 rows): brand, live `dap.status()` text, state chip
|
||||
(` DEBUG · READY ` / ` · RUNNING ` / ` · PAUSED `)
|
||||
- Control bar (bottom, 2 rows, above statusline): clickable buttons for
|
||||
continue, step over, step into, step out, restart (during session),
|
||||
terminate; `(?) help` right-anchored
|
||||
- Italic qualifiers (`over`, `into`, `out`) on step controls — dropped
|
||||
automatically at narrow widths
|
||||
- **Keymap-aware control bar**: legend reflects the user's actual
|
||||
remapped keys (honors `keys` override; disabled actions are omitted)
|
||||
- **IP arrow markers** on stopped lines: paired 👉 gutter sign (priority
|
||||
500 so it always wins over breakpoint signs) + 👈 REASON virtual text
|
||||
at EOL
|
||||
- **Optional debug-mode colorscheme** via `colorscheme` option — theme
|
||||
swaps on debug enter, restores on exit
|
||||
- **Theme-agnostic separator color**: derives from `WinBar.fg` so the bar
|
||||
chrome matches any colorscheme; global `WinSeparator` override during
|
||||
debug mode gives the whole UI a consistent edge color
|
||||
- **`clear_console_on_start`** (default `true`): wipe Console scrollback
|
||||
on session start/restart while preserving the terminal channel
|
||||
- **`collapsed_scopes`** (default `{ "Registers" }`): auto-collapse noisy
|
||||
scopes; codelldb doesn't mark Registers expensive even though it's a
|
||||
huge dump
|
||||
- **`max_value_width`** (default `2000`): per-line truncation for values
|
||||
in Scopes/Watches; prevents register-dump bleed
|
||||
- **`console_refresh_ms`** (default `50`): trailing-edge throttle for
|
||||
Console repaints under high-throughput stdout
|
||||
- **`sidebar`** option — `"left"` (default) or `"right"`
|
||||
- **`active_window_highlight`** (default `true`): dim inactive dapui pane
|
||||
borders so the focused pane is visually obvious
|
||||
- **`W` and `K` in visual mode**: watch / hover operate on the selection
|
||||
- Live `dap.status()` polling via `User DapProgressUpdate` autocmd —
|
||||
status text stays current across progress messages
|
||||
- Source window is focused before DAP jumps on `event_stopped`, so
|
||||
stepped-into library files open in the source area (not in a bar or
|
||||
dapui pane)
|
||||
- Console repaint throttle prevents editor lag under chatty programs
|
||||
|
||||
### Changed
|
||||
|
||||
- Bars are now real splits (not overlapping floats) — dapui and source
|
||||
windows tuck between them naturally
|
||||
- `pin_dapui_sizes()` only re-pins on EXTERNAL geometry changes
|
||||
(bufferline tabline toggle, `wincmd =`) detected by total-height
|
||||
delta; user's manual pane drags survive
|
||||
- `ensure_dapui()` runs ONCE per plugin lifetime (not per `M.enter`),
|
||||
avoiding "Debug adapter didn't respond" timeouts on toggle
|
||||
- Bars clamp to exactly 2 rows on every VimResized / WinResized (prevents
|
||||
stray EOB `~` when nvim grows a fixed-height window)
|
||||
- `setup()` is now fully idempotent — safe to call multiple times with
|
||||
different opts, and `plugin/turbo-debug.lua`'s deferred auto-setup
|
||||
yields to explicit user `setup({...})`
|
||||
- Copyright holder updated to `Stephen Waits <steve@waits.net>`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bars recreate themselves if killed by shrink-below-minimum resize or
|
||||
tabline appearance/disappearance
|
||||
- Inherited `winbar` from the splitting source window is cleared on bar
|
||||
windows (was showing phantom separator rows)
|
||||
- Layout re-asserts correctly after `wincmd =` (tiny-equalizer pass)
|
||||
- Sidebar proportions stable across re-entry (no drift across toggles)
|
||||
- Bar separators bright on both outward edges (top-of-sbar, bottom-of-cbar)
|
||||
- Deferred dependency loading: `plugin/turbo-debug.lua` waits for
|
||||
`vim.pack.add` to finalize runtimepath before calling `setup()`
|
||||
|
||||
### Internal
|
||||
|
||||
- Extracted 10+ helper functions in `mode.lua` (`win_ok`, `buf_ok`,
|
||||
`is_dapui_pane_ft`, `for_each_dapui_pane`, `safe_extmark`,
|
||||
`bar_set_lines`, `safe_set_height`, `if_still_active`,
|
||||
`invalidate_dead_bars`, `focus_source_win`, `unset_and_restore`,
|
||||
`install_cs_listener`) — deduped ~25 call sites
|
||||
- Data-driven sign definitions (`breakpoints.lua`) and Python
|
||||
interpreter candidate probing
|
||||
- Dead code removed: unused `reposition_bars` stub, vestigial
|
||||
`sbar_zones` / `handle_sbar_click` click infrastructure (status bar
|
||||
has no clickable zones)
|
||||
- Added regression tests: `test_render_bars` (sbar/cbar output,
|
||||
dimensions, zones, overflow fallback), `test_highlights_cs`
|
||||
(ColorScheme augroups, highlight re-apply), `test_helpers`
|
||||
(keyof branching, source_window filtering)
|
||||
- `stylua.toml` added; all Lua code formatted
|
||||
|
||||
## 0.1.0 — 2026-04-11
|
||||
|
||||
Initial release.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Steve Waits
|
||||
Copyright (c) 2026 Stephen Waits <steve@waits.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -4,24 +4,64 @@ A zero-config debugging experience for Neovim 0.12+. One plugin install, full
|
||||
IDE-class debugging: inline variable values, hover inspection, persistent
|
||||
breakpoints, watch expressions, and modal keybindings that just work.
|
||||
|
||||
Inspired by Turbo Pascal — the first IDE where you could just hit a key
|
||||
and start debugging.
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
```lua
|
||||
vim.pack.add("swaits/turbo-debug.nvim")
|
||||
vim.pack.add("swaits/turbo-debug")
|
||||
```
|
||||
|
||||
That's it. No other config needed. See `:help turbo-debug` for customization.
|
||||
|
||||
## Usage
|
||||
## How It Works
|
||||
|
||||
Everything lives under `<leader>d` (with which-key support if installed):
|
||||
turbo-debug is **modal**. Press `<leader>dd` and you enter debug mode — the
|
||||
dap-ui layout opens, keybindings activate, virtual text lights up, and
|
||||
you're debugging. Press `<leader>dd` again (or `q`) and everything
|
||||
disappears: the session terminates, the UI closes, and every keybinding is
|
||||
restored to exactly what it was.
|
||||
|
||||
| Key | Action | Available |
|
||||
|-----|--------|-----------|
|
||||
| `<leader>dd` | Toggle debug mode | Always |
|
||||
| `<leader>dx` | Toggle breakpoint | Always |
|
||||
| `<leader>dX` | Conditional breakpoint | Always |
|
||||
| `<leader>dD` | Clear all breakpoints | Always |
|
||||
This modal approach is inspired by
|
||||
[debugmaster.nvim](https://github.com/MironPascalCaseFan/debugmaster.nvim).
|
||||
During a debug session you **navigate** code and **inspect** state; you
|
||||
don't **edit**. So debug mode shadows editing keys (`c`, `d`, `s`, `r`,
|
||||
`x`) with debug actions and leaves all navigation keys untouched (`h`,
|
||||
`j`, `k`, `l`, `w`, `b`, `e`, `f`, `/`, `n`, etc.).
|
||||
|
||||
### A nice text *and* GUI debugger
|
||||
|
||||
The modal keymap is deliberately **left-hand optimized** so the right hand
|
||||
stays on the mouse for interacting with the dap-ui panes:
|
||||
|
||||
- **Left hand on keyboard**: `c s d r C x X D W E q R` fire the twelve
|
||||
rapid-use debug actions without leaving home row.
|
||||
- **Right hand on mouse**: click into Watches/Scopes/Stacks/Breakpoints
|
||||
panes to expand trees, jump to frames, add watch expressions, or toggle
|
||||
breakpoints from the GUI.
|
||||
|
||||
Only two modal keys live on the right hand, both by universal convention:
|
||||
`K` for hover (matches the LSP hover key) and `?` for help.
|
||||
|
||||
Inside dap-ui panes, the native dap-ui mappings still work — `d` removes a
|
||||
watch, `r` sends to REPL, `e` edits. The modal `d` (step-into) and `r`
|
||||
(step-out) yield to dap-ui in those buffers so you can manage the GUI
|
||||
without exiting debug mode.
|
||||
|
||||
## Keybindings
|
||||
|
||||
Everything lives under `<leader>d` (with
|
||||
[which-key](https://github.com/folke/which-key.nvim) support if installed):
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `<leader>dd` | Toggle debug mode |
|
||||
| `<leader>dx` | Toggle breakpoint |
|
||||
| `<leader>dX` | Conditional breakpoint |
|
||||
| `<leader>dD` | Clear all breakpoints |
|
||||
|
||||
During debug mode, single-key controls:
|
||||
|
||||
@@ -32,23 +72,168 @@ During debug mode, single-key controls:
|
||||
| `d` | Step into | `D` | Clear all breakpoints |
|
||||
| `r` | Step out | `W` | Watch expression |
|
||||
| `C` | Run to cursor | `K` | Hover / inspect |
|
||||
| `q` | Terminate session | `E` | Eval in REPL |
|
||||
| `q` | Smart quit (terminate → exit) | `E` | Eval in REPL |
|
||||
| `R` | Restart session | `?` | Help popup |
|
||||
|
||||
All navigation keys preserved. Modal keybindings vanish when you exit debug mode.
|
||||
`q` is context-aware: if a session is running, it terminates the session
|
||||
and leaves you in debug mode (so you can read the console and inspect
|
||||
the exit state). If no session is running, `q` exits debug mode entirely.
|
||||
|
||||
Press `?` during debug mode for a quick reference that also lists dap-ui's
|
||||
native in-pane bindings (`<CR>`, `e`, `o`, `d`, `r`, `t`).
|
||||
|
||||
### Extending keymaps
|
||||
|
||||
Every key is user-overridable. Remap, disable, or add bindings via
|
||||
`setup()`:
|
||||
|
||||
```lua
|
||||
require("turbo-debug").setup({
|
||||
keys = {
|
||||
step_over = "n", -- remap a modal key
|
||||
watch = false, -- disable a modal key
|
||||
},
|
||||
global_keys = {
|
||||
toggle = "<leader>D", -- change the toggle chord
|
||||
},
|
||||
sidebar = "right", -- "left" (default) or "right"
|
||||
help_on_enter = true, -- show help popup on debug mode entry (default false)
|
||||
|
||||
-- Optional theme swap for debug sessions. `nil` (default) keeps the
|
||||
-- current colorscheme; any string is passed to `:colorscheme` on enter
|
||||
-- and the original is restored on exit.
|
||||
colorscheme = nil,
|
||||
|
||||
-- Auto stop-on-entry when no breakpoints are set (Turbo Pascal model):
|
||||
-- press `c` on an empty-BP project and the debugger pauses at main().
|
||||
-- Set false to always run until a breakpoint instead.
|
||||
stop_on_entry_when_no_breakpoints = true,
|
||||
|
||||
-- Cap per-line value length in Scopes/Watches. Values longer than this
|
||||
-- get truncated with `…`. Default 2000 only catches pathological register
|
||||
-- dumps. Set 0 to disable truncation entirely.
|
||||
max_value_width = 2000,
|
||||
|
||||
-- Console repaint throttle (ms). Max 1000/console_refresh_ms redraws per
|
||||
-- second when the debugged program spams stdout. Prevents editor DOS.
|
||||
console_refresh_ms = 50,
|
||||
|
||||
-- Clear the Console pane on session start/restart (preserves the
|
||||
-- terminal channel — only wipes scrollback).
|
||||
clear_console_on_start = true,
|
||||
|
||||
-- Scope names to default-collapse in Scopes. dapui auto-collapses any
|
||||
-- scope the adapter marks `expensive`, but codelldb doesn't flag
|
||||
-- Registers as expensive even though it's a huge dump.
|
||||
collapsed_scopes = { "Registers" },
|
||||
|
||||
-- Dim inactive dapui pane borders so the focused pane is obvious.
|
||||
active_window_highlight = true,
|
||||
})
|
||||
```
|
||||
|
||||
### Chrome bars
|
||||
|
||||
When debug mode is active, two 2-row floating bars appear, completely
|
||||
independent of lualine, heirline, or any other statusline plugin:
|
||||
|
||||
**Status bar (top)** shows:
|
||||
- The `turbo-debug` brand
|
||||
- Live `dap.status()` progress text
|
||||
- Session state chip: ` DEBUG · READY ` / ` · RUNNING ` / ` · PAUSED `
|
||||
(color-coded via `DiagnosticHint` / `DiagnosticInfo` / `DiagnosticError`)
|
||||
|
||||
**Control bar (bottom, above your statusline)** shows:
|
||||
- Clickable buttons: `(c)ontinue` / `(s)tep` *over* / `(d)escend` *into* /
|
||||
`(r)eturn` *out* / `(R)estart` (during session) / `(q) terminate`
|
||||
- `(?) help` right-anchored
|
||||
- Italic qualifiers (`over`, `into`, `out`) are inline when width allows;
|
||||
auto-dropped on narrow terminals
|
||||
|
||||
Both bars:
|
||||
- Recreate themselves if killed by layout changes (bufferline tabline
|
||||
toggles, `wincmd =`, etc.)
|
||||
- Re-render on every DAP state change (|User DapProgressUpdate|)
|
||||
- Close cleanly on exit
|
||||
|
||||
### Design notes
|
||||
|
||||
- **Rounded borders** are on every *floating* window this plugin opens:
|
||||
the help popup (`?`), the hover popup (`K`), and the debug-eval popup
|
||||
(`E`). Floating windows are the only windows Neovim draws borders around
|
||||
in the terminal grid. Splits (the Scopes/Watches/Stacks/Breakpoints/
|
||||
Console panes) use the user's `WinSeparator` character — that's a
|
||||
terminal-rendering limit, not a plugin choice.
|
||||
|
||||
- **No hardcoded colors.** Every highlight links to a standard group
|
||||
(`DiagnosticError`, `Function`, `Comment`, `CursorLine`, `Visual`,
|
||||
`Search`, `Title`, `Special`, `WinBar`, etc.) so the plugin
|
||||
automatically inherits your colorscheme on the fly. Switch themes
|
||||
mid-session (or set `colorscheme = "high-contrast"` in `setup()` for
|
||||
a dedicated debug theme) and everything re-paints.
|
||||
|
||||
- **Emoji** (supplementary-plane, not VS16-dependent) for iconography:
|
||||
🔴 🟡 ⭕ 👉 📝 for gutter signs; 🟢 🚀 👣 🔽 🔼 🔄 🛑 for control
|
||||
buttons; 🔎 👀 📚 🔴 💻 for pane titles. Colored emoji render
|
||||
consistently in modern terminals (Ghostty, Kitty, WezTerm, iTerm)
|
||||
without font-dependent rendering quirks.
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Adapter binaries must be installed (via mason, package manager, etc).
|
||||
turbo-debug ships adapter configs for 22 languages. You install the adapter
|
||||
binary (via [mason.nvim](https://github.com/williamboman/mason.nvim), your
|
||||
package manager, or manually) — turbo-debug handles all the configuration.
|
||||
|
||||
| Language | Adapter | Language | Adapter |
|
||||
|----------|---------|----------|---------|
|
||||
| Python | debugpy | C# | netcoredbg |
|
||||
| JavaScript / TS | js-debug | Java | java-debug-adapter |
|
||||
| Go | delve | Kotlin | kotlin-debug |
|
||||
| C / C++ | codelldb | PHP | php-debug |
|
||||
| Rust | codelldb | Ruby | rdbg |
|
||||
| Swift | codelldb | Lua (Neovim) | osv |
|
||||
| JavaScript / TS | js-debug | F# | netcoredbg |
|
||||
| Go | delve | Java | java-debug-adapter |
|
||||
| C / C++ | codelldb | Kotlin | kotlin-debug |
|
||||
| Rust | codelldb | PHP | php-debug |
|
||||
| Swift | codelldb | Ruby | rdbg |
|
||||
| Zig | codelldb | Elixir | elixir-ls |
|
||||
| Nim | codelldb | Haskell | haskell-debug-adapter |
|
||||
| Crystal | codelldb | OCaml | ocamlearlybird |
|
||||
| Dart / Flutter | dart-debug | R | vscDebugger |
|
||||
| Bash / Sh | bash-debug-adapter | Lua (Neovim) | osv |
|
||||
|
||||
Special treatment:
|
||||
- **Rust**: pressing `c` runs `cargo build` automatically and picks the
|
||||
binary via `vim.ui.select`.
|
||||
- **Python**: auto-detects virtualenvs (`VIRTUAL_ENV`, `CONDA_PREFIX`,
|
||||
`.venv`, `venv`, `env`, `.env`).
|
||||
- **Dart**: if `flutter` is on PATH, a "Launch Flutter app" configuration
|
||||
is added alongside the plain Dart file launch.
|
||||
- **R**: preflights that the `vscDebugger` R package is installed; silently
|
||||
skips registration if missing.
|
||||
|
||||
## What This Plugin Actually Does
|
||||
|
||||
turbo-debug is **glue**. It does not implement a debugger, a UI framework,
|
||||
virtual text rendering, or breakpoint persistence. It configures and wires
|
||||
together these excellent plugins into a single cohesive experience:
|
||||
|
||||
| Plugin | What it provides |
|
||||
|--------|-----------------|
|
||||
| [nvim-dap](https://github.com/mfussenegger/nvim-dap) | DAP client — the core debug engine |
|
||||
| [nvim-dap-ui](https://github.com/rcarriga/nvim-dap-ui) | Scopes, stacks, watches, REPL, console UI |
|
||||
| [nvim-nio](https://github.com/nvim-neotest/nvim-nio) | Async IO (required by nvim-dap-ui) |
|
||||
| [nvim-dap-virtual-text](https://github.com/theHamsta/nvim-dap-virtual-text) | Inline variable values in source code |
|
||||
| [persistent-breakpoints.nvim](https://github.com/Weissle/persistent-breakpoints.nvim) | Breakpoints that survive restarts |
|
||||
|
||||
All five are loaded automatically via `vim.pack.add`. You never declare them.
|
||||
|
||||
Adapter configurations are inlined from the work of these projects (no
|
||||
runtime dependency, we just studied their configs and wrote plain Lua tables):
|
||||
|
||||
- [nvim-dap-python](https://github.com/mfussenegger/nvim-dap-python)
|
||||
- [nvim-dap-go](https://github.com/leoluz/nvim-dap-go)
|
||||
- [nvim-dap-vscode-js](https://github.com/mxsdev/nvim-dap-vscode-js)
|
||||
- [nvim-dap-lldb](https://github.com/julianolf/nvim-dap-lldb)
|
||||
- [nvim-dap-cs](https://github.com/NicholasMata/nvim-dap-cs)
|
||||
- [nvim-dap-ruby](https://github.com/suketa/nvim-dap-ruby)
|
||||
- [nvim-dap-kotlin](https://github.com/Mgenuit/nvim-dap-kotlin)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
+265
-78
@@ -9,34 +9,37 @@ CONTENTS *turbo-debug-contents*
|
||||
4. Debug Mode ............................ |turbo-debug-debug-mode|
|
||||
5. Keybindings ........................... |turbo-debug.keys|
|
||||
6. Breakpoints ........................... |turbo-debug-breakpoints|
|
||||
7. UI Layout ............................. |turbo-debug-layout|
|
||||
8. Adapters .............................. |turbo-debug.adapters|
|
||||
9. API ................................... |turbo-debug-api|
|
||||
10. Troubleshooting ....................... |turbo-debug-troubleshooting|
|
||||
7. Chrome Bars ........................... |turbo-debug-chrome|
|
||||
8. UI Layout ............................. |turbo-debug-layout|
|
||||
9. Adapters .............................. |turbo-debug.adapters|
|
||||
10. API ................................... |turbo-debug-api|
|
||||
11. Troubleshooting ....................... |turbo-debug-troubleshooting|
|
||||
|
||||
==============================================================================
|
||||
1. Introduction *turbo-debug-introduction*
|
||||
|
||||
turbo-debug.nvim gives you a fully-featured, beautiful debugging experience
|
||||
out of one line of config. Under the hood it wires together nvim-dap,
|
||||
nvim-dap-ui, nvim-dap-virtual-text, and persistent-breakpoints into a single
|
||||
cohesive experience with modal keybindings.
|
||||
turbo-debug.nvim gives you a fully-featured debugging experience out of one
|
||||
line of config. Under the hood it wires together nvim-dap, nvim-dap-ui,
|
||||
nvim-dap-virtual-text, and persistent-breakpoints into a single cohesive
|
||||
modal experience.
|
||||
|
||||
Features:
|
||||
- Modal keybindings active only during debug sessions — single-key controls
|
||||
that shadow editing keys, cleanly restored on exit
|
||||
- Inline virtual text showing variable values in source code
|
||||
- Hover inspection with `K`
|
||||
- Persistent breakpoints that survive restarts
|
||||
- Watch expressions from the code window with `W`
|
||||
- Scopes, stacks, REPL, and console panels
|
||||
- Modal keybindings active only during debug sessions
|
||||
- Help box showing all keybindings
|
||||
- Zero-config adapters for 10+ languages
|
||||
- Persistent breakpoints that survive Neovim restarts
|
||||
- Hover inspection (|K|) and watch expressions (|W|) from the code window
|
||||
- Floating REPL eval (|E|)
|
||||
- Floating status bar + control bar chrome (independent of any statusline
|
||||
plugin)
|
||||
- Zero-config adapters for 22 languages
|
||||
- Theme-agnostic colors (every highlight links to a standard group)
|
||||
|
||||
==============================================================================
|
||||
2. Installation *turbo-debug-installation*
|
||||
|
||||
Add one line to your init.lua: >lua
|
||||
vim.pack.add("swaits/turbo-debug.nvim")
|
||||
vim.pack.add("swaits/turbo-debug")
|
||||
<
|
||||
That's it. All dependencies are managed automatically.
|
||||
|
||||
@@ -47,26 +50,125 @@ Requires Neovim >= 0.12.
|
||||
|
||||
Setup is optional. The plugin works with no configuration. To customize: >lua
|
||||
require("turbo-debug").setup({
|
||||
help_on_enter = true, -- show help splash on debug mode entry
|
||||
quit_exits_mode = true, -- q terminates AND exits debug mode
|
||||
builtin_adapters = true, -- register built-in language adapters
|
||||
keys = { ... }, -- override modal keybindings
|
||||
global_keys = { ... }, -- override always-available keybindings
|
||||
adapters = { ... }, -- override or add language adapters
|
||||
dapui = { ... }, -- override nvim-dap-ui layout/settings
|
||||
virtual_text = { ... }, -- override nvim-dap-virtual-text settings
|
||||
help_on_enter = false,
|
||||
builtin_adapters = true,
|
||||
stop_on_entry_when_no_breakpoints = true,
|
||||
max_value_width = 2000,
|
||||
console_refresh_ms = 50,
|
||||
collapsed_scopes = { "Registers" },
|
||||
clear_console_on_start = true,
|
||||
sidebar = "left",
|
||||
colorscheme = nil,
|
||||
active_window_highlight = true,
|
||||
keys = { ... },
|
||||
global_keys = { ... },
|
||||
adapters = { ... },
|
||||
dapui = { ... },
|
||||
virtual_text = { ... },
|
||||
})
|
||||
<
|
||||
*turbo-debug.setup.help_on_enter*
|
||||
help_on_enter ~
|
||||
Type: boolean, Default: `true`
|
||||
Show the help box automatically when entering debug mode.
|
||||
Type: boolean, Default: `false`
|
||||
Show the help popup automatically when entering debug mode. The control
|
||||
bar always shows `(?) help`, so most users don't need the splash.
|
||||
|
||||
*turbo-debug.setup.builtin_adapters*
|
||||
builtin_adapters ~
|
||||
Type: boolean, Default: `true`
|
||||
Register built-in adapters for supported languages. Set to `false` to
|
||||
disable all built-in adapter registration.
|
||||
Register the built-in language adapters. Set to `false` to opt out of
|
||||
all built-in adapter registration and manage adapters yourself.
|
||||
|
||||
*turbo-debug.setup.stop_on_entry_when_no_breakpoints*
|
||||
stop_on_entry_when_no_breakpoints ~
|
||||
Type: boolean, Default: `true`
|
||||
When `c` is pressed with no breakpoints set, launch the first adapter
|
||||
configuration for the filetype with `stopOnEntry = true` so the debugger
|
||||
pauses at the program's entry point. Matches the Turbo Pascal "press
|
||||
key to start debugging" model. Set `false` to always run until a
|
||||
breakpoint.
|
||||
|
||||
*turbo-debug.setup.max_value_width*
|
||||
max_value_width ~
|
||||
Type: integer, Default: `2000`
|
||||
Per-line character cap for variable values rendered in Scopes/Watches.
|
||||
Values exceeding this are truncated with `…`. Default 2000 only catches
|
||||
pathological multi-KB dumps; normal values pass through. Set to `0` to
|
||||
disable truncation entirely.
|
||||
|
||||
*turbo-debug.setup.console_refresh_ms*
|
||||
console_refresh_ms ~
|
||||
Type: integer, Default: `50`
|
||||
Trailing-edge throttle (ms) for Console pane repaints when the debugged
|
||||
program outputs. Max `1000/console_refresh_ms` redraws per second.
|
||||
Prevents editor lag under high-throughput output.
|
||||
|
||||
*turbo-debug.setup.collapsed_scopes*
|
||||
collapsed_scopes ~
|
||||
Type: string[], Default: `{ "Registers" }`
|
||||
Scope names (as reported by the DAP adapter) to default-collapse. Some
|
||||
adapters (codelldb for Rust/C++, for example) don't mark Registers as
|
||||
"expensive" even though it's a huge dump. Any scope name in this list
|
||||
is treated as expensive — dapui auto-collapses expensive scopes.
|
||||
|
||||
*turbo-debug.setup.clear_console_on_start*
|
||||
clear_console_on_start ~
|
||||
Type: boolean, Default: `true`
|
||||
Clear the Console pane on session start/restart. The terminal channel
|
||||
is preserved — only the displayed scrollback is wiped.
|
||||
|
||||
*turbo-debug.setup.sidebar*
|
||||
sidebar ~
|
||||
Type: string, Default: `"left"`
|
||||
Sidebar position: `"left"` or `"right"`. Controls which side of the
|
||||
editor the Stacks/Scopes/Watches/Breakpoints column appears on.
|
||||
|
||||
*turbo-debug.setup.colorscheme*
|
||||
colorscheme ~
|
||||
Type: string|nil, Default: `nil`
|
||||
Optional colorscheme to switch to while debug mode is active. `nil`
|
||||
keeps the user's current colorscheme. Any other string value is passed
|
||||
to `:colorscheme`. The previous colorscheme is restored on exit.
|
||||
|
||||
*turbo-debug.setup.active_window_highlight*
|
||||
active_window_highlight ~
|
||||
Type: boolean, Default: `true`
|
||||
Dim inactive dapui panes' borders so the focused pane is visually
|
||||
obvious.
|
||||
|
||||
*turbo-debug.setup.keys*
|
||||
keys ~
|
||||
Type: table, Default: see |turbo-debug.keys|
|
||||
Override modal keybindings (active only during debug mode). Set any
|
||||
entry to `false` to disable that action entirely.
|
||||
|
||||
*turbo-debug.setup.global_keys*
|
||||
global_keys ~
|
||||
Type: table, Default: see |turbo-debug.keys|
|
||||
Override always-available keybindings. Set any entry to `false` to
|
||||
disable.
|
||||
|
||||
*turbo-debug.setup.adapters*
|
||||
adapters ~
|
||||
Type: table, Default: `{}`
|
||||
Override or add language adapters. Keyed by adapter name; each value
|
||||
is a table with `adapter` (passed to |dap.adapters|) and/or
|
||||
`configurations` (passed to |dap.configurations|) fields. User
|
||||
overrides are applied after the built-ins, so existing adapter entries
|
||||
are replaced.
|
||||
|
||||
*turbo-debug.setup.dapui*
|
||||
dapui ~
|
||||
Type: table
|
||||
Pass-through configuration for nvim-dap-ui's |dapui.setup()|. Override
|
||||
icons, controls, floating window style, render options, etc.
|
||||
|
||||
*turbo-debug.setup.virtual_text*
|
||||
virtual_text ~
|
||||
Type: table
|
||||
Pass-through configuration for nvim-dap-virtual-text. Set
|
||||
`virtual_text = { enabled = false }` to disable inline variable
|
||||
values entirely.
|
||||
|
||||
==============================================================================
|
||||
4. Debug Mode *turbo-debug-debug-mode*
|
||||
@@ -74,17 +176,28 @@ builtin_adapters ~
|
||||
Press `<leader>dd` to toggle debug mode.
|
||||
|
||||
Entering debug mode:
|
||||
1. Opens nvim-dap-ui with the configured layout
|
||||
2. Enables inline virtual text for variable values
|
||||
3. Activates modal keybindings (buffer-local)
|
||||
4. Shows winbar indicator and help splash (3s)
|
||||
1. Switches colorscheme (if |turbo-debug.setup.colorscheme| is set)
|
||||
2. Applies turbo-debug highlight groups
|
||||
3. Opens nvim-dap-ui (sidebar + Console pane)
|
||||
4. Creates the floating status bar (top) and control bar (bottom)
|
||||
5. Installs modal keybindings on every buffer
|
||||
6. Enables nvim-dap-virtual-text
|
||||
7. Optionally shows the help popup (|turbo-debug.setup.help_on_enter|)
|
||||
|
||||
Exiting debug mode (any of):
|
||||
- `<leader>dd` again (manual toggle)
|
||||
- `q` (terminate session — exits debug mode if `quit_exits_mode`)
|
||||
- DAP session ends naturally
|
||||
- `q` when no DAP session is running
|
||||
- Context-aware `q`: if a session IS running, `q` terminates the session
|
||||
but leaves debug mode active so you can inspect the Console / watch
|
||||
post-mortem state. Press `q` again to exit debug mode.
|
||||
|
||||
On exit, everything is restored: keymaps, UI, virtual text, cursor.
|
||||
On exit, everything is restored:
|
||||
- Modal keymaps removed, any prior user mappings re-installed
|
||||
- Source window's original `winbar` restored
|
||||
- `WinSeparator` highlight restored byte-for-byte
|
||||
- Original colorscheme restored (if one was swapped in)
|
||||
- dapui panes closed, control bars removed
|
||||
- Virtual text disabled
|
||||
|
||||
==============================================================================
|
||||
5. Keybindings *turbo-debug.keys*
|
||||
@@ -95,7 +208,7 @@ keys you don't need while debugging, but preserve all navigation keys.
|
||||
Default keymap:
|
||||
|
||||
Key Action Mnemonic ~
|
||||
`c` Continue Universal across debuggers
|
||||
`c` Continue / start Universal across debuggers
|
||||
`s` Step over Step
|
||||
`d` Step into Descend into the function
|
||||
`r` Step out Return from the function
|
||||
@@ -105,8 +218,8 @@ Default keymap:
|
||||
`D` Clear all breakpoints Delete all
|
||||
`W` Watch expr under cursor Watch
|
||||
`K` Hover / inspect Matches LSP hover convention
|
||||
`E` Eval in REPL Evaluate
|
||||
`q` Terminate session Quit debugging
|
||||
`E` Eval in REPL Evaluate (floating REPL)
|
||||
`q` Smart quit Context-aware terminate / exit
|
||||
`R` Restart session Rerun
|
||||
`?` Help popup Show keybindings
|
||||
|
||||
@@ -117,29 +230,30 @@ Always available (under `<leader>d` prefix, with which-key support):
|
||||
`<leader>dX` Conditional breakpoint Shift variant
|
||||
`<leader>dD` Clear all breakpoints Delete all
|
||||
|
||||
`W` and `K` also work in visual mode — they operate on the visual
|
||||
selection.
|
||||
|
||||
Override any key: >lua
|
||||
require("turbo-debug").setup({
|
||||
keys = { -- modal keys (debug mode only)
|
||||
continue = "g",
|
||||
step_over = false, -- set to false to disable
|
||||
keys = {
|
||||
continue = "g", -- remap a modal key
|
||||
step_over = false, -- disable a modal key
|
||||
},
|
||||
global_keys = { -- always-available keys
|
||||
global_keys = {
|
||||
toggle = "<leader>DD",
|
||||
breakpoint = "<leader>db",
|
||||
},
|
||||
})
|
||||
<
|
||||
Preserved keys (not shadowed):
|
||||
- Navigation: `h j k l w b e f F t T / n N 0 ^ $ G g { } % [ ]`
|
||||
- Insert: `i a o O` (needed for REPL/terminal)
|
||||
- Visual/yank: `v V y p u`
|
||||
- All Ctrl/leader combos
|
||||
Inside dapui panes (Watches / Scopes / Stacks / Breakpoints / REPL), the
|
||||
native dapui mappings `<CR>` / `e` / `o` / `d` / `r` / `t` still work —
|
||||
our modal `d` (step-into) and `r` (step-out) yield inside those buffers.
|
||||
|
||||
*turbo-debug.setup.quit_exits_mode*
|
||||
quit_exits_mode ~
|
||||
Type: boolean, Default: `true`
|
||||
Whether `q` (terminate) also exits debug mode. Set to `false` if you
|
||||
want `q` to terminate the process but keep the debug UI open.
|
||||
Preserved keys (never shadowed in source buffers):
|
||||
- Navigation: `h j k l w b e f F t T / n N 0 ^ $ G g { } % [ ]`
|
||||
- Insert: `i a o O`
|
||||
- Visual/yank/edit: `v V y p u`
|
||||
- All Ctrl/Alt/leader combos
|
||||
|
||||
==============================================================================
|
||||
6. Breakpoints *turbo-debug-breakpoints*
|
||||
@@ -147,23 +261,57 @@ quit_exits_mode ~
|
||||
Breakpoints are managed by persistent-breakpoints.nvim. They persist across
|
||||
Neovim restarts and are visible in the sign column at all times.
|
||||
|
||||
`<leader>dx` toggles a breakpoint (always available).
|
||||
`<leader>dX` sets a conditional breakpoint (always available).
|
||||
`x`, `X`, `D` also work as shortcuts during debug mode.
|
||||
Global shortcuts (always available):
|
||||
`<leader>dx` Toggle breakpoint
|
||||
`<leader>dX` Conditional breakpoint (prompts for condition)
|
||||
`<leader>dD` Clear all breakpoints
|
||||
|
||||
Modal shortcuts (debug mode only): `x` `X` `D` do the same.
|
||||
|
||||
Gutter signs (colorful emoji in modern terminals):
|
||||
🔴 breakpoint
|
||||
🟡 conditional breakpoint
|
||||
⭕ rejected breakpoint (DAP server couldn't set it)
|
||||
👉 current execution line (paired with `👈 REASON` virtual text)
|
||||
📝 logpoint
|
||||
|
||||
==============================================================================
|
||||
7. UI Layout *turbo-debug-layout*
|
||||
7. Chrome Bars *turbo-debug-chrome*
|
||||
|
||||
Debug mode opens two floating bars, independent of any statusline plugin:
|
||||
|
||||
Status bar (top, 2 rows) shows:
|
||||
- `turbo-debug` brand
|
||||
- `dap.status()` progress text (right of brand)
|
||||
- State chip: ` DEBUG · READY ` / ` · RUNNING ` / ` · PAUSED `
|
||||
|
||||
Control bar (bottom, 2 rows, above your statusline) shows:
|
||||
- Clickable buttons for `(c)ontinue`, `(s)tep` over, `(d)escend` into,
|
||||
`(r)eturn` out, `(R)estart` (during session), `(q)uit`
|
||||
- Italic qualifiers (`over`, `into`, `out`) shown inline when width allows
|
||||
- `(?) help` anchored on the right
|
||||
- Click any button to fire its action
|
||||
|
||||
Both bars update live on DAP state transitions (|User DapProgressUpdate|)
|
||||
and on editor resize. They survive tabline toggles (bufferline appear/
|
||||
disappear) — killed bars are recreated automatically.
|
||||
|
||||
==============================================================================
|
||||
8. UI Layout *turbo-debug-layout*
|
||||
|
||||
Default layout:
|
||||
|
||||
Left sidebar (40 columns):
|
||||
- Scopes (50%) — local/global variables in current frame
|
||||
- Watches (25%) — user-added watch expressions
|
||||
- Stacks (25%) — call stack navigation
|
||||
Sidebar (|turbo-debug.setup.sidebar|, left or right, 25-40 cols):
|
||||
- Call Stack (25%) — current frames, navigate with `<CR>`
|
||||
- Scopes (25%) — local/global variables in current frame
|
||||
- Watches (25%) — user-added watch expressions (|W|)
|
||||
- Breakpoints (25%) — all persistent breakpoints, jump with `<CR>`
|
||||
|
||||
Bottom tray (12 lines):
|
||||
- REPL (50%) — evaluate expressions
|
||||
- Console (50%) — program stdout/stderr
|
||||
Bottom pane (20% of editor lines, min 8 rows):
|
||||
- Console — program stdout/stderr + adapter output
|
||||
|
||||
REPL is NOT in the default layout. Press `E` during debug mode to open it
|
||||
as a floating window on demand.
|
||||
|
||||
Override the layout: >lua
|
||||
require("turbo-debug").setup({
|
||||
@@ -173,7 +321,7 @@ Override the layout: >lua
|
||||
})
|
||||
<
|
||||
==============================================================================
|
||||
8. Adapters *turbo-debug.adapters*
|
||||
9. Adapters *turbo-debug.adapters*
|
||||
|
||||
Built-in adapter support (adapter binary must be installed):
|
||||
|
||||
@@ -184,33 +332,67 @@ Built-in adapter support (adapter binary must be installed):
|
||||
C/C++ codelldb codelldb
|
||||
Rust codelldb codelldb
|
||||
Swift codelldb codelldb
|
||||
Zig codelldb codelldb
|
||||
Nim codelldb codelldb
|
||||
Crystal codelldb codelldb
|
||||
C# netcoredbg netcoredbg
|
||||
F# netcoredbg netcoredbg
|
||||
Java java-debug java-debug-adapter
|
||||
Kotlin kotlin-debug kotlin-debug-adapter
|
||||
PHP php-debug php-debug-adapter
|
||||
Ruby rdbg rdbg
|
||||
Dart dart-debug dart debug_adapter
|
||||
Flutter flutter-debug flutter debug_adapter
|
||||
Bash / Sh bash-debug bash-debug-adapter
|
||||
Elixir elixir-ls elixir-ls-debugger
|
||||
Haskell haskell-debug haskell-debug-adapter
|
||||
OCaml earlybird ocamlearlybird
|
||||
R vscDebugger R (with vscDebugger pkg)
|
||||
Lua (Neovim) osv one-small-step-for-vimkind
|
||||
|
||||
Path resolution order:
|
||||
1. User override via `opts.adapters`
|
||||
1. User override via |turbo-debug.setup.adapters|
|
||||
2. Binary on `$PATH`
|
||||
3. Standard mason install path (~/.local/share/nvim/mason/bin/)
|
||||
|
||||
Adapter binaries are the user's responsibility to install.
|
||||
Rust gets special treatment: pressing `c` runs `cargo build` automatically
|
||||
and picks the binary via `vim.ui.select` if multiple artifacts exist.
|
||||
|
||||
Python auto-detects virtualenvs: `VIRTUAL_ENV`, `CONDA_PREFIX`, then the
|
||||
common directories `.venv`, `venv`, `env`, `.env` relative to `cwd`.
|
||||
|
||||
Dart auto-detects Flutter: if `flutter` is on PATH alongside `dart`, a
|
||||
"Launch Flutter app" configuration is added to the `dart` filetype in
|
||||
addition to the plain Dart file launch.
|
||||
|
||||
R requires the `vscDebugger` R package. turbo-debug preflights it via
|
||||
`Rscript` and silently skips registration if the package is missing. Install
|
||||
with `install.packages("vscDebugger")` or
|
||||
`remotes::install_github("ManuelHentschel/vscDebugger")`.
|
||||
|
||||
Adapter binaries are the user's responsibility to install (mason.nvim is
|
||||
the easiest route).
|
||||
|
||||
Override or add adapters: >lua
|
||||
require("turbo-debug").setup({
|
||||
adapters = {
|
||||
python = { ... }, -- override built-in
|
||||
zig = { ... }, -- add new language
|
||||
python = { adapter = { ... }, configurations = { ... } },
|
||||
zig = { adapter = { ... }, configurations = { ... } },
|
||||
},
|
||||
builtin_adapters = false, -- disable all built-ins
|
||||
})
|
||||
<
|
||||
Existing user nvim-dap configurations are never overridden.
|
||||
Existing user nvim-dap configurations are never overridden — if a user
|
||||
has already set `dap.adapters[name]` before `turbo-debug.setup()` runs,
|
||||
we skip it.
|
||||
|
||||
==============================================================================
|
||||
9. API *turbo-debug-api*
|
||||
10. API *turbo-debug-api*
|
||||
|
||||
*turbo-debug.setup()*
|
||||
require("turbo-debug").setup({opts})
|
||||
Initialize the plugin with optional configuration. Idempotent — safe
|
||||
to call multiple times; the last call's opts win.
|
||||
|
||||
*turbo-debug.toggle()*
|
||||
require("turbo-debug").toggle()
|
||||
@@ -222,24 +404,29 @@ require("turbo-debug").active()
|
||||
Useful for statusline integration.
|
||||
|
||||
==============================================================================
|
||||
10. Troubleshooting *turbo-debug-troubleshooting*
|
||||
11. Troubleshooting *turbo-debug-troubleshooting*
|
||||
|
||||
No adapter found ~
|
||||
Ensure the adapter binary is installed and on your PATH, or installed
|
||||
via mason.nvim. turbo-debug configures adapters, it does not install
|
||||
via mason.nvim. turbo-debug configures adapters; it does not install
|
||||
them.
|
||||
|
||||
Breakpoints not persisting ~
|
||||
Check that persistent-breakpoints.nvim is loaded. Run:
|
||||
`:lua print(require("persistent-breakpoints") and "ok")`
|
||||
|
||||
Check that persistent-breakpoints.nvim is loaded: >vim
|
||||
:lua print(require("persistent-breakpoints") and "ok")
|
||||
<
|
||||
Virtual text not appearing ~
|
||||
Virtual text only appears during an active DAP session in debug mode.
|
||||
Ensure nvim-dap-virtual-text is loaded:
|
||||
`:lua print(require("nvim-dap-virtual-text") and "ok")`
|
||||
Ensure nvim-dap-virtual-text is loaded: >vim
|
||||
:lua print(require("nvim-dap-virtual-text") and "ok")
|
||||
<
|
||||
Bars look squished / have stray `~` markers ~
|
||||
The bars auto-clamp to 2 rows on every resize event. If you see this
|
||||
persistently, please open an issue with your terminal and
|
||||
`:set lines? columns?` output.
|
||||
|
||||
Version error ~
|
||||
turbo-debug.nvim requires Neovim >= 0.12 for vim.pack support.
|
||||
turbo-debug.nvim requires Neovim >= 0.12 for `vim.pack` support.
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:ft=help:norl:
|
||||
@@ -0,0 +1,39 @@
|
||||
local loader = require("turbo-debug.adapters.init")
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.register(dap)
|
||||
local exe = loader.find_executable({ "bash-debug-adapter" })
|
||||
if not exe then return end
|
||||
|
||||
local bashdb_dir = vim.fn.stdpath("data") .. "/mason/packages/bash-debug-adapter/extension/bashdb_dir"
|
||||
if vim.fn.isdirectory(bashdb_dir) == 0 then bashdb_dir = nil end
|
||||
|
||||
loader.register(dap, "bashdb", {
|
||||
type = "executable",
|
||||
command = exe,
|
||||
name = "bashdb",
|
||||
}, {
|
||||
{
|
||||
type = "bashdb",
|
||||
request = "launch",
|
||||
name = "Launch file",
|
||||
showDebugOutput = true,
|
||||
pathBashdb = bashdb_dir and (bashdb_dir .. "/bashdb") or "bashdb",
|
||||
pathBashdbLib = bashdb_dir,
|
||||
trace = true,
|
||||
file = "${file}",
|
||||
program = "${file}",
|
||||
cwd = "${workspaceFolder}",
|
||||
pathCat = "cat",
|
||||
pathBash = "/bin/bash",
|
||||
pathMkfifo = "mkfifo",
|
||||
pathPkill = "pkill",
|
||||
args = {},
|
||||
env = {},
|
||||
terminalKind = "integrated",
|
||||
},
|
||||
}, { "sh", "bash" })
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -4,15 +4,11 @@ local M = {}
|
||||
|
||||
local function pick_executable(executables)
|
||||
local co = coroutine.running()
|
||||
if not co then
|
||||
return executables[1]
|
||||
end
|
||||
if not co then return executables[1] end
|
||||
vim.ui.select(executables, {
|
||||
prompt = "Select executable",
|
||||
format_item = function(path) return vim.fn.fnamemodify(path, ":t") end,
|
||||
}, function(choice)
|
||||
coroutine.resume(co, choice)
|
||||
end)
|
||||
}, function(choice) coroutine.resume(co, choice) end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
@@ -54,9 +50,7 @@ function M.register(dap)
|
||||
},
|
||||
}
|
||||
|
||||
if not dap.adapters.codelldb then
|
||||
dap.adapters.codelldb = adapter
|
||||
end
|
||||
if not dap.adapters.codelldb then dap.adapters.codelldb = adapter end
|
||||
|
||||
-- C/C++/Swift: pick from compiled executables in cwd
|
||||
local prompt_config = {
|
||||
@@ -64,26 +58,25 @@ function M.register(dap)
|
||||
type = "codelldb",
|
||||
request = "launch",
|
||||
name = "Launch executable",
|
||||
program = function()
|
||||
return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file")
|
||||
end,
|
||||
program = function() return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") end,
|
||||
cwd = "${workspaceFolder}",
|
||||
},
|
||||
}
|
||||
for _, ft in ipairs({ "c", "cpp", "swift" }) do
|
||||
for _, ft in ipairs({ "c", "cpp", "swift", "zig", "nim", "crystal" }) do
|
||||
dap.configurations[ft] = dap.configurations[ft] or prompt_config
|
||||
end
|
||||
|
||||
-- Rust: cargo build + picker
|
||||
dap.configurations.rust = dap.configurations.rust or {
|
||||
{
|
||||
type = "codelldb",
|
||||
request = "launch",
|
||||
name = "Cargo build & launch",
|
||||
program = cargo_build_and_pick,
|
||||
cwd = "${workspaceFolder}",
|
||||
},
|
||||
}
|
||||
dap.configurations.rust = dap.configurations.rust
|
||||
or {
|
||||
{
|
||||
type = "codelldb",
|
||||
request = "launch",
|
||||
name = "Cargo build & launch",
|
||||
program = cargo_build_and_pick,
|
||||
cwd = "${workspaceFolder}",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,46 @@
|
||||
local loader = require("turbo-debug.adapters.init")
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.register(dap)
|
||||
local dart = loader.find_executable({ "dart" })
|
||||
if not dart then return end
|
||||
|
||||
local configs = {
|
||||
{
|
||||
type = "dart",
|
||||
request = "launch",
|
||||
name = "Launch Dart file",
|
||||
program = "${file}",
|
||||
cwd = "${workspaceFolder}",
|
||||
toolArgs = { "--enable-vm-service" },
|
||||
},
|
||||
}
|
||||
|
||||
local flutter = loader.find_executable({ "flutter" })
|
||||
if flutter then
|
||||
if not dap.adapters.flutter then
|
||||
dap.adapters.flutter = {
|
||||
type = "executable",
|
||||
command = flutter,
|
||||
args = { "debug_adapter" },
|
||||
}
|
||||
end
|
||||
table.insert(configs, {
|
||||
type = "flutter",
|
||||
request = "launch",
|
||||
name = "Launch Flutter app",
|
||||
program = "lib/main.dart",
|
||||
cwd = "${workspaceFolder}",
|
||||
flutterMode = "debug",
|
||||
})
|
||||
end
|
||||
|
||||
loader.register(dap, "dart", {
|
||||
type = "executable",
|
||||
command = dart,
|
||||
args = { "debug_adapter" },
|
||||
}, configs, { "dart" })
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -23,43 +23,173 @@ end
|
||||
-- simple executable adapters (inlined — no separate files needed)
|
||||
local function register_simple_adapters(dap)
|
||||
local simple = {
|
||||
{ "netcoredbg", { "netcoredbg" }, {
|
||||
type = "executable", args = { "--interpreter=vscode" },
|
||||
}, { {
|
||||
type = "netcoredbg", request = "launch", name = "Launch .NET",
|
||||
program = function() return vim.fn.input("Path to DLL: ", vim.fn.getcwd() .. "/bin/Debug/", "file") end,
|
||||
} }, { "cs" } },
|
||||
{
|
||||
"netcoredbg",
|
||||
{ "netcoredbg" },
|
||||
{
|
||||
type = "executable",
|
||||
args = { "--interpreter=vscode" },
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "netcoredbg",
|
||||
request = "launch",
|
||||
name = "Launch .NET",
|
||||
program = function() return vim.fn.input("Path to DLL: ", vim.fn.getcwd() .. "/bin/Debug/", "file") end,
|
||||
},
|
||||
},
|
||||
{ "cs", "fsharp" },
|
||||
},
|
||||
|
||||
{ "java", { "java-debug-adapter" }, {
|
||||
type = "executable",
|
||||
}, { {
|
||||
type = "java", request = "launch", name = "Launch class",
|
||||
mainClass = function() return vim.fn.input("Main class: ") end,
|
||||
cwd = "${workspaceFolder}",
|
||||
} }, { "java" } },
|
||||
{
|
||||
"java",
|
||||
{ "java-debug-adapter" },
|
||||
{
|
||||
type = "executable",
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "java",
|
||||
request = "launch",
|
||||
name = "Launch class",
|
||||
mainClass = function() return vim.fn.input("Main class: ") end,
|
||||
cwd = "${workspaceFolder}",
|
||||
},
|
||||
},
|
||||
{ "java" },
|
||||
},
|
||||
|
||||
{ "kotlin", { "kotlin-debug-adapter" }, {
|
||||
type = "executable",
|
||||
}, { {
|
||||
type = "kotlin", request = "launch", name = "Launch main",
|
||||
mainClass = function() return vim.fn.input("Main class: ") end,
|
||||
projectRoot = "${workspaceFolder}",
|
||||
} }, { "kotlin" } },
|
||||
{
|
||||
"kotlin",
|
||||
{ "kotlin-debug-adapter" },
|
||||
{
|
||||
type = "executable",
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "kotlin",
|
||||
request = "launch",
|
||||
name = "Launch main",
|
||||
mainClass = function() return vim.fn.input("Main class: ") end,
|
||||
projectRoot = "${workspaceFolder}",
|
||||
},
|
||||
},
|
||||
{ "kotlin" },
|
||||
},
|
||||
|
||||
{ "php", { "php-debug-adapter" }, {
|
||||
type = "executable",
|
||||
}, { {
|
||||
type = "php", request = "launch", name = "Listen for Xdebug", port = 9003,
|
||||
} }, { "php" } },
|
||||
{
|
||||
"php",
|
||||
{ "php-debug-adapter" },
|
||||
{
|
||||
type = "executable",
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "php",
|
||||
request = "launch",
|
||||
name = "Listen for Xdebug",
|
||||
port = 9003,
|
||||
},
|
||||
},
|
||||
{ "php" },
|
||||
},
|
||||
|
||||
{ "rdbg", { "rdbg" }, {
|
||||
type = "server", port = "${port}",
|
||||
executable = { command = nil, args = { "-n", "--open", "--port", "${port}", "-c", "--", "ruby", "${file}" } },
|
||||
}, { {
|
||||
type = "rdbg", request = "launch", name = "Launch file", command = "ruby", script = "${file}",
|
||||
}, {
|
||||
type = "rdbg", request = "attach", name = "Attach", localfsMap = "${workspaceFolder}",
|
||||
} }, { "ruby" } },
|
||||
{
|
||||
"rdbg",
|
||||
{ "rdbg" },
|
||||
{
|
||||
type = "server",
|
||||
port = "${port}",
|
||||
executable = { command = nil, args = { "-n", "--open", "--port", "${port}", "-c", "--", "ruby", "${file}" } },
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "rdbg",
|
||||
request = "launch",
|
||||
name = "Launch file",
|
||||
command = "ruby",
|
||||
script = "${file}",
|
||||
},
|
||||
{
|
||||
type = "rdbg",
|
||||
request = "attach",
|
||||
name = "Attach",
|
||||
localfsMap = "${workspaceFolder}",
|
||||
},
|
||||
},
|
||||
{ "ruby" },
|
||||
},
|
||||
|
||||
{
|
||||
"mix_task",
|
||||
{
|
||||
"elixir-ls-debugger",
|
||||
"elixir-ls-debug-adapter",
|
||||
vim.fn.stdpath("data") .. "/mason/packages/elixir-ls/debugger.sh",
|
||||
},
|
||||
{
|
||||
type = "executable",
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "mix_task",
|
||||
request = "launch",
|
||||
name = "mix test",
|
||||
task = "test",
|
||||
taskArgs = { "--trace" },
|
||||
startApps = true,
|
||||
projectDir = "${workspaceFolder}",
|
||||
requireFiles = { "test/**/test_helper.exs", "test/**/*_test.exs" },
|
||||
},
|
||||
},
|
||||
{ "elixir" },
|
||||
},
|
||||
|
||||
{
|
||||
"haskell",
|
||||
{ "haskell-debug-adapter" },
|
||||
{
|
||||
type = "executable",
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "haskell",
|
||||
request = "launch",
|
||||
name = "Launch file",
|
||||
workspace = "${workspaceFolder}",
|
||||
startup = "${file}",
|
||||
stopOnEntry = true,
|
||||
logFile = vim.fn.stdpath("data") .. "/haskell-dap.log",
|
||||
logLevel = "WARNING",
|
||||
ghciEnv = vim.empty_dict(),
|
||||
ghciPrompt = "H>>= ",
|
||||
ghciInitialPrompt = "Prelude>",
|
||||
ghciCmd = "cabal exec -- ghci-dap --interactive -i -i${workspaceFolder}",
|
||||
},
|
||||
},
|
||||
{ "haskell" },
|
||||
},
|
||||
|
||||
{
|
||||
"ocamlearlybird",
|
||||
{ "ocamlearlybird" },
|
||||
{
|
||||
type = "executable",
|
||||
args = { "debug" },
|
||||
},
|
||||
{
|
||||
{
|
||||
type = "ocamlearlybird",
|
||||
request = "launch",
|
||||
name = "Launch bytecode",
|
||||
program = function()
|
||||
return vim.fn.input("Path to bytecode: ", vim.fn.getcwd() .. "/_build/default/", "file")
|
||||
end,
|
||||
stopOnEntry = false,
|
||||
},
|
||||
},
|
||||
{ "ocaml" },
|
||||
},
|
||||
}
|
||||
|
||||
for _, s in ipairs(simple) do
|
||||
@@ -77,10 +207,13 @@ local function register_simple_adapters(dap)
|
||||
local ok, osv = pcall(require, "osv")
|
||||
if ok then
|
||||
M.register(dap, "nlua", {
|
||||
type = "server", host = "127.0.0.1",
|
||||
type = "server",
|
||||
host = "127.0.0.1",
|
||||
port = function() return osv.launch({ port = 0 }) end,
|
||||
}, { {
|
||||
type = "nlua", request = "attach", name = "Attach to running Neovim",
|
||||
type = "nlua",
|
||||
request = "attach",
|
||||
name = "Attach to running Neovim",
|
||||
} }, { "lua" })
|
||||
end
|
||||
end
|
||||
@@ -91,11 +224,9 @@ function M.setup()
|
||||
local dap = require("dap")
|
||||
|
||||
-- load adapters with non-trivial logic from separate files
|
||||
for _, name in ipairs({ "python", "go", "c", "javascript" }) do
|
||||
for _, name in ipairs({ "python", "go", "c", "javascript", "dart", "bash", "r" }) do
|
||||
local ok, adapter_mod = pcall(require, "turbo-debug.adapters." .. name)
|
||||
if ok and adapter_mod.register then
|
||||
adapter_mod.register(dap)
|
||||
end
|
||||
if ok and adapter_mod.register then adapter_mod.register(dap) end
|
||||
end
|
||||
|
||||
-- register simple adapters inlined above
|
||||
|
||||
@@ -27,15 +27,16 @@ function M.register(dap)
|
||||
type = "pwa-node",
|
||||
request = "attach",
|
||||
name = "Attach",
|
||||
processId = function()
|
||||
return require("dap.utils").pick_process()
|
||||
end,
|
||||
processId = function() return require("dap.utils").pick_process() end,
|
||||
cwd = "${workspaceFolder}",
|
||||
},
|
||||
}
|
||||
|
||||
loader.register(dap, "pwa-node", adapter, configurations, {
|
||||
"javascript", "typescript", "javascriptreact", "typescriptreact",
|
||||
"javascript",
|
||||
"typescript",
|
||||
"javascriptreact",
|
||||
"typescriptreact",
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -2,31 +2,22 @@ local loader = require("turbo-debug.adapters.init")
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Try in order: active virtualenv, conda, common local venv dirs, system.
|
||||
-- First executable match wins. Using a single ordered list keeps the probe
|
||||
-- sequence explicit (ipairs preserves numeric order).
|
||||
local function find_python()
|
||||
-- check active virtualenv
|
||||
local venv = vim.env.VIRTUAL_ENV
|
||||
if venv then
|
||||
local path = venv .. "/bin/python"
|
||||
if vim.fn.executable(path) == 1 then return path end
|
||||
end
|
||||
|
||||
-- check conda
|
||||
local conda = vim.env.CONDA_PREFIX
|
||||
if conda then
|
||||
local path = conda .. "/bin/python"
|
||||
if vim.fn.executable(path) == 1 then return path end
|
||||
end
|
||||
|
||||
-- probe common venv directories relative to cwd
|
||||
local candidates = {}
|
||||
if vim.env.VIRTUAL_ENV then candidates[#candidates + 1] = vim.env.VIRTUAL_ENV .. "/bin/python" end
|
||||
if vim.env.CONDA_PREFIX then candidates[#candidates + 1] = vim.env.CONDA_PREFIX .. "/bin/python" end
|
||||
local cwd = vim.fn.getcwd()
|
||||
for _, dir in ipairs({ ".venv", "venv", "env", ".env" }) do
|
||||
local path = cwd .. "/" .. dir .. "/bin/python"
|
||||
if vim.fn.executable(path) == 1 then return path end
|
||||
candidates[#candidates + 1] = cwd .. "/" .. dir .. "/bin/python"
|
||||
end
|
||||
candidates[#candidates + 1] = "python3"
|
||||
candidates[#candidates + 1] = "python"
|
||||
for _, p in ipairs(candidates) do
|
||||
if vim.fn.executable(p) == 1 then return p end
|
||||
end
|
||||
|
||||
-- fallback to system python
|
||||
if vim.fn.executable("python3") == 1 then return "python3" end
|
||||
if vim.fn.executable("python") == 1 then return "python" end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
local loader = require("turbo-debug.adapters.init")
|
||||
|
||||
local M = {}
|
||||
|
||||
-- vscDebugger is an R package, not a standalone binary. It must be installed
|
||||
-- in the user's R library (install.packages("vscDebugger") or remotes::install_github).
|
||||
-- Silently skip registration if it's missing — matches the "no noise when
|
||||
-- prereqs missing" invariant used by the other adapters.
|
||||
local function has_vsc_debugger()
|
||||
if vim.fn.executable("Rscript") ~= 1 then return false end
|
||||
vim.fn.system({
|
||||
"Rscript",
|
||||
"-e",
|
||||
"if (!requireNamespace('vscDebugger', quietly=TRUE)) quit(status=1)",
|
||||
})
|
||||
return vim.v.shell_error == 0
|
||||
end
|
||||
|
||||
function M.register(dap)
|
||||
local R = loader.find_executable({ "R" })
|
||||
if not R then return end
|
||||
if not has_vsc_debugger() then return end
|
||||
|
||||
loader.register(dap, "r", {
|
||||
type = "executable",
|
||||
command = R,
|
||||
args = { "-e", "vscDebugger::.vsc.listenForDAP()" },
|
||||
}, {
|
||||
{
|
||||
type = "r",
|
||||
request = "launch",
|
||||
name = "Launch R file",
|
||||
program = "${file}",
|
||||
cwd = "${workspaceFolder}",
|
||||
},
|
||||
}, { "r", "rmd" })
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -2,29 +2,104 @@ local config = require("turbo-debug.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
local initialized = false
|
||||
|
||||
-- This function is idempotent — every operation inside is safe to re-run.
|
||||
-- That property matters because turbo-debug.setup() can get called twice in
|
||||
-- practice:
|
||||
-- (1) Deferred auto-setup from plugin/turbo-debug.lua, which runs after
|
||||
-- vim.pack.add has finished registering all deps in runtimepath. The
|
||||
-- defer is necessary — calling setup synchronously during
|
||||
-- vim.pack.add would try to `require('persistent-breakpoints.api')`
|
||||
-- before the plugin's lua/ directory is on the runtimepath and
|
||||
-- crash with "module not found".
|
||||
-- (2) User's explicit setup({...}) in their init.lua with config
|
||||
-- overrides. It runs immediately (before the deferred auto-setup).
|
||||
--
|
||||
-- Previously a single `initialized` flag guarded this function so it only
|
||||
-- ran once. That guard was fragile: if the first call errored partway
|
||||
-- (case above), the flag was set but the setup was incomplete, and the
|
||||
-- second call skipped the retry. We now let every call do its full work;
|
||||
-- persistent-breakpoints.setup re-reads its bps file, sign_define overrides
|
||||
-- a previous definition, nvim_set_hl overrides a previous link, and
|
||||
-- vim.keymap.set overrides a previous binding.
|
||||
function M.setup()
|
||||
if initialized then return end
|
||||
initialized = true
|
||||
|
||||
require("persistent-breakpoints").setup({
|
||||
load_breakpoints_event = { "BufReadPost" },
|
||||
})
|
||||
|
||||
-- sign highlights (default = true so colorschemes can override)
|
||||
vim.api.nvim_set_hl(0, "DapBreakpoint", { default = true, fg = "#e06c75" })
|
||||
vim.api.nvim_set_hl(0, "DapBreakpointCondition", { default = true, fg = "#e5c07b" })
|
||||
vim.api.nvim_set_hl(0, "DapBreakpointRejected", { default = true, fg = "#5c6370" })
|
||||
vim.api.nvim_set_hl(0, "DapStopped", { default = true, fg = "#98c379" })
|
||||
vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, bg = "#2e3b2e" })
|
||||
vim.api.nvim_set_hl(0, "DapLogPoint", { default = true, fg = "#61afef" })
|
||||
-- link sign highlights to standard diagnostic groups so they pick up the
|
||||
-- user's colorscheme instead of hardcoding hex colors.
|
||||
vim.api.nvim_set_hl(0, "DapBreakpoint", { default = true, link = "DiagnosticError" })
|
||||
vim.api.nvim_set_hl(0, "DapBreakpointCondition", { default = true, link = "DiagnosticWarn" })
|
||||
vim.api.nvim_set_hl(0, "DapBreakpointRejected", { default = true, link = "DiagnosticHint" })
|
||||
vim.api.nvim_set_hl(0, "DapStopped", { default = true, link = "DiagnosticOk" })
|
||||
vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Search" })
|
||||
vim.api.nvim_set_hl(0, "DapLogPoint", { default = true, link = "DiagnosticInfo" })
|
||||
|
||||
vim.fn.sign_define("DapBreakpoint", { text = "🔴", texthl = "DapBreakpoint" })
|
||||
vim.fn.sign_define("DapBreakpointCondition", { text = "🟡", texthl = "DapBreakpointCondition" })
|
||||
vim.fn.sign_define("DapBreakpointRejected", { text = "⭕", texthl = "DapBreakpointRejected" })
|
||||
vim.fn.sign_define("DapStopped", { text = "➤", texthl = "DapStopped", linehl = "DapStoppedLine" })
|
||||
vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DapLogPoint" })
|
||||
-- Gutter signs: colorful emoji for instant recognition. Modern terminals
|
||||
-- (Ghostty, Kitty, WezTerm, iTerm) render these full-color.
|
||||
-- 🔴 U+1F534 large red circle = breakpoint
|
||||
-- 🟡 U+1F7E1 large yellow circle = conditional breakpoint
|
||||
-- ⭕ U+2B55 heavy large circle = rejected breakpoint
|
||||
-- 👉 U+1F449 backhand pointing right = current execution line (IP)
|
||||
-- 📝 U+1F4DD memo = log point
|
||||
-- stylua: ignore start
|
||||
local signs = {
|
||||
{ "DapBreakpoint", "\xf0\x9f\x94\xb4" },
|
||||
{ "DapBreakpointCondition", "\xf0\x9f\x9f\xa1" },
|
||||
{ "DapBreakpointRejected", "\xe2\xad\x95" },
|
||||
{ "DapStopped", "\xf0\x9f\x91\x89", { linehl = "DapStoppedLine" } },
|
||||
{ "DapLogPoint", "\xf0\x9f\x93\x9d" },
|
||||
}
|
||||
-- stylua: ignore end
|
||||
for _, s in ipairs(signs) do
|
||||
local spec = { text = s[2], texthl = s[1], numhl = s[1] }
|
||||
if s[3] then
|
||||
for k, v in pairs(s[3]) do
|
||||
spec[k] = v
|
||||
end
|
||||
end
|
||||
vim.fn.sign_define(s[1], spec)
|
||||
end
|
||||
|
||||
-- unobtrusive inline virtual text: italic, comment-colored
|
||||
vim.api.nvim_set_hl(0, "NvimDapVirtualText", { default = true, link = "Comment", italic = true })
|
||||
vim.api.nvim_set_hl(0, "NvimDapVirtualTextChanged", { default = true, link = "DiagnosticWarn", italic = true })
|
||||
vim.api.nvim_set_hl(0, "NvimDapVirtualTextError", { default = true, link = "DiagnosticError", italic = true })
|
||||
vim.api.nvim_set_hl(0, "NvimDapVirtualTextInfo", { default = true, link = "DiagnosticInfo", italic = true })
|
||||
|
||||
-- turbo-debug's own UI groups (help float, active-pane borders, winbar badge)
|
||||
-- stylua: ignore start
|
||||
vim.api.nvim_set_hl(0, "TurboDebugHelpBrand", { default = true, link = "Title" })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugHelpTagline", { default = true, link = "Comment", italic = true })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugHelpSection", { default = true, link = "Function" })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugHelpSectionIcon", { default = true, link = "Constant" })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugHelpKey", { default = true, link = "Special" })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugHelpParen", { default = true, link = "Comment" })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugCtrlButton", { default = true, link = "Function" })
|
||||
vim.api.nvim_set_hl(0, "TurboDebugCtrlMuted", { default = true, link = "Comment" })
|
||||
-- stylua: ignore end
|
||||
|
||||
-- derived highlight: the IP "reason" vtext (STEP / BREAKPOINT / EXCEPTION /
|
||||
-- PAUSE / etc.) — bold + reverse + WarningMsg color so it renders as an
|
||||
-- inverted-color badge that absolutely demands attention. nvim_set_hl
|
||||
-- can't combine link + bold, so copy attrs explicitly and re-apply on
|
||||
-- ColorScheme so theme switches don't blank it out.
|
||||
local function apply_ip_reason_hl()
|
||||
local ok, src = pcall(vim.api.nvim_get_hl, 0, { name = "WarningMsg", link = false })
|
||||
if not ok or not src or not src.fg then return end
|
||||
vim.api.nvim_set_hl(0, "TurboDebugIPReason", {
|
||||
fg = src.fg,
|
||||
bg = src.bg,
|
||||
bold = true,
|
||||
reverse = true,
|
||||
default = true,
|
||||
})
|
||||
end
|
||||
apply_ip_reason_hl()
|
||||
vim.api.nvim_create_autocmd("ColorScheme", {
|
||||
group = vim.api.nvim_create_augroup("TurboDebugColors", { clear = true }),
|
||||
callback = apply_ip_reason_hl,
|
||||
})
|
||||
|
||||
-- global mappings (work outside debug mode)
|
||||
local gk = config.opts.global_keys
|
||||
@@ -35,9 +110,7 @@ function M.setup()
|
||||
{ gk.clear_breakpoints, pb.clear_all_breakpoints, "Clear all breakpoints" },
|
||||
}
|
||||
for _, b in ipairs(bindings) do
|
||||
if b[1] then
|
||||
vim.keymap.set("n", b[1], b[2], { silent = true, desc = b[3] })
|
||||
end
|
||||
if b[1] then vim.keymap.set("n", b[1], b[2], { silent = true, desc = b[3] }) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+81
-23
@@ -1,9 +1,37 @@
|
||||
local M = {}
|
||||
|
||||
M.defaults = {
|
||||
help_on_enter = true,
|
||||
-- The 3-row control bar shows `(?)help` so users can always see where to
|
||||
-- get the key reference. No auto-popup needed on entry.
|
||||
help_on_enter = false,
|
||||
builtin_adapters = true,
|
||||
quit_exits_mode = true, -- q terminates AND exits debug mode (false = terminate only)
|
||||
|
||||
-- When `c` is pressed with no breakpoints set, launch the first adapter
|
||||
-- configuration for the filetype with stopOnEntry so the debugger pauses
|
||||
-- at the program's entry point. Matches the Turbo Pascal "press key to
|
||||
-- start debugging" model. Set false to always run until a breakpoint.
|
||||
stop_on_entry_when_no_breakpoints = true,
|
||||
|
||||
-- Maximum per-line character width for variable values rendered in dapui
|
||||
-- panes. Anything over this gets truncated with `…`. Set to 0 to disable.
|
||||
-- Default 2000 only catches pathological multi-KB register/memory dumps;
|
||||
-- normal values pass through and wrap naturally inside the pane.
|
||||
max_value_width = 2000,
|
||||
|
||||
-- Throttle window (ms) for console repaints when the debugged program
|
||||
-- outputs. Trailing-edge — max 1000/console_refresh_ms redraws per second.
|
||||
console_refresh_ms = 50,
|
||||
|
||||
-- Scope names (from the DAP adapter) to default-collapse. dapui auto-
|
||||
-- collapses scopes that the adapter marks `expensive`, but many adapters
|
||||
-- (codelldb for Rust/C++, for example) don't mark Registers expensive
|
||||
-- even though it's a huge dump. Any scope name in this list is treated
|
||||
-- as expensive by intercepting the `scopes` response before dapui sees
|
||||
-- it. Users can click/<CR> to expand manually.
|
||||
collapsed_scopes = { "Registers" },
|
||||
|
||||
-- Clear the Console pane on session start/restart.
|
||||
clear_console_on_start = true,
|
||||
|
||||
-- modal keys (active only during debug mode, single-key)
|
||||
keys = {
|
||||
@@ -31,40 +59,70 @@ M.defaults = {
|
||||
clear_breakpoints = "<leader>dD",
|
||||
},
|
||||
|
||||
-- sidebar position: "left" (default) or "right"
|
||||
sidebar = "left",
|
||||
|
||||
-- Optional colorscheme to switch to while debug mode is active.
|
||||
-- `nil` (default) keeps the user's current colorscheme. Any other
|
||||
-- string value is passed directly to `:colorscheme`. On M.exit the
|
||||
-- previous colorscheme is restored.
|
||||
colorscheme = nil,
|
||||
|
||||
-- highlight the active dapui pane's border/separator vs. dim inactive panes
|
||||
active_window_highlight = true,
|
||||
|
||||
dapui = {
|
||||
icons = { expanded = "▼", collapsed = "▶", current_frame = "➤" },
|
||||
-- Unicode icons via byte escapes so they survive tool round-trips.
|
||||
-- ▾ U+25BE down-triangle (expanded), ▸ U+25B8 right-triangle (collapsed),
|
||||
-- ▶ U+25B6 right-pointing triangle (current frame / play-head)
|
||||
icons = {
|
||||
expanded = "\xe2\x96\xbe",
|
||||
collapsed = "\xe2\x96\xb8",
|
||||
current_frame = "\xe2\x96\xb6",
|
||||
},
|
||||
controls = {
|
||||
enabled = true,
|
||||
element = "repl",
|
||||
-- ▶ play, ⏸ pause, ⤓ step-into, ⇒ step-over, ⤒ step-out, ↶ step-back,
|
||||
-- ↻ run-last, ■ terminate, ⊗ disconnect
|
||||
icons = {
|
||||
play = "\xe2\x96\xb6",
|
||||
pause = "\xe2\x8f\xb8",
|
||||
step_into = "\xe2\xa4\x93",
|
||||
step_over = "\xe2\x87\x92",
|
||||
step_out = "\xe2\xa4\x92",
|
||||
step_back = "\xe2\x86\xb6",
|
||||
run_last = "\xe2\x86\xbb",
|
||||
terminate = "\xe2\x96\xa0",
|
||||
disconnect = "\xe2\x8a\x97",
|
||||
},
|
||||
},
|
||||
floating = { border = "rounded" },
|
||||
floating = { border = "rounded", mappings = { close = { "q", "<Esc>" } } },
|
||||
render = { indent = 2, max_type_length = 20 },
|
||||
layouts = {
|
||||
{
|
||||
position = "right",
|
||||
size = 40,
|
||||
elements = {
|
||||
{ id = "scopes", size = 0.50 },
|
||||
{ id = "watches", size = 0.25 },
|
||||
{ id = "stacks", size = 0.25 },
|
||||
},
|
||||
},
|
||||
{
|
||||
position = "bottom",
|
||||
size = 12,
|
||||
elements = {
|
||||
{ id = "repl", size = 0.50 },
|
||||
{ id = "console", size = 0.50 },
|
||||
},
|
||||
},
|
||||
},
|
||||
-- dapui's `expand_lines` spawns a cursor-pinned floating window when a
|
||||
-- line is longer than the pane width — that float bleeds across the
|
||||
-- entire screen for long register dumps. We wrap inside the pane instead
|
||||
-- (wrap=true + breakindent + showbreak in set_dapui_window_opts), so this
|
||||
-- hover is both redundant and destructive. Kill it.
|
||||
expand_lines = false,
|
||||
-- layouts are built dynamically from `sidebar` in mode.lua; override
|
||||
-- `layouts` here to fully customize.
|
||||
layouts = nil,
|
||||
},
|
||||
|
||||
virtual_text = {
|
||||
enabled = true,
|
||||
commented = true,
|
||||
virt_text_pos = "eol",
|
||||
show_stop_reason = true,
|
||||
highlight_changed_variables = true,
|
||||
highlight_new_as_changed = false,
|
||||
only_first_definition = false,
|
||||
all_frames = false,
|
||||
display_callback = function(variable)
|
||||
if #variable.value > 80 then return " = " .. variable.value:sub(1, 77) .. "…" end
|
||||
return " = " .. variable.value
|
||||
end,
|
||||
},
|
||||
|
||||
adapters = {},
|
||||
|
||||
+162
-74
@@ -4,90 +4,193 @@ local M = {}
|
||||
|
||||
local win_id = nil
|
||||
local buf_id = nil
|
||||
local timer = nil
|
||||
|
||||
local modal_info = {
|
||||
{ "continue", "Continue" },
|
||||
{ "step_over", "Step over" },
|
||||
{ "step_into", "Step into" },
|
||||
{ "step_out", "Step out" },
|
||||
{ "run_to_cursor", "Run to cursor" },
|
||||
{ "breakpoint", "Toggle breakpoint" },
|
||||
{ "cond_breakpoint", "Conditional breakpoint" },
|
||||
{ "clear_breakpoints", "Clear all breakpoints" },
|
||||
{ "watch", "Watch expression" },
|
||||
{ "hover", "Hover / inspect" },
|
||||
{ "eval", "Eval in REPL" },
|
||||
{ "terminate", "Quit debugging" },
|
||||
{ "restart", "Restart session" },
|
||||
-- Each entry:
|
||||
-- { action, key_letter, pre, post }
|
||||
-- The formatted line is: " <pre>(<key>)<post>" with <key> taken from the
|
||||
-- config.opts.keys[action] lookup. Highlight spans are computed against
|
||||
-- the actual key letter in the final line.
|
||||
local flow = {
|
||||
{ "continue", "c", "(", ")ontinue" },
|
||||
{ "step_over", "s", "(", ")tep over" },
|
||||
{ "step_into", "d", "(", ")escend into" },
|
||||
{ "step_out", "r", "(", ")eturn out" },
|
||||
{ "run_to_cursor", "C", "run to (", ") cursor" },
|
||||
{ "restart", "R", "(", ")estart session" },
|
||||
{ "terminate", "q", "(", ")uit debugging" },
|
||||
}
|
||||
|
||||
local global_info = {
|
||||
{ "toggle", "Toggle debug mode" },
|
||||
{ "breakpoint", "Toggle breakpoint" },
|
||||
{ "cond_breakpoint", "Conditional breakpoint" },
|
||||
{ "clear_breakpoints", "Clear all breakpoints" },
|
||||
local breakpoints = {
|
||||
{ "breakpoint", "x", "(", ") toggle breakpoint" },
|
||||
{ "cond_breakpoint", "X", "(", ") conditional breakpoint" },
|
||||
{ "clear_breakpoints", "D", "(", ")elete all breakpoints" },
|
||||
}
|
||||
|
||||
local function build_content()
|
||||
local lines = { " turbo-debug", "" }
|
||||
local width = #lines[1]
|
||||
local inspect = {
|
||||
{ "watch", "W", "(", ")atch expression" },
|
||||
{ "hover", "K", "(", ") hover / inspect" },
|
||||
{ "eval", "E", "(", ")val in REPL" },
|
||||
}
|
||||
|
||||
local function add(key, label)
|
||||
local line = string.format(" %-12s %s", key, label)
|
||||
local meta = {
|
||||
{ "help", "?", "(", ") this popup" },
|
||||
}
|
||||
|
||||
local globals = {
|
||||
{ "breakpoint", "toggle breakpoint" },
|
||||
{ "cond_breakpoint", "conditional breakpoint" },
|
||||
{ "clear_breakpoints", "delete all breakpoints" },
|
||||
}
|
||||
|
||||
local dapui_panes = {
|
||||
{ "<CR>", "expand / jump to target" },
|
||||
{ "o", "open target (jump to frame / breakpoint)" },
|
||||
{ "e", "edit (watches)" },
|
||||
{ "d", "remove (watches)" },
|
||||
{ "r", "send to REPL" },
|
||||
{ "t", "toggle subtle frames (call stack)" },
|
||||
}
|
||||
|
||||
-- ▶ U+25B6, ● U+25CF, ◉ U+25C9, ◆ U+25C6, ❯ U+276F, ≡ U+2261
|
||||
local ICON_FLOW = "\xe2\x96\xb6"
|
||||
local ICON_BRK = "\xe2\x97\x8f"
|
||||
local ICON_INSPECT = "\xe2\x97\x89"
|
||||
local ICON_META = "\xe2\x97\x86"
|
||||
local ICON_CHEVRON = "\xe2\x9d\xaf"
|
||||
local ICON_STACK = "\xe2\x89\xa1"
|
||||
|
||||
-- a brand header and tagline
|
||||
local BRAND_TITLE = " " .. ICON_FLOW .. " TURBO DEBUG"
|
||||
local BRAND_TAGLINE = " modal text + GUI debugger · left hand keys, right hand mouse"
|
||||
|
||||
local function build()
|
||||
local lines = { "", BRAND_TITLE, BRAND_TAGLINE, "" }
|
||||
local hl = {} -- list of { line_idx, col_start, col_end, hl_group }
|
||||
|
||||
-- title + tagline highlights
|
||||
hl[#hl + 1] = { 1, 0, #BRAND_TITLE, "TurboDebugHelpBrand" }
|
||||
hl[#hl + 1] = { 2, 0, #BRAND_TAGLINE, "TurboDebugHelpTagline" }
|
||||
|
||||
local function section(icon, title)
|
||||
lines[#lines + 1] = " " .. icon .. " " .. title
|
||||
local idx = #lines - 1
|
||||
hl[#hl + 1] = { idx, 2, 2 + #icon, "TurboDebugHelpSectionIcon" }
|
||||
hl[#hl + 1] = { idx, 2 + #icon + 2, 2 + #icon + 2 + #title, "TurboDebugHelpSection" }
|
||||
end
|
||||
|
||||
local function entry(pre, keyletter, post)
|
||||
-- format: " <pre>(<keyletter>)<tail>"
|
||||
-- e.g., " (s)tep over" or " run to (C) cursor"
|
||||
-- the key letter is inside the parens pair
|
||||
local indent = " "
|
||||
-- pre already ends at "("; post already starts with ")"
|
||||
local line = indent .. pre .. keyletter .. post
|
||||
local idx = #lines
|
||||
lines[#lines + 1] = line
|
||||
if #line > width then width = #line end
|
||||
-- highlight: the key letter itself
|
||||
local col = #indent + #pre
|
||||
hl[#hl + 1] = { idx, col, col + #keyletter, "TurboDebugHelpKey" }
|
||||
-- highlight the surrounding parens dimmer
|
||||
hl[#hl + 1] = { idx, col - 1, col, "TurboDebugHelpParen" }
|
||||
hl[#hl + 1] = { idx, col + #keyletter, col + #keyletter + 1, "TurboDebugHelpParen" }
|
||||
end
|
||||
|
||||
lines[#lines + 1] = " Debug mode:"
|
||||
for _, pair in ipairs(modal_info) do
|
||||
local key = config.opts.keys[pair[1]]
|
||||
if key then add(key, pair[2]) end
|
||||
local function add_modal(list)
|
||||
for _, row in ipairs(list) do
|
||||
local action, letter, pre, post = row[1], row[2], row[3], row[4]
|
||||
local configured = config.opts.keys[action]
|
||||
if configured then
|
||||
-- if the user remapped, show their actual key (preserves surprise-free behavior)
|
||||
local lk = configured
|
||||
entry(pre, lk, post)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function add_global(list)
|
||||
for _, row in ipairs(list) do
|
||||
local action, label = row[1], row[2]
|
||||
local key = config.opts.global_keys[action]
|
||||
if key then
|
||||
local indent = " "
|
||||
local line = indent .. string.format("%-13s ", key) .. label
|
||||
local idx = #lines
|
||||
lines[#lines + 1] = line
|
||||
hl[#hl + 1] = { idx, #indent, #indent + #key, "TurboDebugHelpKey" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function add_pane(list)
|
||||
for _, row in ipairs(list) do
|
||||
local key, label = row[1], row[2]
|
||||
local indent = " "
|
||||
local line = indent .. string.format("%-6s ", key) .. label
|
||||
local idx = #lines
|
||||
lines[#lines + 1] = line
|
||||
hl[#hl + 1] = { idx, #indent, #indent + #key, "TurboDebugHelpKey" }
|
||||
end
|
||||
end
|
||||
|
||||
section(ICON_FLOW, "Flow")
|
||||
add_modal(flow)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
section(ICON_BRK, "Breakpoints")
|
||||
add_modal(breakpoints)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
section(ICON_INSPECT, "Inspect")
|
||||
add_modal(inspect)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
section(ICON_META, "Help")
|
||||
add_modal(meta)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
section(ICON_CHEVRON, "Always available")
|
||||
add_global(globals)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
section(ICON_STACK, "In dap-ui panes")
|
||||
add_pane(dapui_panes)
|
||||
lines[#lines + 1] = ""
|
||||
lines[#lines + 1] = " Always available:"
|
||||
for _, pair in ipairs(global_info) do
|
||||
local key = config.opts.global_keys[pair[1]]
|
||||
if key then add(key, pair[2]) end
|
||||
end
|
||||
|
||||
-- highlight line at the bottom
|
||||
local help_key = config.opts.keys.help or "?"
|
||||
lines[#lines + 1] = ""
|
||||
lines[#lines + 1] = " Press " .. help_key .. " for help"
|
||||
lines[#lines + 1] = " press " .. help_key .. " anytime to reopen"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
return lines, width + 2
|
||||
local width = 0
|
||||
for _, l in ipairs(lines) do
|
||||
if vim.fn.strdisplaywidth(l) > width then width = vim.fn.strdisplaywidth(l) end
|
||||
end
|
||||
return lines, width + 2, hl
|
||||
end
|
||||
|
||||
function M.close()
|
||||
if timer then
|
||||
timer:stop()
|
||||
timer:close()
|
||||
timer = nil
|
||||
end
|
||||
if win_id and vim.api.nvim_win_is_valid(win_id) then
|
||||
vim.api.nvim_win_close(win_id, true)
|
||||
end
|
||||
if win_id and vim.api.nvim_win_is_valid(win_id) then vim.api.nvim_win_close(win_id, true) end
|
||||
win_id = nil
|
||||
if buf_id and vim.api.nvim_buf_is_valid(buf_id) then
|
||||
vim.api.nvim_buf_delete(buf_id, { force = true })
|
||||
end
|
||||
if buf_id and vim.api.nvim_buf_is_valid(buf_id) then vim.api.nvim_buf_delete(buf_id, { force = true }) end
|
||||
buf_id = nil
|
||||
end
|
||||
|
||||
local function show(auto_dismiss)
|
||||
function M.open()
|
||||
M.close()
|
||||
|
||||
local lines, width = build_content()
|
||||
local lines, width, hl = build()
|
||||
local height = #lines
|
||||
|
||||
buf_id = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
|
||||
|
||||
local hl_line = height - 2
|
||||
vim.api.nvim_buf_add_highlight(buf_id, -1, "Bold", hl_line, 0, -1)
|
||||
local ns = vim.api.nvim_create_namespace("turbo-debug.help")
|
||||
for _, span in ipairs(hl) do
|
||||
pcall(vim.api.nvim_buf_set_extmark, buf_id, ns, span[1], span[2], {
|
||||
end_row = span[1],
|
||||
end_col = span[3],
|
||||
hl_group = span[4],
|
||||
})
|
||||
end
|
||||
|
||||
vim.bo[buf_id].modifiable = false
|
||||
vim.bo[buf_id].bufhidden = "wipe"
|
||||
@@ -107,27 +210,12 @@ local function show(auto_dismiss)
|
||||
noautocmd = true,
|
||||
})
|
||||
|
||||
if auto_dismiss then
|
||||
timer = vim.uv.new_timer()
|
||||
timer:start(3000, 0, vim.schedule_wrap(function() M.close() end))
|
||||
else
|
||||
vim.api.nvim_create_autocmd({ "CursorMoved", "InsertEnter", "CmdlineEnter" }, {
|
||||
once = true,
|
||||
callback = function() M.close() end,
|
||||
})
|
||||
end
|
||||
vim.api.nvim_create_autocmd({ "CursorMoved", "InsertEnter", "CmdlineEnter" }, {
|
||||
once = true,
|
||||
callback = function() M.close() end,
|
||||
})
|
||||
end
|
||||
|
||||
function M.open()
|
||||
show(false)
|
||||
end
|
||||
|
||||
function M.splash()
|
||||
show(true)
|
||||
end
|
||||
|
||||
function M.is_open()
|
||||
return win_id ~= nil and vim.api.nvim_win_is_valid(win_id)
|
||||
end
|
||||
function M.is_open() return win_id ~= nil and vim.api.nvim_win_is_valid(win_id) end
|
||||
|
||||
return M
|
||||
@@ -4,6 +4,21 @@ local mode = require("turbo-debug.mode")
|
||||
local M = {}
|
||||
|
||||
function M.setup(opts)
|
||||
-- Self-defer if dependencies aren't yet on the runtimepath. This happens
|
||||
-- when the user calls `setup({...})` from their init.lua right after
|
||||
-- `vim.pack.add(..., turbo-debug)`: our plugin/turbo-debug.lua fires
|
||||
-- nested `vim.pack.add` calls for dap/dapui/persistent-breakpoints, but
|
||||
-- those nested adds only register the paths on the NEXT event loop tick
|
||||
-- — so a synchronous follow-up `require('persistent-breakpoints')`
|
||||
-- crashes with "module not found". `vim.schedule` lets the pack module
|
||||
-- finalize before we retry. `_setup_called` still gets set on the retry,
|
||||
-- so plugin/turbo-debug.lua's own deferred auto-setup correctly skips.
|
||||
if not pcall(require, "persistent-breakpoints") then
|
||||
vim.schedule(function() M.setup(opts) end)
|
||||
return
|
||||
end
|
||||
|
||||
M._setup_called = true
|
||||
config.merge(opts)
|
||||
require("turbo-debug.breakpoints").setup()
|
||||
require("turbo-debug.adapters").setup()
|
||||
@@ -12,17 +27,14 @@ function M.setup(opts)
|
||||
local toggle_key = config.opts.global_keys.toggle
|
||||
if toggle_key then
|
||||
vim.keymap.set("n", toggle_key, function() mode.toggle() end, {
|
||||
silent = true, desc = "Toggle debug mode",
|
||||
silent = true,
|
||||
desc = "Toggle debug mode",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.toggle()
|
||||
mode.toggle()
|
||||
end
|
||||
function M.toggle() mode.toggle() end
|
||||
|
||||
function M.active()
|
||||
return mode.is_active()
|
||||
end
|
||||
function M.active() return mode.is_active() end
|
||||
|
||||
return M
|
||||
+1430
-142
File diff suppressed because it is too large.
Load diff
+16
-5
@@ -12,9 +12,20 @@ vim.pack.add({ "theHamsta/nvim-dap-virtual-text" })
|
||||
vim.pack.add({ "Weissle/persistent-breakpoints.nvim" })
|
||||
|
||||
-- register which-key group if available
|
||||
pcall(function()
|
||||
require("which-key").add({ { "<leader>d", group = "debug" } })
|
||||
end)
|
||||
pcall(function() require("which-key").add({ { "<leader>d", group = "debug" } }) end)
|
||||
|
||||
-- setup immediately (idempotent — user can call setup() again with overrides)
|
||||
require("turbo-debug").setup()
|
||||
-- Deferred auto-setup. Must be deferred (not called synchronously) because
|
||||
-- the vim.pack.add calls above might not have finished registering the
|
||||
-- dep modules in runtimepath by the time this script's top-level runs —
|
||||
-- a synchronous `require('persistent-breakpoints.api')` inside setup would
|
||||
-- crash with "module not found". vim.schedule guarantees we run AFTER the
|
||||
-- current event loop tick, at which point pack has finalized the paths.
|
||||
--
|
||||
-- If the user calls setup({...}) themselves (typically from init.lua after
|
||||
-- vim.pack.add), `M._setup_called` flips true and this deferred call
|
||||
-- becomes a no-op — otherwise we'd overwrite their user opts with defaults.
|
||||
vim.schedule(function()
|
||||
local td = require("turbo-debug")
|
||||
if td._setup_called then return end
|
||||
pcall(td.setup)
|
||||
end)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# stylua.toml
|
||||
# A modern, idiomatic configuration optimized for Neovim/Luau ecosystems.
|
||||
|
||||
# General Formatting
|
||||
column_width = 120 # Provides enough horizontal space for modern monitors without wrapping too early.
|
||||
line_endings = "Unix" # Enforces LF (\n) line endings to prevent cross-platform Git issues.
|
||||
|
||||
# Indentation
|
||||
indent_type = "Spaces" # Spaces are the modern standard over tabs in Lua ecosystems.
|
||||
indent_width = 2 # 2 spaces prevent deep nesting (common in Lua tables/callbacks) from drifting too far right.
|
||||
|
||||
# Quotes & Strings
|
||||
quote_style = "AutoPreferDouble" # Defaults to " but uses ' if it prevents needing escape characters.
|
||||
|
||||
# Function Calls & Statements
|
||||
call_parentheses = "Always" # Forces `require("module")` instead of `require "module"` for clarity.
|
||||
collapse_simple_statement = "Always" # Allows simple statements (like `if not x then return end`) to live on one line.
|
||||
+9
-3
@@ -1,9 +1,15 @@
|
||||
local M = {}
|
||||
|
||||
function M.assert_eq(a, b, msg)
|
||||
if a ~= b then
|
||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
||||
end
|
||||
if a ~= b then error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a))) end
|
||||
end
|
||||
|
||||
function M.assert_true(v, msg)
|
||||
if not v then error(string.format("FAIL: %s — expected truthy, got %s", msg, vim.inspect(v))) end
|
||||
end
|
||||
|
||||
function M.assert_false(v, msg)
|
||||
if v then error(string.format("FAIL: %s — expected falsy, got %s", msg, vim.inspect(v))) end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -9,7 +9,7 @@ assert_eq(loader.find_executable({ "totally-fake-binary-xyz" }), nil, "non-exist
|
||||
assert_eq(loader.find_executable({ "sh" }) ~= nil, true, "find_executable finds sh")
|
||||
|
||||
-- all adapter modules load and have register()
|
||||
for _, name in ipairs({ "python", "go", "c", "javascript" }) do
|
||||
for _, name in ipairs({ "python", "go", "c", "javascript", "dart", "bash", "r" }) do
|
||||
assert_eq(type(require("turbo-debug.adapters." .. name).register), "function", name .. " has register()")
|
||||
end
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ assert_eq(opts.keys.continue, "c", "default continue key")
|
||||
assert_eq(opts.keys.breakpoint, "x", "default modal breakpoint key")
|
||||
assert_eq(opts.global_keys.toggle, "<leader>dd", "default toggle key")
|
||||
assert_eq(opts.global_keys.breakpoint, "<leader>dx", "default global breakpoint key")
|
||||
assert_eq(opts.help_on_enter, true, "default help_on_enter")
|
||||
assert_eq(opts.quit_exits_mode, true, "default quit_exits_mode")
|
||||
assert_eq(opts.help_on_enter, false, "default help_on_enter")
|
||||
assert_eq(opts.stop_on_entry_when_no_breakpoints, true, "default stop_on_entry_when_no_breakpoints")
|
||||
assert_eq(opts.max_value_width, 2000, "default max_value_width")
|
||||
assert_eq(opts.console_refresh_ms, 50, "default console_refresh_ms")
|
||||
|
||||
-- partial key override
|
||||
opts = config.merge({ keys = { continue = "g" } })
|
||||
@@ -24,9 +26,9 @@ opts = config.merge({ global_keys = { toggle = "<leader>D" } })
|
||||
assert_eq(opts.global_keys.toggle, "<leader>D", "overridden toggle key")
|
||||
assert_eq(opts.global_keys.breakpoint, "<leader>dx", "preserved breakpoint key")
|
||||
|
||||
-- quit_exits_mode override
|
||||
opts = config.merge({ quit_exits_mode = false })
|
||||
assert_eq(opts.quit_exits_mode, false, "overridden quit_exits_mode")
|
||||
-- max_value_width override
|
||||
opts = config.merge({ max_value_width = 0 })
|
||||
assert_eq(opts.max_value_width, 0, "overridden max_value_width")
|
||||
|
||||
-- merge doesn't mutate defaults
|
||||
config.merge({ keys = { continue = "z" } })
|
||||
|
||||
+2
-2
@@ -10,13 +10,13 @@ assert_eq(help.is_open(), true, "help window is open")
|
||||
help.close()
|
||||
assert_eq(help.is_open(), false, "help window is closed")
|
||||
|
||||
-- remapped key shows in help content
|
||||
-- remapped key shows in help content (new format wraps the key letter in parens)
|
||||
config.merge({ keys = { continue = "g" } })
|
||||
help.open()
|
||||
local found_g = false
|
||||
for _, w in ipairs(vim.api.nvim_list_wins()) do
|
||||
for _, line in ipairs(vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(w), 0, -1, false)) do
|
||||
if line:find("g%s+Continue") then found_g = true end
|
||||
if line:find("%(g%)") then found_g = true end
|
||||
end
|
||||
end
|
||||
assert_eq(found_g, true, "remapped key 'g' appears in help")
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
-- Unit tests for the helpers introduced during cleanup refactor.
|
||||
-- Exercises helpers indirectly via observable side effects on bar/pane state.
|
||||
|
||||
local h = require("helpers")
|
||||
local assert_eq = h.assert_eq
|
||||
local assert_true = h.assert_true
|
||||
local assert_false = h.assert_false
|
||||
|
||||
local mode = require("turbo-debug.mode")
|
||||
local config = require("turbo-debug.config")
|
||||
config.merge({})
|
||||
|
||||
-- keyof ---------------------------------------------------------------------
|
||||
-- Reset, then exercise the three return paths.
|
||||
config.merge({})
|
||||
assert_eq(mode._test.keyof("continue", "c"), "c", "default key")
|
||||
config.merge({ keys = { continue = "n" } })
|
||||
assert_eq(mode._test.keyof("continue", "c"), "n", "user override")
|
||||
config.merge({ keys = { continue = false } })
|
||||
assert_eq(mode._test.keyof("continue", "c"), nil, "disabled returns nil")
|
||||
config.merge({})
|
||||
|
||||
-- source_window -------------------------------------------------------------
|
||||
-- Skips bars/dapui-panes; returns a source window when one exists.
|
||||
do
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[buf].filetype = "lua"
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
local w = mode._test.source_window()
|
||||
assert_true(w ~= nil, "source_window finds lua buffer")
|
||||
end
|
||||
|
||||
do
|
||||
-- Switch current to a dapui-pane filetype; source_window should still
|
||||
-- find another source window if one exists.
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[buf].filetype = "dapui_scopes"
|
||||
vim.cmd("split")
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
local w = mode._test.source_window()
|
||||
if w then
|
||||
local ft = vim.bo[vim.api.nvim_win_get_buf(w)].filetype
|
||||
assert_false(ft:match("^dapui_"), "source_window skips dapui_* filetypes")
|
||||
assert_true(ft ~= "dap-repl", "source_window skips dap-repl")
|
||||
assert_true(ft ~= "TurboDebugBar", "source_window skips TurboDebugBar")
|
||||
end
|
||||
end
|
||||
|
||||
print("ALL HELPER TESTS PASSED")
|
||||
@@ -0,0 +1,40 @@
|
||||
local h = require("helpers")
|
||||
local assert_eq = h.assert_eq
|
||||
local assert_true = h.assert_true
|
||||
|
||||
local mode = require("turbo-debug.mode")
|
||||
local config = require("turbo-debug.config")
|
||||
config.merge({})
|
||||
|
||||
-- Invoke define_highlights via the _test hook.
|
||||
mode._test.define_highlights()
|
||||
|
||||
-- The three ColorScheme augroups must exist with at least one autocmd each.
|
||||
local function group_has_colorscheme_autocmd(group_name)
|
||||
local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = group_name, event = "ColorScheme" })
|
||||
return ok and autocmds and #autocmds >= 1
|
||||
end
|
||||
|
||||
assert_true(group_has_colorscheme_autocmd("TurboDebugBarItalicHL"), "italic augroup has ColorScheme autocmd")
|
||||
assert_true(group_has_colorscheme_autocmd("TurboDebugBarKeyHL"), "key augroup has ColorScheme autocmd")
|
||||
assert_true(group_has_colorscheme_autocmd("TurboDebugBarSepHL"), "sep augroup has ColorScheme autocmd")
|
||||
|
||||
-- The highlight groups must exist after define_highlights.
|
||||
local function hl_exists(name)
|
||||
local ok, hl = pcall(vim.api.nvim_get_hl, 0, { name = name })
|
||||
return ok and hl ~= nil and next(hl) ~= nil
|
||||
end
|
||||
|
||||
assert_true(hl_exists("TurboDebugBarItalic"), "TurboDebugBarItalic exists")
|
||||
assert_true(hl_exists("TurboDebugBarKey"), "TurboDebugBarKey exists")
|
||||
assert_true(hl_exists("TurboDebugBarSeparator"), "TurboDebugBarSeparator exists")
|
||||
assert_true(hl_exists("TurboDebugBar"), "TurboDebugBar exists")
|
||||
assert_true(hl_exists("TurboDebugBarBrand"), "TurboDebugBarBrand exists")
|
||||
|
||||
-- Fire ColorScheme event; the callbacks should re-apply highlights (idempotent).
|
||||
vim.cmd("doautocmd ColorScheme")
|
||||
assert_true(hl_exists("TurboDebugBarItalic"), "TurboDebugBarItalic still exists after ColorScheme")
|
||||
assert_true(hl_exists("TurboDebugBarKey"), "TurboDebugBarKey still exists after ColorScheme")
|
||||
assert_true(hl_exists("TurboDebugBarSeparator"), "TurboDebugBarSeparator still exists after ColorScheme")
|
||||
|
||||
print("ALL HIGHLIGHT CS TESTS PASSED")
|
||||
@@ -0,0 +1,145 @@
|
||||
local h = require("helpers")
|
||||
local assert_eq = h.assert_eq
|
||||
local assert_true = h.assert_true
|
||||
local assert_false = h.assert_false
|
||||
|
||||
local mode = require("turbo-debug.mode")
|
||||
local config = require("turbo-debug.config")
|
||||
|
||||
-- Ensure config is initialized with defaults so keyof/render_cbar can read it
|
||||
config.merge({})
|
||||
|
||||
-- Helpers --------------------------------------------------------------------
|
||||
|
||||
local function make_bar_win()
|
||||
vim.cmd("noautocmd keepalt topleft split")
|
||||
local win = vim.api.nvim_get_current_win()
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_win_set_buf(win, buf)
|
||||
vim.api.nvim_win_set_height(win, 2)
|
||||
return win, buf
|
||||
end
|
||||
|
||||
local function cleanup(win)
|
||||
if win and vim.api.nvim_win_is_valid(win) then pcall(vim.api.nvim_win_close, win, true) end
|
||||
end
|
||||
|
||||
local function line(buf, i) return vim.api.nvim_buf_get_lines(buf, i, i + 1, false)[1] or "" end
|
||||
|
||||
-- build_control_text -------------------------------------------------------
|
||||
|
||||
-- Inline form when key matches first letter case-insensitively.
|
||||
local text, kcol, kend, icol, iend = mode._test.build_control_text(">", "s", "step", "over")
|
||||
assert_true(text:find("%(s%)tep") ~= nil, "inline form: (s)tep present")
|
||||
assert_true(kcol < kend, "inline form: key_col < key_end_col")
|
||||
assert_true(icol ~= nil and iend ~= nil, "inline form: italic span present")
|
||||
assert_true(text:sub(icol + 1, iend) == "over", "inline form: italic text is 'over'")
|
||||
|
||||
-- Prefix form when key does NOT match first letter.
|
||||
local text2, _, _, icol2 = mode._test.build_control_text(">", "n", "step", "over")
|
||||
assert_true(text2:find("%(n%) step") ~= nil, "prefix form: (n) step present")
|
||||
assert_true(icol2 ~= nil, "prefix form: italic still rendered")
|
||||
|
||||
-- No italic when italic param is nil.
|
||||
local text3, _, _, icol3 = mode._test.build_control_text(">", "c", "continue", nil)
|
||||
assert_eq(icol3, nil, "no italic span when italic=nil")
|
||||
assert_true(text3:find("%(c%)ontinue") ~= nil, "no italic: (c)ontinue rendered")
|
||||
|
||||
-- keyof ----------------------------------------------------------------------
|
||||
|
||||
-- Save any user override then reset to defaults
|
||||
config.merge({})
|
||||
assert_eq(mode._test.keyof("continue", "c"), "c", "keyof returns default when unset")
|
||||
|
||||
-- Disabled action returns nil
|
||||
config.merge({ keys = { continue = false } })
|
||||
assert_eq(mode._test.keyof("continue", "c"), nil, "keyof returns nil when key=false")
|
||||
|
||||
-- User override
|
||||
config.merge({ keys = { continue = "g" } })
|
||||
assert_eq(mode._test.keyof("continue", "c"), "g", "keyof returns user override")
|
||||
|
||||
-- Empty string falls back to default
|
||||
config.merge({ keys = { continue = "" } })
|
||||
assert_eq(mode._test.keyof("continue", "c"), "c", "keyof falls back on empty string")
|
||||
|
||||
-- Reset config to defaults for subsequent render tests
|
||||
config.merge({})
|
||||
|
||||
-- render_sbar ----------------------------------------------------------------
|
||||
|
||||
do
|
||||
local win, buf = make_bar_win()
|
||||
mode._test.set_bars(win, buf, nil, nil)
|
||||
mode._test.render_sbar()
|
||||
local count = vim.api.nvim_buf_line_count(buf)
|
||||
assert_eq(count, 2, "sbar has exactly 2 lines")
|
||||
local sep = line(buf, 0)
|
||||
local content = line(buf, 1)
|
||||
assert_true(#sep > 0, "sbar separator non-empty")
|
||||
assert_true(content:find(" turbo%-debug ") ~= nil, "sbar contains brand text")
|
||||
assert_true(content:find("DEBUG") ~= nil, "sbar contains DEBUG chip")
|
||||
-- sbar has no click zones
|
||||
local zones = mode._test.get_sbar_zones()
|
||||
assert_eq(#zones, 0, "sbar has zero click zones")
|
||||
mode._test.set_bars(nil, nil, nil, nil)
|
||||
cleanup(win)
|
||||
end
|
||||
|
||||
-- render_cbar ----------------------------------------------------------------
|
||||
|
||||
do
|
||||
local win, buf = make_bar_win()
|
||||
vim.api.nvim_win_set_width(win, 120)
|
||||
mode._test.set_bars(nil, nil, win, buf)
|
||||
mode._test.render_cbar()
|
||||
local count = vim.api.nvim_buf_line_count(buf)
|
||||
assert_eq(count, 2, "cbar has exactly 2 lines")
|
||||
local content = line(buf, 0)
|
||||
local sep = line(buf, 1)
|
||||
assert_true(#sep > 0, "cbar separator non-empty")
|
||||
assert_true(content:find(" help ") ~= nil, "cbar contains 'help' text")
|
||||
-- Click zones: at least 5 controls (continue, step_over, step_into, step_out, terminate) + 1 help
|
||||
-- When no session, restart is hidden so zone count = 5 + 1 = 6.
|
||||
local zones = mode._test.get_cbar_zones()
|
||||
assert_true(#zones >= 6, "cbar has at least 6 click zones: " .. #zones)
|
||||
mode._test.set_bars(nil, nil, nil, nil)
|
||||
cleanup(win)
|
||||
end
|
||||
|
||||
-- overflow fallback: at narrow widths, italic qualifiers are dropped
|
||||
do
|
||||
local win, buf = make_bar_win()
|
||||
vim.api.nvim_win_set_width(win, 25)
|
||||
mode._test.set_bars(nil, nil, win, buf)
|
||||
mode._test.render_cbar()
|
||||
local content = line(buf, 0)
|
||||
-- italic qualifiers are "over", "into", "out" — on overflow they're omitted.
|
||||
-- At width 25 with 5+ controls, there's no way they all fit with italics.
|
||||
assert_false(
|
||||
content:find(" over ") or content:find(" into ") or content:find(" out "),
|
||||
"overflow: italic qualifiers absent at narrow width"
|
||||
)
|
||||
mode._test.set_bars(nil, nil, nil, nil)
|
||||
cleanup(win)
|
||||
end
|
||||
|
||||
-- disabling step_over reduces zone count
|
||||
do
|
||||
local win, buf = make_bar_win()
|
||||
vim.api.nvim_win_set_width(win, 120)
|
||||
mode._test.set_bars(nil, nil, win, buf)
|
||||
mode._test.render_cbar()
|
||||
local default_count = #mode._test.get_cbar_zones()
|
||||
|
||||
config.merge({ keys = { step_over = false } })
|
||||
mode._test.render_cbar()
|
||||
local disabled_count = #mode._test.get_cbar_zones()
|
||||
assert_eq(disabled_count, default_count - 1, "disabling step_over drops one zone")
|
||||
|
||||
config.merge({}) -- reset
|
||||
mode._test.set_bars(nil, nil, nil, nil)
|
||||
cleanup(win)
|
||||
end
|
||||
|
||||
print("ALL RENDER BAR TESTS PASSED")
|
||||
-1120
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user