Files
turbo-debug.nvim/lua/turbo-debug/help.lua
T
swaits d062ac27ff 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.
2026-04-12 20:00:59 -06:00

222 lines
6.5 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local config = require("turbo-debug.config")
local M = {}
local win_id = nil
local buf_id = nil
-- Each entry:
-- { action, key_letter, pre, post }
-- The formatted line is: " <pre>(<key>)<post>" with <key> taken from the
-- config.opts.keys[action] lookup. Highlight spans are computed against
-- the actual key letter in the final line.
local flow = {
{ "continue", "c", "(", ")ontinue" },
{ "step_over", "s", "(", ")tep over" },
{ "step_into", "d", "(", ")escend into" },
{ "step_out", "r", "(", ")eturn out" },
{ "run_to_cursor", "C", "run to (", ") cursor" },
{ "restart", "R", "(", ")estart session" },
{ "terminate", "q", "(", ")uit debugging" },
}
local breakpoints = {
{ "breakpoint", "x", "(", ") toggle breakpoint" },
{ "cond_breakpoint", "X", "(", ") conditional breakpoint" },
{ "clear_breakpoints", "D", "(", ")elete all breakpoints" },
}
local inspect = {
{ "watch", "W", "(", ")atch expression" },
{ "hover", "K", "(", ") hover / inspect" },
{ "eval", "E", "(", ")val in REPL" },
}
local meta = {
{ "help", "?", "(", ") this popup" },
}
local globals = {
{ "breakpoint", "toggle breakpoint" },
{ "cond_breakpoint", "conditional breakpoint" },
{ "clear_breakpoints", "delete all breakpoints" },
}
local dapui_panes = {
{ "<CR>", "expand / jump to target" },
{ "o", "open target (jump to frame / breakpoint)" },
{ "e", "edit (watches)" },
{ "d", "remove (watches)" },
{ "r", "send to REPL" },
{ "t", "toggle subtle frames (call stack)" },
}
-- ▶ U+25B6, ● U+25CF, ◉ U+25C9, ◆ U+25C6, ❯ U+276F, ≡ U+2261
local ICON_FLOW = "\xe2\x96\xb6"
local ICON_BRK = "\xe2\x97\x8f"
local ICON_INSPECT = "\xe2\x97\x89"
local ICON_META = "\xe2\x97\x86"
local ICON_CHEVRON = "\xe2\x9d\xaf"
local ICON_STACK = "\xe2\x89\xa1"
-- a brand header and tagline
local BRAND_TITLE = " " .. ICON_FLOW .. " TURBO DEBUG"
local BRAND_TAGLINE = " modal text + GUI debugger · left hand keys, right hand mouse"
local function build()
local lines = { "", BRAND_TITLE, BRAND_TAGLINE, "" }
local hl = {} -- list of { line_idx, col_start, col_end, hl_group }
-- title + tagline highlights
hl[#hl + 1] = { 1, 0, #BRAND_TITLE, "TurboDebugHelpBrand" }
hl[#hl + 1] = { 2, 0, #BRAND_TAGLINE, "TurboDebugHelpTagline" }
local function section(icon, title)
lines[#lines + 1] = " " .. icon .. " " .. title
local idx = #lines - 1
hl[#hl + 1] = { idx, 2, 2 + #icon, "TurboDebugHelpSectionIcon" }
hl[#hl + 1] = { idx, 2 + #icon + 2, 2 + #icon + 2 + #title, "TurboDebugHelpSection" }
end
local function entry(pre, keyletter, post)
-- format: " <pre>(<keyletter>)<tail>"
-- e.g., " (s)tep over" or " run to (C) cursor"
-- the key letter is inside the parens pair
local indent = " "
-- pre already ends at "("; post already starts with ")"
local line = indent .. pre .. keyletter .. post
local idx = #lines
lines[#lines + 1] = line
-- highlight: the key letter itself
local col = #indent + #pre
hl[#hl + 1] = { idx, col, col + #keyletter, "TurboDebugHelpKey" }
-- highlight the surrounding parens dimmer
hl[#hl + 1] = { idx, col - 1, col, "TurboDebugHelpParen" }
hl[#hl + 1] = { idx, col + #keyletter, col + #keyletter + 1, "TurboDebugHelpParen" }
end
local function add_modal(list)
for _, row in ipairs(list) do
local action, letter, pre, post = row[1], row[2], row[3], row[4]
local configured = config.opts.keys[action]
if configured then
-- if the user remapped, show their actual key (preserves surprise-free behavior)
local lk = configured
entry(pre, lk, post)
end
end
end
local function add_global(list)
for _, row in ipairs(list) do
local action, label = row[1], row[2]
local key = config.opts.global_keys[action]
if key then
local indent = " "
local line = indent .. string.format("%-13s ", key) .. label
local idx = #lines
lines[#lines + 1] = line
hl[#hl + 1] = { idx, #indent, #indent + #key, "TurboDebugHelpKey" }
end
end
end
local function add_pane(list)
for _, row in ipairs(list) do
local key, label = row[1], row[2]
local indent = " "
local line = indent .. string.format("%-6s ", key) .. label
local idx = #lines
lines[#lines + 1] = line
hl[#hl + 1] = { idx, #indent, #indent + #key, "TurboDebugHelpKey" }
end
end
section(ICON_FLOW, "Flow")
add_modal(flow)
lines[#lines + 1] = ""
section(ICON_BRK, "Breakpoints")
add_modal(breakpoints)
lines[#lines + 1] = ""
section(ICON_INSPECT, "Inspect")
add_modal(inspect)
lines[#lines + 1] = ""
section(ICON_META, "Help")
add_modal(meta)
lines[#lines + 1] = ""
section(ICON_CHEVRON, "Always available")
add_global(globals)
lines[#lines + 1] = ""
section(ICON_STACK, "In dap-ui panes")
add_pane(dapui_panes)
lines[#lines + 1] = ""
local help_key = config.opts.keys.help or "?"
lines[#lines + 1] = " press " .. help_key .. " anytime to reopen"
lines[#lines + 1] = ""
local width = 0
for _, l in ipairs(lines) do
if vim.fn.strdisplaywidth(l) > width then width = vim.fn.strdisplaywidth(l) end
end
return lines, width + 2, hl
end
function M.close()
if win_id and vim.api.nvim_win_is_valid(win_id) then vim.api.nvim_win_close(win_id, true) end
win_id = nil
if buf_id and vim.api.nvim_buf_is_valid(buf_id) then vim.api.nvim_buf_delete(buf_id, { force = true }) end
buf_id = nil
end
function M.open()
M.close()
local lines, width, hl = build()
local height = #lines
buf_id = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
local ns = vim.api.nvim_create_namespace("turbo-debug.help")
for _, span in ipairs(hl) do
pcall(vim.api.nvim_buf_set_extmark, buf_id, ns, span[1], span[2], {
end_row = span[1],
end_col = span[3],
hl_group = span[4],
})
end
vim.bo[buf_id].modifiable = false
vim.bo[buf_id].bufhidden = "wipe"
local row = math.floor((vim.o.lines - height) / 2)
local col = math.floor((vim.o.columns - width) / 2)
win_id = vim.api.nvim_open_win(buf_id, false, {
relative = "editor",
row = row,
col = col,
width = width,
height = height,
style = "minimal",
border = "rounded",
focusable = false,
noautocmd = true,
})
vim.api.nvim_create_autocmd({ "CursorMoved", "InsertEnter", "CmdlineEnter" }, {
once = true,
callback = function() M.close() end,
})
end
function M.is_open() return win_id ~= nil and vim.api.nvim_win_is_valid(win_id) end
return M