fix: restore x/X/D as modal debug keys, add quit_exits_mode config, track mapped keys exactly
This commit is contained in:
6 files changed
+64
-26
No files matched your search
@@ -27,12 +27,13 @@ During debug mode, single-key controls:
|
|||||||
|
|
||||||
| Key | Action | Key | Action |
|
| Key | Action | Key | Action |
|
||||||
|-----|--------|-----|--------|
|
|-----|--------|-----|--------|
|
||||||
| `c` | Continue | `W` | Watch expression |
|
| `c` | Continue | `x` | Toggle breakpoint |
|
||||||
| `s` | Step over | `K` | Hover / inspect |
|
| `s` | Step over | `X` | Conditional breakpoint |
|
||||||
| `d` | Step into | `E` | Eval in REPL |
|
| `d` | Step into | `D` | Clear all breakpoints |
|
||||||
| `r` | Step out | `q` | Terminate session |
|
| `r` | Step out | `W` | Watch expression |
|
||||||
| `C` | Run to cursor | `R` | Restart session |
|
| `C` | Run to cursor | `K` | Hover / inspect |
|
||||||
| `?` | Help popup | | |
|
| `q` | Terminate session | `E` | Eval in REPL |
|
||||||
|
| `R` | Restart session | `?` | Help popup |
|
||||||
|
|
||||||
All navigation keys preserved. Modal keybindings vanish when you exit debug mode.
|
All navigation keys preserved. Modal keybindings vanish when you exit debug mode.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ local M = {}
|
|||||||
M.defaults = {
|
M.defaults = {
|
||||||
help_on_enter = true,
|
help_on_enter = true,
|
||||||
builtin_adapters = true,
|
builtin_adapters = true,
|
||||||
|
quit_exits_mode = true, -- q terminates AND exits debug mode (false = terminate only)
|
||||||
|
|
||||||
-- modal keys (active only during debug mode, single-key)
|
-- modal keys (active only during debug mode, single-key)
|
||||||
keys = {
|
keys = {
|
||||||
@@ -11,6 +12,9 @@ M.defaults = {
|
|||||||
step_into = "d",
|
step_into = "d",
|
||||||
step_out = "r",
|
step_out = "r",
|
||||||
run_to_cursor = "C",
|
run_to_cursor = "C",
|
||||||
|
breakpoint = "x",
|
||||||
|
cond_breakpoint = "X",
|
||||||
|
clear_breakpoints = "D",
|
||||||
watch = "W",
|
watch = "W",
|
||||||
hover = "K",
|
hover = "K",
|
||||||
eval = "E",
|
eval = "E",
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ local modal_info = {
|
|||||||
{ "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" },
|
||||||
|
|||||||
+28
-12
@@ -8,6 +8,7 @@ local active_buf = nil
|
|||||||
local active_win = nil
|
local active_win = nil
|
||||||
local stashed_maps = {}
|
local stashed_maps = {}
|
||||||
local saved_winbar = nil
|
local saved_winbar = nil
|
||||||
|
local mapped_keys = {} -- track exactly which keys we set, for reliable cleanup
|
||||||
|
|
||||||
-- one-time dap-ui and virtual-text initialization
|
-- one-time dap-ui and virtual-text initialization
|
||||||
local dapui_initialized = false
|
local dapui_initialized = false
|
||||||
@@ -92,16 +93,27 @@ local visual_actions = { watch = true, hover = true }
|
|||||||
local function setup_actions()
|
local function setup_actions()
|
||||||
if next(actions) then return end
|
if next(actions) then return end
|
||||||
local dap = require("dap")
|
local dap = require("dap")
|
||||||
|
local pb = require("persistent-breakpoints.api")
|
||||||
|
|
||||||
actions.continue = function() dap.continue() end
|
actions.continue = function() dap.continue() end
|
||||||
actions.step_over = function() dap.step_over() end
|
actions.step_over = function() dap.step_over() end
|
||||||
actions.step_into = function() dap.step_into() end
|
actions.step_into = function() dap.step_into() end
|
||||||
actions.step_out = function() dap.step_out() end
|
actions.step_out = function() dap.step_out() end
|
||||||
actions.run_to_cursor = function() dap.run_to_cursor() end
|
actions.run_to_cursor = function() dap.run_to_cursor() end
|
||||||
actions.terminate = function() dap.terminate(); M.exit() end
|
|
||||||
actions.restart = function() dap.restart() end
|
actions.restart = function() dap.restart() end
|
||||||
actions.help = function()
|
actions.help = function()
|
||||||
if help.is_open() then help.close() else help.open() end
|
if help.is_open() then help.close() else help.open() end
|
||||||
end
|
end
|
||||||
|
actions.terminate = function()
|
||||||
|
dap.terminate()
|
||||||
|
if config.opts.quit_exits_mode then M.exit() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- breakpoint actions (modal shortcuts for the <leader>d* globals)
|
||||||
|
actions.breakpoint = function() pb.toggle_breakpoint() end
|
||||||
|
actions.cond_breakpoint = function() pb.set_conditional_breakpoint() end
|
||||||
|
actions.clear_breakpoints = function() pb.clear_all_breakpoints() end
|
||||||
|
|
||||||
actions.watch = function()
|
actions.watch = function()
|
||||||
local expr
|
local expr
|
||||||
if vim.fn.mode() == "v" or vim.fn.mode() == "V" then
|
if vim.fn.mode() == "v" or vim.fn.mode() == "V" then
|
||||||
@@ -119,8 +131,10 @@ local function setup_actions()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- keymap lifecycle
|
-- keymap lifecycle
|
||||||
|
-- track every key we set so cleanup is exact — never miss one, never clear the wrong one
|
||||||
|
|
||||||
local function set_keymaps(buf)
|
local function set_keymaps(buf)
|
||||||
|
mapped_keys = {}
|
||||||
local keys = config.opts.keys
|
local keys = config.opts.keys
|
||||||
for name, key in pairs(keys) do
|
for name, key in pairs(keys) do
|
||||||
if key and actions[name] then
|
if key and actions[name] then
|
||||||
@@ -129,21 +143,25 @@ local function set_keymaps(buf)
|
|||||||
if visual_actions[name] then
|
if visual_actions[name] then
|
||||||
vim.keymap.set("v", key, actions[name], { buffer = buf, silent = true, desc = "tiny-debugger: " .. name })
|
vim.keymap.set("v", key, actions[name], { buffer = buf, silent = true, desc = "tiny-debugger: " .. name })
|
||||||
end
|
end
|
||||||
|
mapped_keys[#mapped_keys + 1] = { name = name, key = key }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function clear_keymaps(buf)
|
local function clear_keymaps(buf)
|
||||||
local keys = config.opts.keys
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
for name, key in pairs(keys) do
|
mapped_keys = {}
|
||||||
if key and actions[name] then
|
stashed_maps = {}
|
||||||
pcall(vim.keymap.del, "n", key, { buffer = buf })
|
return
|
||||||
if visual_actions[name] then
|
|
||||||
pcall(vim.keymap.del, "v", key, { buffer = buf })
|
|
||||||
end
|
|
||||||
restore_mapping(buf, key)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
for _, mk in ipairs(mapped_keys) do
|
||||||
|
pcall(vim.keymap.del, "n", mk.key, { buffer = buf })
|
||||||
|
if visual_actions[mk.name] then
|
||||||
|
pcall(vim.keymap.del, "v", mk.key, { buffer = buf })
|
||||||
|
end
|
||||||
|
restore_mapping(buf, mk.key)
|
||||||
|
end
|
||||||
|
mapped_keys = {}
|
||||||
stashed_maps = {}
|
stashed_maps = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -153,7 +171,6 @@ local function set_debug_chrome()
|
|||||||
active_win = vim.api.nvim_get_current_win()
|
active_win = vim.api.nvim_get_current_win()
|
||||||
saved_winbar = vim.wo[active_win].winbar
|
saved_winbar = vim.wo[active_win].winbar
|
||||||
vim.wo[active_win].winbar = "%#DiagnosticError# 🐛 DEBUG %* %f"
|
vim.wo[active_win].winbar = "%#DiagnosticError# 🐛 DEBUG %* %f"
|
||||||
|
|
||||||
vim.api.nvim_set_hl(0, "Cursor", { bg = "#ff6600", fg = "#000000" })
|
vim.api.nvim_set_hl(0, "Cursor", { bg = "#ff6600", fg = "#000000" })
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -163,7 +180,6 @@ local function clear_debug_chrome()
|
|||||||
end
|
end
|
||||||
active_win = nil
|
active_win = nil
|
||||||
saved_winbar = nil
|
saved_winbar = nil
|
||||||
|
|
||||||
vim.api.nvim_set_hl(0, "Cursor", {})
|
vim.api.nvim_set_hl(0, "Cursor", {})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ 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.keys.breakpoint, "x", "default modal breakpoint key")
|
||||||
assert_eq(opts.global_keys.toggle, "<leader>dd", "default toggle 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.global_keys.breakpoint, "<leader>dx", "default global 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.quit_exits_mode, true, "default quit_exits_mode")
|
||||||
|
|
||||||
-- partial key override
|
-- partial key override
|
||||||
opts = config.merge({ keys = { continue = "g" } })
|
opts = config.merge({ keys = { continue = "g" } })
|
||||||
@@ -22,9 +24,9 @@ opts = config.merge({ global_keys = { toggle = "<leader>D" } })
|
|||||||
assert_eq(opts.global_keys.toggle, "<leader>D", "overridden toggle key")
|
assert_eq(opts.global_keys.toggle, "<leader>D", "overridden toggle key")
|
||||||
assert_eq(opts.global_keys.breakpoint, "<leader>dx", "preserved breakpoint key")
|
assert_eq(opts.global_keys.breakpoint, "<leader>dx", "preserved breakpoint key")
|
||||||
|
|
||||||
-- disable a global key
|
-- quit_exits_mode override
|
||||||
opts = config.merge({ global_keys = { breakpoint = false } })
|
opts = config.merge({ quit_exits_mode = false })
|
||||||
assert_eq(opts.global_keys.breakpoint, false, "disabled breakpoint key")
|
assert_eq(opts.quit_exits_mode, false, "overridden quit_exits_mode")
|
||||||
|
|
||||||
-- merge doesn't mutate defaults
|
-- merge doesn't mutate defaults
|
||||||
config.merge({ keys = { continue = "z" } })
|
config.merge({ keys = { continue = "z" } })
|
||||||
|
|||||||
+16
-4
@@ -15,8 +15,8 @@ assert_eq(td.active(), false, "starts inactive")
|
|||||||
td.toggle()
|
td.toggle()
|
||||||
assert_eq(td.active(), true, "active after toggle")
|
assert_eq(td.active(), true, "active after toggle")
|
||||||
|
|
||||||
-- keymaps are set
|
-- all modal keymaps are set (including breakpoint keys)
|
||||||
for _, key in ipairs({ "c", "s", "d", "r", "q" }) do
|
for _, key in ipairs({ "c", "s", "d", "r", "q", "x", "X", "D" }) do
|
||||||
assert_eq(has_buf_map(key), true, key .. " key mapped")
|
assert_eq(has_buf_map(key), true, key .. " key mapped")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -24,9 +24,21 @@ end
|
|||||||
td.toggle()
|
td.toggle()
|
||||||
assert_eq(td.active(), false, "inactive after second toggle")
|
assert_eq(td.active(), false, "inactive after second toggle")
|
||||||
|
|
||||||
-- keymaps are removed
|
-- all modal keymaps are removed
|
||||||
for _, key in ipairs({ "c", "s", "d", "r", "q" }) do
|
for _, key in ipairs({ "c", "s", "d", "r", "q", "x", "X", "D" }) do
|
||||||
assert_eq(has_buf_map(key), false, key .. " key removed")
|
assert_eq(has_buf_map(key), false, key .. " key removed")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- double exit is safe
|
||||||
|
td.toggle() -- enter
|
||||||
|
td.toggle() -- exit
|
||||||
|
td.toggle() -- enter
|
||||||
|
td.toggle() -- exit
|
||||||
|
assert_eq(td.active(), false, "stable after multiple toggles")
|
||||||
|
|
||||||
|
-- verify no leaked keymaps after rapid toggling
|
||||||
|
for _, key in ipairs({ "c", "s", "d", "r", "q", "x", "X", "D" }) do
|
||||||
|
assert_eq(has_buf_map(key), false, key .. " clean after rapid toggle")
|
||||||
|
end
|
||||||
|
|
||||||
print("ALL MODE TESTS PASSED")
|
print("ALL MODE TESTS PASSED")
|
||||||
Reference in new issue
Block a user