diff --git a/README.md b/README.md index 92687e9..42af51d 100644 --- a/README.md +++ b/README.md @@ -14,26 +14,25 @@ That's it. No other config needed. See `:help tiny-debugger` for customization. ## Usage -`d` toggles debug mode. `x` sets breakpoints (works anytime). +Everything lives under `d` (with which-key support if installed): + +| Key | Action | Available | +|-----|--------|-----------| +| `dd` | Toggle debug mode | Always | +| `dx` | Toggle breakpoint | Always | +| `dX` | Conditional breakpoint | Always | +| `dD` | Clear all breakpoints | Always | During debug mode, single-key controls: -| Key | Action | Key | Action | -|-----|------------------------|-----|------------------------| -| `c` | Continue | `W` | Watch expression | -| `s` | Step over | `K` | Hover / inspect | -| `d` | Step into | `E` | Eval in REPL | -| `r` | Step out | `q` | Terminate session | -| `C` | Run to cursor | `R` | Restart session | -| `?` | Help popup | | | - -Always available (no debug mode needed): - -| Key | Action | -|-----|------------------------| -| `x` | Toggle breakpoint | -| `X` | Conditional breakpoint | -| `D` | Clear all breakpoints | +| Key | Action | Key | Action | +|-----|--------|-----|--------| +| `c` | Continue | `W` | Watch expression | +| `s` | Step over | `K` | Hover / inspect | +| `d` | Step into | `E` | Eval in REPL | +| `r` | Step out | `q` | Terminate session | +| `C` | Run to cursor | `R` | Restart session | +| `?` | Help popup | | | All navigation keys preserved. Modal keybindings vanish when you exit debug mode. @@ -41,14 +40,14 @@ All navigation keys preserved. Modal keybindings vanish when you exit debug mode Adapter binaries must be installed (via mason, package manager, etc). -| Language | Adapter | Language | Adapter | -|-----------------|------------|--------------|--------------------| -| Python | debugpy | C# | netcoredbg | -| JavaScript / TS | js-debug | Java | java-debug-adapter | -| Go | delve | Kotlin | kotlin-debug | -| C / C++ | codelldb | PHP | php-debug | -| Rust | codelldb | Ruby | rdbg | -| Swift | codelldb | Lua (Neovim) | osv | +| Language | Adapter | Language | Adapter | +|----------|---------|----------|---------| +| Python | debugpy | C# | netcoredbg | +| JavaScript / TS | js-debug | Java | java-debug-adapter | +| Go | delve | Kotlin | kotlin-debug | +| C / C++ | codelldb | PHP | php-debug | +| Rust | codelldb | Ruby | rdbg | +| Swift | codelldb | Lua (Neovim) | osv | ## License diff --git a/doc/tiny-debugger.txt b/doc/tiny-debugger.txt index fbadda8..635464c 100644 --- a/doc/tiny-debugger.txt +++ b/doc/tiny-debugger.txt @@ -106,17 +106,22 @@ Default keymap: `R` Restart session Rerun `?` Help popup Show keybindings -Always available (not part of debug mode): +Always available (under `d` prefix, with which-key support): - `x` Toggle breakpoint X marks the spot - `X` Conditional breakpoint Shift variant - `D` Clear all breakpoints Delete all + `dd` Toggle debug mode + `dx` Toggle breakpoint X marks the spot + `dX` Conditional breakpoint Shift variant + `dD` Clear all breakpoints Delete all Override any key: >lua require("tiny-debugger").setup({ - keys = { - continue = "g", -- remap continue to g - step_over = false, -- disable step_over binding + keys = { -- modal keys (debug mode only) + continue = "g", + step_over = false, -- set to false to disable + }, + global_keys = { -- always-available keys + toggle = "DD", + breakpoint = "db", }, }) < diff --git a/lua/tiny-debugger/breakpoints.lua b/lua/tiny-debugger/breakpoints.lua index 71445e8..7fb9174 100644 --- a/lua/tiny-debugger/breakpoints.lua +++ b/lua/tiny-debugger/breakpoints.lua @@ -27,16 +27,16 @@ function M.setup() vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DapLogPoint" }) -- global mappings (work outside debug mode) - local keys = config.opts.keys + local gk = config.opts.global_keys local pb = require("persistent-breakpoints.api") local bindings = { - { keys.breakpoint, pb.toggle_breakpoint, "toggle breakpoint" }, - { keys.cond_breakpoint, pb.set_conditional_breakpoint, "conditional breakpoint" }, - { keys.clear_breakpoints, pb.clear_all_breakpoints, "clear all breakpoints" }, + { gk.breakpoint, pb.toggle_breakpoint, "Toggle breakpoint" }, + { gk.cond_breakpoint, pb.set_conditional_breakpoint, "Conditional breakpoint" }, + { gk.clear_breakpoints, pb.clear_all_breakpoints, "Clear all breakpoints" }, } for _, b in ipairs(bindings) do if b[1] then - vim.keymap.set("n", b[1], b[2], { silent = true, desc = "tiny-debugger: " .. b[3] }) + vim.keymap.set("n", b[1], b[2], { silent = true, desc = b[3] }) end end end diff --git a/lua/tiny-debugger/config.lua b/lua/tiny-debugger/config.lua index 577557b..adff4e5 100644 --- a/lua/tiny-debugger/config.lua +++ b/lua/tiny-debugger/config.lua @@ -4,23 +4,29 @@ M.defaults = { help_on_enter = true, builtin_adapters = true, + -- modal keys (active only during debug mode, single-key) keys = { continue = "c", step_over = "s", step_into = "d", step_out = "r", run_to_cursor = "C", - breakpoint = "x", - cond_breakpoint = "X", watch = "W", hover = "K", eval = "E", terminate = "q", restart = "R", - clear_breakpoints = "D", help = "?", }, + -- global keys (always available, d prefix) + global_keys = { + toggle = "dd", + breakpoint = "dx", + cond_breakpoint = "dX", + clear_breakpoints = "dD", + }, + dapui = { controls = { enabled = false }, layouts = { @@ -58,9 +64,13 @@ M.opts = M.defaults function M.merge(user_opts) M.opts = vim.tbl_deep_extend("force", vim.deepcopy(M.defaults), user_opts or {}) - if user_opts and user_opts.keys then - for k, v in pairs(user_opts.keys) do - if v == false then M.opts.keys[k] = false end + if user_opts then + for _, tbl_name in ipairs({ "keys", "global_keys" }) do + if user_opts[tbl_name] then + for k, v in pairs(user_opts[tbl_name]) do + if v == false then M.opts[tbl_name][k] = false end + end + end end end return M.opts diff --git a/lua/tiny-debugger/help.lua b/lua/tiny-debugger/help.lua index 0686275..9f37e79 100644 --- a/lua/tiny-debugger/help.lua +++ b/lua/tiny-debugger/help.lua @@ -5,15 +5,12 @@ local M = {} local win_id = nil local buf_id = nil -local key_info = { +local modal_info = { { "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" }, - { "clear_breakpoints", "Clear all breakpoints" }, { "watch", "Watch expression" }, { "hover", "Hover / inspect" }, { "eval", "Eval in REPL" }, @@ -22,18 +19,36 @@ local key_info = { { "help", "Show this help" }, } +local global_info = { + { "toggle", "Toggle debug mode" }, + { "breakpoint", "Toggle breakpoint" }, + { "cond_breakpoint", "Conditional breakpoint" }, + { "clear_breakpoints", "Clear all breakpoints" }, +} + local function build_content() - local lines = { " tiny-debugger keybindings", "" } + local lines = { " tiny-debugger", "" } local width = #lines[1] - local keys = config.opts.keys - for _, pair in ipairs(key_info) do - local key = keys[pair[1]] - if key then - local line = string.format(" %s %s", key, pair[2]) - lines[#lines + 1] = line - if #line > width then width = #line end - end + + local function add(key, label) + local line = string.format(" %-12s %s", key, label) + lines[#lines + 1] = line + if #line > width then width = #line end end + + lines[#lines + 1] = " Debug mode:" + for _, pair in ipairs(modal_info) do + local key = config.opts.keys[pair[1]] + if key then add(key, pair[2]) end + end + + lines[#lines + 1] = "" + lines[#lines + 1] = " Always available:" + for _, pair in ipairs(global_info) do + local key = config.opts.global_keys[pair[1]] + if key then add(key, pair[2]) end + end + lines[#lines + 1] = "" return lines, width + 2 end diff --git a/lua/tiny-debugger/init.lua b/lua/tiny-debugger/init.lua index 626ce76..a2b458c 100644 --- a/lua/tiny-debugger/init.lua +++ b/lua/tiny-debugger/init.lua @@ -3,10 +3,23 @@ local mode = require("tiny-debugger.mode") local M = {} +local setup_done = false + function M.setup(opts) + if setup_done then return end + setup_done = true + config.merge(opts) require("tiny-debugger.breakpoints").setup() require("tiny-debugger.adapters").setup() + + -- global toggle keybinding + local toggle_key = config.opts.global_keys.toggle + if toggle_key then + vim.keymap.set("n", toggle_key, function() mode.toggle() end, { + silent = true, desc = "Toggle debug mode", + }) + end end function M.toggle() diff --git a/plugin/tiny-debugger.lua b/plugin/tiny-debugger.lua index 59a4b0f..a65b858 100644 --- a/plugin/tiny-debugger.lua +++ b/plugin/tiny-debugger.lua @@ -11,11 +11,12 @@ vim.pack.add({ "nvim-neotest/nvim-nio" }) vim.pack.add({ "theHamsta/nvim-dap-virtual-text" }) vim.pack.add({ "Weissle/persistent-breakpoints.nvim" }) +-- register which-key group if available +pcall(function() + require("which-key").add({ { "d", group = "debug" } }) +end) + -- defer setup so user has time to call setup() with overrides vim.schedule(function() require("tiny-debugger").setup() end) - -vim.keymap.set("n", "d", function() - require("tiny-debugger").toggle() -end, { silent = true, desc = "tiny-debugger: toggle debug mode" }) diff --git a/tests/test_breakpoints.lua b/tests/test_breakpoints.lua index 09c7c2a..650a631 100644 --- a/tests/test_breakpoints.lua +++ b/tests/test_breakpoints.lua @@ -2,15 +2,16 @@ local assert_eq = require("helpers").assert_eq local td = require("tiny-debugger") td.setup() --- global x mapping exists -assert_eq(vim.fn.maparg("x", "n", false, true).desc, "tiny-debugger: toggle breakpoint", "global x mapping") +-- global breakpoint mappings exist under d prefix +assert_eq(vim.fn.maparg("dx", "n", false, true).desc, "Toggle breakpoint", "dx mapping") +assert_eq(vim.fn.maparg("dX", "n", false, true).desc, "Conditional breakpoint", "dX mapping") +assert_eq(vim.fn.maparg("dD", "n", false, true).desc, "Clear all breakpoints", "dD mapping") --- global X mapping exists -assert_eq(vim.fn.maparg("X", "n", false, true).desc, "tiny-debugger: conditional breakpoint", "global X mapping") +-- toggle mapping exists +assert_eq(vim.fn.maparg("dd", "n", false, true).desc, "Toggle debug mode", "dd mapping") -- persistent-breakpoints API is callable local api = require("persistent-breakpoints.api") assert_eq(type(api.toggle_breakpoint), "function", "toggle_breakpoint is a function") -assert_eq(type(api.set_conditional_breakpoint), "function", "set_conditional_breakpoint is a function") print("ALL BREAKPOINT TESTS PASSED") diff --git a/tests/test_config.lua b/tests/test_config.lua index 59fe071..5f67b8c 100644 --- a/tests/test_config.lua +++ b/tests/test_config.lua @@ -4,24 +4,27 @@ local config = require("tiny-debugger.config") -- defaults unchanged after empty merge local opts = config.merge({}) assert_eq(opts.keys.continue, "c", "default continue key") +assert_eq(opts.global_keys.toggle, "dd", "default toggle key") +assert_eq(opts.global_keys.breakpoint, "dx", "default breakpoint key") assert_eq(opts.help_on_enter, true, "default help_on_enter") -assert_eq(opts.builtin_adapters, true, "default builtin_adapters") -- partial key override opts = config.merge({ keys = { continue = "g" } }) assert_eq(opts.keys.continue, "g", "overridden continue key") assert_eq(opts.keys.step_over, "s", "preserved step_over key") -assert_eq(opts.keys.terminate, "q", "preserved terminate key") -- disable a key with false opts = config.merge({ keys = { continue = false } }) assert_eq(opts.keys.continue, false, "disabled continue key") -assert_eq(opts.keys.step_over, "s", "preserved step_over after disable") --- override help_on_enter -opts = config.merge({ help_on_enter = false }) -assert_eq(opts.help_on_enter, false, "overridden help_on_enter") -assert_eq(opts.keys.continue, "c", "keys preserved after help override") +-- global key override +opts = config.merge({ global_keys = { toggle = "D" } }) +assert_eq(opts.global_keys.toggle, "D", "overridden toggle key") +assert_eq(opts.global_keys.breakpoint, "dx", "preserved breakpoint key") + +-- disable a global key +opts = config.merge({ global_keys = { breakpoint = false } }) +assert_eq(opts.global_keys.breakpoint, false, "disabled breakpoint key") -- merge doesn't mutate defaults config.merge({ keys = { continue = "z" } })