From 6931334904b5dc2c3d81148b0379a1fcff6af48d Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Sat, 11 Apr 2026 16:37:13 -0600 Subject: [PATCH] help: floating help box with dynamic keybinding content --- lua/tiny-debugger/help.lua | 105 +++++++++++++++++++++++++++++++++++++ lua/tiny-debugger/mode.lua | 7 +++ tests/test_help.lua | 40 ++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 lua/tiny-debugger/help.lua create mode 100644 tests/test_help.lua diff --git a/lua/tiny-debugger/help.lua b/lua/tiny-debugger/help.lua new file mode 100644 index 0000000..ced6514 --- /dev/null +++ b/lua/tiny-debugger/help.lua @@ -0,0 +1,105 @@ +local config = require("tiny-debugger.config") + +local M = {} + +local win_id = nil +local buf_id = nil + +local key_labels = { + continue = "Continue", + step_over = "Step over", + step_into = "Step into", + step_out = "Step out", + run_to_cursor = "Run to cursor", + breakpoint = "Toggle breakpoint", + cond_breakpoint = "Conditional breakpoint", + watch = "Watch expression", + hover = "Hover / inspect", + eval = "Eval in REPL", + terminate = "Quit debugging", + restart = "Restart session", + help = "Show this help", +} + +-- ordered list of key names for consistent display +local key_order = { + "continue", "step_over", "step_into", "step_out", "run_to_cursor", + "breakpoint", "cond_breakpoint", "watch", "hover", "eval", + "terminate", "restart", "help", +} + +local function build_lines() + local lines = { " tiny-debugger keybindings", "" } + local keys = config.opts.keys + for _, name in ipairs(key_order) do + local key = keys[name] + if key then + local label = key_labels[name] or name + table.insert(lines, string.format(" %s %s", key, label)) + end + end + table.insert(lines, "") + return lines +end + +local function calc_width(lines) + local max = 0 + for _, line in ipairs(lines) do + max = math.max(max, #line) + end + return max + 2 +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 = build_lines() + local width = calc_width(lines) + local height = #lines + + buf_id = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines) + vim.bo[buf_id].modifiable = false + vim.bo[buf_id].bufhidden = "wipe" + + local editor_width = vim.o.columns + local editor_height = vim.o.lines + + win_id = vim.api.nvim_open_win(buf_id, false, { + relative = "editor", + anchor = "SE", + row = editor_height - 2, + col = editor_width, + width = width, + height = height, + style = "minimal", + border = "rounded", + focusable = false, + noautocmd = true, + }) + + -- auto-close on next keypress + 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 diff --git a/lua/tiny-debugger/mode.lua b/lua/tiny-debugger/mode.lua index 953942c..b4bb429 100644 --- a/lua/tiny-debugger/mode.lua +++ b/lua/tiny-debugger/mode.lua @@ -1,4 +1,5 @@ local config = require("tiny-debugger.config") +local help = require("tiny-debugger.help") local M = {} @@ -55,6 +56,7 @@ local function setup_default_actions() M.exit() end actions.restart = function() dap.restart() end + actions.help = function() help.open() end end local function set_keymaps(buf) @@ -103,6 +105,10 @@ function M.enter() local buf = vim.api.nvim_get_current_buf() set_keymaps(buf) set_cursor_highlight() + + if config.opts.help_on_enter then + help.open() + end end function M.exit() @@ -112,6 +118,7 @@ function M.exit() local buf = vim.api.nvim_get_current_buf() clear_keymaps(buf) restore_cursor_highlight() + help.close() end function M.toggle() diff --git a/tests/test_help.lua b/tests/test_help.lua new file mode 100644 index 0000000..807069f --- /dev/null +++ b/tests/test_help.lua @@ -0,0 +1,40 @@ +-- tests for help box +local help = require("tiny-debugger.help") +local config = require("tiny-debugger.config") + +local function assert_eq(a, b, msg) + if a ~= b then + error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a))) + end +end + +-- test 1: open creates a valid window +help.open() +assert_eq(help.is_open(), true, "help window is open") + +-- test 2: close removes the window +help.close() +assert_eq(help.is_open(), false, "help window is closed") + +-- test 3: remapped key shows in help content +config.merge({ keys = { continue = "g" } }) +help.open() +-- read the buffer content +local wins = vim.api.nvim_list_wins() +local found_g = false +for _, w in ipairs(wins) do + local buf = vim.api.nvim_win_get_buf(w) + local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + for _, line in ipairs(lines) do + if line:find("g%s+Continue") then + found_g = true + end + end +end +assert_eq(found_g, true, "remapped key 'g' appears in help") +help.close() + +-- reset config +config.merge({}) + +print("ALL HELP TESTS PASSED")