diff --git a/lua/turbo-debug/breakpoints.lua b/lua/turbo-debug/breakpoints.lua index d7adb72..e114812 100644 --- a/lua/turbo-debug/breakpoints.lua +++ b/lua/turbo-debug/breakpoints.lua @@ -21,21 +21,18 @@ function M.setup() vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Search" }) vim.api.nvim_set_hl(0, "DapLogPoint", { default = true, link = "DiagnosticInfo" }) - -- Gutter signs: Nerd Font Codicons (byte-escaped for tool-pipeline safety). - -- The DapStopped IP sign is an emoji ๐Ÿ‘‰ (backhand index pointing right, - -- U+1F449) that literally points AT the paused line from the left. Pairs - -- with the ๐Ÿ‘ˆ emoji in the EOL virtual text (mode.lua) โ€” both fingers - -- point at the current line from opposite sides, unmistakable. - -- - -- nf-cod-debug-breakpoint = U+EA71 "" - -- nf-cod-debug-breakpoint-conditional = U+EA9F "" - -- nf-cod-circle-slash = U+EABE "" - -- nf-cod-debug-breakpoint-log = U+EBE4 "" - vim.fn.sign_define("DapBreakpoint", { text = "\xee\xa9\xb1", texthl = "DapBreakpoint", numhl = "DapBreakpoint" }) - vim.fn.sign_define("DapBreakpointCondition", { text = "\xee\xaa\x9f", texthl = "DapBreakpointCondition", numhl = "DapBreakpointCondition" }) - vim.fn.sign_define("DapBreakpointRejected", { text = "\xee\xaa\xbe", texthl = "DapBreakpointRejected", numhl = "DapBreakpointRejected" }) + -- Gutter signs: colorful emoji for instant recognition. Modern terminals + -- (Ghostty, Kitty, WezTerm, iTerm) render these full-color. + -- ๐Ÿ”ด U+1F534 large red circle = breakpoint + -- ๐ŸŸก U+1F7E1 large yellow circle = conditional breakpoint + -- โญ• U+2B55 heavy large circle = rejected breakpoint + -- ๐Ÿ‘‰ U+1F449 backhand pointing right = current execution line (IP) + -- ๐Ÿ“ U+1F4DD memo = log point + vim.fn.sign_define("DapBreakpoint", { text = "\xf0\x9f\x94\xb4", texthl = "DapBreakpoint", numhl = "DapBreakpoint" }) + vim.fn.sign_define("DapBreakpointCondition", { text = "\xf0\x9f\x9f\xa1", texthl = "DapBreakpointCondition", numhl = "DapBreakpointCondition" }) + vim.fn.sign_define("DapBreakpointRejected", { text = "\xe2\xad\x95", texthl = "DapBreakpointRejected", numhl = "DapBreakpointRejected" }) vim.fn.sign_define("DapStopped", { text = "\xf0\x9f\x91\x89", texthl = "DapStopped", linehl = "DapStoppedLine", numhl = "DapStopped" }) - vim.fn.sign_define("DapLogPoint", { text = "\xee\xaf\xa4", texthl = "DapLogPoint", numhl = "DapLogPoint" }) + vim.fn.sign_define("DapLogPoint", { text = "\xf0\x9f\x93\x9d", texthl = "DapLogPoint", numhl = "DapLogPoint" }) -- unobtrusive inline virtual text: italic, comment-colored vim.api.nvim_set_hl(0, "NvimDapVirtualText", { default = true, link = "Comment", italic = true }) @@ -54,14 +51,15 @@ function M.setup() vim.api.nvim_set_hl(0, "TurboDebugCtrlMuted", { default = true, link = "Comment" }) -- derived highlight: the IP "reason" vtext (STEP / BREAKPOINT / EXCEPTION / - -- PAUSE / etc.) โ€” bold + the WarningMsg color so it jumps off the screen. - -- nvim_set_hl can't combine link + bold, so copy attrs explicitly and - -- re-apply on ColorScheme so theme switches don't blank it out. + -- PAUSE / etc.) โ€” bold + reverse + WarningMsg color so it renders as an + -- inverted-color badge that absolutely demands attention. nvim_set_hl + -- can't combine link + bold, so copy attrs explicitly and re-apply on + -- ColorScheme so theme switches don't blank it out. local function apply_ip_reason_hl() local ok, src = pcall(vim.api.nvim_get_hl, 0, { name = "WarningMsg", link = false }) if not ok or not src or not src.fg then return end vim.api.nvim_set_hl(0, "TurboDebugIPReason", { - fg = src.fg, bg = src.bg, bold = true, default = true, + fg = src.fg, bg = src.bg, bold = true, reverse = true, default = true, }) end apply_ip_reason_hl() diff --git a/lua/turbo-debug/config.lua b/lua/turbo-debug/config.lua index cef70e3..fc0d89e 100644 --- a/lua/turbo-debug/config.lua +++ b/lua/turbo-debug/config.lua @@ -80,6 +80,12 @@ M.defaults = { }, floating = { border = "rounded", mappings = { close = { "q", "" } } }, render = { indent = 2, max_type_length = 20 }, + -- dapui's `expand_lines` spawns a cursor-pinned floating window when a + -- line is longer than the pane width โ€” that float bleeds across the + -- entire screen for long register dumps. We wrap inside the pane instead + -- (wrap=true + breakindent + showbreak in set_dapui_window_opts), so this + -- hover is both redundant and destructive. Kill it. + expand_lines = false, -- layouts are built dynamically from `sidebar` in mode.lua; override -- `layouts` here to fully customize. layouts = nil, diff --git a/lua/turbo-debug/mode.lua b/lua/turbo-debug/mode.lua index 97d6ff3..4afdd26 100644 --- a/lua/turbo-debug/mode.lua +++ b/lua/turbo-debug/mode.lua @@ -21,40 +21,34 @@ local vt_initialized = false -- file stays ASCII through any tool pipeline that strips PUA codepoints. local ICON = { - -- controls (match dapui's own controls-panel defaults) - play = "\xee\xab\x98", -- U+EAD8 debug-start - pause = "\xee\xab\x99", -- U+EAD9 debug-pause - step_over = "\xee\xab\x96", -- U+EAD6 debug-step-over - step_into = "\xee\xab\x95", -- U+EAD5 debug-step-into - step_out = "\xee\xab\x94", -- U+EAD4 debug-step-out - step_back = "\xee\xac\xb4", -- U+EB34 debug-step-back - restart = "\xee\xad\x84", -- U+EB44 debug-restart - stop = "\xee\xab\x97", -- U+EAD7 debug-stop - disconnect = "\xee\xab\xb7", -- U+EAF7 debug-disconnect - close = "\xee\xa9\xb6", -- U+EA76 close + -- control-bar buttons (colorful emoji for instant recognition) + play = "\xe2\x96\xb6\xef\xb8\x8f", -- โ–ถ๏ธ (U+25B6 + VS16) + step_over = "\xe2\x8f\xad\xef\xb8\x8f", -- โญ๏ธ next track (U+23ED + VS16) + step_into = "\xe2\xac\x87\xef\xb8\x8f", -- โฌ‡๏ธ downwards arrow (U+2B07 + VS16) + step_out = "\xe2\xac\x86\xef\xb8\x8f", -- โฌ†๏ธ upwards arrow (U+2B06 + VS16) + restart = "\xf0\x9f\x94\x84", -- ๐Ÿ”„ counterclockwise arrows (U+1F504) + stop = "\xf0\x9f\x9b\x91", -- ๐Ÿ›‘ octagonal sign (U+1F6D1) + close = "\xe2\x9d\x8c", -- โŒ cross mark (U+274C) - -- signs - breakpoint = "\xee\xa9\xb1", -- U+EA71 debug-breakpoint - bp_cond = "\xee\xaa\x9f", -- U+EA9F debug-breakpoint-conditional - bp_rej = "\xee\xaa\xbe", -- U+EABE circle-slash - stackframe = "\xee\xae\x8b", -- U+EB8B debug-stackframe - logpoint = "\xee\xaf\xa4", -- U+EBE4 debug-breakpoint-log + -- pane-title emoji + scopes = "\xf0\x9f\x94\x8e", -- ๐Ÿ”Ž magnifier right (U+1F50E) + watches = "\xf0\x9f\x91\x81\xef\xb8\x8f", -- ๐Ÿ‘๏ธ eye (U+1F441 + VS16) + stacks = "\xf0\x9f\x93\x9a", -- ๐Ÿ“š books (U+1F4DA) + breakpoints = "\xf0\x9f\x94\xb4", -- ๐Ÿ”ด red circle (U+1F534) โ€” matches BP sign + terminal = "\xf0\x9f\x92\xbb", -- ๐Ÿ’ป laptop (U+1F4BB) + repl = "\xf0\x9f\x92\xac", -- ๐Ÿ’ฌ speech balloon (U+1F4AC) - -- pane titles - scopes = "\xee\xaa\x8f", -- U+EA8F variable-group - watches = "\xee\xa9\xb0", -- U+EA70 eye - stacks = "\xee\xae\x83", -- U+EB83 list-tree - terminal = "\xee\xaa\x85", -- U+EA85 terminal - chevron = "\xee\xaa\xb6", -- U+EAB6 chevron-right + -- debug badge + bug = "\xf0\x9f\x90\x9b", -- ๐Ÿ› bug (U+1F41B) - -- debug badge (bug-in-circle) - debug_alt = "\xee\xae\x91", -- U+EB91 debug-alt + -- IP arrows + ip_left = "\xf0\x9f\x91\x88", -- ๐Ÿ‘ˆ backhand index pointing left (U+1F448) -- misc - ip_left = "\xf0\x9f\x91\x88", -- ๐Ÿ‘ˆ backhand index pointing left (U+1F448) - divider = "\xc2\xb7", -- ยท middle dot - ellipsis = "\xe2\x80\xa6", -- โ€ฆ horizontal ellipsis - wrap_mark = "\xe2\x86\xb3", -- โ†ณ downward-arrow-with-tip-rightwards + divider = "\xc2\xb7", -- ยท middle dot + ellipsis = "\xe2\x80\xa6", -- โ€ฆ horizontal ellipsis + wrap_mark = "\xe2\x86\xb3", -- โ†ณ downward arrow w/ tip right + hline = "\xe2\x94\x80", -- โ”€ box drawings light horizontal } -- โ”€โ”€โ”€ layout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -91,6 +85,7 @@ local function define_highlights() hl(0, "TurboDebugBorderInactive", { default = true, link = "Comment" }) hl(0, "TurboDebugWinbar", { default = true, link = "DiagnosticError" }) hl(0, "TurboDebugBar", { default = true, link = "StatusLine" }) + hl(0, "TurboDebugBarSeparator", { default = true, link = "WinSeparator" }) hl(0, "TurboDebugBarCtrl", { default = true, link = "Function" }) hl(0, "TurboDebugBarStatus", { default = true, link = "Comment" }) hl(0, "TurboDebugBarReady", { default = true, link = "DiagnosticHint" }) @@ -121,62 +116,119 @@ local function setup_active_win_highlights() }) end --- โ”€โ”€โ”€ global control buttons (referenced by bar winbar expression) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-- โ”€โ”€โ”€ control bar (buffer-rendered, not winbar โ€” survives floating windows) โ”€โ”€ -_G.turbo_debug_continue = function() M.actions().continue() end -_G.turbo_debug_step_over = function() require("dap").step_over() end -_G.turbo_debug_step_into = function() require("dap").step_into() end -_G.turbo_debug_step_out = function() require("dap").step_out() end -_G.turbo_debug_restart = function() require("dap").restart() end -_G.turbo_debug_terminate = function() M.actions().terminate() end -_G.turbo_debug_toggle = function() M.toggle() end +-- Layout: a 2-row floating window pinned above the statusline. +-- Row 0: a full-width โ”€ separator line (distinguishes bar from REPL/Console). +-- Row 1: state chip + clickable control buttons + dap status + cursor pos. +-- Content is written to the buffer directly (winbar doesn't render reliably +-- in minimal-style floats). Highlights via extmarks. Clicks via a buffer-local +-- keymap that inspects cursor column and dispatches. -function _G.turbo_debug_bar() - local dap_ok, dap = pcall(require, "dap") - local state, state_hl - if not dap_ok then - state, state_hl = "READY", "TurboDebugBarReady" - else - local s = dap.session() - if not s then - state, state_hl = "READY", "TurboDebugBarReady" - elseif s.stopped_thread_id then - state, state_hl = "PAUSED", "TurboDebugBarPaused" - else - state, state_hl = "RUNNING", "TurboDebugBarRunning" - end - end +local bar_ns = vim.api.nvim_create_namespace("turbo-debug.bar") - local parts = { - "%#" .. state_hl .. "# " .. ICON.debug_alt .. " " .. state .. " %*", - " ", - "%#TurboDebugBarCtrl#", - "%@v:lua.turbo_debug_continue@ " .. ICON.play .. " %X", - "%@v:lua.turbo_debug_step_over@ " .. ICON.step_over .. " %X", - "%@v:lua.turbo_debug_step_into@ " .. ICON.step_into .. " %X", - "%@v:lua.turbo_debug_step_out@ " .. ICON.step_out .. " %X", - "%@v:lua.turbo_debug_restart@ " .. ICON.restart .. " %X", - "%@v:lua.turbo_debug_terminate@ " .. ICON.stop .. " %X", - "%@v:lua.turbo_debug_toggle@ " .. ICON.close .. " %X", - "%*", - " %=", - "%#TurboDebugBarStatus#%{v:lua.require'dap'.status()}%*", - " %l:%c ", - } - return table.concat(parts, "") +-- `click_zones` is populated by render_bar: each entry is { col_start, col_end, fn }. +-- On in the bar, we look up the cursor column and fire the matching fn. +local click_zones = {} + +local function dap_state() + local ok, dap = pcall(require, "dap") + if not ok then return "READY", "TurboDebugBarReady" end + local s = dap.session() + if not s then return "READY", "TurboDebugBarReady" end + if s.stopped_thread_id then return "PAUSED", "TurboDebugBarPaused" end + return "RUNNING", "TurboDebugBarRunning" end --- โ”€โ”€โ”€ floating control bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +local function action(name) + return function() local a = M.actions(); if a[name] then a[name]() end end +end + +local function render_bar() + if not (bar_buf and vim.api.nvim_buf_is_valid(bar_buf)) then return end + if not (bar_win and vim.api.nvim_win_is_valid(bar_win)) then return end + + local width = vim.api.nvim_win_get_width(bar_win) + local sep = string.rep(ICON.hline, width) + + -- build the content row as a list of { text, hl, click_fn? } segments + local state, state_hl = dap_state() + + local buttons = { + { text = " " .. ICON.play .. " ", fn = action("continue"), hl = "TurboDebugBarCtrl" }, + { text = " " .. ICON.step_over .. " ", fn = action("step_over"), hl = "TurboDebugBarCtrl" }, + { text = " " .. ICON.step_into .. " ", fn = action("step_into"), hl = "TurboDebugBarCtrl" }, + { text = " " .. ICON.step_out .. " ", fn = action("step_out"), hl = "TurboDebugBarCtrl" }, + { text = " " .. ICON.restart .. " ", fn = action("restart"), hl = "TurboDebugBarCtrl" }, + { text = " " .. ICON.stop .. " ", fn = action("terminate"), hl = "TurboDebugBarCtrl" }, + { text = " " .. ICON.close .. " ", fn = function() M.toggle() end, hl = "TurboDebugBarCtrl" }, + } + + -- state chip: " ๐Ÿ› DEBUG ยท READY " + local chip = " " .. ICON.bug .. " DEBUG " .. ICON.divider .. " " .. state .. " " + + -- right-side: dap.status() + " %l:%c " (no %l/%c in buffer โ€” fill with something static) + local status_ok, status_msg = pcall(function() return require("dap").status() end) + if not status_ok then status_msg = "" end + local right = (status_msg ~= "" and (status_msg .. " ") or "") + + -- compose line: [chip][buttons...][right-align fills][right] + local left = chip + for _, b in ipairs(buttons) do left = left .. b.text end + local pad_len = width - vim.fn.strdisplaywidth(left) - vim.fn.strdisplaywidth(right) + if pad_len < 1 then pad_len = 1 end + local line = left .. string.rep(" ", pad_len) .. right + if vim.fn.strdisplaywidth(line) > width then + -- crude truncate to avoid wrap + line = line:sub(1, width) + end + + vim.bo[bar_buf].modifiable = true + vim.api.nvim_buf_set_lines(bar_buf, 0, -1, false, { sep, line }) + vim.bo[bar_buf].modifiable = false + + -- separator line highlight + vim.api.nvim_buf_clear_namespace(bar_buf, bar_ns, 0, -1) + vim.api.nvim_buf_set_extmark(bar_buf, bar_ns, 0, 0, { + end_row = 0, end_col = #sep, hl_group = "TurboDebugBarSeparator", + }) + + -- state chip highlight + vim.api.nvim_buf_set_extmark(bar_buf, bar_ns, 1, 0, { + end_row = 1, end_col = #chip, hl_group = state_hl, + }) + + -- button highlights + click zones (reset first) + click_zones = {} + local col = #chip + for _, b in ipairs(buttons) do + vim.api.nvim_buf_set_extmark(bar_buf, bar_ns, 1, col, { + end_row = 1, end_col = col + #b.text, hl_group = b.hl, + }) + -- click zones use DISPLAY columns for comparison, computed from byte offset + local text_before = line:sub(1, col) + local text_after = line:sub(1, col + #b.text) + local dcol_start = vim.fn.strdisplaywidth(text_before) + local dcol_end = vim.fn.strdisplaywidth(text_after) + click_zones[#click_zones + 1] = { dcol_start, dcol_end, b.fn } + col = col + #b.text + end + + -- right-side status highlight + if right ~= "" then + local right_byte_start = #line - #right + vim.api.nvim_buf_set_extmark(bar_buf, bar_ns, 1, right_byte_start, { + end_row = 1, end_col = #line, hl_group = "TurboDebugBarStatus", + }) + end +end local function bar_position() - -- sit one row above the statusline; statusline is at (lines - cmdheight - 1). - -- Our bar goes at (lines - cmdheight - 2). If laststatus == 0 there's no - -- statusline, so sit at the bottom (lines - cmdheight - 1). local row if vim.o.laststatus > 0 then - row = vim.o.lines - vim.o.cmdheight - 2 + row = vim.o.lines - vim.o.cmdheight - 3 -- 2 rows for bar + 1 for statusline else - row = vim.o.lines - vim.o.cmdheight - 1 + row = vim.o.lines - vim.o.cmdheight - 2 -- 2 rows for bar end if row < 0 then row = 0 end return { @@ -184,7 +236,7 @@ local function bar_position() row = row, col = 0, width = vim.o.columns, - height = 1, + height = 2, style = "minimal", border = "none", focusable = false, @@ -193,18 +245,44 @@ local function bar_position() } end +local function handle_bar_click() + -- mouse click dispatch: look up current column, find matching zone + local pos = vim.fn.getmousepos() + if not pos or pos.winid ~= bar_win then return end + if pos.line ~= 2 then return end -- only row 2 (the controls row) + local col = pos.wincol - 1 -- 1-based โ†’ 0-based display column + for _, zone in ipairs(click_zones) do + if col >= zone[1] and col < zone[2] then + zone[3]() + return + end + end +end + local function open_bar() - if bar_win and vim.api.nvim_win_is_valid(bar_win) then return end + if bar_win and vim.api.nvim_win_is_valid(bar_win) then + render_bar() + return + end bar_buf = vim.api.nvim_create_buf(false, true) vim.bo[bar_buf].bufhidden = "wipe" + vim.bo[bar_buf].filetype = "TurboDebugBar" bar_win = vim.api.nvim_open_win(bar_buf, false, bar_position()) - vim.wo[bar_win].winbar = "%!v:lua.turbo_debug_bar()" - vim.wo[bar_win].winhighlight = "Normal:TurboDebugBar,WinBar:TurboDebugBar,WinBarNC:TurboDebugBar" + vim.wo[bar_win].winhighlight = "Normal:TurboDebugBar,EndOfBuffer:TurboDebugBar" vim.wo[bar_win].winfixheight = true vim.wo[bar_win].list = false + vim.wo[bar_win].cursorline = false vim.wo[bar_win].number = false vim.wo[bar_win].relativenumber = false vim.wo[bar_win].signcolumn = "no" + vim.wo[bar_win].statuscolumn = "" + vim.wo[bar_win].wrap = false + + -- click dispatch (both mouse button and drag โ€” drag counts as click on bar_buf) + vim.keymap.set("n", "", handle_bar_click, { buffer = bar_buf, silent = true, nowait = true }) + vim.keymap.set("n", "", "", { buffer = bar_buf, silent = true, nowait = true }) + + render_bar() end local function close_bar() @@ -212,11 +290,13 @@ local function close_bar() pcall(vim.api.nvim_win_close, bar_win, true) end bar_win, bar_buf = nil, nil + click_zones = {} end local function reposition_bar() if bar_win and vim.api.nvim_win_is_valid(bar_win) then pcall(vim.api.nvim_win_set_config, bar_win, bar_position()) + render_bar() end end @@ -295,16 +375,38 @@ local function ensure_dapui() -- console refresh: trailing-edge throttled so chatty programs don't DOS us dap.listeners.after.event_output["turbo-debug-redraw"] = schedule_console_redraw - -- IP marker: paired arrows. Gutter sign โ–ถ + EOL vtext โ—€ REASON flanking the - -- paused line. + -- IP marker: paired arrows. Gutter sign ๐Ÿ‘‰ + EOL vtext ๐Ÿ‘ˆ REASON flanking + -- the paused line. clear_ip() wipes both our own extmarks AND nvim-dap's + -- DapStopped gutter signs, so a restart (or a natural session end) doesn't + -- leave a stale ๐Ÿ‘‰ behind on the last-paused line. local ip_ns = vim.api.nvim_create_namespace("turbo-debug.ip") local function clear_ip() for _, buf in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_valid(buf) then vim.api.nvim_buf_clear_namespace(buf, ip_ns, 0, -1) + -- unplace any lingering DapStopped signs (nvim-dap doesn't always + -- clear these on restart โ€” it's the exact bug the user hit). + local ok, placed = pcall(vim.fn.sign_getplaced, buf, { group = "*" }) + if ok and placed and placed[1] then + for _, s in ipairs(placed[1].signs or {}) do + if s.name == "DapStopped" then + pcall(vim.fn.sign_unplace, s.group or "", { buffer = buf, id = s.id }) + end + end + end end end end + + -- re-render the control bar on any dap state change so the READY/RUNNING/ + -- PAUSED chip and dap.status() text stay live. + local function refresh_bar() vim.schedule(render_bar) end + dap.listeners.after.event_initialized["turbo-debug-bar"] = refresh_bar + dap.listeners.after.event_stopped["turbo-debug-bar"] = refresh_bar + dap.listeners.after.event_continued["turbo-debug-bar"] = refresh_bar + dap.listeners.after.event_terminated["turbo-debug-bar"] = refresh_bar + dap.listeners.after.event_exited["turbo-debug-bar"] = refresh_bar + dap.listeners.after.event_stopped["turbo-debug-ip"] = function(session, body) vim.defer_fn(function() clear_ip() @@ -329,14 +431,14 @@ local function ensure_dapui() dap.listeners.before.event_terminated["turbo-debug-ip"] = function() clear_ip() end dap.listeners.before.event_exited["turbo-debug-ip"] = function() clear_ip() end - -- winbar titles on dapui panes (Codicons, byte-escaped) + -- winbar titles on dapui panes โ€” colorful emoji for instant pane recognition local titles = { - dapui_scopes = " " .. ICON.scopes .. " Scopes", - dapui_watches = " " .. ICON.watches .. " Watches", - dapui_stacks = " " .. ICON.stacks .. " Call Stack", - dapui_breakpoints = " " .. ICON.breakpoint .. " Breakpoints", - dapui_console = " " .. ICON.terminal .. " Console", - ["dap-repl"] = " " .. ICON.chevron .. " REPL", + dapui_scopes = " " .. ICON.scopes .. " Scopes", + dapui_watches = " " .. ICON.watches .. " Watches", + dapui_stacks = " " .. ICON.stacks .. " Call Stack", + dapui_breakpoints = " " .. ICON.breakpoints .. " Breakpoints", + dapui_console = " " .. ICON.terminal .. " Console", + ["dap-repl"] = " " .. ICON.repl .. " REPL", } vim.api.nvim_create_autocmd("FileType", { pattern = { "dapui_*", "dap-repl" }, @@ -487,22 +589,33 @@ end function M._install_for_buf(buf) if not vim.api.nvim_buf_is_valid(buf) then return end + -- Skip the control-bar buffer entirely โ€” its own keymap handles + -- button clicks, and debug keys like `c` would fire accidentally on click. + if vim.bo[buf].filetype == "TurboDebugBar" then return end + local acts = setup_actions() local ft = vim.bo[buf].filetype or "" local is_dapui = ft:match("^dapui_") or ft == "dap-repl" installed[buf] = installed[buf] or {} for name, key in pairs(config.opts.keys) do - if key and acts[name] and not installed[buf][name] then + if key and acts[name] then -- yield d/r to dapui's native remove/repl inside its own panes if not (is_dapui and (name == "step_into" or name == "step_out")) then - local prev_n = find_buf_mapping(buf, key, "n") - local prev_v = visual_actions[name] and find_buf_mapping(buf, key, "v") or nil + -- Only record prev-mapping on first install; subsequent installs + -- (force-reinstall on focus change) keep the original stash so + -- exit-time restoration still works. + local entry = installed[buf][name] + if not entry then + local prev_n = find_buf_mapping(buf, key, "n") + local prev_v = visual_actions[name] and find_buf_mapping(buf, key, "v") or nil + entry = { key = key, prev_n = prev_n, prev_v = prev_v } + installed[buf][name] = entry + end local opts = { buffer = buf, silent = true, nowait = true, desc = "turbo-debug: " .. name } pcall(vim.keymap.set, "n", key, acts[name], opts) if visual_actions[name] then pcall(vim.keymap.set, "v", key, acts[name], opts) end - installed[buf][name] = { key = key, prev_n = prev_n, prev_v = prev_v } end end end @@ -529,7 +642,7 @@ end local function set_debug_chrome() active_win = vim.api.nvim_get_current_win() saved_winbar = vim.wo[active_win].winbar - vim.wo[active_win].winbar = "%#TurboDebugWinbar# " .. ICON.debug_alt .. " DEBUG %* " .. ICON.divider .. " %f" + vim.wo[active_win].winbar = "%#TurboDebugWinbar# " .. ICON.bug .. " DEBUG %* " .. ICON.divider .. " %f" end local function clear_debug_chrome()