72087d4

Branches > main > 72087d4 > files / nvim / lua

Add epic completion code from the LLM

Grahp, 2026-10-25

Commit: 72087d4205db567269f122e912d395a806877cfb
Parent: 70744c0

3 changed files:

nvim/init.lua modified (view file)

@@ -16,6 +16,7 @@   require("lsp")   require("format")   require("lazy_ui")+  require("complete") end)  --- TODO there's a bug tha parens and virtual text likes to stick around for longer than it should...

nvim/lua/complete.lua added (view file)

@@ -0,0 +1,377 @@+local api = vim.api+local autocmd = api.nvim_create_autocmd+local protocol = vim.lsp.protocol++local max_shown = 200+local clojure_lsp_max_items = 600+local symbol_chars = "[^%s%(%)%[%]{}\"',`;@^~\\]"++local prose_filetypes = {[""] = true, markdown = true, text = true, gitcommit = true}++local function syntax_names_at(buf, row, col)+  local names = {}+  if vim.treesitter.highlighter.active[buf] then+    for _, capture in ipairs(vim.treesitter.get_captures_at_pos(buf, row - 1, col - 1)) do+      names[#names + 1] = capture.capture+    end+  else+    for _, id in ipairs(vim.fn.synstack(row, col)) do+      names[#names + 1] = vim.fn.synIDattr(id, "name")+    end+  end+  return names+end++local function in_string_or_comment(buf, row, col)+  if col == 0 then return false end+  for _, name in ipairs(syntax_names_at(buf, row, col)) do+    name = name:lower()+    if name:match("string") or name:match("comment") then return true end+  end+  return false+end++local function namespace_of(detail)+  if not detail then return end+  return detail:match("^([%w%.%-_]+)/") or detail:match("^([%w%._]+)%.[%w_$]+$")+end++local function from_lsp(item)+  local is_snippet = item.insertTextFormat == protocol.InsertTextFormat.Snippet+  local insert = item.textEdit and item.textEdit.newText or item.insertText or item.label+  return {label = item.label,+          word = is_snippet and item.label or insert,+          snippet = is_snippet and insert or nil,+          ns = namespace_of(item.detail),+          is_local = item.kind == protocol.CompletionItemKind.Variable and item.detail == "",+          lsp = item}+end++local function ns_and_aliases(buf)+  local ns_lines = {}+  for _, line in ipairs(api.nvim_buf_get_lines(buf, 0, 200, false)) do+    if #ns_lines > 0 and line:match("^%(") then break end+    if #ns_lines > 0 or line:match("^%(ns%s") then ns_lines[#ns_lines + 1] = line end+  end+  local ns_form = table.concat(ns_lines, "\n")++  local aliases = {}+  for lib, alias in ns_form:gmatch("%[([^%s%]]+)[^%]]-:as%s+([^%s%]]+)") do+    aliases[lib] = alias+  end+  return ns_form:match("^%(ns%s+([^%s%)]+)"), aliases+end++local function auto_resolved_keyword(keyword)+  return {label = keyword, word = keyword, auto_resolved = true}+end++local function add_auto_resolved_keywords(buf, items)+  local ns, aliases = ns_and_aliases(buf)+  local keywords = {}+  for _, item in ipairs(items) do+    local kw_ns, name = item.label:match("^:([^/]+)/(.+)$")+    if kw_ns == ns then+      keywords[#keywords + 1] = auto_resolved_keyword("::" .. name)+    elseif aliases[kw_ns] then+      keywords[#keywords + 1] = auto_resolved_keyword("::" .. aliases[kw_ns] .. "/" .. name)+    end+  end++  for _, line in ipairs(api.nvim_buf_get_lines(buf, 0, -1, false)) do+    for keyword in line:gmatch("::" .. symbol_chars .. "+") do+      keywords[#keywords + 1] = auto_resolved_keyword(keyword)+    end+  end+  return vim.list_extend(items, keywords)+end++local function is_unfiltered_fallback(items, word)+  if word == "" or #items < clojure_lsp_max_items then return false end+  for _, item in ipairs(items) do+    if not vim.startswith(item.label:lower(), word:lower()) then return true end+  end+  return false+end++local clojure = {}++function clojure.word(before)+  return (before:match(symbol_chars .. "*$"):gsub("^#+", ""))+end++function clojure.request_prefix(word)+  return word:match("^:") or word:match("^.*/") or word:sub(1, 1)+end++function clojure.completes_nothing_typed(before)+  return before:match("[%s%(%[{]$") ~= nil+end++local last_locals = {}++function clojure.fetch(buf, word, done)+  local client = vim.lsp.get_clients({bufnr = buf, method = "textDocument/completion"})[1]+  if not client then return end++  local form_start = vim.fn.search("^[(\\[{]", "bcnW")+  local function same_form()+    return last_locals.buf == buf and last_locals.form_start == form_start+  end++  if word == "" then+    local before = api.nvim_get_current_line():sub(1, api.nvim_win_get_cursor(0)[2])+    local after_open_bracket = before:match("[%(%[{]%s*$")+    if not after_open_bracket then+      if same_form() then done(last_locals.items) end+      return+    end+  end++  local params = vim.lsp.util.make_position_params(0, client.offset_encoding)+  local request_id, cancelled++  local function request(retries_left)+    _, request_id = client:request("textDocument/completion", params, function(err, result)+      if err or not result or cancelled then return end+      local items = vim.tbl_map(from_lsp, result.items or result)++      if retries_left > 0 and is_unfiltered_fallback(items, word) then+        return vim.defer_fn(function()+          if not cancelled then request(retries_left - 1) end+        end, 50)+      end++      if word == "" then+        local locals = vim.tbl_filter(function(item) return item.is_local end, items)+        if #locals > 0 then+          last_locals = {buf = buf, form_start = form_start, items = locals}+        elseif same_form() then+          items = last_locals.items+        end+      end++      local truncated = result.isIncomplete or #items >= clojure_lsp_max_items+      done(add_auto_resolved_keywords(buf, items), client, truncated)+    end, buf)+  end+  request(5)++  return function()+    cancelled = true+    if request_id then client:cancel_request(request_id) end+  end+end++local buffer_words = {}++function buffer_words.word(before)+  return before:match("[%w_]*$")+end++function buffer_words.request_prefix()+  return ""+end++function buffer_words.fetch(buf, word, done)+  local row, col = unpack(api.nvim_win_get_cursor(0))+  local items, seen = {}, {}++  for i, line in ipairs(api.nvim_buf_get_lines(buf, 0, -1, false)) do+    if i == row then line = line:sub(1, col - #word) .. " " .. line:sub(col + 1) end++    for w in line:gmatch("[%w_]+") do+      if not seen[w] and not w:match("^%d") then+        seen[w] = true+        items[#items + 1] = {label = w, word = w}+      end+    end+  end++  done(items)+end++local function source_for(buf)+  local ft = vim.bo[buf].filetype+  if ft == "clojure" then return clojure end+  if vim.bo[buf].buftype == "" and not prose_filetypes[ft] then return buffer_words end+end++local session+local cancel_fetch+local accepted_at++local function close()+  if vim.fn.pumvisible() == 1 then+    vim.fn.complete(vim.fn.col("."), {})+  end+end++local function set_candidates(s, items)+  s.items, s.labels, s.by_label = items, {}, {}+  for _, item in ipairs(items) do+    if not s.by_label[item.label] then+      s.by_label[item.label] = item+      s.labels[#s.labels + 1] = item.label+    end+  end+end++local function group_of(item, word_is_capitalized)+  if item.is_local then return 1 end+  if word_is_capitalized and not item.label:match("^%u") then return 3 end+  return 2+end++local function matching(s, word)+  if word == "" then+    return vim.tbl_filter(function(item) return item.is_local end, s.items)+  end++  local fuzzy = {}+  for _, label in ipairs(vim.fn.matchfuzzy(s.labels, word)) do+    local item = s.by_label[label]+    if not item.auto_resolved or word:match("^::") then fuzzy[#fuzzy + 1] = item end+  end++  local prefixed = vim.tbl_filter(function(item)+    return vim.startswith(item.label:lower(), word:lower())+  end, fuzzy)+  local items = #prefixed > 0 and prefixed or fuzzy++  local fuzzy_rank = {}+  for i, item in ipairs(items) do fuzzy_rank[item] = i end+  local word_is_capitalized = word:match("^%u") ~= nil++  table.sort(items, function(a, b)+    local group_a, group_b = group_of(a, word_is_capitalized), group_of(b, word_is_capitalized)+    if group_a ~= group_b then return group_a < group_b end+    if #prefixed > 0 and #a.label ~= #b.label then return #a.label < #b.label end+    return fuzzy_rank[a] < fuzzy_rank[b]+  end)+  return items+end++local function show(s, word)+  local items = matching(s, word)+  if #items == 0 then return close() end++  local entries = {}+  for i = 1, math.min(#items, max_shown) do+    entries[i] = {word = items[i].word,+                  abbr = items[i].label,+                  menu = items[i].ns,+                  equal = 1,+                  user_data = {complete = i}}+  end++  s.shown = items+  vim.fn.complete(s.start + 1, entries)+end++local function refresh()+  local buf = api.nvim_get_current_buf()+  local row, col = unpack(api.nvim_win_get_cursor(0))++  if accepted_at and vim.deep_equal(accepted_at, {buf, row, col}) then return end+  accepted_at = nil++  local navigating_menu = vim.fn.complete_info({"selected"}).selected ~= -1+  if navigating_menu then return end++  local source = source_for(buf)+  if not source then return end++  local before = api.nvim_get_current_line():sub(1, col)+  local word = source.word(before)+  local wants_completion = word ~= ""+                           or (source.completes_nothing_typed and source.completes_nothing_typed(before))++  if in_string_or_comment(buf, row, col) or word:match("^%d") or not wants_completion then+    session = nil+    return close()+  end++  local start = col - #word+  local key = table.concat({buf, row, start, source.request_prefix(word)}, ":")++  local still_typing_same_word = session and session.key == key and vim.startswith(word, session.word)+  local more_typing_narrows_results = session and session.truncated and word ~= session.word+                                      and not word:match("^::")+  if still_typing_same_word and not more_typing_narrows_results then+    if session.items then show(session, word) end+    return+  end++  if cancel_fetch then cancel_fetch() end++  local s = {key = key, row = row, start = start, word = word}+  if session and session.items and session.row == row and session.start == start then+    s.items, s.labels, s.by_label = session.items, session.labels, session.by_label+    show(s, word)+  end+  session = s++  cancel_fetch = source.fetch(buf, word, function(items, client, truncated)+    if session ~= s or not api.nvim_get_mode().mode:match("^i") then return end+    cancel_fetch = nil+    set_candidates(s, items)+    s.client, s.truncated = client, truncated+    refresh()+  end)+end++local function apply_extra_edits(s, item)+  local client, lsp_item = s.client, item.lsp+  if not (client and lsp_item) then return end++  local buf = api.nvim_get_current_buf()+  local function apply(resolved)+    if resolved.additionalTextEdits then+      vim.lsp.util.apply_text_edits(resolved.additionalTextEdits, buf, client.offset_encoding)+    end+  end++  if lsp_item.additionalTextEdits or not client:supports_method("completionItem/resolve") then+    apply(lsp_item)+  else+    client:request("completionItem/resolve", lsp_item, function(err, resolved)+      if not err and resolved then apply(resolved) end+    end, buf)+  end+end++local function accept(s, item)+  if item.snippet then+    local row, col = unpack(api.nvim_win_get_cursor(0))+    api.nvim_buf_set_text(0, row - 1, s.start, row - 1, col, {""})+    api.nvim_win_set_cursor(0, {row, s.start})+    vim.snippet.expand(item.snippet)+  end+  apply_extra_edits(s, item)+end++autocmd({"TextChangedI", "TextChangedP"}, {callback = refresh})++autocmd("CompleteDone", {callback = function()+  local data = vim.v.completed_item.user_data+  if type(data) ~= "table" or not data.complete then return end++  local s = session+  local item = s and s.shown and s.shown[data.complete]+  local col = api.nvim_win_get_cursor(0)[2]+  local was_inserted = item and api.nvim_get_current_line():sub(s.start + 1, col) == item.word+  if not was_inserted then return end++  accept(s, item)+  local row, new_col = unpack(api.nvim_win_get_cursor(0))+  accepted_at = {api.nvim_get_current_buf(), row, new_col}+end})++autocmd("InsertLeave", {callback = function()+  if cancel_fetch then cancel_fetch() end+  session, cancel_fetch, accepted_at = nil, nil, nil+end})++vim.opt.completeopt = {"menu", "menuone", "noinsert", "noselect"}+vim.opt.shortmess:append("c")

nvim/lua/opts.lua modified (view file)

@@ -38,8 +38,4 @@   o.swapfile = false   o.backup = false   o.undofile = true--  --- Completion-  o.completeopt = {"menu", "menuone", "fuzzy"}- end)