diff options
| author | 2026-09-16 08:47:13 +0200 | |
|---|---|---|
| committer | 2026-09-16 08:47:13 +0200 | |
| commit | a5e8bbeb9d79d905a193e95a39acd29bc37e6c66 (patch) | |
| tree | 5b42d54e6b91d454bcbac89f7aac42b0863a869d /neovim/.config/nvim/lua/plugins/dap.lua | |
| parent | 6906a9d55928c6250ff106d041b73fdd1ac8aae6 (diff) | |
| download | dotfiles-a5e8bbeb9d79d905a193e95a39acd29bc37e6c66.tar.gz dotfiles-a5e8bbeb9d79d905a193e95a39acd29bc37e6c66.tar.bz2 dotfiles-a5e8bbeb9d79d905a193e95a39acd29bc37e6c66.zip | |
Updated dotfiles
Diffstat (limited to 'neovim/.config/nvim/lua/plugins/dap.lua')
| -rw-r--r-- | neovim/.config/nvim/lua/plugins/dap.lua | 181 |
1 files changed, 181 insertions, 0 deletions
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 @@ | |||
| 1 | -- Debugging via nvim-dap. Adapter installs are bridged through Mason | ||
| 2 | -- (mason-nvim-dap.nvim), the same pattern lsp.lua uses for language servers, | ||
| 3 | -- so adding a debug adapter for another language later is just adding it to | ||
| 4 | -- ensure_installed here - Python specifics live in dap-python.lua, Node | ||
| 5 | -- specifics in lua/config/dap_node.lua. | ||
| 6 | return { | ||
| 7 | "mfussenegger/nvim-dap", | ||
| 8 | -- Not left to lazy-load off `keys`: a project's .nvim.lua (see | ||
| 9 | -- lua/config/project_run.lua) needs `require("dap")` to work immediately | ||
| 10 | -- when exrc sources it, which requires the plugin already being on | ||
| 11 | -- runtimepath rather than deferred until a <leader>d keymap fires. | ||
| 12 | lazy = false, | ||
| 13 | dependencies = { | ||
| 14 | { | ||
| 15 | "rcarriga/nvim-dap-ui", | ||
| 16 | dependencies = { "nvim-neotest/nvim-nio" }, | ||
| 17 | }, | ||
| 18 | "theHamsta/nvim-dap-virtual-text", | ||
| 19 | { | ||
| 20 | "jay-babu/mason-nvim-dap.nvim", | ||
| 21 | dependencies = { "mason-org/mason.nvim" }, | ||
| 22 | }, | ||
| 23 | }, | ||
| 24 | keys = { | ||
| 25 | { "<leader>db", function() require("dap").toggle_breakpoint() end, desc = "Toggle breakpoint" }, | ||
| 26 | { | ||
| 27 | "<leader>dB", | ||
| 28 | function() require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) end, | ||
| 29 | desc = "Conditional breakpoint", | ||
| 30 | }, | ||
| 31 | { | ||
| 32 | "<leader>dc", | ||
| 33 | function() | ||
| 34 | local dap = require("dap") | ||
| 35 | |||
| 36 | if dap.session() then | ||
| 37 | -- Existing session: resume past a breakpoint, or nvim-dap's | ||
| 38 | -- terminate/pause/restart prompt if active but not stopped. | ||
| 39 | -- This branch doesn't consult buffer filetype, so it's fine to | ||
| 40 | -- call from any window. | ||
| 41 | dap.continue() | ||
| 42 | return | ||
| 43 | end | ||
| 44 | |||
| 45 | -- No active session: pick a config to start. dap.continue()'s own | ||
| 46 | -- path for this (select_config_and_run) only offers | ||
| 47 | -- dap.configurations[<current buffer's filetype>], so calling it | ||
| 48 | -- from the task-output dock or some other non-source window finds | ||
| 49 | -- nothing. Flatten across every filetype instead - same as | ||
| 50 | -- <leader>rr does for run tasks - so this works from anywhere and | ||
| 51 | -- you just pick the config you want from the list. | ||
| 52 | local entries = {} | ||
| 53 | for filetype, configs in pairs(dap.configurations) do | ||
| 54 | for _, config in ipairs(configs) do | ||
| 55 | table.insert(entries, { config = config, filetype = filetype }) | ||
| 56 | end | ||
| 57 | end | ||
| 58 | |||
| 59 | -- .vscode/launch.json (if present in cwd) is read on-demand by | ||
| 60 | -- nvim-dap's own "dap.launch.json" provider - dap.ext.vscode's | ||
| 61 | -- load_launchjs() is deprecated now that this exists. That provider | ||
| 62 | -- is normally consulted inside dap.continue()'s own | ||
| 63 | -- select_config_and_run, which we're bypassing above, so call it | ||
| 64 | -- directly here to fold its configs into the same picker - the | ||
| 65 | -- debug-side counterpart to overseer's tasks.json support (README's | ||
| 66 | -- Run configurations section). Pair a launch.json entry with a | ||
| 67 | -- same-named tasks.json entry for PyCharm-style "run or debug the | ||
| 68 | -- same thing". | ||
| 69 | local ok, launchjson_configs = pcall(dap.providers.configs["dap.launch.json"]) | ||
| 70 | if ok then | ||
| 71 | for _, config in ipairs(launchjson_configs) do | ||
| 72 | table.insert(entries, { config = config, filetype = config.type }) | ||
| 73 | end | ||
| 74 | end | ||
| 75 | |||
| 76 | if vim.tbl_isempty(entries) then | ||
| 77 | vim.notify("No debug configurations registered (see project_run.lua)", vim.log.levels.WARN) | ||
| 78 | return | ||
| 79 | end | ||
| 80 | |||
| 81 | vim.ui.select(entries, { | ||
| 82 | prompt = "Configuration: ", | ||
| 83 | format_item = function(e) return e.config.name end, | ||
| 84 | }, function(entry) | ||
| 85 | if entry then | ||
| 86 | -- Pass along the filetype the config was registered under (not | ||
| 87 | -- necessarily the invoking buffer's) so breakpoint-hit window | ||
| 88 | -- jumping still picks a sensible window. | ||
| 89 | dap.run(entry.config, { filetype = entry.filetype }) | ||
| 90 | end | ||
| 91 | end) | ||
| 92 | end, | ||
| 93 | desc = "Continue / start debugging", | ||
| 94 | }, | ||
| 95 | { "<leader>do", function() require("dap").step_over() end, desc = "Step over" }, | ||
| 96 | { "<leader>di", function() require("dap").step_into() end, desc = "Step into" }, | ||
| 97 | { "<leader>dO", function() require("dap").step_out() end, desc = "Step out" }, | ||
| 98 | { "<leader>dt", function() require("dap").terminate() end, desc = "Terminate session" }, | ||
| 99 | { "<leader>dr", function() require("dap").repl.toggle() end, desc = "Toggle REPL" }, | ||
| 100 | { "<leader>du", function() require("dapui").toggle() end, desc = "Toggle debug UI" }, | ||
| 101 | }, | ||
| 102 | config = function() | ||
| 103 | require("dapui").setup() | ||
| 104 | require("nvim-dap-virtual-text").setup() | ||
| 105 | |||
| 106 | -- nvim-dap doesn't define these signs itself - it just places them | ||
| 107 | -- (breakpoints.lua, session.lua's DapStopped placement) and expects the | ||
| 108 | -- config to have already called sign_define, so without this nothing | ||
| 109 | -- renders: no breakpoint marker in the sign column, and no indication | ||
| 110 | -- of the current line when a session stops (the stop itself still | ||
| 111 | -- works, sign_place just errors silently into a pcall). | ||
| 112 | vim.fn.sign_define("DapBreakpoint", { text = "●", texthl = "DiagnosticError" }) | ||
| 113 | vim.fn.sign_define("DapBreakpointCondition", { text = "◆", texthl = "DiagnosticWarn" }) | ||
| 114 | vim.fn.sign_define("DapBreakpointRejected", { text = "✗", texthl = "DiagnosticError" }) | ||
| 115 | vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DiagnosticInfo" }) | ||
| 116 | vim.fn.sign_define("DapStopped", { text = "▶", texthl = "DiagnosticWarn", linehl = "DapStoppedLine" }) | ||
| 117 | -- default = true so a colorscheme (or later config) can override this | ||
| 118 | -- without a load-order fight; Visual is just a visible fallback. | ||
| 119 | vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) | ||
| 120 | |||
| 121 | -- handlers = {} opts out of mason-nvim-dap's automatic adapter | ||
| 122 | -- registration: nvim-dap-python (dap-python.lua) registers its own | ||
| 123 | -- "python" adapter/config, so we don't want a second one clobbering it. | ||
| 124 | -- Adding a language without its own dap-<lang>.lua later should drop | ||
| 125 | -- this override so its adapter gets auto-registered instead. | ||
| 126 | require("mason-nvim-dap").setup({ | ||
| 127 | -- ensure_installed wants nvim-dap *adapter/source* names, not Mason | ||
| 128 | -- *package* names - mason-nvim-dap looks each entry up in its own | ||
| 129 | -- name -> package mapping (source.lua: python -> debugpy) and just | ||
| 130 | -- silently skips anything it doesn't recognize, no error/log. Passing | ||
| 131 | -- "debugpy" here (the package name) matched nothing, so it never | ||
| 132 | -- triggered an install at all. | ||
| 133 | ensure_installed = { "python", "js" }, | ||
| 134 | automatic_installation = true, | ||
| 135 | handlers = {}, | ||
| 136 | }) | ||
| 137 | |||
| 138 | -- Node/JS/TS adapter + default configs - not left to mason-nvim-dap's | ||
| 139 | -- own handler since "js" has no default adapter mapping there (only the | ||
| 140 | -- deprecated node2 does), see lua/config/dap_node.lua for why. | ||
| 141 | require("config.dap_node").setup() | ||
| 142 | |||
| 143 | local dap, dapui = require("dap"), require("dapui") | ||
| 144 | |||
| 145 | -- nvim-dap jumps to the stopped line using Neovim's 'switchbuf' option, | ||
| 146 | -- which defaults to "uselast": reuse the current window if it's already | ||
| 147 | -- a plain/source buffer, else fall back to the alternate window - and | ||
| 148 | -- if neither applies (e.g. <leader>dc was started from the task-output | ||
| 149 | -- dock or some other non-source window, which the picker now allows), | ||
| 150 | -- it silently does nothing. No error, no jump - the session *is* | ||
| 151 | -- stopped (stepping/continuing still works), it just never shows where. | ||
| 152 | -- Append "vsplit" so there's always a guaranteed-successful last resort | ||
| 153 | -- that opens the source file if nothing else matched. | ||
| 154 | dap.defaults.fallback.switchbuf = "usevisible,useopen,uselast,vsplit" | ||
| 155 | |||
| 156 | -- Open/close dap-ui automatically around a debug session, so <leader>du | ||
| 157 | -- is only needed to bring it back after manually closing it mid-session. | ||
| 158 | dap.listeners.after.event_initialized["dapui_config"] = function() | ||
| 159 | dapui.open() | ||
| 160 | end | ||
| 161 | dap.listeners.before.event_terminated["dapui_config"] = function() | ||
| 162 | dapui.close() | ||
| 163 | end | ||
| 164 | dap.listeners.before.event_exited["dapui_config"] = function() | ||
| 165 | dapui.close() | ||
| 166 | end | ||
| 167 | |||
| 168 | -- These events arrive asynchronously over debugpy's socket (a libuv | ||
| 169 | -- callback, not a keypress), and nvim-dap updates its state - cursor | ||
| 170 | -- position, signs, dapui panels - without that alone forcing Neovim to | ||
| 171 | -- repaint. So hitting a breakpoint, continuing past one, or a session | ||
| 172 | -- ending can all silently not *look* like anything happened until the | ||
| 173 | -- next keystroke forces a redraw that reveals the already-current | ||
| 174 | -- state (e.g. <leader>dc appearing to need pressing twice to resume). | ||
| 175 | for _, event in ipairs({ "event_stopped", "event_continued", "event_terminated", "event_exited" }) do | ||
| 176 | dap.listeners.after[event]["dap_redraw"] = function() | ||
| 177 | vim.cmd("redraw") | ||
| 178 | end | ||
| 179 | end | ||
| 180 | end, | ||
| 181 | } | ||
