From a5e8bbeb9d79d905a193e95a39acd29bc37e6c66 Mon Sep 17 00:00:00 2001 From: Tom van der Lee Date: Wed, 16 Sep 2026 08:47:13 +0200 Subject: Updated dotfiles --- neovim/.config/nvim/lua/plugins/dap.lua | 181 ++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 neovim/.config/nvim/lua/plugins/dap.lua (limited to 'neovim/.config/nvim/lua/plugins/dap.lua') diff --git a/neovim/.config/nvim/lua/plugins/dap.lua b/neovim/.config/nvim/lua/plugins/dap.lua new file mode 100644 index 0000000..975eaea --- /dev/null +++ b/neovim/.config/nvim/lua/plugins/dap.lua @@ -0,0 +1,181 @@ +-- Debugging via nvim-dap. Adapter installs are bridged through Mason +-- (mason-nvim-dap.nvim), the same pattern lsp.lua uses for language servers, +-- so adding a debug adapter for another language later is just adding it to +-- ensure_installed here - Python specifics live in dap-python.lua, Node +-- specifics in lua/config/dap_node.lua. +return { + "mfussenegger/nvim-dap", + -- Not left to lazy-load off `keys`: a project's .nvim.lua (see + -- lua/config/project_run.lua) needs `require("dap")` to work immediately + -- when exrc sources it, which requires the plugin already being on + -- runtimepath rather than deferred until a d keymap fires. + lazy = false, + dependencies = { + { + "rcarriga/nvim-dap-ui", + dependencies = { "nvim-neotest/nvim-nio" }, + }, + "theHamsta/nvim-dap-virtual-text", + { + "jay-babu/mason-nvim-dap.nvim", + dependencies = { "mason-org/mason.nvim" }, + }, + }, + keys = { + { "db", function() require("dap").toggle_breakpoint() end, desc = "Toggle breakpoint" }, + { + "dB", + function() require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) end, + desc = "Conditional breakpoint", + }, + { + "dc", + function() + local dap = require("dap") + + if dap.session() then + -- Existing session: resume past a breakpoint, or nvim-dap's + -- terminate/pause/restart prompt if active but not stopped. + -- This branch doesn't consult buffer filetype, so it's fine to + -- call from any window. + dap.continue() + return + end + + -- No active session: pick a config to start. dap.continue()'s own + -- path for this (select_config_and_run) only offers + -- dap.configurations[], so calling it + -- from the task-output dock or some other non-source window finds + -- nothing. Flatten across every filetype instead - same as + -- rr does for run tasks - so this works from anywhere and + -- you just pick the config you want from the list. + local entries = {} + for filetype, configs in pairs(dap.configurations) do + for _, config in ipairs(configs) do + table.insert(entries, { config = config, filetype = filetype }) + end + end + + -- .vscode/launch.json (if present in cwd) is read on-demand by + -- nvim-dap's own "dap.launch.json" provider - dap.ext.vscode's + -- load_launchjs() is deprecated now that this exists. That provider + -- is normally consulted inside dap.continue()'s own + -- select_config_and_run, which we're bypassing above, so call it + -- directly here to fold its configs into the same picker - the + -- debug-side counterpart to overseer's tasks.json support (README's + -- Run configurations section). Pair a launch.json entry with a + -- same-named tasks.json entry for PyCharm-style "run or debug the + -- same thing". + local ok, launchjson_configs = pcall(dap.providers.configs["dap.launch.json"]) + if ok then + for _, config in ipairs(launchjson_configs) do + table.insert(entries, { config = config, filetype = config.type }) + end + end + + if vim.tbl_isempty(entries) then + vim.notify("No debug configurations registered (see project_run.lua)", vim.log.levels.WARN) + return + end + + vim.ui.select(entries, { + prompt = "Configuration: ", + format_item = function(e) return e.config.name end, + }, function(entry) + if entry then + -- Pass along the filetype the config was registered under (not + -- necessarily the invoking buffer's) so breakpoint-hit window + -- jumping still picks a sensible window. + dap.run(entry.config, { filetype = entry.filetype }) + end + end) + end, + desc = "Continue / start debugging", + }, + { "do", function() require("dap").step_over() end, desc = "Step over" }, + { "di", function() require("dap").step_into() end, desc = "Step into" }, + { "dO", function() require("dap").step_out() end, desc = "Step out" }, + { "dt", function() require("dap").terminate() end, desc = "Terminate session" }, + { "dr", function() require("dap").repl.toggle() end, desc = "Toggle REPL" }, + { "du", function() require("dapui").toggle() end, desc = "Toggle debug UI" }, + }, + config = function() + require("dapui").setup() + require("nvim-dap-virtual-text").setup() + + -- nvim-dap doesn't define these signs itself - it just places them + -- (breakpoints.lua, session.lua's DapStopped placement) and expects the + -- config to have already called sign_define, so without this nothing + -- renders: no breakpoint marker in the sign column, and no indication + -- of the current line when a session stops (the stop itself still + -- works, sign_place just errors silently into a pcall). + vim.fn.sign_define("DapBreakpoint", { text = "●", texthl = "DiagnosticError" }) + vim.fn.sign_define("DapBreakpointCondition", { text = "◆", texthl = "DiagnosticWarn" }) + vim.fn.sign_define("DapBreakpointRejected", { text = "✗", texthl = "DiagnosticError" }) + vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DiagnosticInfo" }) + vim.fn.sign_define("DapStopped", { text = "▶", texthl = "DiagnosticWarn", linehl = "DapStoppedLine" }) + -- default = true so a colorscheme (or later config) can override this + -- without a load-order fight; Visual is just a visible fallback. + vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) + + -- handlers = {} opts out of mason-nvim-dap's automatic adapter + -- registration: nvim-dap-python (dap-python.lua) registers its own + -- "python" adapter/config, so we don't want a second one clobbering it. + -- Adding a language without its own dap-.lua later should drop + -- this override so its adapter gets auto-registered instead. + require("mason-nvim-dap").setup({ + -- ensure_installed wants nvim-dap *adapter/source* names, not Mason + -- *package* names - mason-nvim-dap looks each entry up in its own + -- name -> package mapping (source.lua: python -> debugpy) and just + -- silently skips anything it doesn't recognize, no error/log. Passing + -- "debugpy" here (the package name) matched nothing, so it never + -- triggered an install at all. + ensure_installed = { "python", "js" }, + automatic_installation = true, + handlers = {}, + }) + + -- Node/JS/TS adapter + default configs - not left to mason-nvim-dap's + -- own handler since "js" has no default adapter mapping there (only the + -- deprecated node2 does), see lua/config/dap_node.lua for why. + require("config.dap_node").setup() + + local dap, dapui = require("dap"), require("dapui") + + -- nvim-dap jumps to the stopped line using Neovim's 'switchbuf' option, + -- which defaults to "uselast": reuse the current window if it's already + -- a plain/source buffer, else fall back to the alternate window - and + -- if neither applies (e.g. dc was started from the task-output + -- dock or some other non-source window, which the picker now allows), + -- it silently does nothing. No error, no jump - the session *is* + -- stopped (stepping/continuing still works), it just never shows where. + -- Append "vsplit" so there's always a guaranteed-successful last resort + -- that opens the source file if nothing else matched. + dap.defaults.fallback.switchbuf = "usevisible,useopen,uselast,vsplit" + + -- Open/close dap-ui automatically around a debug session, so du + -- is only needed to bring it back after manually closing it mid-session. + dap.listeners.after.event_initialized["dapui_config"] = function() + dapui.open() + end + dap.listeners.before.event_terminated["dapui_config"] = function() + dapui.close() + end + dap.listeners.before.event_exited["dapui_config"] = function() + dapui.close() + end + + -- These events arrive asynchronously over debugpy's socket (a libuv + -- callback, not a keypress), and nvim-dap updates its state - cursor + -- position, signs, dapui panels - without that alone forcing Neovim to + -- repaint. So hitting a breakpoint, continuing past one, or a session + -- ending can all silently not *look* like anything happened until the + -- next keystroke forces a redraw that reveals the already-current + -- state (e.g. dc appearing to need pressing twice to resume). + for _, event in ipairs({ "event_stopped", "event_continued", "event_terminated", "event_exited" }) do + dap.listeners.after[event]["dap_redraw"] = function() + vim.cmd("redraw") + end + end + end, +} -- cgit v1.3