chore: release 0.2.0

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`). Control bar always
  shows `(?) help`, so the auto-popup is no longer needed.
- Default layout changed: sidebar is 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 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),
  control bar (bottom, 2 rows, clickable flow buttons + `(?) help`
  right-anchored). Italic qualifiers on step controls auto-drop at
  narrow widths.
- Keymap-aware control bar — legend reflects user's actual remapped
  keys; disabled actions are omitted entirely.
- IP arrow markers on stopped lines: 👉 gutter sign (priority 500) +
  👈 REASON virtual text at EOL.
- Optional debug-mode colorscheme via `colorscheme` option.
- Theme-agnostic separator color (derives from WinBar.fg); global
  WinSeparator override during debug mode for consistent edge color.
- `clear_console_on_start` (default true) — wipe Console scrollback on
  session start while preserving the terminal channel.
- `collapsed_scopes` (default {"Registers"}) — auto-collapse noisy
  scopes that adapters don't flag expensive.
- `max_value_width` (default 2000) — per-line truncation for values in
  Scopes/Watches.
- `console_refresh_ms` (default 50) — trailing-edge Console repaint
  throttle.
- `sidebar` option — "left" (default) or "right".
- `active_window_highlight` (default true) — dim inactive dapui pane
  borders.
- `W` and `K` now work in visual mode (operate on the selection).
- Source window focused before DAP jumps on event_stopped.

CHANGED
- Bars are real splits (not overlapping floats).
- pin_dapui_sizes() only re-pins on external geometry changes (tabline
  toggle); user manual pane drags survive.
- ensure_dapui() runs ONCE per plugin lifetime, avoiding "Debug adapter
  didn't respond" timeouts on toggle.
- Bars clamp to exactly 2 rows on every resize (no stray EOB `~`).
- setup() is fully idempotent; plugin/turbo-debug.lua's deferred
  auto-setup yields to explicit user setup({...}).
- Copyright holder: Stephen Waits <steve@waits.net>.

FIXED
- Bars recreate if killed by shrink-below-minimum resize or tabline
  appear/disappear.
- Inherited winbar cleared on bar windows (no phantom separator rows).
- Layout re-asserts after `wincmd =`.
- Sidebar proportions stable across re-entry.
- Bar separators bright on both outward edges.
- Deferred dependency loading waits for vim.pack.add to finalize.

INTERNAL
- Extracted 10+ helper functions in mode.lua; deduped ~25 call sites.
- Data-driven sign definitions and Python interpreter probing.
- Dead code removed: unused reposition_bars, vestigial sbar_zones/
  handle_sbar_click.
- Added regression tests: test_render_bars, test_highlights_cs,
  test_helpers.
- stylua.toml added; all Lua code formatted.

See CHANGELOG.md for the full 0.2.0 entry.
This commit is contained in:
swaits committed 2026-04-12 20:00:59 -06:00
1 parent 85ff3b1688
commit d062ac27ff
18 files changed
+780 -1501

No files matched your search

+107
View File
@@ -1,5 +1,112 @@
# Changelog # Changelog
## 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 ## 0.1.0 — 2026-04-11
Initial release. Initial release.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+48 -19
View File
@@ -97,7 +97,12 @@ require("turbo-debug").setup({
toggle = "<leader>D", -- change the toggle chord toggle = "<leader>D", -- change the toggle chord
}, },
sidebar = "right", -- "left" (default) or "right" sidebar = "right", -- "left" (default) or "right"
help_on_enter = false, -- disable the help splash 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): -- 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(). -- press `c` on an empty-BP project and the debugger pauses at main().
@@ -112,23 +117,44 @@ require("turbo-debug").setup({
-- Console repaint throttle (ms). Max 1000/console_refresh_ms redraws per -- Console repaint throttle (ms). Max 1000/console_refresh_ms redraws per
-- second when the debugged program spams stdout. Prevents editor DOS. -- second when the debugged program spams stdout. Prevents editor DOS.
console_refresh_ms = 50, 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,
}) })
``` ```
### Control bar ### Chrome bars
When debug mode is active a 1-row floating bar appears directly above your When debug mode is active, two 2-row floating bars appear, completely
statusline, showing: independent of lualine, heirline, or any other statusline plugin:
- `● READY` / `● RUNNING` / `● PAUSED` session state chip (color-coded) **Status bar (top)** shows:
- Clickable controls: continue `▶`, step over, step into, step out, - The `turbo-debug` brand
restart, terminate, exit debug mode - Live `dap.status()` progress text
- `dap.status()` progress text (right-aligned) - Session state chip: ` DEBUG · READY ` / ` · RUNNING ` / ` · PAUSED `
- Cursor position (color-coded via `DiagnosticHint` / `DiagnosticInfo` / `DiagnosticError`)
The bar is completely independent of lualine, heirline, or any other **Control bar (bottom, above your statusline)** shows:
statusline plugin — it opens in its own floating window and closes cleanly - Clickable buttons: `(c)ontinue` / `(s)tep` *over* / `(d)escend` *into* /
when debug mode exits. `(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 ### Design notes
@@ -136,18 +162,21 @@ when debug mode exits.
the help popup (`?`), the hover popup (`K`), and the debug-eval popup the help popup (`?`), the hover popup (`K`), and the debug-eval popup
(`E`). Floating windows are the only windows Neovim draws borders around (`E`). Floating windows are the only windows Neovim draws borders around
in the terminal grid. Splits (the Scopes/Watches/Stacks/Breakpoints/ in the terminal grid. Splits (the Scopes/Watches/Stacks/Breakpoints/
REPL/Console panes) use the user's `WinSeparator` character — that's a Console panes) use the user's `WinSeparator` character — that's a
terminal-rendering limit, not a plugin choice. terminal-rendering limit, not a plugin choice.
- **No hardcoded colors.** Every highlight links to a standard group - **No hardcoded colors.** Every highlight links to a standard group
(`DiagnosticError`, `Function`, `Comment`, `CursorLine`, `Visual`, (`DiagnosticError`, `Function`, `Comment`, `CursorLine`, `Visual`,
`Search`, `Title`, `Special`, etc.) so the plugin automatically inherits `Search`, `Title`, `Special`, `WinBar`, etc.) so the plugin
your colorscheme on the fly. Switch themes mid-session and everything automatically inherits your colorscheme on the fly. Switch themes
re-paints. mid-session (or set `colorscheme = "high-contrast"` in `setup()` for
a dedicated debug theme) and everything re-paints.
- **Nerd Font Codicons** for all functional iconography (gutter signs, - **Emoji** (supplementary-plane, not VS16-dependent) for iconography:
control buttons, pane titles). Emoji (`👈` / `👉`) for the paused-line 🔴 🟡 ⭕ 👉 📝 for gutter signs; 🟢 🚀 👣 🔽 🔼 🔄 🛑 for control
arrows, because fingers-pointing-at-the-line is unmissable. buttons; 🔎 👀 📚 🔴 💻 for pane titles. Colored emoji render
consistently in modern terminals (Ghostty, Kitty, WezTerm, iTerm)
without font-dependent rendering quirks.
## Supported Languages ## Supported Languages
Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 148 KiB

+244 -77
View File
@@ -9,28 +9,31 @@ CONTENTS *turbo-debug-contents*
4. Debug Mode ............................ |turbo-debug-debug-mode| 4. Debug Mode ............................ |turbo-debug-debug-mode|
5. Keybindings ........................... |turbo-debug.keys| 5. Keybindings ........................... |turbo-debug.keys|
6. Breakpoints ........................... |turbo-debug-breakpoints| 6. Breakpoints ........................... |turbo-debug-breakpoints|
7. UI Layout ............................. |turbo-debug-layout| 7. Chrome Bars ........................... |turbo-debug-chrome|
8. Adapters .............................. |turbo-debug.adapters| 8. UI Layout ............................. |turbo-debug-layout|
9. API ................................... |turbo-debug-api| 9. Adapters .............................. |turbo-debug.adapters|
10. Troubleshooting ....................... |turbo-debug-troubleshooting| 10. API ................................... |turbo-debug-api|
11. Troubleshooting ....................... |turbo-debug-troubleshooting|
============================================================================== ==============================================================================
1. Introduction *turbo-debug-introduction* 1. Introduction *turbo-debug-introduction*
turbo-debug.nvim gives you a fully-featured, beautiful debugging experience turbo-debug.nvim gives you a fully-featured debugging experience out of one
out of one line of config. Under the hood it wires together nvim-dap, line of config. Under the hood it wires together nvim-dap, nvim-dap-ui,
nvim-dap-ui, nvim-dap-virtual-text, and persistent-breakpoints into a single nvim-dap-virtual-text, and persistent-breakpoints into a single cohesive
cohesive experience with modal keybindings. modal experience.
Features: 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 - Inline virtual text showing variable values in source code
- Hover inspection with `K` - Persistent breakpoints that survive Neovim restarts
- Persistent breakpoints that survive restarts - Hover inspection (|K|) and watch expressions (|W|) from the code window
- Watch expressions from the code window with `W` - Floating REPL eval (|E|)
- Scopes, stacks, REPL, and console panels - Floating status bar + control bar chrome (independent of any statusline
- Modal keybindings active only during debug sessions plugin)
- Help box showing all keybindings - Zero-config adapters for 12 languages
- Zero-config adapters for 10+ languages - Theme-agnostic colors (every highlight links to a standard group)
============================================================================== ==============================================================================
2. Installation *turbo-debug-installation* 2. Installation *turbo-debug-installation*
@@ -47,26 +50,125 @@ Requires Neovim >= 0.12.
Setup is optional. The plugin works with no configuration. To customize: >lua Setup is optional. The plugin works with no configuration. To customize: >lua
require("turbo-debug").setup({ require("turbo-debug").setup({
help_on_enter = true, -- show help splash on debug mode entry help_on_enter = false,
quit_exits_mode = true, -- q terminates AND exits debug mode builtin_adapters = true,
builtin_adapters = true, -- register built-in language adapters stop_on_entry_when_no_breakpoints = true,
keys = { ... }, -- override modal keybindings max_value_width = 2000,
global_keys = { ... }, -- override always-available keybindings console_refresh_ms = 50,
adapters = { ... }, -- override or add language adapters collapsed_scopes = { "Registers" },
dapui = { ... }, -- override nvim-dap-ui layout/settings clear_console_on_start = true,
virtual_text = { ... }, -- override nvim-dap-virtual-text settings sidebar = "left",
colorscheme = nil,
active_window_highlight = true,
keys = { ... },
global_keys = { ... },
adapters = { ... },
dapui = { ... },
virtual_text = { ... },
}) })
< <
*turbo-debug.setup.help_on_enter* *turbo-debug.setup.help_on_enter*
help_on_enter ~ help_on_enter ~
Type: boolean, Default: `true` Type: boolean, Default: `false`
Show the help box automatically when entering debug mode. 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* *turbo-debug.setup.builtin_adapters*
builtin_adapters ~ builtin_adapters ~
Type: boolean, Default: `true` Type: boolean, Default: `true`
Register built-in adapters for supported languages. Set to `false` to Register the built-in language adapters. Set to `false` to opt out of
disable all built-in adapter registration. 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* 4. Debug Mode *turbo-debug-debug-mode*
@@ -74,17 +176,28 @@ builtin_adapters ~
Press `<leader>dd` to toggle debug mode. Press `<leader>dd` to toggle debug mode.
Entering debug mode: Entering debug mode:
1. Opens nvim-dap-ui with the configured layout 1. Switches colorscheme (if |turbo-debug.setup.colorscheme| is set)
2. Enables inline virtual text for variable values 2. Applies turbo-debug highlight groups
3. Activates modal keybindings (buffer-local) 3. Opens nvim-dap-ui (sidebar + Console pane)
4. Shows winbar indicator and help splash (3s) 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): Exiting debug mode (any of):
- `<leader>dd` again (manual toggle) - `<leader>dd` again (manual toggle)
- `q` (terminate session — exits debug mode if `quit_exits_mode`) - `q` when no DAP session is running
- DAP session ends naturally - 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* 5. Keybindings *turbo-debug.keys*
@@ -95,7 +208,7 @@ keys you don't need while debugging, but preserve all navigation keys.
Default keymap: Default keymap:
Key Action Mnemonic ~ Key Action Mnemonic ~
`c` Continue Universal across debuggers `c` Continue / start Universal across debuggers
`s` Step over Step `s` Step over Step
`d` Step into Descend into the function `d` Step into Descend into the function
`r` Step out Return from the function `r` Step out Return from the function
@@ -105,8 +218,8 @@ Default keymap:
`D` Clear all breakpoints Delete all `D` Clear all breakpoints Delete all
`W` Watch expr under cursor Watch `W` Watch expr under cursor Watch
`K` Hover / inspect Matches LSP hover convention `K` Hover / inspect Matches LSP hover convention
`E` Eval in REPL Evaluate `E` Eval in REPL Evaluate (floating REPL)
`q` Terminate session Quit debugging `q` Smart quit Context-aware terminate / exit
`R` Restart session Rerun `R` Restart session Rerun
`?` Help popup Show keybindings `?` 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>dX` Conditional breakpoint Shift variant
`<leader>dD` Clear all breakpoints Delete all `<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 Override any key: >lua
require("turbo-debug").setup({ require("turbo-debug").setup({
keys = { -- modal keys (debug mode only) keys = {
continue = "g", continue = "g", -- remap a modal key
step_over = false, -- set to false to disable step_over = false, -- disable a modal key
}, },
global_keys = { -- always-available keys global_keys = {
toggle = "<leader>DD", toggle = "<leader>DD",
breakpoint = "<leader>db", breakpoint = "<leader>db",
}, },
}) })
< <
Preserved keys (not shadowed): Inside dapui panes (Watches / Scopes / Stacks / Breakpoints / REPL), the
- Navigation: `h j k l w b e f F t T / n N 0 ^ $ G g { } % [ ]` native dapui mappings `<CR>` / `e` / `o` / `d` / `r` / `t` still work —
- Insert: `i a o O` (needed for REPL/terminal) our modal `d` (step-into) and `r` (step-out) yield inside those buffers.
- Visual/yank: `v V y p u`
- All Ctrl/leader combos
*turbo-debug.setup.quit_exits_mode* Preserved keys (never shadowed in source buffers):
quit_exits_mode ~ - Navigation: `h j k l w b e f F t T / n N 0 ^ $ G g { } % [ ]`
Type: boolean, Default: `true` - Insert: `i a o O`
Whether `q` (terminate) also exits debug mode. Set to `false` if you - Visual/yank/edit: `v V y p u`
want `q` to terminate the process but keep the debug UI open. - All Ctrl/Alt/leader combos
============================================================================== ==============================================================================
6. Breakpoints *turbo-debug-breakpoints* 6. Breakpoints *turbo-debug-breakpoints*
@@ -147,23 +261,57 @@ quit_exits_mode ~
Breakpoints are managed by persistent-breakpoints.nvim. They persist across Breakpoints are managed by persistent-breakpoints.nvim. They persist across
Neovim restarts and are visible in the sign column at all times. Neovim restarts and are visible in the sign column at all times.
`<leader>dx` toggles a breakpoint (always available). Global shortcuts (always available):
`<leader>dX` sets a conditional breakpoint (always available). `<leader>dx` Toggle breakpoint
`x`, `X`, `D` also work as shortcuts during debug mode. `<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: Default layout:
Left sidebar (40 columns): Sidebar (|turbo-debug.setup.sidebar|, left or right, 25-40 cols):
- Scopes (50%) — local/global variables in current frame - Call Stack (25%) — current frames, navigate with `<CR>`
- Watches (25%) — user-added watch expressions - Scopes (25%) — local/global variables in current frame
- Stacks (25%) — call stack navigation - Watches (25%) — user-added watch expressions (|W|)
- Breakpoints (25%) — all persistent breakpoints, jump with `<CR>`
Bottom tray (12 lines): Bottom pane (20% of editor lines, min 8 rows):
- REPL (50%) — evaluate expressions - Console — program stdout/stderr + adapter output
- Console (50%) — program stdout/stderr
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 Override the layout: >lua
require("turbo-debug").setup({ 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): Built-in adapter support (adapter binary must be installed):
@@ -192,25 +340,39 @@ Built-in adapter support (adapter binary must be installed):
Lua (Neovim) osv one-small-step-for-vimkind Lua (Neovim) osv one-small-step-for-vimkind
Path resolution order: Path resolution order:
1. User override via `opts.adapters` 1. User override via |turbo-debug.setup.adapters|
2. Binary on `$PATH` 2. Binary on `$PATH`
3. Standard mason install path (~/.local/share/nvim/mason/bin/) 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`.
Adapter binaries are the user's responsibility to install (mason.nvim is
the easiest route).
Override or add adapters: >lua Override or add adapters: >lua
require("turbo-debug").setup({ require("turbo-debug").setup({
adapters = { adapters = {
python = { ... }, -- override built-in python = { adapter = { ... }, configurations = { ... } },
zig = { ... }, -- add new language zig = { adapter = { ... }, configurations = { ... } },
}, },
builtin_adapters = false, -- disable all built-ins 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()* *turbo-debug.toggle()*
require("turbo-debug").toggle() require("turbo-debug").toggle()
@@ -222,24 +384,29 @@ require("turbo-debug").active()
Useful for statusline integration. Useful for statusline integration.
============================================================================== ==============================================================================
10. Troubleshooting *turbo-debug-troubleshooting* 11. Troubleshooting *turbo-debug-troubleshooting*
No adapter found ~ No adapter found ~
Ensure the adapter binary is installed and on your PATH, or installed 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. them.
Breakpoints not persisting ~ Breakpoints not persisting ~
Check that persistent-breakpoints.nvim is loaded. Run: Check that persistent-breakpoints.nvim is loaded: >vim
`:lua print(require("persistent-breakpoints") and "ok")` :lua print(require("persistent-breakpoints") and "ok")
<
Virtual text not appearing ~ Virtual text not appearing ~
Virtual text only appears during an active DAP session in debug mode. Virtual text only appears during an active DAP session in debug mode.
Ensure nvim-dap-virtual-text is loaded: Ensure nvim-dap-virtual-text is loaded: >vim
`:lua print(require("nvim-dap-virtual-text") and "ok")` :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 ~ 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: vim:tw=78:ts=8:ft=help:norl:
+14 -21
View File
@@ -4,15 +4,11 @@ local M = {}
local function pick_executable(executables) local function pick_executable(executables)
local co = coroutine.running() local co = coroutine.running()
if not co then if not co then return executables[1] end
return executables[1]
end
vim.ui.select(executables, { vim.ui.select(executables, {
prompt = "Select executable", prompt = "Select executable",
format_item = function(path) return vim.fn.fnamemodify(path, ":t") end, format_item = function(path) return vim.fn.fnamemodify(path, ":t") end,
}, function(choice) }, function(choice) coroutine.resume(co, choice) end)
coroutine.resume(co, choice)
end)
return coroutine.yield() return coroutine.yield()
end end
@@ -54,9 +50,7 @@ function M.register(dap)
}, },
} }
if not dap.adapters.codelldb then if not dap.adapters.codelldb then dap.adapters.codelldb = adapter end
dap.adapters.codelldb = adapter
end
-- C/C++/Swift: pick from compiled executables in cwd -- C/C++/Swift: pick from compiled executables in cwd
local prompt_config = { local prompt_config = {
@@ -64,9 +58,7 @@ function M.register(dap)
type = "codelldb", type = "codelldb",
request = "launch", request = "launch",
name = "Launch executable", name = "Launch executable",
program = function() program = function() return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") end,
return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file")
end,
cwd = "${workspaceFolder}", cwd = "${workspaceFolder}",
}, },
} }
@@ -75,15 +67,16 @@ function M.register(dap)
end end
-- Rust: cargo build + picker -- Rust: cargo build + picker
dap.configurations.rust = dap.configurations.rust or { dap.configurations.rust = dap.configurations.rust
{ or {
type = "codelldb", {
request = "launch", type = "codelldb",
name = "Cargo build & launch", request = "launch",
program = cargo_build_and_pick, name = "Cargo build & launch",
cwd = "${workspaceFolder}", program = cargo_build_and_pick,
}, cwd = "${workspaceFolder}",
} },
}
end end
return M return M
+98 -38
View File
@@ -23,43 +23,102 @@ end
-- simple executable adapters (inlined — no separate files needed) -- simple executable adapters (inlined — no separate files needed)
local function register_simple_adapters(dap) local function register_simple_adapters(dap)
local simple = { local simple = {
{ "netcoredbg", { "netcoredbg" }, { {
type = "executable", args = { "--interpreter=vscode" }, "netcoredbg",
}, { { { "netcoredbg" },
type = "netcoredbg", request = "launch", name = "Launch .NET", {
program = function() return vim.fn.input("Path to DLL: ", vim.fn.getcwd() .. "/bin/Debug/", "file") end, type = "executable",
} }, { "cs" } }, 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" },
},
{ "java", { "java-debug-adapter" }, { {
type = "executable", "java",
}, { { { "java-debug-adapter" },
type = "java", request = "launch", name = "Launch class", {
mainClass = function() return vim.fn.input("Main class: ") end, type = "executable",
cwd = "${workspaceFolder}", },
} }, { "java" } }, {
{
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", "kotlin",
}, { { { "kotlin-debug-adapter" },
type = "kotlin", request = "launch", name = "Launch main", {
mainClass = function() return vim.fn.input("Main class: ") end, type = "executable",
projectRoot = "${workspaceFolder}", },
} }, { "kotlin" } }, {
{
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", "php",
}, { { { "php-debug-adapter" },
type = "php", request = "launch", name = "Listen for Xdebug", port = 9003, {
} }, { "php" } }, type = "executable",
},
{
{
type = "php",
request = "launch",
name = "Listen for Xdebug",
port = 9003,
},
},
{ "php" },
},
{ "rdbg", { "rdbg" }, { {
type = "server", port = "${port}", "rdbg",
executable = { command = nil, args = { "-n", "--open", "--port", "${port}", "-c", "--", "ruby", "${file}" } }, { "rdbg" },
}, { { {
type = "rdbg", request = "launch", name = "Launch file", command = "ruby", script = "${file}", type = "server",
}, { port = "${port}",
type = "rdbg", request = "attach", name = "Attach", localfsMap = "${workspaceFolder}", executable = { command = nil, args = { "-n", "--open", "--port", "${port}", "-c", "--", "ruby", "${file}" } },
} }, { "ruby" } }, },
{
{
type = "rdbg",
request = "launch",
name = "Launch file",
command = "ruby",
script = "${file}",
},
{
type = "rdbg",
request = "attach",
name = "Attach",
localfsMap = "${workspaceFolder}",
},
},
{ "ruby" },
},
} }
for _, s in ipairs(simple) do for _, s in ipairs(simple) do
@@ -77,10 +136,13 @@ local function register_simple_adapters(dap)
local ok, osv = pcall(require, "osv") local ok, osv = pcall(require, "osv")
if ok then if ok then
M.register(dap, "nlua", { 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, 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" }) } }, { "lua" })
end end
end end
@@ -93,9 +155,7 @@ function M.setup()
-- load adapters with non-trivial logic from separate files -- 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" }) do
local ok, adapter_mod = pcall(require, "turbo-debug.adapters." .. name) local ok, adapter_mod = pcall(require, "turbo-debug.adapters." .. name)
if ok and adapter_mod.register then if ok and adapter_mod.register then adapter_mod.register(dap) end
adapter_mod.register(dap)
end
end end
-- register simple adapters inlined above -- register simple adapters inlined above
+5 -4
View File
@@ -27,15 +27,16 @@ function M.register(dap)
type = "pwa-node", type = "pwa-node",
request = "attach", request = "attach",
name = "Attach", name = "Attach",
processId = function() processId = function() return require("dap.utils").pick_process() end,
return require("dap.utils").pick_process()
end,
cwd = "${workspaceFolder}", cwd = "${workspaceFolder}",
}, },
} }
loader.register(dap, "pwa-node", adapter, configurations, { loader.register(dap, "pwa-node", adapter, configurations, {
"javascript", "typescript", "javascriptreact", "typescriptreact", "javascript",
"typescript",
"javascriptreact",
"typescriptreact",
}) })
end end
+15 -5
View File
@@ -42,6 +42,7 @@ function M.setup()
-- ⭕ U+2B55 heavy large circle = rejected breakpoint -- ⭕ U+2B55 heavy large circle = rejected breakpoint
-- 👉 U+1F449 backhand pointing right = current execution line (IP) -- 👉 U+1F449 backhand pointing right = current execution line (IP)
-- 📝 U+1F4DD memo = log point -- 📝 U+1F4DD memo = log point
-- stylua: ignore start
local signs = { local signs = {
{ "DapBreakpoint", "\xf0\x9f\x94\xb4" }, { "DapBreakpoint", "\xf0\x9f\x94\xb4" },
{ "DapBreakpointCondition", "\xf0\x9f\x9f\xa1" }, { "DapBreakpointCondition", "\xf0\x9f\x9f\xa1" },
@@ -49,9 +50,14 @@ function M.setup()
{ "DapStopped", "\xf0\x9f\x91\x89", { linehl = "DapStoppedLine" } }, { "DapStopped", "\xf0\x9f\x91\x89", { linehl = "DapStoppedLine" } },
{ "DapLogPoint", "\xf0\x9f\x93\x9d" }, { "DapLogPoint", "\xf0\x9f\x93\x9d" },
} }
-- stylua: ignore end
for _, s in ipairs(signs) do for _, s in ipairs(signs) do
local spec = { text = s[2], texthl = s[1], numhl = s[1] } 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 if s[3] then
for k, v in pairs(s[3]) do
spec[k] = v
end
end
vim.fn.sign_define(s[1], spec) vim.fn.sign_define(s[1], spec)
end end
@@ -62,6 +68,7 @@ function M.setup()
vim.api.nvim_set_hl(0, "NvimDapVirtualTextInfo", { default = true, link = "DiagnosticInfo", 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) -- 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, "TurboDebugHelpBrand", { default = true, link = "Title" })
vim.api.nvim_set_hl(0, "TurboDebugHelpTagline", { default = true, link = "Comment", italic = true }) 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, "TurboDebugHelpSection", { default = true, link = "Function" })
@@ -70,6 +77,7 @@ function M.setup()
vim.api.nvim_set_hl(0, "TurboDebugHelpParen", { default = true, link = "Comment" }) 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, "TurboDebugCtrlButton", { default = true, link = "Function" })
vim.api.nvim_set_hl(0, "TurboDebugCtrlMuted", { default = true, link = "Comment" }) vim.api.nvim_set_hl(0, "TurboDebugCtrlMuted", { default = true, link = "Comment" })
-- stylua: ignore end
-- derived highlight: the IP "reason" vtext (STEP / BREAKPOINT / EXCEPTION / -- derived highlight: the IP "reason" vtext (STEP / BREAKPOINT / EXCEPTION /
-- PAUSE / etc.) — bold + reverse + WarningMsg color so it renders as an -- PAUSE / etc.) — bold + reverse + WarningMsg color so it renders as an
@@ -80,7 +88,11 @@ function M.setup()
local ok, src = pcall(vim.api.nvim_get_hl, 0, { name = "WarningMsg", link = false }) 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 if not ok or not src or not src.fg then return end
vim.api.nvim_set_hl(0, "TurboDebugIPReason", { vim.api.nvim_set_hl(0, "TurboDebugIPReason", {
fg = src.fg, bg = src.bg, bold = true, reverse = true, default = true, fg = src.fg,
bg = src.bg,
bold = true,
reverse = true,
default = true,
}) })
end end
apply_ip_reason_hl() apply_ip_reason_hl()
@@ -98,9 +110,7 @@ function M.setup()
{ gk.clear_breakpoints, pb.clear_all_breakpoints, "Clear all breakpoints" }, { gk.clear_breakpoints, pb.clear_all_breakpoints, "Clear all breakpoints" },
} }
for _, b in ipairs(bindings) do for _, b in ipairs(bindings) do
if b[1] then if b[1] then vim.keymap.set("n", b[1], b[2], { silent = true, desc = b[3] }) end
vim.keymap.set("n", b[1], b[2], { silent = true, desc = b[3] })
end
end end
end end
+10 -10
View File
@@ -76,8 +76,8 @@ M.defaults = {
-- ▾ U+25BE down-triangle (expanded), ▸ U+25B8 right-triangle (collapsed), -- ▾ U+25BE down-triangle (expanded), ▸ U+25B8 right-triangle (collapsed),
-- ▶ U+25B6 right-pointing triangle (current frame / play-head) -- ▶ U+25B6 right-pointing triangle (current frame / play-head)
icons = { icons = {
expanded = "\xe2\x96\xbe", expanded = "\xe2\x96\xbe",
collapsed = "\xe2\x96\xb8", collapsed = "\xe2\x96\xb8",
current_frame = "\xe2\x96\xb6", current_frame = "\xe2\x96\xb6",
}, },
controls = { controls = {
@@ -86,14 +86,14 @@ M.defaults = {
-- ▶ play, ⏸ pause, ⤓ step-into, ⇒ step-over, ⤒ step-out, ↶ step-back, -- ▶ play, ⏸ pause, ⤓ step-into, ⇒ step-over, ⤒ step-out, ↶ step-back,
-- ↻ run-last, ■ terminate, ⊗ disconnect -- ↻ run-last, ■ terminate, ⊗ disconnect
icons = { icons = {
play = "\xe2\x96\xb6", play = "\xe2\x96\xb6",
pause = "\xe2\x8f\xb8", pause = "\xe2\x8f\xb8",
step_into = "\xe2\xa4\x93", step_into = "\xe2\xa4\x93",
step_over = "\xe2\x87\x92", step_over = "\xe2\x87\x92",
step_out = "\xe2\xa4\x92", step_out = "\xe2\xa4\x92",
step_back = "\xe2\x86\xb6", step_back = "\xe2\x86\xb6",
run_last = "\xe2\x86\xbb", run_last = "\xe2\x86\xbb",
terminate = "\xe2\x96\xa0", terminate = "\xe2\x96\xa0",
disconnect = "\xe2\x8a\x97", disconnect = "\xe2\x8a\x97",
}, },
}, },
+31 -33
View File
@@ -11,25 +11,25 @@ local buf_id = nil
-- config.opts.keys[action] lookup. Highlight spans are computed against -- config.opts.keys[action] lookup. Highlight spans are computed against
-- the actual key letter in the final line. -- the actual key letter in the final line.
local flow = { local flow = {
{ "continue", "c", "(", ")ontinue" }, { "continue", "c", "(", ")ontinue" },
{ "step_over", "s", "(", ")tep over" }, { "step_over", "s", "(", ")tep over" },
{ "step_into", "d", "(", ")escend into" }, { "step_into", "d", "(", ")escend into" },
{ "step_out", "r", "(", ")eturn out" }, { "step_out", "r", "(", ")eturn out" },
{ "run_to_cursor", "C", "run to (", ") cursor" }, { "run_to_cursor", "C", "run to (", ") cursor" },
{ "restart", "R", "(", ")estart session" }, { "restart", "R", "(", ")estart session" },
{ "terminate", "q", "(", ")uit debugging" }, { "terminate", "q", "(", ")uit debugging" },
} }
local breakpoints = { local breakpoints = {
{ "breakpoint", "x", "(", ") toggle breakpoint" }, { "breakpoint", "x", "(", ") toggle breakpoint" },
{ "cond_breakpoint", "X", "(", ") conditional breakpoint" }, { "cond_breakpoint", "X", "(", ") conditional breakpoint" },
{ "clear_breakpoints", "D", "(", ")elete all breakpoints" }, { "clear_breakpoints", "D", "(", ")elete all breakpoints" },
} }
local inspect = { local inspect = {
{ "watch", "W", "(", ")atch expression" }, { "watch", "W", "(", ")atch expression" },
{ "hover", "K", "(", ") hover / inspect" }, { "hover", "K", "(", ") hover / inspect" },
{ "eval", "E", "(", ")val in REPL" }, { "eval", "E", "(", ")val in REPL" },
} }
local meta = { local meta = {
@@ -37,27 +37,27 @@ local meta = {
} }
local globals = { local globals = {
{ "breakpoint", "toggle breakpoint" }, { "breakpoint", "toggle breakpoint" },
{ "cond_breakpoint", "conditional breakpoint" }, { "cond_breakpoint", "conditional breakpoint" },
{ "clear_breakpoints", "delete all breakpoints" }, { "clear_breakpoints", "delete all breakpoints" },
} }
local dapui_panes = { local dapui_panes = {
{ "<CR>", "expand / jump to target" }, { "<CR>", "expand / jump to target" },
{ "o", "open target (jump to frame / breakpoint)" }, { "o", "open target (jump to frame / breakpoint)" },
{ "e", "edit (watches)" }, { "e", "edit (watches)" },
{ "d", "remove (watches)" }, { "d", "remove (watches)" },
{ "r", "send to REPL" }, { "r", "send to REPL" },
{ "t", "toggle subtle frames (call stack)" }, { "t", "toggle subtle frames (call stack)" },
} }
-- ▶ U+25B6, ● U+25CF, ◉ U+25C9, ◆ U+25C6, ❯ U+276F, ≡ U+2261 -- ▶ U+25B6, ● U+25CF, ◉ U+25C9, ◆ U+25C6, ❯ U+276F, ≡ U+2261
local ICON_FLOW = "\xe2\x96\xb6" local ICON_FLOW = "\xe2\x96\xb6"
local ICON_BRK = "\xe2\x97\x8f" local ICON_BRK = "\xe2\x97\x8f"
local ICON_INSPECT = "\xe2\x97\x89" local ICON_INSPECT = "\xe2\x97\x89"
local ICON_META = "\xe2\x97\x86" local ICON_META = "\xe2\x97\x86"
local ICON_CHEVRON = "\xe2\x9d\xaf" local ICON_CHEVRON = "\xe2\x9d\xaf"
local ICON_STACK = "\xe2\x89\xa1" local ICON_STACK = "\xe2\x89\xa1"
-- a brand header and tagline -- a brand header and tagline
local BRAND_TITLE = " " .. ICON_FLOW .. " TURBO DEBUG" local BRAND_TITLE = " " .. ICON_FLOW .. " TURBO DEBUG"
@@ -68,7 +68,7 @@ local function build()
local hl = {} -- list of { line_idx, col_start, col_end, hl_group } local hl = {} -- list of { line_idx, col_start, col_end, hl_group }
-- title + tagline highlights -- title + tagline highlights
hl[#hl + 1] = { 1, 0, #BRAND_TITLE, "TurboDebugHelpBrand" } hl[#hl + 1] = { 1, 0, #BRAND_TITLE, "TurboDebugHelpBrand" }
hl[#hl + 1] = { 2, 0, #BRAND_TAGLINE, "TurboDebugHelpTagline" } hl[#hl + 1] = { 2, 0, #BRAND_TAGLINE, "TurboDebugHelpTagline" }
local function section(icon, title) local function section(icon, title)
@@ -161,18 +161,16 @@ local function build()
lines[#lines + 1] = "" lines[#lines + 1] = ""
local width = 0 local width = 0
for _, l in ipairs(lines) do if vim.fn.strdisplaywidth(l) > width then width = vim.fn.strdisplaywidth(l) end end 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 return lines, width + 2, hl
end end
function M.close() function M.close()
if win_id and vim.api.nvim_win_is_valid(win_id) then if win_id and vim.api.nvim_win_is_valid(win_id) then vim.api.nvim_win_close(win_id, true) end
vim.api.nvim_win_close(win_id, true)
end
win_id = nil win_id = nil
if buf_id and vim.api.nvim_buf_is_valid(buf_id) then if buf_id and vim.api.nvim_buf_is_valid(buf_id) then vim.api.nvim_buf_delete(buf_id, { force = true }) end
vim.api.nvim_buf_delete(buf_id, { force = true })
end
buf_id = nil buf_id = nil
end end
@@ -188,7 +186,9 @@ function M.open()
local ns = vim.api.nvim_create_namespace("turbo-debug.help") local ns = vim.api.nvim_create_namespace("turbo-debug.help")
for _, span in ipairs(hl) do for _, span in ipairs(hl) do
pcall(vim.api.nvim_buf_set_extmark, buf_id, ns, span[1], span[2], { 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_row = span[1],
end_col = span[3],
hl_group = span[4],
}) })
end end
@@ -216,8 +216,6 @@ function M.open()
}) })
end end
function M.is_open() function M.is_open() return win_id ~= nil and vim.api.nvim_win_is_valid(win_id) end
return win_id ~= nil and vim.api.nvim_win_is_valid(win_id)
end
return M return M
+4 -7
View File
@@ -27,17 +27,14 @@ function M.setup(opts)
local toggle_key = config.opts.global_keys.toggle local toggle_key = config.opts.global_keys.toggle
if toggle_key then if toggle_key then
vim.keymap.set("n", toggle_key, function() mode.toggle() end, { vim.keymap.set("n", toggle_key, function() mode.toggle() end, {
silent = true, desc = "Toggle debug mode", silent = true,
desc = "Toggle debug mode",
}) })
end end
end end
function M.toggle() function M.toggle() mode.toggle() end
mode.toggle()
end
function M.active() function M.active() return mode.is_active() end
return mode.is_active()
end
return M return M
+176 -146
View File
@@ -57,6 +57,7 @@ local vt_initialized = false
-- All in the PUA range U+EA60..U+EC1E. Encoded as UTF-8 bytes so the source -- All in the PUA range U+EA60..U+EC1E. Encoded as UTF-8 bytes so the source
-- file stays ASCII through any tool pipeline that strips PUA codepoints. -- file stays ASCII through any tool pipeline that strips PUA codepoints.
-- stylua: ignore start
local ICON = { local ICON = {
-- Control-bar emoji — strictly supplementary-plane (U+1F000+) or -- Control-bar emoji — strictly supplementary-plane (U+1F000+) or
-- standalone-emoji-default (like 🛑) codepoints. No VS16-dependent -- standalone-emoji-default (like 🛑) codepoints. No VS16-dependent
@@ -105,6 +106,7 @@ local ICON = {
wrap_mark = "\xe2\x86\xb3", -- ↳ downward arrow w/ tip right wrap_mark = "\xe2\x86\xb3", -- ↳ downward arrow w/ tip right
hline = "\xe2\x94\x80", -- ─ light horizontal hline = "\xe2\x94\x80", -- ─ light horizontal
} }
-- stylua: ignore end
-- ─── layout ────────────────────────────────────────────────────────────────── -- ─── layout ──────────────────────────────────────────────────────────────────
@@ -114,8 +116,7 @@ local function build_default_layouts()
-- Console height: max(8 rows, 20% of total editor lines). More -- Console height: max(8 rows, 20% of total editor lines). More
-- generous than the previous 15% — user reported the Console kept -- generous than the previous 15% — user reported the Console kept
-- feeling "laughably small" so we're allocating real estate. -- feeling "laughably small" so we're allocating real estate.
local console_size = config.opts.console_height local console_size = config.opts.console_height or math.max(8, math.floor(vim.o.lines * 0.2))
or math.max(8, math.floor(vim.o.lines * 0.2))
-- Sidebar width: ideal 40 cols, never less than 25, never more than 1/3 -- Sidebar width: ideal 40 cols, never less than 25, never more than 1/3
-- of tty width. Scales down gracefully on narrow terminals, stops at 40 -- of tty width. Scales down gracefully on narrow terminals, stops at 40
@@ -129,9 +130,9 @@ local function build_default_layouts()
-- Order: Call Stack → Scopes → Watches → Breakpoints. -- Order: Call Stack → Scopes → Watches → Breakpoints.
-- Equal 25% share per user preference. -- Equal 25% share per user preference.
elements = { elements = {
{ id = "stacks", size = 0.25 }, { id = "stacks", size = 0.25 },
{ id = "scopes", size = 0.25 }, { id = "scopes", size = 0.25 },
{ id = "watches", size = 0.25 }, { id = "watches", size = 0.25 },
{ id = "breakpoints", size = 0.25 }, { id = "breakpoints", size = 0.25 },
}, },
}, },
@@ -151,14 +152,14 @@ end
local function define_highlights() local function define_highlights()
local hl = vim.api.nvim_set_hl local hl = vim.api.nvim_set_hl
hl(0, "TurboDebugBorderActive", { default = true, link = "Function" }) hl(0, "TurboDebugBorderActive", { default = true, link = "Function" })
hl(0, "TurboDebugBorderInactive", { default = true, link = "Comment" }) hl(0, "TurboDebugBorderInactive", { default = true, link = "Comment" })
hl(0, "TurboDebugWinbar", { default = true, link = "DiagnosticError" }) hl(0, "TurboDebugWinbar", { default = true, link = "DiagnosticError" })
-- Bar background: link to Normal so the bar blends with dapui's own -- Bar background: link to Normal so the bar blends with dapui's own
-- pane borders (which use Normal or WinBar — both share the editor's -- pane borders (which use Normal or WinBar — both share the editor's
-- default background). StatusLine was too dark and created a visible -- default background). StatusLine was too dark and created a visible
-- seam between the bar and the surrounding panes. -- seam between the bar and the surrounding panes.
hl(0, "TurboDebugBar", { default = true, link = "Normal" }) hl(0, "TurboDebugBar", { default = true, link = "Normal" })
-- Bar separator: fg derived from WinBar.fg at runtime so it matches -- Bar separator: fg derived from WinBar.fg at runtime so it matches
-- whatever color the user's theme uses for pane-title chrome -- whatever color the user's theme uses for pane-title chrome
-- (dapui renders each pane's title in its winbar, so this is -- (dapui renders each pane's title in its winbar, so this is
@@ -168,17 +169,17 @@ local function define_highlights()
-- --
-- Placeholder link so the group exists as a sane default even -- Placeholder link so the group exists as a sane default even
-- before the derive runs. -- before the derive runs.
hl(0, "TurboDebugBarSeparator", { default = true, link = "WinSeparator" }) hl(0, "TurboDebugBarSeparator", { default = true, link = "WinSeparator" })
-- Control labels: link to Normal so the text blends with the bar's -- Control labels: link to Normal so the text blends with the bar's
-- Normal-bg surface. StatusLine bg was creating a visible color patch -- Normal-bg surface. StatusLine bg was creating a visible color patch
-- behind each label. The key letter still pops via TurboDebugBarKey -- behind each label. The key letter still pops via TurboDebugBarKey
-- (Special fg, bold), and icons carry their own emoji color. -- (Special fg, bold), and icons carry their own emoji color.
hl(0, "TurboDebugBarCtrl", { default = true, link = "Normal" }) hl(0, "TurboDebugBarCtrl", { default = true, link = "Normal" })
hl(0, "TurboDebugBarStatus", { default = true, link = "Comment" }) hl(0, "TurboDebugBarStatus", { default = true, link = "Comment" })
hl(0, "TurboDebugBarBrand", { default = true, link = "Title" }) hl(0, "TurboDebugBarBrand", { default = true, link = "Title" })
hl(0, "TurboDebugBarReady", { default = true, link = "DiagnosticHint" }) hl(0, "TurboDebugBarReady", { default = true, link = "DiagnosticHint" })
hl(0, "TurboDebugBarRunning", { default = true, link = "DiagnosticInfo" }) hl(0, "TurboDebugBarRunning", { default = true, link = "DiagnosticInfo" })
hl(0, "TurboDebugBarPaused", { default = true, link = "DiagnosticError" }) hl(0, "TurboDebugBarPaused", { default = true, link = "DiagnosticError" })
-- Italic qualifier words ("over", "into", "out") shown after the -- Italic qualifier words ("over", "into", "out") shown after the
-- step/descend/return labels. Subordinate styling: Comment fg + italic, -- step/descend/return labels. Subordinate styling: Comment fg + italic,
-- so the key label ("(s)tep") reads primary and the qualifier ("over") -- so the key label ("(s)tep") reads primary and the qualifier ("over")
@@ -191,7 +192,9 @@ local function define_highlights()
return return
end end
vim.api.nvim_set_hl(0, "TurboDebugBarItalic", { vim.api.nvim_set_hl(0, "TurboDebugBarItalic", {
fg = src.fg, italic = true, default = true, fg = src.fg,
italic = true,
default = true,
}) })
end end
local function install_cs_listener(name, fn) local function install_cs_listener(name, fn)
@@ -215,7 +218,10 @@ local function define_highlights()
return return
end end
vim.api.nvim_set_hl(0, "TurboDebugBarKey", { vim.api.nvim_set_hl(0, "TurboDebugBarKey", {
fg = src.fg, bg = src.bg, bold = true, default = true, fg = src.fg,
bg = src.bg,
bold = true,
default = true,
}) })
end end
install_cs_listener("TurboDebugBarKeyHL", apply_key_hl) install_cs_listener("TurboDebugBarKeyHL", apply_key_hl)
@@ -256,8 +262,10 @@ end
-- forces the pane's winbar row to stay bright even when the pane is -- forces the pane's winbar row to stay bright even when the pane is
-- unfocused (nvim renders unfocused wins with WinBarNC). Nordfox happens to -- unfocused (nvim renders unfocused wins with WinBarNC). Nordfox happens to
-- make them identical; other themes don't. -- make them identical; other themes don't.
local DAPUI_WINHL_ACTIVE = "WinBar:WinBar,WinBarNC:WinBar,WinSeparator:TurboDebugBorderActive,FloatBorder:TurboDebugBorderActive" local DAPUI_WINHL_ACTIVE =
local DAPUI_WINHL_INACTIVE = "WinBar:WinBar,WinBarNC:WinBar,WinSeparator:TurboDebugBorderInactive,FloatBorder:TurboDebugBorderInactive" "WinBar:WinBar,WinBarNC:WinBar,WinSeparator:TurboDebugBorderActive,FloatBorder:TurboDebugBorderActive"
local DAPUI_WINHL_INACTIVE =
"WinBar:WinBar,WinBarNC:WinBar,WinSeparator:TurboDebugBorderInactive,FloatBorder:TurboDebugBorderInactive"
local function setup_active_win_highlights() local function setup_active_win_highlights()
local active_hl = DAPUI_WINHL_ACTIVE local active_hl = DAPUI_WINHL_ACTIVE
@@ -267,9 +275,7 @@ local function setup_active_win_highlights()
local function apply(win, hl) local function apply(win, hl)
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_config(win).relative == "" then if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_config(win).relative == "" then
local ft = vim.bo[vim.api.nvim_win_get_buf(win)].filetype or "" local ft = vim.bo[vim.api.nvim_win_get_buf(win)].filetype or ""
if ft:match("^dapui_") or ft == "dap-repl" then if ft:match("^dapui_") or ft == "dap-repl" then vim.wo[win].winhighlight = hl end
vim.wo[win].winhighlight = hl
end
end end
end end
vim.api.nvim_create_autocmd("WinEnter", { vim.api.nvim_create_autocmd("WinEnter", {
@@ -299,8 +305,12 @@ local function buf_ok(b) return b and vim.api.nvim_buf_is_valid(b) end
-- setup_active_win_highlights / the FileType autocmd, and different from -- setup_active_win_highlights / the FileType autocmd, and different from
-- schedule_console_redraw's `dapui_console` single-check — don't unify. -- schedule_console_redraw's `dapui_console` single-check — don't unify.
local DAPUI_PANE_FTS = { local DAPUI_PANE_FTS = {
dapui_stacks = true, dapui_scopes = true, dapui_watches = true, dapui_stacks = true,
dapui_breakpoints = true, dapui_console = true, ["dap-repl"] = true, dapui_scopes = true,
dapui_watches = true,
dapui_breakpoints = true,
dapui_console = true,
["dap-repl"] = true,
} }
local function is_dapui_pane_ft(ft) return ft ~= nil and DAPUI_PANE_FTS[ft] == true end local function is_dapui_pane_ft(ft) return ft ~= nil and DAPUI_PANE_FTS[ft] == true end
@@ -317,9 +327,7 @@ local function for_each_dapui_pane(cb)
end end
-- Bar extmarks go through bar_ns + pcall. IP marker uses ip_ns separately. -- Bar extmarks go through bar_ns + pcall. IP marker uses ip_ns separately.
local function safe_extmark(buf, row, col, opts) local function safe_extmark(buf, row, col, opts) pcall(vim.api.nvim_buf_set_extmark, buf, bar_ns, row, col, opts) end
pcall(vim.api.nvim_buf_set_extmark, buf, bar_ns, row, col, opts)
end
local function bar_set_lines(buf, lines) local function bar_set_lines(buf, lines)
vim.bo[buf].modifiable = true vim.bo[buf].modifiable = true
@@ -332,12 +340,18 @@ local function safe_set_height(w, h) pcall(vim.api.nvim_win_set_height, w, h) en
-- Defer fn via vim.schedule; re-check `active` at fire time so the body -- Defer fn via vim.schedule; re-check `active` at fire time so the body
-- doesn't run after M.exit cleared state. -- doesn't run after M.exit cleared state.
local function if_still_active(fn) local function if_still_active(fn)
vim.schedule(function() if active then fn() end end) vim.schedule(function()
if active then fn() end
end)
end end
local function invalidate_dead_bars() local function invalidate_dead_bars()
if not win_ok(sbar_win) then sbar_win, sbar_buf = nil, nil end if not win_ok(sbar_win) then
if not win_ok(cbar_win) then cbar_win, cbar_buf = nil, nil end sbar_win, sbar_buf = nil, nil
end
if not win_ok(cbar_win) then
cbar_win, cbar_buf = nil, nil
end
end end
-- click zones for the control bar. Each entry: { line_1based, dcol_start, dcol_end, fn } -- click zones for the control bar. Each entry: { line_1based, dcol_start, dcol_end, fn }
@@ -354,7 +368,10 @@ local function dap_state()
end end
local function action(name) local function action(name)
return function() local a = M.actions(); if a[name] then a[name]() end end return function()
local a = M.actions()
if a[name] then a[name]() end
end
end end
-- Returns the user's configured key for an action, or the fallback default -- Returns the user's configured key for an action, or the fallback default
@@ -388,11 +405,14 @@ local function build_control_text(icon, actual_key, label, italic)
local function add(s) parts[#parts + 1] = s end local function add(s) parts[#parts + 1] = s end
local function col() return #table.concat(parts) end local function col() return #table.concat(parts) end
local inline = actual_key and label and #actual_key == 1 local inline = actual_key
and #label >= 1 and label
and label:sub(1, 1):lower() == actual_key:lower() and #actual_key == 1
and #label >= 1
and label:sub(1, 1):lower() == actual_key:lower()
add(icon); add(" ") add(icon)
add(" ")
add("(") add("(")
local key_col = col() local key_col = col()
add(actual_key) add(actual_key)
@@ -459,21 +479,29 @@ local function render_sbar()
vim.api.nvim_buf_clear_namespace(sbar_buf, bar_ns, 0, -1) vim.api.nvim_buf_clear_namespace(sbar_buf, bar_ns, 0, -1)
safe_extmark(sbar_buf, 0, 0, { safe_extmark(sbar_buf, 0, 0, {
end_row = 0, end_col = #sep, hl_group = "TurboDebugBarSeparator", end_row = 0,
end_col = #sep,
hl_group = "TurboDebugBarSeparator",
}) })
-- content row -- content row
safe_extmark(sbar_buf, 1, 0, { safe_extmark(sbar_buf, 1, 0, {
end_row = 1, end_col = #brand_text, hl_group = "TurboDebugBarBrand", end_row = 1,
end_col = #brand_text,
hl_group = "TurboDebugBarBrand",
}) })
if status_dw > 0 then if status_dw > 0 then
local status_byte_start = #brand_text + pad_left local status_byte_start = #brand_text + pad_left
safe_extmark(sbar_buf, 1, status_byte_start, { safe_extmark(sbar_buf, 1, status_byte_start, {
end_row = 1, end_col = status_byte_start + #status_msg, hl_group = "TurboDebugBarStatus", end_row = 1,
end_col = status_byte_start + #status_msg,
hl_group = "TurboDebugBarStatus",
}) })
end end
local chip_byte_start = #content_line - #state_chip local chip_byte_start = #content_line - #state_chip
safe_extmark(sbar_buf, 1, chip_byte_start, { safe_extmark(sbar_buf, 1, chip_byte_start, {
end_row = 1, end_col = #content_line, hl_group = state_hl, end_row = 1,
end_col = #content_line,
hl_group = state_hl,
}) })
end end
@@ -504,17 +532,27 @@ local function render_cbar()
local continue_label = has_session and "continue" or "start" local continue_label = has_session and "continue" or "start"
local terminate_label = has_session and "terminate" or "quit" local terminate_label = has_session and "terminate" or "quit"
-- stylua: ignore start
local controls = { local controls = {
{ id = "continue", icon = continue_icon, default = "c", label = continue_label, italic = nil, fn = action("continue") }, { id = "continue", icon = continue_icon, default = "c", label = continue_label, italic = nil, fn = action("continue") },
{ id = "step_over", icon = ICON.step_over, default = "s", label = "step", italic = "over", fn = action("step_over") }, { id = "step_over", icon = ICON.step_over, default = "s", label = "step", italic = "over", fn = action("step_over") },
{ id = "step_into", icon = ICON.step_into, default = "d", label = "descend", italic = "into", fn = action("step_into") }, { id = "step_into", icon = ICON.step_into, default = "d", label = "descend", italic = "into", fn = action("step_into") },
{ id = "step_out", icon = ICON.step_out, default = "r", label = "return", italic = "out", fn = action("step_out") }, { id = "step_out", icon = ICON.step_out, default = "r", label = "return", italic = "out", fn = action("step_out") },
} }
-- stylua: ignore end
-- (R)estart is only meaningful during an active session -- (R)estart is only meaningful during an active session
if has_session then if has_session then
controls[#controls + 1] = { id = "restart", icon = ICON.restart, default = "R", label = "restart", italic = nil, fn = action("restart") } controls[#controls + 1] =
{ id = "restart", icon = ICON.restart, default = "R", label = "restart", italic = nil, fn = action("restart") }
end end
controls[#controls + 1] = { id = "terminate", icon = ICON.stop, default = "q", label = terminate_label, italic = nil, fn = action("terminate") } controls[#controls + 1] = {
id = "terminate",
icon = ICON.stop,
default = "q",
label = terminate_label,
italic = nil,
fn = action("terminate"),
}
local gap = " " local gap = " "
local function build_controls(include_italic) local function build_controls(include_italic)
@@ -534,9 +572,7 @@ local function render_cbar()
parts[#parts + 1] = txt parts[#parts + 1] = txt
spans[#spans + 1] = { prefix_len, prefix_len + #txt, "TurboDebugBarCtrl" } spans[#spans + 1] = { prefix_len, prefix_len + #txt, "TurboDebugBarCtrl" }
key_spans[#key_spans + 1] = { prefix_len + kcol, prefix_len + kend } key_spans[#key_spans + 1] = { prefix_len + kcol, prefix_len + kend }
if icol then if icol then italic_spans[#italic_spans + 1] = { prefix_len + icol, prefix_len + iend } end
italic_spans[#italic_spans + 1] = { prefix_len + icol, prefix_len + iend }
end
zones[#zones + 1] = { prefix_len, prefix_len + #txt, c.fn } zones[#zones + 1] = { prefix_len, prefix_len + #txt, c.fn }
end end
end end
@@ -546,12 +582,10 @@ local function render_cbar()
-- Try with italic qualifiers first; fall back to the compact form if -- Try with italic qualifiers first; fall back to the compact form if
-- the label row would overflow the available width. Min 1-char padding -- the label row would overflow the available width. Min 1-char padding
-- on each side of the controls-plus-help block. -- on each side of the controls-plus-help block.
local controls_text, ctrl_spans, ctrl_key_spans, ctrl_italic_spans, ctrl_zones_bytes = local controls_text, ctrl_spans, ctrl_key_spans, ctrl_italic_spans, ctrl_zones_bytes = build_controls(true)
build_controls(true)
local controls_dw = vim.fn.strdisplaywidth(controls_text) local controls_dw = vim.fn.strdisplaywidth(controls_text)
if controls_dw + help_dw + 2 > width then if controls_dw + help_dw + 2 > width then
controls_text, ctrl_spans, ctrl_key_spans, ctrl_italic_spans, ctrl_zones_bytes = controls_text, ctrl_spans, ctrl_key_spans, ctrl_italic_spans, ctrl_zones_bytes = build_controls(false)
build_controls(false)
controls_dw = vim.fn.strdisplaywidth(controls_text) controls_dw = vim.fn.strdisplaywidth(controls_text)
end end
@@ -570,30 +604,40 @@ local function render_cbar()
vim.api.nvim_buf_clear_namespace(cbar_buf, bar_ns, 0, -1) vim.api.nvim_buf_clear_namespace(cbar_buf, bar_ns, 0, -1)
safe_extmark(cbar_buf, 1, 0, { safe_extmark(cbar_buf, 1, 0, {
end_row = 1, end_col = #sep, hl_group = "TurboDebugBarSeparator", end_row = 1,
end_col = #sep,
hl_group = "TurboDebugBarSeparator",
}) })
-- control labels on row 0 (content row) -- control labels on row 0 (content row)
local ctrl_byte_offset = ctrl_pad_left local ctrl_byte_offset = ctrl_pad_left
for _, span in ipairs(ctrl_spans) do for _, span in ipairs(ctrl_spans) do
safe_extmark(cbar_buf, 0, ctrl_byte_offset + span[1], { safe_extmark(cbar_buf, 0, ctrl_byte_offset + span[1], {
end_row = 0, end_col = ctrl_byte_offset + span[2], hl_group = span[3], end_row = 0,
end_col = ctrl_byte_offset + span[2],
hl_group = span[3],
}) })
end end
for _, span in ipairs(ctrl_key_spans) do for _, span in ipairs(ctrl_key_spans) do
safe_extmark(cbar_buf, 0, ctrl_byte_offset + span[1], { safe_extmark(cbar_buf, 0, ctrl_byte_offset + span[1], {
end_row = 0, end_col = ctrl_byte_offset + span[2], hl_group = "TurboDebugBarKey", end_row = 0,
end_col = ctrl_byte_offset + span[2],
hl_group = "TurboDebugBarKey",
}) })
end end
for _, span in ipairs(ctrl_italic_spans) do for _, span in ipairs(ctrl_italic_spans) do
safe_extmark(cbar_buf, 0, ctrl_byte_offset + span[1], { safe_extmark(cbar_buf, 0, ctrl_byte_offset + span[1], {
end_row = 0, end_col = ctrl_byte_offset + span[2], hl_group = "TurboDebugBarItalic", end_row = 0,
end_col = ctrl_byte_offset + span[2],
hl_group = "TurboDebugBarItalic",
}) })
end end
local help_byte_start = #content_line - #help_text local help_byte_start = #content_line - #help_text
safe_extmark(cbar_buf, 0, help_byte_start, { safe_extmark(cbar_buf, 0, help_byte_start, {
end_row = 0, end_col = #content_line, hl_group = "TurboDebugBarCtrl", end_row = 0,
end_col = #content_line,
hl_group = "TurboDebugBarCtrl",
}) })
cbar_zones = {} cbar_zones = {}
@@ -601,14 +645,13 @@ local function render_cbar()
local byte_start = ctrl_byte_offset + z[1] local byte_start = ctrl_byte_offset + z[1]
local byte_end = ctrl_byte_offset + z[2] local byte_end = ctrl_byte_offset + z[2]
local dcol_start = vim.fn.strdisplaywidth(content_line:sub(1, byte_start)) local dcol_start = vim.fn.strdisplaywidth(content_line:sub(1, byte_start))
local dcol_end = vim.fn.strdisplaywidth(content_line:sub(1, byte_end)) local dcol_end = vim.fn.strdisplaywidth(content_line:sub(1, byte_end))
-- content is on buffer row 0 = window line 1 -- content is on buffer row 0 = window line 1
cbar_zones[#cbar_zones + 1] = { 1, dcol_start, dcol_end, z[3] } cbar_zones[#cbar_zones + 1] = { 1, dcol_start, dcol_end, z[3] }
end end
local help_dcol_start = vim.fn.strdisplaywidth(content_line:sub(1, help_byte_start)) local help_dcol_start = vim.fn.strdisplaywidth(content_line:sub(1, help_byte_start))
local help_dcol_end = vim.fn.strdisplaywidth(content_line) local help_dcol_end = vim.fn.strdisplaywidth(content_line)
cbar_zones[#cbar_zones + 1] = { 1, help_dcol_start, help_dcol_end, cbar_zones[#cbar_zones + 1] = { 1, help_dcol_start, help_dcol_end, function() require("turbo-debug.help").open() end }
function() require("turbo-debug.help").open() end }
end end
local function render_bars() local function render_bars()
@@ -652,8 +695,9 @@ local function setup_bar_win(win)
-- might pick up (with laststatus=2 every window gets one) blends -- might pick up (with laststatus=2 every window gets one) blends
-- into the bar's Normal bg instead of rendering in the dark default -- into the bar's Normal bg instead of rendering in the dark default
-- StatusLine color — which was the "wrong color border below top bar". -- StatusLine color — which was the "wrong color border below top bar".
vim.wo[win].winhighlight = "Normal:TurboDebugBar,EndOfBuffer:TurboDebugBar,StatusLine:TurboDebugBar,StatusLineNC:TurboDebugBar" vim.wo[win].winhighlight =
vim.wo[win].statusline = " " -- empty content; hl makes it invisible "Normal:TurboDebugBar,EndOfBuffer:TurboDebugBar,StatusLine:TurboDebugBar,StatusLineNC:TurboDebugBar"
vim.wo[win].statusline = " " -- empty content; hl makes it invisible
vim.wo[win].winfixheight = true vim.wo[win].winfixheight = true
vim.wo[win].list = false vim.wo[win].list = false
vim.wo[win].cursorline = false vim.wo[win].cursorline = false
@@ -671,9 +715,7 @@ local function bounce_out(buf)
buffer = buf, buffer = buf,
callback = function() callback = function()
vim.schedule(function() vim.schedule(function()
if vim.api.nvim_get_current_buf() == buf then if vim.api.nvim_get_current_buf() == buf then pcall(vim.cmd, "wincmd p") end
pcall(vim.cmd, "wincmd p")
end
end) end)
end, end,
}) })
@@ -700,9 +742,7 @@ local function open_bars()
bounce_out(sbar_buf) bounce_out(sbar_buf)
end end
if vim.api.nvim_win_is_valid(caller_win) then if vim.api.nvim_win_is_valid(caller_win) then pcall(vim.api.nvim_set_current_win, caller_win) end
pcall(vim.api.nvim_set_current_win, caller_win)
end
-- control bar (bottom) -- control bar (bottom)
if not (win_ok(cbar_win)) then if not (win_ok(cbar_win)) then
@@ -719,9 +759,7 @@ local function open_bars()
bounce_out(cbar_buf) bounce_out(cbar_buf)
end end
if vim.api.nvim_win_is_valid(caller_win) then if vim.api.nvim_win_is_valid(caller_win) then pcall(vim.api.nvim_set_current_win, caller_win) end
pcall(vim.api.nvim_set_current_win, caller_win)
end
render_bars() render_bars()
end end
@@ -735,15 +773,12 @@ local function pin_dapui_sizes()
local pane_wins = {} local pane_wins = {}
for_each_dapui_pane(function(buf, ft) for_each_dapui_pane(function(buf, ft)
local wins = vim.fn.win_findbuf(buf) local wins = vim.fn.win_findbuf(buf)
if wins and win_ok(wins[1]) then if wins and win_ok(wins[1]) then pane_wins[ft] = wins[1] end
pane_wins[ft] = wins[1]
end
end) end)
-- Console to configured height -- Console to configured height
if pane_wins.dapui_console then if pane_wins.dapui_console then
local target = config.opts.console_height local target = config.opts.console_height or math.max(8, math.floor(vim.o.lines * 0.2))
or math.max(8, math.floor(vim.o.lines * 0.2))
safe_set_height(pane_wins.dapui_console, target) safe_set_height(pane_wins.dapui_console, target)
end end
@@ -790,12 +825,8 @@ end
-- subsequent pin_dapui_sizes() call redistributes it across the dapui -- subsequent pin_dapui_sizes() call redistributes it across the dapui
-- panes according to our initial proportions. -- panes according to our initial proportions.
local function ensure_bar_heights() local function ensure_bar_heights()
if win_ok(sbar_win) and vim.api.nvim_win_get_height(sbar_win) ~= 2 then if win_ok(sbar_win) and vim.api.nvim_win_get_height(sbar_win) ~= 2 then safe_set_height(sbar_win, 2) end
safe_set_height(sbar_win, 2) if win_ok(cbar_win) and vim.api.nvim_win_get_height(cbar_win) ~= 2 then safe_set_height(cbar_win, 2) end
end
if win_ok(cbar_win) and vim.api.nvim_win_get_height(cbar_win) ~= 2 then
safe_set_height(cbar_win, 2)
end
end end
-- Sum the heights of all currently-open dapui pane windows. Used to detect -- Sum the heights of all currently-open dapui pane windows. Used to detect
@@ -828,6 +859,7 @@ local function set_dapui_window_opts(win)
-- rendered gutter), and the `~` end-of-buffer markers — these are pure -- rendered gutter), and the `~` end-of-buffer markers — these are pure
-- noise in debug-info panes where there's no file to navigate. -- noise in debug-info panes where there's no file to navigate.
if vim.api.nvim_win_is_valid(win) then if vim.api.nvim_win_is_valid(win) then
-- stylua: ignore start
vim.wo[win].wrap = true vim.wo[win].wrap = true
vim.wo[win].breakindent = true vim.wo[win].breakindent = true
vim.wo[win].linebreak = true vim.wo[win].linebreak = true
@@ -845,6 +877,7 @@ local function set_dapui_window_opts(win)
-- first render (not just after the first WinEnter/WinLeave cycle). -- first render (not just after the first WinEnter/WinLeave cycle).
-- setup_active_win_highlights overwrites on focus change. -- setup_active_win_highlights overwrites on focus change.
vim.wo[win].winhighlight = DAPUI_WINHL_INACTIVE vim.wo[win].winhighlight = DAPUI_WINHL_INACTIVE
-- stylua: ignore end
end end
end end
@@ -896,19 +929,18 @@ local function ensure_dapui()
patch_format_value() patch_format_value()
local dap = require("dap") local dap = require("dap")
dap.listeners.after.event_initialized["turbo-debug"] = function() dap.listeners.after.event_initialized["turbo-debug"] = function() require("dapui").open() end
require("dapui").open()
end
-- K = hover during a live dap session (global, not per-buffer) -- K = hover during a live dap session (global, not per-buffer)
dap.listeners.after.event_initialized["turbo-debug-hover"] = function() dap.listeners.after.event_initialized["turbo-debug-hover"] = function()
vim.keymap.set("n", "K", function() vim.keymap.set(
require("dap.ui.widgets").hover(nil, { border = "rounded" }) "n",
end, { silent = true, desc = "DAP hover" }) "K",
end function() require("dap.ui.widgets").hover(nil, { border = "rounded" }) end,
dap.listeners.after.event_terminated["turbo-debug-hover"] = function() { silent = true, desc = "DAP hover" }
pcall(vim.keymap.del, "n", "K") )
end end
dap.listeners.after.event_terminated["turbo-debug-hover"] = function() pcall(vim.keymap.del, "n", "K") end
-- console refresh: trailing-edge throttled so chatty programs don't DOS us -- console refresh: trailing-edge throttled so chatty programs don't DOS us
dap.listeners.after.event_output["turbo-debug-redraw"] = schedule_console_redraw dap.listeners.after.event_output["turbo-debug-redraw"] = schedule_console_redraw
@@ -927,9 +959,7 @@ local function ensure_dapui()
local ok, placed = pcall(vim.fn.sign_getplaced, buf, { group = "*" }) local ok, placed = pcall(vim.fn.sign_getplaced, buf, { group = "*" })
if ok and placed and placed[1] then if ok and placed and placed[1] then
for _, s in ipairs(placed[1].signs or {}) do for _, s in ipairs(placed[1].signs or {}) do
if s.name == "DapStopped" then if s.name == "DapStopped" then pcall(vim.fn.sign_unplace, s.group or "", { buffer = buf, id = s.id }) end
pcall(vim.fn.sign_unplace, s.group or "", { buffer = buf, id = s.id })
end
end end
end end
end end
@@ -948,11 +978,11 @@ local function ensure_dapui()
-- event_stopped/event_continued leaves queued messages behind and -- event_stopped/event_continued leaves queued messages behind and
-- the bar shows stale state until the next render. -- the bar shows stale state until the next render.
local function refresh_bars() vim.schedule(render_bars) end local function refresh_bars() vim.schedule(render_bars) end
dap.listeners.after.event_initialized["turbo-debug-bar"] = refresh_bars dap.listeners.after.event_initialized["turbo-debug-bar"] = refresh_bars
dap.listeners.after.event_stopped["turbo-debug-bar"] = refresh_bars dap.listeners.after.event_stopped["turbo-debug-bar"] = refresh_bars
dap.listeners.after.event_continued["turbo-debug-bar"] = refresh_bars dap.listeners.after.event_continued["turbo-debug-bar"] = refresh_bars
dap.listeners.after.event_terminated["turbo-debug-bar"] = refresh_bars dap.listeners.after.event_terminated["turbo-debug-bar"] = refresh_bars
dap.listeners.after.event_exited["turbo-debug-bar"] = refresh_bars dap.listeners.after.event_exited["turbo-debug-bar"] = refresh_bars
vim.api.nvim_create_autocmd("User", { vim.api.nvim_create_autocmd("User", {
pattern = "DapProgressUpdate", pattern = "DapProgressUpdate",
group = vim.api.nvim_create_augroup("TurboDebugDapProgress", { clear = true }), group = vim.api.nvim_create_augroup("TurboDebugDapProgress", { clear = true }),
@@ -985,11 +1015,11 @@ local function ensure_dapui()
-- Our extmark sign at priority 500 always wins, guaranteeing the -- Our extmark sign at priority 500 always wins, guaranteeing the
-- paused line has the finger-pointing icon in the gutter. -- paused line has the finger-pointing icon in the gutter.
pcall(vim.api.nvim_buf_set_extmark, buf, ip_ns, line, 0, { pcall(vim.api.nvim_buf_set_extmark, buf, ip_ns, line, 0, {
sign_text = "\xf0\x9f\x91\x89", -- 👉 U+1F449 sign_text = "\xf0\x9f\x91\x89", -- 👉 U+1F449
sign_hl_group = "DapStopped", sign_hl_group = "DapStopped",
virt_text = { virt_text = {
{ " " .. ICON.ip_left .. " ", "DapStopped" }, { " " .. ICON.ip_left .. " ", "DapStopped" },
{ reason, "TurboDebugIPReason" }, { reason, "TurboDebugIPReason" },
}, },
virt_text_pos = "eol", virt_text_pos = "eol",
hl_mode = "combine", hl_mode = "combine",
@@ -997,9 +1027,9 @@ local function ensure_dapui()
}) })
end, 50) end, 50)
end end
dap.listeners.after.event_continued["turbo-debug-ip"] = function() clear_ip() end dap.listeners.after.event_continued["turbo-debug-ip"] = function() clear_ip() end
dap.listeners.before.event_terminated["turbo-debug-ip"] = function() clear_ip() end dap.listeners.before.event_terminated["turbo-debug-ip"] = function() clear_ip() end
dap.listeners.before.event_exited["turbo-debug-ip"] = function() clear_ip() end dap.listeners.before.event_exited["turbo-debug-ip"] = function() clear_ip() end
-- Clear the Console pane on session start/restart. -- Clear the Console pane on session start/restart.
-- --
@@ -1029,8 +1059,8 @@ local function ensure_dapui()
end end
-- Fire before every session-starting verb so old scrollback is gone -- Fire before every session-starting verb so old scrollback is gone
-- before the new session's output begins. -- before the new session's output begins.
dap.listeners.before.launch["turbo-debug-clear-console"] = clear_console dap.listeners.before.launch["turbo-debug-clear-console"] = clear_console
dap.listeners.before.attach["turbo-debug-clear-console"] = clear_console dap.listeners.before.attach["turbo-debug-clear-console"] = clear_console
dap.listeners.before.restart["turbo-debug-clear-console"] = clear_console dap.listeners.before.restart["turbo-debug-clear-console"] = clear_console
-- Default-collapse scopes the user considers noisy (Registers on -- Default-collapse scopes the user considers noisy (Registers on
@@ -1056,12 +1086,12 @@ local function ensure_dapui()
-- recognition. REPL is iconless per user preference (its prompt is -- recognition. REPL is iconless per user preference (its prompt is
-- already visually distinct enough). -- already visually distinct enough).
local titles = { local titles = {
dapui_scopes = " " .. ICON.scopes .. " Scopes", dapui_scopes = " " .. ICON.scopes .. " Scopes",
dapui_watches = " " .. ICON.watches .. " Watches", dapui_watches = " " .. ICON.watches .. " Watches",
dapui_stacks = " " .. ICON.stacks .. " Call Stack", dapui_stacks = " " .. ICON.stacks .. " Call Stack",
dapui_breakpoints = " " .. ICON.breakpoints .. " Breakpoints", dapui_breakpoints = " " .. ICON.breakpoints .. " Breakpoints",
dapui_console = " " .. ICON.terminal .. " Console", dapui_console = " " .. ICON.terminal .. " Console",
["dap-repl"] = " REPL", ["dap-repl"] = " REPL",
} }
vim.api.nvim_create_autocmd("FileType", { vim.api.nvim_create_autocmd("FileType", {
pattern = { "dapui_*", "dap-repl" }, pattern = { "dapui_*", "dap-repl" },
@@ -1118,10 +1148,7 @@ source_window = function()
if not vim.api.nvim_win_is_valid(w) then return false end if not vim.api.nvim_win_is_valid(w) then return false end
if vim.api.nvim_win_get_config(w).relative ~= "" then return false end if vim.api.nvim_win_get_config(w).relative ~= "" then return false end
local ft = vim.bo[vim.api.nvim_win_get_buf(w)].filetype or "" local ft = vim.bo[vim.api.nvim_win_get_buf(w)].filetype or ""
return ft ~= "" return ft ~= "" and not ft:match("^dapui_") and ft ~= "dap-repl" and ft ~= "TurboDebugBar"
and not ft:match("^dapui_")
and ft ~= "dap-repl"
and ft ~= "TurboDebugBar"
end end
if is_source(cur) then return cur end if is_source(cur) then return cur end
for _, w in ipairs(vim.api.nvim_list_wins()) do for _, w in ipairs(vim.api.nvim_list_wins()) do
@@ -1136,9 +1163,7 @@ end
-- already returned focus — the picker ends up unfocused. Actually shift. -- already returned focus — the picker ends up unfocused. Actually shift.
local function focus_source_win() local function focus_source_win()
local w = source_window() local w = source_window()
if w and w ~= vim.api.nvim_get_current_win() then if w and w ~= vim.api.nvim_get_current_win() then pcall(vim.api.nvim_set_current_win, w) end
pcall(vim.api.nvim_set_current_win, w)
end
end end
local function run_in_source(fn) local function run_in_source(fn)
@@ -1162,7 +1187,10 @@ end
local function smart_continue() local function smart_continue()
local dap = require("dap") local dap = require("dap")
if dap.session() then dap.continue(); return end if dap.session() then
dap.continue()
return
end
run_in_source(function() run_in_source(function()
if config.opts.stop_on_entry_when_no_breakpoints ~= false and not has_any_breakpoint() then if config.opts.stop_on_entry_when_no_breakpoints ~= false and not has_any_breakpoint() then
launch_with_stop_on_entry() launch_with_stop_on_entry()
@@ -1198,18 +1226,22 @@ local function setup_actions()
local pb = require("persistent-breakpoints.api") local pb = require("persistent-breakpoints.api")
actions_cache = { actions_cache = {
continue = smart_continue, continue = smart_continue,
step_over = function() step_or_launch(dap.step_over) end, step_over = function() step_or_launch(dap.step_over) end,
step_into = function() step_or_launch(dap.step_into) end, step_into = function() step_or_launch(dap.step_into) end,
step_out = function() step_or_launch(dap.step_out) end, step_out = function() step_or_launch(dap.step_out) end,
run_to_cursor = function() dap.run_to_cursor() end, run_to_cursor = function() dap.run_to_cursor() end,
restart = function() dap.restart() end, restart = function() dap.restart() end,
terminate = smart_terminate, terminate = smart_terminate,
help = function() help = function()
if help.is_open() then help.close() else help.open() end if help.is_open() then
help.close()
else
help.open()
end
end, end,
breakpoint = function() pb.toggle_breakpoint() end, breakpoint = function() pb.toggle_breakpoint() end,
cond_breakpoint = function() pb.set_conditional_breakpoint() end, cond_breakpoint = function() pb.set_conditional_breakpoint() end,
clear_breakpoints = function() pb.clear_all_breakpoints() end, clear_breakpoints = function() pb.clear_all_breakpoints() end,
watch = function() watch = function()
local expr local expr
@@ -1219,12 +1251,10 @@ local function setup_actions()
else else
expr = vim.fn.expand("<cexpr>") expr = vim.fn.expand("<cexpr>")
end end
if expr and expr ~= "" then if expr and expr ~= "" then require("dapui").elements.watches.add(expr) end
require("dapui").elements.watches.add(expr)
end
end, end,
hover = function() require("dapui").eval() end, hover = function() require("dapui").eval() end,
eval = function() require("dapui").float_element("repl") end, eval = function() require("dapui").float_element("repl") end,
} }
return actions_cache return actions_cache
end end
@@ -1278,9 +1308,7 @@ function M._install_for_buf(buf)
end end
local opts = { buffer = buf, silent = true, nowait = true, desc = "turbo-debug: " .. name } local opts = { buffer = buf, silent = true, nowait = true, desc = "turbo-debug: " .. name }
pcall(vim.keymap.set, "n", key, acts[name], opts) pcall(vim.keymap.set, "n", key, acts[name], opts)
if visual_actions[name] then if visual_actions[name] then pcall(vim.keymap.set, "v", key, acts[name], opts) end
pcall(vim.keymap.set, "v", key, acts[name], opts)
end
end end
end end
end end
@@ -1291,9 +1319,7 @@ end
-- clobbered by our subsequent del. -- clobbered by our subsequent del.
local function unset_and_restore(buf, name, entry) local function unset_and_restore(buf, name, entry)
pcall(vim.keymap.del, "n", entry.key, { buffer = buf }) pcall(vim.keymap.del, "n", entry.key, { buffer = buf })
if visual_actions[name] then if visual_actions[name] then pcall(vim.keymap.del, "v", entry.key, { buffer = buf }) end
pcall(vim.keymap.del, "v", entry.key, { buffer = buf })
end
if entry.prev_n then restore_mapping(buf, "n", entry.prev_n) end if entry.prev_n then restore_mapping(buf, "n", entry.prev_n) end
if entry.prev_v then restore_mapping(buf, "v", entry.prev_v) end if entry.prev_v then restore_mapping(buf, "v", entry.prev_v) end
end end
@@ -1321,9 +1347,7 @@ local function set_debug_chrome()
end end
local function clear_debug_chrome() local function clear_debug_chrome()
if win_ok(active_win) then if win_ok(active_win) then vim.wo[active_win].winbar = saved_winbar or "" end
vim.wo[active_win].winbar = saved_winbar or ""
end
active_win = nil active_win = nil
saved_winbar = nil saved_winbar = nil
end end
@@ -1335,8 +1359,11 @@ function M.enter()
active = true active = true
-- Switch colorscheme BEFORE define_highlights so the TurboDebug* -- Switch colorscheme BEFORE define_highlights so the TurboDebug*
-- highlights derive their colors from the debug-mode theme. -- highlights derive their colors from the debug-mode theme.
if type(config.opts.colorscheme) == "string" and config.opts.colorscheme ~= "" if
and vim.g.colors_name ~= config.opts.colorscheme then type(config.opts.colorscheme) == "string"
and config.opts.colorscheme ~= ""
and vim.g.colors_name ~= config.opts.colorscheme
then
saved_colorscheme = vim.g.colors_name saved_colorscheme = vim.g.colors_name
pcall(vim.cmd.colorscheme, config.opts.colorscheme) pcall(vim.cmd.colorscheme, config.opts.colorscheme)
end end
@@ -1384,13 +1411,10 @@ function M.enter()
if not active then return end if not active then return end
vim.schedule(function() vim.schedule(function()
invalidate_dead_bars() invalidate_dead_bars()
if not (sbar_win and cbar_win) then if not (sbar_win and cbar_win) then open_bars() end
open_bars()
end
ensure_bar_heights() ensure_bar_heights()
local dapui_total = compute_dapui_total_height() local dapui_total = compute_dapui_total_height()
if last_dapui_total_height ~= nil if last_dapui_total_height ~= nil and dapui_total ~= last_dapui_total_height then
and dapui_total ~= last_dapui_total_height then
pin_dapui_sizes() pin_dapui_sizes()
dapui_total = compute_dapui_total_height() dapui_total = compute_dapui_total_height()
end end
@@ -1465,7 +1489,11 @@ function M.exit()
end end
function M.toggle() function M.toggle()
if active then M.exit() else M.enter() end if active then
M.exit()
else
M.enter()
end
end end
function M.is_active() return active end function M.is_active() return active end
@@ -1478,7 +1506,9 @@ M._test = {
build_control_text = build_control_text, build_control_text = build_control_text,
define_highlights = define_highlights, define_highlights = define_highlights,
source_window = function() return source_window() end, source_window = function() return source_window() end,
set_bars = function(sw, sb, cw, cb) sbar_win, sbar_buf, cbar_win, cbar_buf = sw, sb, cw, cb end, set_bars = function(sw, sb, cw, cb)
sbar_win, sbar_buf, cbar_win, cbar_buf = sw, sb, cw, cb
end,
get_bars = function() return sbar_win, sbar_buf, cbar_win, cbar_buf end, get_bars = function() return sbar_win, sbar_buf, cbar_win, cbar_buf end,
get_sbar_zones = function() return {} end, get_sbar_zones = function() return {} end,
get_cbar_zones = function() return cbar_zones end, get_cbar_zones = function() return cbar_zones end,
+1 -3
View File
@@ -12,9 +12,7 @@ vim.pack.add({ "theHamsta/nvim-dap-virtual-text" })
vim.pack.add({ "Weissle/persistent-breakpoints.nvim" }) vim.pack.add({ "Weissle/persistent-breakpoints.nvim" })
-- register which-key group if available -- register which-key group if available
pcall(function() pcall(function() require("which-key").add({ { "<leader>d", group = "debug" } }) end)
require("which-key").add({ { "<leader>d", group = "debug" } })
end)
-- Deferred auto-setup. Must be deferred (not called synchronously) because -- Deferred auto-setup. Must be deferred (not called synchronously) because
-- the vim.pack.add calls above might not have finished registering the -- the vim.pack.add calls above might not have finished registering the
+17
View File
@@ -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.
+3 -9
View File
@@ -1,21 +1,15 @@
local M = {} local M = {}
function M.assert_eq(a, b, msg) function M.assert_eq(a, b, msg)
if a ~= b then if a ~= b then error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a))) end
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
end
end end
function M.assert_true(v, msg) function M.assert_true(v, msg)
if not v then if not v then error(string.format("FAIL: %s — expected truthy, got %s", msg, vim.inspect(v))) end
error(string.format("FAIL: %s — expected truthy, got %s", msg, vim.inspect(v)))
end
end end
function M.assert_false(v, msg) function M.assert_false(v, msg)
if v then if v then error(string.format("FAIL: %s — expected falsy, got %s", msg, vim.inspect(v))) end
error(string.format("FAIL: %s — expected falsy, got %s", msg, vim.inspect(v)))
end
end end
return M return M
+6 -8
View File
@@ -21,14 +21,10 @@ local function make_bar_win()
end end
local function cleanup(win) local function cleanup(win)
if win and vim.api.nvim_win_is_valid(win) then if win and vim.api.nvim_win_is_valid(win) then pcall(vim.api.nvim_win_close, win, true) end
pcall(vim.api.nvim_win_close, win, true)
end
end end
local function line(buf, i) local function line(buf, i) return vim.api.nvim_buf_get_lines(buf, i, i + 1, false)[1] or "" end
return vim.api.nvim_buf_get_lines(buf, i, i + 1, false)[1] or ""
end
-- build_control_text ------------------------------------------------------- -- build_control_text -------------------------------------------------------
@@ -120,8 +116,10 @@ do
local content = line(buf, 0) local content = line(buf, 0)
-- italic qualifiers are "over", "into", "out" — on overflow they're omitted. -- 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. -- 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 "), assert_false(
"overflow: italic qualifiers absent at narrow width") 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) mode._test.set_bars(nil, nil, nil, nil)
cleanup(win) cleanup(win)
end end
-1120
View File
File diff suppressed because it is too large. Load diff