Files
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

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")