Files
nvim-config/fnl/plugins/init.fnl
2026-10-15 11:18:36 -09:00

337 lines
16 KiB
Fennel

;; Plugin specs in Fennel
;; This file is compiled to lua/plugins/init.lua by nfnl
(local repo 1) ;; lazy.nvim spec index for the git repo (e.g., "folke/plugin")
(local lhs 1) ;; lazy.nvim/which-key spec index for key sequence
(local rhs 2) ;; lazy.nvim keys spec index for command/action
[;; Tokyonight - Colorscheme
{repo "folke/tokyonight.nvim"
:lazy false
:priority 1000
:config (fn []
(let [tokyonight (require :tokyonight)]
(tokyonight.setup
{:style "night" ; storm, moon, night, or day
:transparent false
:terminal_colors true}))
(vim.cmd.colorscheme "tokyonight"))}
;; Conjure - Interactive REPL for Fennel, Clojure, Lisp, etc.
{repo "Olical/conjure"
:ft ["fennel" "clojure" "lisp" "scheme" "racket" "lua"]
:config (fn []
;; HUD
(set vim.g.conjure#log#hud#enabled true)
(set vim.g.conjure#log#hud#width 0.42)
(set vim.g.conjure#log#hud#height 0.30)
;; Auto-start lein repl when no .nrepl-port found
(set vim.g.conjure#client#clojure#nrepl#connection#auto_repl#enabled true)
(set vim.g.conjure#client#clojure#nrepl#connection#auto_repl#cmd "lein repl :headless")
;; Show result inline as virtual text
(set vim.g.conjure#eval#result_register "*")
(set vim.g.conjure#eval#inline#enabled true)
(set vim.g.conjure#eval#inline#prefix "=> ")
;; Open log as vertical split
(set vim.g.conjure#log#split#enabled true)
(set vim.g.conjure#log#split#direction "vertical")
;; Clojure-specific: strip ANSI from nREPL output
(set vim.g.conjure#client#clojure#nrepl#eval#raw_out true)
;; Keymaps
(let [km vim.keymap.set]
(km :n "<localleader>lv"
(fn [] (vim.cmd "ConjureLogVSplit"))
{:desc "Conjure log vertical split"})
(km :n "<localleader>lt"
(fn [] (vim.cmd "ConjureLogTab"))
{:desc "Conjure log tab"})))}
;; vim-sexp + tpope's mappings - Structural editing for S-expressions
;; Loaded eagerly so VimEnter fires before plugin sources, letting
;; vim-sexp-mappings-for-regular-people register its FileType autocmds.
{repo "tpope/vim-sexp-mappings-for-regular-people" :lazy false
:dependencies ["tpope/vim-repeat"
"guns/vim-sexp"]
:config (fn []
(set vim.g.sexp_filetypes "clojure,scheme,lisp,timl,fennel,racket")
(set vim.g.sexp_enable_insert_mode_mappings 0))}
;; vim-surround - Add/change/delete surrounding pairs (quotes, brackets, tags)
{repo "tpope/vim-surround"
:dependencies ["tpope/vim-repeat"]
:event "VeryLazy"}
;; Mason - Package manager for LSP servers, DAP servers, linters, formatters
{repo "williamboman/mason.nvim"
:lazy false
:build ":MasonUpdate"
:opts {:ui {:border "rounded"}
:registries ["github:mason-org/mason-registry"
"github:Crashdummyy/mason-registry"]}}
;; Auto-install LSP servers and tools via Mason
{repo "WhoIsSethDaniel/mason-tool-installer.nvim"
:lazy false
:dependencies ["williamboman/mason.nvim"]
:opts {:ensure_installed ["clojure-lsp" "clj-kondo" "fennel-ls" "lua-language-server" "rust-analyzer" "roslyn" "tree-sitter-cli"]}}
;; Hop - EasyMotion-like word jumping
{repo "smoka7/hop.nvim"
:version "*"
:event "VeryLazy"
:config (fn []
(let [hop (require :hop)]
(hop.setup)
(vim.keymap.set :n "gw" (fn [] (hop.hint_words))
{:desc "Hop to word"})))}
;; LispSchool - Interactive structural editing tutorial
;; Source: fnl/lisp-school.fnl (compiled by nfnl)
{:name "lisp-school"
:dir (vim.fn.stdpath "config")
:cmd ["LispSchool" "LispSchoolNext"]
:config (fn []
(vim.api.nvim_create_user_command "LispSchool"
(fn [] (let [ls (require :lisp-school)] (ls.start)))
{:desc "Start LispSchool tutorial"})
(vim.api.nvim_create_user_command "LispSchoolNext"
(fn [] (let [ls (require :lisp-school)] (ls.next)))
{:desc "Next LispSchool lesson"}))}
;; which-key - Shows keybinding hints
;; lhs constant defined at top - which-key specs use index [1] for the key sequence
{repo "folke/which-key.nvim"
:event "VeryLazy"
:opts {}
:config (fn [_ opts]
(local wk (require :which-key))
(wk.setup opts)
(wk.add
[{lhs "<leader>f" :group "find"}
{lhs "<leader>b" :group "buffer"}
{lhs "<leader>r" :group "refactor"}
{lhs "<leader>c" :group "code"}
{lhs "<leader>h" :group "hunk"}
{lhs "<leader>t" :group "toggle"}
{lhs "<leader>l" :group "log"}
{lhs "<leader>a" :group "AI/Claude"}
{lhs "<leader>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 "<F5>" rhs #((. (require :dap) :continue)) :desc "Debug: start/continue"}
{lhs "<F6>" rhs #((. (require :dap) :terminate)) :desc "Debug: stop"}
{lhs "<F7>" rhs #((. (require :dapui) :toggle)) :desc "Debug: toggle UI"}
{lhs "<F8>" rhs #((. (require :dap) :run_to_cursor)) :desc "Debug: run to cursor"}
{lhs "<F9>" rhs #((. (require :dap) :toggle_breakpoint)) :desc "Debug: toggle breakpoint"}
{lhs "<F10>" rhs #((. (require :dap) :step_over)) :desc "Debug: step over"}
{lhs "<F11>" rhs #((. (require :dap) :step_into)) :desc "Debug: step into"}
{lhs "<F12>" rhs #((. (require :dap) :step_out)) :desc "Debug: step out"}
{lhs "<leader>db" rhs #((. (require :dap) :set_breakpoint) (vim.fn.input "Condition: "))
:desc "Conditional breakpoint"}
{lhs "<leader>dl" rhs #((. (require :dap) :set_breakpoint) nil nil (vim.fn.input "Log message: "))
:desc "Log point"}
{lhs "<leader>dB" rhs #((. (require :dap) :clear_breakpoints)) :desc "Clear breakpoints"}
{lhs "<leader>dr" rhs #((. (require :dap) :run_last)) :desc "Re-run last session"}
{lhs "<leader>dR" rhs #((. (require :dap) :restart)) :desc "Restart session"}
{lhs "<leader>dk" rhs #((. (require :dap) :up)) :desc "Frame up"}
{lhs "<leader>dj" rhs #((. (require :dap) :down)) :desc "Frame down"}
{lhs "<leader>de" rhs #((. (require :dapui) :eval)) :mode [:n :v] :desc "Eval expression"}
{lhs "<leader>dE" rhs #((. (require :dapui) :eval) (vim.fn.input "Expression: "))
:desc "Eval prompt"}
{lhs "<leader>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"
:dependencies ["folke/snacks.nvim"]
:config true
:keys [{lhs "<leader>a" :desc "AI/Claude Code"}
{lhs "<leader>ac" rhs "<cmd>ClaudeCode<cr>" :desc "Toggle Claude"}
{lhs "<leader>af" rhs "<cmd>ClaudeCodeFocus<cr>" :desc "Focus Claude"}
{lhs "<leader>ar" rhs "<cmd>ClaudeCode --resume<cr>" :desc "Resume Claude"}
{lhs "<leader>aC" rhs "<cmd>ClaudeCode --continue<cr>" :desc "Continue Claude"}
{lhs "<leader>am" rhs "<cmd>ClaudeCodeSelectModel<cr>" :desc "Select Claude model"}
{lhs "<leader>ab" rhs "<cmd>ClaudeCodeAdd %<cr>" :desc "Add current buffer"}
{lhs "<leader>as" rhs "<cmd>ClaudeCodeSend<cr>" :mode "v" :desc "Send to Claude"}
{lhs "<leader>as" rhs "<cmd>ClaudeCodeTreeAdd<cr>" :desc "Add file"
:ft ["NvimTree" "neo-tree" "oil" "minifiles" "netrw"]}
{lhs "<leader>aa" rhs "<cmd>ClaudeCodeDiffAccept<cr>" :desc "Accept diff"}
{lhs "<leader>ad" rhs "<cmd>ClaudeCodeDiffDeny<cr>" :desc "Deny diff"}]}
;; Supermaven - AI code completion
{repo "supermaven-inc/supermaven-nvim"
:enabled false
:event "InsertEnter"
:opts {:disable_inline_completion true}}
;; blink.cmp - Fast completion engine with LSP support
{repo "saghen/blink.cmp"
:version "*"
:event ["InsertEnter" "CmdlineEnter"]
:dependencies ["supermaven-inc/supermaven-nvim"]
:opts {:keymap {:preset "super-tab"
"<C-Space>" [(fn [cmp] (cmp.show {:providers ["supermaven"]})) :fallback]
"<Tab>" [:select_and_accept :snippet_forward :fallback]
"<Esc>" [(fn [cmp] (cmp.cancel) false) :fallback]}
:completion {:list {:selection {:preselect true :auto_insert false}}
:documentation {:auto_show true}}
:sources {:default ["lsp" "path" "buffer"]
:providers {:supermaven {:name "Supermaven"
:module "supermaven-blink"}}}}}
;; roslyn.nvim - C# and Blazor/Razor LSP via Roslyn language server
;; Must not be ft-gated: plugin/roslyn.lua calls vim.lsp.enable which needs to
;; run before the FileType event fires, or existing .cs buffers won't attach.
{repo "seblj/roslyn.nvim"
:lazy false
:config (fn []
(let [roslyn (require :roslyn)]
(roslyn.setup {})))}
;; lean.nvim - Lean 4 support: LSP (via `lake serve`), infoview, unicode
;; abbreviations (\to -> →), :Telescope loogle. Self-activates on *.lean and
;; registers its own LSP client, so no vim.lsp.config entry is needed.
;; Needs `lake`/`lean` on PATH (elan, ~/.elan/bin); :init prepends it so a
;; stale parent shell/tmux PATH doesn't cause "language server not installed".
;; Configured via vim.g.lean_config; require("lean").setup is deprecated, so
;; :config is disabled to keep lazy.nvim from calling it through :opts.
{repo "Julian/lean.nvim"
:event ["BufReadPre *.lean" "BufNewFile *.lean"]
:init (fn []
(let [elan-bin (vim.fn.expand "~/.elan/bin")]
(when (and (= 1 (vim.fn.isdirectory elan-bin))
(not (vim.startswith vim.env.PATH (.. elan-bin ":"))))
(set vim.env.PATH (.. elan-bin ":" vim.env.PATH))))
(set vim.g.lean_config {:mappings true}))
:config false}
;; marks.nvim - Show mark letters in the sign column
{repo "chentoast/marks.nvim"
:event "VeryLazy"
:opts {:default_mappings true
:builtin_marks []
:cyclic true
:force_write_shada true
:sign_priority 10}}
;; gitsigns.nvim - Git diff signs in the sign column
{repo "lewis6991/gitsigns.nvim"
:event ["BufReadPre" "BufNewFile"]
:opts {:signs {:add {:text "│"}
:change {:text "│"}
:delete {:text "_"}
:topdelete {:text "‾"}
:changedelete {:text "~"}
:untracked {:text "┆"}}
:sign_priority 6
:update_debounce 100
:max_file_length 40000
:preview_config {:border "single"
:style "minimal"
:relative "cursor"
:row 0
:col 1}
:on_attach (fn [bufnr]
(local gs (require :gitsigns))
(fn map [mode l r opts]
(let [o (or opts {})]
(set o.buffer bufnr)
(vim.keymap.set mode l r o)))
(map :n "]c"
(fn []
(if vim.wo.diff "]c"
(do (vim.schedule #(gs.next_hunk)) "<Ignore>")))
{:expr true :desc "Next hunk"})
(map :n "[c"
(fn []
(if vim.wo.diff "[c"
(do (vim.schedule #(gs.prev_hunk)) "<Ignore>")))
{:expr true :desc "Prev hunk"})
(map [:n :v] "<leader>hs" ":Gitsigns stage_hunk<CR>" {:desc "Stage hunk"})
(map [:n :v] "<leader>hr" ":Gitsigns reset_hunk<CR>" {:desc "Reset hunk"})
(map :n "<leader>hS" gs.stage_buffer {:desc "Stage buffer"})
(map :n "<leader>hu" gs.undo_stage_hunk {:desc "Undo stage"})
(map :n "<leader>hR" gs.reset_buffer {:desc "Reset buffer"})
(map :n "<leader>hp" gs.preview_hunk {:desc "Preview hunk"})
(map :n "<leader>hb" #(gs.blame_line {:full true}) {:desc "Blame line"})
(map :n "<leader>tb" gs.toggle_current_line_blame {:desc "Toggle blame"})
(map :n "<leader>hd" gs.diffthis {:desc "Diff this"})
(map :n "<leader>hD" #(gs.diffthis "~") {:desc "Diff this ~"})
(map :n "<leader>td" gs.toggle_deleted {:desc "Toggle deleted"})
(map [:o :x] "ih" ":<C-U>Gitsigns select_hunk<CR>" {:desc "Select hunk"}))}}]