diff --git a/.config/nvim/lua/config/keymaps.lua b/.config/nvim/lua/config/keymaps.lua index e01e867..3446508 100644 --- a/.config/nvim/lua/config/keymaps.lua +++ b/.config/nvim/lua/config/keymaps.lua @@ -81,6 +81,31 @@ vim.keymap.set( vim.keymap.set("n", "xL", "Trouble loclist toggle", { desc = "Location List (Trouble)" }) vim.keymap.set("n", "xQ", "Trouble qflist toggle", { desc = "Quickfix List (Trouble)" }) +-- DAP (debugging) +vim.keymap.set("n", "db", function() require("dap").toggle_breakpoint() end, { desc = "Toggle breakpoint" }) +vim.keymap.set("n", "dc", function() require("dap").continue() end, { desc = "Continue / attach" }) +vim.keymap.set("n", "dn", function() require("dap").step_over() end, { desc = "Step over" }) +vim.keymap.set("n", "di", function() require("dap").step_into() end, { desc = "Step into" }) +vim.keymap.set("n", "do", function() require("dap").step_out() end, { desc = "Step out" }) +vim.keymap.set("n", "dq", function() require("dap").terminate() end, { desc = "Terminate session" }) +vim.keymap.set("n", "du", function() require("dapui").toggle() end, { desc = "Toggle DAP UI" }) +vim.keymap.set("n", "dr", function() require("dap").repl.toggle() end, { desc = "Toggle REPL" }) +-- Run RSpec under the debugger — skips the picker, binding.b breaks into dap-ui +vim.keymap.set("n", "ds", function() + require("dap").run({ + type = "ruby", request = "launch", + name = "RSpec (current file)", + command = "rspec", args = { vim.fn.expand("%:p") }, + }) +end, { desc = "Debug: RSpec current file" }) +vim.keymap.set("n", "dl", function() + require("dap").run({ + type = "ruby", request = "launch", + name = "RSpec (current line)", + command = "rspec", args = { vim.fn.expand("%:p") .. ":" .. vim.fn.line(".") }, + }) +end, { desc = "Debug: RSpec current line" }) + -- LazyGit vim.keymap.set("n", "lg", "LazyGit", { desc = "LazyGit" }) diff --git a/.config/nvim/lua/config/options.lua b/.config/nvim/lua/config/options.lua index ddbc628..d4014e4 100644 --- a/.config/nvim/lua/config/options.lua +++ b/.config/nvim/lua/config/options.lua @@ -19,3 +19,7 @@ vim.opt.conceallevel = 0 -- Window options vim.wo.wrap = true + +-- Load per-project .nvim.lua files (used for DAP launch configs, etc.) +vim.opt.exrc = true +vim.opt.secure = true diff --git a/.config/nvim/lua/config/plugins/dap.lua b/.config/nvim/lua/config/plugins/dap.lua new file mode 100644 index 0000000..a0c4534 --- /dev/null +++ b/.config/nvim/lua/config/plugins/dap.lua @@ -0,0 +1,83 @@ +return { + { + "mfussenegger/nvim-dap", + dependencies = { + { "rcarriga/nvim-dap-ui", dependencies = { "nvim-neotest/nvim-nio" } }, + }, + config = function() + local dap = require("dap") + local dapui = require("dapui") + + dapui.setup() + + -- Ruby adapter. + -- + -- "attach" mode: connect to an already-running rdbg TCP server. + -- Used for Rails server (started externally with RUBY_DEBUG_OPEN=1) + -- and for Vagrant+Docker (port forwarded to localhost). + -- + -- "launch" mode: rdbg starts the process as a child. + -- rdbg opens a TCP server, pauses before running any code, and waits + -- for nvim-dap to connect and register breakpoints. Once that handshake + -- completes, the "entry" stopped event fires — we auto-continue past it + -- (see listener below) so execution runs straight to your breakpoint. + -- cwd is resolved by finding the Gemfile so bundle exec works from any + -- subdirectory. + dap.adapters.ruby = function(callback, config) + if config.request == "attach" then + callback({ + type = "server", + host = config.host or "127.0.0.1", + port = config.port or 12345, + }) + else + local gemfile = vim.fn.findfile("Gemfile", ".;") + local cwd = gemfile ~= "" and vim.fn.fnamemodify(gemfile, ":p:h") or vim.fn.getcwd() + callback({ + type = "server", + host = "127.0.0.1", + port = config.port or 12345, + executable = { + command = "bundle", + args = { + "exec", "rdbg", + "--open", "--port", tostring(config.port or 12345), + "-c", "--", config.command or "ruby", + unpack(config.args or {}), + }, + cwd = cwd, + }, + }) + end + end + + -- For a running Rails server, start it with: + -- RUBY_DEBUG_OPEN=1 RUBY_DEBUG_PORT=12345 RUBY_DEBUG_HOST=127.0.0.1 bundle exec rails s + -- then dc → "Attach". For Vagrant projects, override in .nvim.lua. + -- For RSpec use ds (file) or dl (line) — skips this picker. + dap.configurations.ruby = { + { + type = "ruby", + name = "Attach", + request = "attach", + port = 12345, + }, + } + + -- Auto-open dap-ui when a session initialises. + -- dap-ui stays open after the session ends so you can inspect the final + -- state — close it manually with du. + dap.listeners.after.event_initialized["dapui"] = dapui.open + + -- In launch mode rdbg pauses at the program entry point before running + -- any code, ensuring nvim-dap has registered all breakpoints first. This + -- listener auto-continues past that initial pause so ds is one + -- keypress — execution runs straight to your first breakpoint. + dap.listeners.after.event_stopped["ruby_skip_entry"] = function(_, body) + if body.reason == "entry" then + dap.continue() + end + end + end, + }, +}