From 409c5ee3849703e69eeb2a9e14c5710df7897076 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 30 Apr 2026 10:27:39 -0400 Subject: [PATCH] add stuff --- fnl/config/init.fnl | 8 +- fnl/config/parinfer.fnl | 2 +- fnl/plugins/init.fnl | 57 ++++++++++- ftdetect/clje.lua | 5 + lazy-lock.json | 14 +-- lua/clje/hover.lua | 218 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 295 insertions(+), 9 deletions(-) create mode 100644 ftdetect/clje.lua create mode 100644 lua/clje/hover.lua diff --git a/fnl/config/init.fnl b/fnl/config/init.fnl index f5bb8f8..1acfc7c 100644 --- a/fnl/config/init.fnl +++ b/fnl/config/init.fnl @@ -98,7 +98,13 @@ (keymap :n "gD" vim.lsp.buf.declaration opts) (keymap :n "gr" vim.lsp.buf.references opts) (keymap :n "gi" vim.lsp.buf.implementation opts) - (keymap :n "K" vim.lsp.buf.hover opts) + (keymap :n "K" + (fn [] + (if (= vim.bo.filetype "clojure") + (let [clje-hover (require :clje.hover)] + (clje-hover.hover)) + (vim.lsp.buf.hover))) + opts) (keymap :n "rn" vim.lsp.buf.rename opts) (keymap :n "ca" vim.lsp.buf.code_action opts) (keymap :n "[d" (fn [] (vim.diagnostic.jump {:count -1})) opts) diff --git a/fnl/config/parinfer.fnl b/fnl/config/parinfer.fnl index ecd99b5..fa6cb6f 100644 --- a/fnl/config/parinfer.fnl +++ b/fnl/config/parinfer.fnl @@ -11,7 +11,7 @@ ;; ── Configuration ──────────────────────────────────────────────── ;; Change this single flag to flip the default for all sexp filetypes. -(local default-enabled true) +(local default-enabled false) ;; ── Implementation ─────────────────────────────────────────────── diff --git a/fnl/plugins/init.fnl b/fnl/plugins/init.fnl index a4e5bc8..8a17f75 100644 --- a/fnl/plugins/init.fnl +++ b/fnl/plugins/init.fnl @@ -149,5 +149,60 @@ {lhs "b" :group "buffer"} {lhs "r" :group "refactor"} {lhs "c" :group "code"} + {lhs "h" :group "hunk"} {lhs "t" :group "toggle"} - {lhs "l" :group "log"}]))}] + {lhs "l" :group "log"}]))} + + ;; blink.cmp - Fast completion engine with LSP support + {repo "saghen/blink.cmp" + :version "*" + :event ["InsertEnter" "CmdlineEnter"] + :opts {:keymap {:preset "super-tab"} + :completion {:documentation {:auto_show true}} + :sources {:default ["lsp" "path" "buffer"]}}} + + ;; 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)) ""))) + {:expr true :desc "Next hunk"}) + (map :n "[c" + (fn [] + (if vim.wo.diff "[c" + (do (vim.schedule #(gs.prev_hunk)) ""))) + {:expr true :desc "Prev hunk"}) + (map [:n :v] "hs" ":Gitsigns stage_hunk" {:desc "Stage hunk"}) + (map [:n :v] "hr" ":Gitsigns reset_hunk" {:desc "Reset hunk"}) + (map :n "hS" gs.stage_buffer {:desc "Stage buffer"}) + (map :n "hu" gs.undo_stage_hunk {:desc "Undo stage"}) + (map :n "hR" gs.reset_buffer {:desc "Reset buffer"}) + (map :n "hp" gs.preview_hunk {:desc "Preview hunk"}) + (map :n "hb" #(gs.blame_line {:full true}) {:desc "Blame line"}) + (map :n "tb" gs.toggle_current_line_blame {:desc "Toggle blame"}) + (map :n "hd" gs.diffthis {:desc "Diff this"}) + (map :n "hD" #(gs.diffthis "~") {:desc "Diff this ~"}) + (map :n "td" gs.toggle_deleted {:desc "Toggle deleted"}) + (map [:o :x] "ih" ":Gitsigns select_hunk" {:desc "Select hunk"}))}}] diff --git a/ftdetect/clje.lua b/ftdetect/clje.lua new file mode 100644 index 0000000..b7d5119 --- /dev/null +++ b/ftdetect/clje.lua @@ -0,0 +1,5 @@ +vim.filetype.add({ + extension = { + clje = "clojure", + }, +}) diff --git a/lazy-lock.json b/lazy-lock.json index 86b9654..af57721 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -1,15 +1,17 @@ { - "conjure": { "branch": "main", "commit": "f66b3e7f45d325bd556cd785502761e583971920" }, + "blink.cmp": { "branch": "main", "commit": "4b18c32adef2898f95cdef6192cbd5796c1a332d" }, + "conjure": { "branch": "main", "commit": "403639610bcb9b6a5dfc494dc3179cc19773a471" }, + "gitsigns.nvim": { "branch": "main", "commit": "7c4faa3540d0781a28588cafbd4dd187a28ac6e3" }, "hop.nvim": { "branch": "master", "commit": "08ddca799089ab96a6d1763db0b8adc5320bf050" }, "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" }, - "mason.nvim": { "branch": "main", "commit": "12ddd182d9efbdc848b540f16484a583d52da0fb" }, - "nfnl": { "branch": "main", "commit": "ac0177c5549df7abba7a19554c18a7765386c894" }, + "mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" }, + "nfnl": { "branch": "main", "commit": "fecf731e02bc51d88372c4f992fe1ef0c19c02ae" }, "nvim-parinfer": { "branch": "master", "commit": "3968e669d9f02589aa311d33cb475b16b27c5fbb" }, - "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" }, - "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, + "nvim-treesitter": { "branch": "main", "commit": "f8bbc3177d929dc86e272c41cc15219f0a7aa1ac" }, + "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, "telescope.nvim": { "branch": "master", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" }, - "tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" }, + "tokyonight.nvim": { "branch": "main", "commit": "5da1b76e64daf4c5d410f06bcb6b9cb640da7dfd" }, "vim-repeat": { "branch": "master", "commit": "65846025c15494983dafe5e3b46c8f88ab2e9635" }, "vim-sexp": { "branch": "master", "commit": "2fc0a7c7e44fe94e48156d6a5b5fea28538430eb" }, "vim-sexp-mappings-for-regular-people": { "branch": "master", "commit": "4debb74b0a3e530f1b18e5b7dff98a40b2ad26f1" }, diff --git a/lua/clje/hover.lua b/lua/clje/hover.lua new file mode 100644 index 0000000..dd2ae7e --- /dev/null +++ b/lua/clje/hover.lua @@ -0,0 +1,218 @@ +-- Custom hover for CljElixir .clje files +-- Resolves Module/function patterns against stub files in the project's stubs/ directory +local M = {} + +--- Walk up from the current buffer's directory to find the stubs/ directory +local function find_stubs_dir() + local bufpath = vim.api.nvim_buf_get_name(0) + if bufpath == "" then return nil end + local dir = vim.fn.fnamemodify(bufpath, ":p:h") + for _ = 1, 20 do + if dir == "/" or dir == "" then break end + if vim.fn.isdirectory(dir .. "/stubs") == 1 then + return dir .. "/stubs" + end + dir = vim.fn.fnamemodify(dir, ":h") + end + return nil +end + +--- Extract the symbol under cursor, handling namespace/function patterns. +--- Returns (namespace, function_name) or nil. +local function symbol_under_cursor() + local line = vim.api.nvim_get_current_line() + local col = vim.api.nvim_win_get_cursor(0)[2] -- 0-indexed byte col + + -- Expand outward from cursor to find the full symbol (letters, digits, -, ?, !, ., /, _) + local s, e = col + 1, col + 1 + local sym_chars = "[%w%-%?%!%.%/_><=*+]" + while s > 1 and line:sub(s - 1, s - 1):match(sym_chars) do + s = s - 1 + end + while e < #line and line:sub(e + 1, e + 1):match(sym_chars) do + e = e + 1 + end + local word = line:sub(s, e) + if not word or word == "" then return nil, nil end + + -- Must contain a / to be a qualified call + local slash = word:find("/") + if not slash then return nil, nil end + + local ns = word:sub(1, slash - 1) + local fn_name = word:sub(slash + 1) + if ns == "" or fn_name == "" then return nil, nil end + + return ns, fn_name +end + +--- Resolve a namespace name to a stub file path +local function resolve_stub_file(stubs_dir, ns) + -- Direct match (e.g., Process.clj, erlang.clj) + local path = stubs_dir .. "/" .. ns .. ".clj" + if vim.fn.filereadable(path) == 1 then return path end + + -- erl_ prefix for case-insensitive collisions (e.g., erl_io.clj for :io) + path = stubs_dir .. "/erl_" .. ns .. ".clj" + if vim.fn.filereadable(path) == 1 then return path end + + -- Subdirectory (e.g., clje/core.clj) + path = stubs_dir .. "/clje/" .. ns .. ".clj" + if vim.fn.filereadable(path) == 1 then return path end + + return nil +end + +--- Find a (defn ...) or (defmacro ...) form in the file lines and return its text +local function find_defn(lines, fn_name) + -- Escape special pattern chars in the function name + local pat = fn_name:gsub("([%-%.%?%!%*%+%[%]%(%)%^%$%%])", "%%%1") + + local start_idx = nil + for i, line in ipairs(lines) do + -- Match (defn name or (defmacro name at the start of a line + if line:match("^%(defn%s+" .. pat .. "$") + or line:match("^%(defn%s+" .. pat .. "%s") + or line:match("^%(defmacro%s+" .. pat .. "$") + or line:match("^%(defmacro%s+" .. pat .. "%s") then + start_idx = i + break + end + end + + if not start_idx then return nil end + + -- Collect lines for this form by tracking paren depth + local form_lines = {} + local depth = 0 + for i = start_idx, #lines do + local line = lines[i] + table.insert(form_lines, line) + for ch in line:gmatch(".") do + if ch == "(" then depth = depth + 1 + elseif ch == ")" then depth = depth - 1 end + end + if depth <= 0 and i > start_idx then break end + end + + return form_lines +end + +--- Extract a vector [args] from a signature line, stripping outer parens and trailing junk +local function extract_sig(s) + -- Match the innermost [args] vector + local vec = s:match("%[(.-)%]") + if vec then + return "[" .. vec .. "]" + end + return nil +end + +--- Extract docstring and signatures from defn form lines for a cleaner display +local function parse_defn(form_lines, ns, fn_name) + local md = {} + local docstring_parts = {} + local signatures = {} + local in_docstring = false + + for i, line in ipairs(form_lines) do + if i == 1 then goto continue end -- skip (defn name line + + local trimmed = line:match("^%s*(.-)%s*$") + + -- Detect docstring (starts and/or ends with ") + if not in_docstring and trimmed:match('^"') then + in_docstring = true + -- Single-line docstring + if trimmed:match('^"(.*)"$') then + table.insert(docstring_parts, trimmed:match('^"(.*)"$')) + in_docstring = false + else + table.insert(docstring_parts, trimmed:match('^"(.*)$')) + end + elseif in_docstring then + if trimmed:match('"[%)]*$') then + table.insert(docstring_parts, trimmed:match('^(.-)"')) + in_docstring = false + else + table.insert(docstring_parts, trimmed) + end + elseif trimmed:match("%[") then + -- Signature line containing [args] + local sig = extract_sig(trimmed) + if sig then + table.insert(signatures, sig) + end + end + + ::continue:: + end + + -- Build markdown + table.insert(md, "### `" .. ns .. "/" .. fn_name .. "`") + table.insert(md, "") + + if #signatures > 0 then + table.insert(md, "```clojure") + for _, sig in ipairs(signatures) do + if sig == "[]" then + table.insert(md, "(" .. ns .. "/" .. fn_name .. ")") + else + table.insert(md, "(" .. ns .. "/" .. fn_name .. " " .. sig .. ")") + end + end + table.insert(md, "```") + table.insert(md, "") + end + + if #docstring_parts > 0 then + for _, part in ipairs(docstring_parts) do + table.insert(md, part) + end + end + + return md +end + +--- Main hover function — looks up Module/function in stubs +function M.hover() + local ns, fn_name = symbol_under_cursor() + if not ns or not fn_name then + vim.lsp.buf.hover() + return + end + + local stubs_dir = find_stubs_dir() + if not stubs_dir then + vim.lsp.buf.hover() + return + end + + local stub_file = resolve_stub_file(stubs_dir, ns) + if not stub_file then + vim.lsp.buf.hover() + return + end + + local lines = vim.fn.readfile(stub_file) + if not lines or #lines == 0 then + vim.lsp.buf.hover() + return + end + + local form_lines = find_defn(lines, fn_name) + if not form_lines then + -- Function not found in stub, fall back to LSP + vim.lsp.buf.hover() + return + end + + local md = parse_defn(form_lines, ns, fn_name) + + vim.lsp.util.open_floating_preview(md, "markdown", { + border = "rounded", + focus_id = "clje-hover", + }) +end + +return M