feat: move all debug keys under <leader>d prefix with which-key support

This commit is contained in:
swaits committed 2026-04-11 19:16:36 -06:00
1 parent 94eef8eaca
commit bc015cd8dc
9 files changed
+119 -72

No files matched your search

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