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.
146 lines
5.4 KiB
Lua
146 lines
5.4 KiB
Lua
local h = require("helpers")
|
|
local assert_eq = h.assert_eq
|
|
local assert_true = h.assert_true
|
|
local assert_false = h.assert_false
|
|
|
|
local mode = require("turbo-debug.mode")
|
|
local config = require("turbo-debug.config")
|
|
|
|
-- Ensure config is initialized with defaults so keyof/render_cbar can read it
|
|
config.merge({})
|
|
|
|
-- Helpers --------------------------------------------------------------------
|
|
|
|
local function make_bar_win()
|
|
vim.cmd("noautocmd keepalt topleft split")
|
|
local win = vim.api.nvim_get_current_win()
|
|
local buf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_win_set_buf(win, buf)
|
|
vim.api.nvim_win_set_height(win, 2)
|
|
return win, buf
|
|
end
|
|
|
|
local function cleanup(win)
|
|
if win and vim.api.nvim_win_is_valid(win) then pcall(vim.api.nvim_win_close, win, true) end
|
|
end
|
|
|
|
local function line(buf, i) return vim.api.nvim_buf_get_lines(buf, i, i + 1, false)[1] or "" end
|
|
|
|
-- build_control_text -------------------------------------------------------
|
|
|
|
-- Inline form when key matches first letter case-insensitively.
|
|
local text, kcol, kend, icol, iend = mode._test.build_control_text(">", "s", "step", "over")
|
|
assert_true(text:find("%(s%)tep") ~= nil, "inline form: (s)tep present")
|
|
assert_true(kcol < kend, "inline form: key_col < key_end_col")
|
|
assert_true(icol ~= nil and iend ~= nil, "inline form: italic span present")
|
|
assert_true(text:sub(icol + 1, iend) == "over", "inline form: italic text is 'over'")
|
|
|
|
-- Prefix form when key does NOT match first letter.
|
|
local text2, _, _, icol2 = mode._test.build_control_text(">", "n", "step", "over")
|
|
assert_true(text2:find("%(n%) step") ~= nil, "prefix form: (n) step present")
|
|
assert_true(icol2 ~= nil, "prefix form: italic still rendered")
|
|
|
|
-- No italic when italic param is nil.
|
|
local text3, _, _, icol3 = mode._test.build_control_text(">", "c", "continue", nil)
|
|
assert_eq(icol3, nil, "no italic span when italic=nil")
|
|
assert_true(text3:find("%(c%)ontinue") ~= nil, "no italic: (c)ontinue rendered")
|
|
|
|
-- keyof ----------------------------------------------------------------------
|
|
|
|
-- Save any user override then reset to defaults
|
|
config.merge({})
|
|
assert_eq(mode._test.keyof("continue", "c"), "c", "keyof returns default when unset")
|
|
|
|
-- Disabled action returns nil
|
|
config.merge({ keys = { continue = false } })
|
|
assert_eq(mode._test.keyof("continue", "c"), nil, "keyof returns nil when key=false")
|
|
|
|
-- User override
|
|
config.merge({ keys = { continue = "g" } })
|
|
assert_eq(mode._test.keyof("continue", "c"), "g", "keyof returns user override")
|
|
|
|
-- Empty string falls back to default
|
|
config.merge({ keys = { continue = "" } })
|
|
assert_eq(mode._test.keyof("continue", "c"), "c", "keyof falls back on empty string")
|
|
|
|
-- Reset config to defaults for subsequent render tests
|
|
config.merge({})
|
|
|
|
-- render_sbar ----------------------------------------------------------------
|
|
|
|
do
|
|
local win, buf = make_bar_win()
|
|
mode._test.set_bars(win, buf, nil, nil)
|
|
mode._test.render_sbar()
|
|
local count = vim.api.nvim_buf_line_count(buf)
|
|
assert_eq(count, 2, "sbar has exactly 2 lines")
|
|
local sep = line(buf, 0)
|
|
local content = line(buf, 1)
|
|
assert_true(#sep > 0, "sbar separator non-empty")
|
|
assert_true(content:find(" turbo%-debug ") ~= nil, "sbar contains brand text")
|
|
assert_true(content:find("DEBUG") ~= nil, "sbar contains DEBUG chip")
|
|
-- sbar has no click zones
|
|
local zones = mode._test.get_sbar_zones()
|
|
assert_eq(#zones, 0, "sbar has zero click zones")
|
|
mode._test.set_bars(nil, nil, nil, nil)
|
|
cleanup(win)
|
|
end
|
|
|
|
-- render_cbar ----------------------------------------------------------------
|
|
|
|
do
|
|
local win, buf = make_bar_win()
|
|
vim.api.nvim_win_set_width(win, 120)
|
|
mode._test.set_bars(nil, nil, win, buf)
|
|
mode._test.render_cbar()
|
|
local count = vim.api.nvim_buf_line_count(buf)
|
|
assert_eq(count, 2, "cbar has exactly 2 lines")
|
|
local content = line(buf, 0)
|
|
local sep = line(buf, 1)
|
|
assert_true(#sep > 0, "cbar separator non-empty")
|
|
assert_true(content:find(" help ") ~= nil, "cbar contains 'help' text")
|
|
-- Click zones: at least 5 controls (continue, step_over, step_into, step_out, terminate) + 1 help
|
|
-- When no session, restart is hidden so zone count = 5 + 1 = 6.
|
|
local zones = mode._test.get_cbar_zones()
|
|
assert_true(#zones >= 6, "cbar has at least 6 click zones: " .. #zones)
|
|
mode._test.set_bars(nil, nil, nil, nil)
|
|
cleanup(win)
|
|
end
|
|
|
|
-- overflow fallback: at narrow widths, italic qualifiers are dropped
|
|
do
|
|
local win, buf = make_bar_win()
|
|
vim.api.nvim_win_set_width(win, 25)
|
|
mode._test.set_bars(nil, nil, win, buf)
|
|
mode._test.render_cbar()
|
|
local content = line(buf, 0)
|
|
-- italic qualifiers are "over", "into", "out" — on overflow they're omitted.
|
|
-- At width 25 with 5+ controls, there's no way they all fit with italics.
|
|
assert_false(
|
|
content:find(" over ") or content:find(" into ") or content:find(" out "),
|
|
"overflow: italic qualifiers absent at narrow width"
|
|
)
|
|
mode._test.set_bars(nil, nil, nil, nil)
|
|
cleanup(win)
|
|
end
|
|
|
|
-- disabling step_over reduces zone count
|
|
do
|
|
local win, buf = make_bar_win()
|
|
vim.api.nvim_win_set_width(win, 120)
|
|
mode._test.set_bars(nil, nil, win, buf)
|
|
mode._test.render_cbar()
|
|
local default_count = #mode._test.get_cbar_zones()
|
|
|
|
config.merge({ keys = { step_over = false } })
|
|
mode._test.render_cbar()
|
|
local disabled_count = #mode._test.get_cbar_zones()
|
|
assert_eq(disabled_count, default_count - 1, "disabling step_over drops one zone")
|
|
|
|
config.merge({}) -- reset
|
|
mode._test.set_bars(nil, nil, nil, nil)
|
|
cleanup(win)
|
|
end
|
|
|
|
print("ALL RENDER BAR TESTS PASSED")
|