From 24392b0e5bc7f05f821e0538a6082ae6dc57c2e8 Mon Sep 17 00:00:00 2001 From: ajet Date: Thu, 15 Oct 2026 11:18:36 -0900 Subject: [PATCH] add debugger support --- fnl/config/init.fnl | 15 ++++++- fnl/plugins/init.fnl | 102 ++++++++++++++++++++++++++++++++++++++++++- lazy-lock.json | 3 ++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/fnl/config/init.fnl b/fnl/config/init.fnl index 342b24d..d73a531 100644 --- a/fnl/config/init.fnl +++ b/fnl/config/init.fnl @@ -126,7 +126,20 @@ (keymap :n "[d" (fn [] (vim.diagnostic.jump {:count -1 :float true})) opts) (keymap :n "]d" (fn [] (vim.diagnostic.jump {:count 1 :float true})) opts) (keymap :n "e" vim.diagnostic.open_float opts) - (keymap :n "F" vim.lsp.buf.format opts))}) + (keymap :n "F" vim.lsp.buf.format opts) + (let [client (vim.lsp.get_client_by_id ev.data.client_id)] + (when (and client (client.supports_method "textDocument/references")) + (pcall vim.cmd "anoremenu PopUp.Show\\ References lua vim.lsp.buf.references()"))))}) + +;; Remove "Show References" from the context menu once no attached client supports it +(autocmd "LspDetach" + {:callback (fn [] + (var has-references false) + (each [_ client (ipairs (vim.lsp.get_clients))] + (when (client.supports_method "textDocument/references") + (set has-references true))) + (when (not has-references) + (pcall vim.cmd "aunmenu PopUp.Show\\ References")))}) ;; LSP commands (lspconfig-style) (local usercmd vim.api.nvim_create_user_command) diff --git a/fnl/plugins/init.fnl b/fnl/plugins/init.fnl index 0abf112..1ef508a 100644 --- a/fnl/plugins/init.fnl +++ b/fnl/plugins/init.fnl @@ -114,7 +114,107 @@ {lhs "h" :group "hunk"} {lhs "t" :group "toggle"} {lhs "l" :group "log"} - {lhs "a" :group "AI/Claude"}]))} + {lhs "a" :group "AI/Claude"} + {lhs "d" :group "debug"}]))} + + ;; nvim-dap + nvim-dap-ui - Debugging via the Debug Adapter Protocol + ;; Adapters: lldb-dap (LLVM) for Rust, C and compiled rust-mal .clj + ;; executables, and gdb (-i dap) for .clj too. rust-mal's executables + ;; carry their gdb script (mal_Value variables shown as Clojure prints + ;; them); gdb runs it once auto-loading is allowed for their directory. + ;; The lldb one is imported when the cwd has debug/rust_mal_lldb.py. + {repo "rcarriga/nvim-dap-ui" + :dependencies ["mfussenegger/nvim-dap" "nvim-neotest/nvim-nio"] + :keys [{lhs "" rhs #((. (require :dap) :continue)) :desc "Debug: start/continue"} + {lhs "" rhs #((. (require :dap) :terminate)) :desc "Debug: stop"} + {lhs "" rhs #((. (require :dapui) :toggle)) :desc "Debug: toggle UI"} + {lhs "" rhs #((. (require :dap) :run_to_cursor)) :desc "Debug: run to cursor"} + {lhs "" rhs #((. (require :dap) :toggle_breakpoint)) :desc "Debug: toggle breakpoint"} + {lhs "" rhs #((. (require :dap) :step_over)) :desc "Debug: step over"} + {lhs "" rhs #((. (require :dap) :step_into)) :desc "Debug: step into"} + {lhs "" rhs #((. (require :dap) :step_out)) :desc "Debug: step out"} + {lhs "db" rhs #((. (require :dap) :set_breakpoint) (vim.fn.input "Condition: ")) + :desc "Conditional breakpoint"} + {lhs "dl" rhs #((. (require :dap) :set_breakpoint) nil nil (vim.fn.input "Log message: ")) + :desc "Log point"} + {lhs "dB" rhs #((. (require :dap) :clear_breakpoints)) :desc "Clear breakpoints"} + {lhs "dr" rhs #((. (require :dap) :run_last)) :desc "Re-run last session"} + {lhs "dR" rhs #((. (require :dap) :restart)) :desc "Restart session"} + {lhs "dk" rhs #((. (require :dap) :up)) :desc "Frame up"} + {lhs "dj" rhs #((. (require :dap) :down)) :desc "Frame down"} + {lhs "de" rhs #((. (require :dapui) :eval)) :mode [:n :v] :desc "Eval expression"} + {lhs "dE" rhs #((. (require :dapui) :eval) (vim.fn.input "Expression: ")) + :desc "Eval prompt"} + {lhs "do" rhs #((. (require :dap) :repl :toggle)) :desc "Toggle debug REPL"}] + :config (fn [] + (local dap (require :dap)) + (local dapui (require :dapui)) + (dapui.setup) + + ;; Open/close the UI with the session + (set dap.listeners.after.event_initialized.dapui #(dapui.open)) + (set dap.listeners.before.event_terminated.dapui #(dapui.close)) + (set dap.listeners.before.event_exited.dapui #(dapui.close)) + + (vim.fn.sign_define "DapBreakpoint" {:text "●" :texthl "DiagnosticError"}) + (vim.fn.sign_define "DapBreakpointCondition" {:text "◆" :texthl "DiagnosticWarn"}) + (vim.fn.sign_define "DapLogPoint" {:text "◉" :texthl "DiagnosticInfo"}) + (vim.fn.sign_define "DapStopped" {:text "▶" :texthl "DiagnosticOk" + :linehl "Visual"}) + + (set dap.adapters.lldb {:type "executable" + :command "lldb-dap" + :name "lldb"}) + + ;; Import rust-mal's lldb formatters when debugging from a rust-mal checkout + (fn init-commands [] + (let [script (.. (vim.fn.getcwd) "/debug/rust_mal_lldb.py")] + (if (= 1 (vim.fn.filereadable script)) + [(.. "command script import " script)] + []))) + + (fn launch [default-program] + {:name "Launch executable" + :type "lldb" + :request "launch" + :program #(vim.fn.input "Executable: " (default-program) "file") + :args #(vim.split (vim.fn.input "Args: ") " " {:trimempty true}) + :cwd "${workspaceFolder}" + :stopOnEntry false + :initCommands init-commands}) + + ;; gdb's own DAP server (gdb 14+), allowed to auto-load the scripts of + ;; the program's directory (rust-mal's pretty-printers are in the + ;; executable). Unlike lldb-dap, in .clj files its watches and + ;; breakpoint conditions are Clojure (built with --debug) + (set dap.adapters.gdb + (fn [callback config] + (let [program (or config.program "") + dir (if (= program "") + (vim.fn.getcwd) + (vim.fn.fnamemodify program ":p:h"))] + (callback {:type "executable" + :command "gdb" + :args ["-q" "-i" "dap" + "-iex" (.. "add-auto-load-safe-path " dir)]})))) + + (fn gdb-launch [default-program] + {:name "Launch executable (gdb)" + :type "gdb" + :request "launch" + :program #(vim.fn.input "Executable: " (default-program) "file") + :args #(vim.split (vim.fn.input "Args: ") " " {:trimempty true}) + :cwd "${workspaceFolder}" + :stopAtBeginningOfMainSubprogram false}) + + ;; rust-mal: `rust-mal -O0 foo.clj` writes ./foo next to foo.clj + (set dap.configurations.clojure + [(gdb-launch #(vim.fn.expand "%:p:r")) + (launch #(vim.fn.expand "%:p:r"))]) + (set dap.configurations.rust + [(launch #(.. (vim.fn.getcwd) "/target/debug/"))]) + (set dap.configurations.c + [(launch #(.. (vim.fn.getcwd) "/"))]))} ;; claudecode.nvim - Claude Code AI assistant integration {repo "coder/claudecode.nvim" diff --git a/lazy-lock.json b/lazy-lock.json index c2231f8..fb92ed2 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -10,6 +10,9 @@ "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" }, "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" }, "nfnl": { "branch": "main", "commit": "a4c47a1e7e52aaf234b358f73135acf783054e35" }, + "nvim-dap": { "branch": "master", "commit": "cfa2d58f4537aca6ca83e2de1a0d9f1491121264" }, + "nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" }, + "nvim-nio": { "branch": "master", "commit": "edcc181a875301dd21840189aa2f2f9ad69fc172" }, "nvim-treesitter": { "branch": "main", "commit": "f603a2f4da48728f80257fb5fbb90145fd1dc173" }, "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, "roslyn.nvim": { "branch": "main", "commit": "bf1bd4e70bc7068d1b4aebf4ce03cc19a86615be" },