diff --git a/after/lsp/bashls.lua b/after/lsp/bashls.lua index 63dbf5f..9c5abca 100644 --- a/after/lsp/bashls.lua +++ b/after/lsp/bashls.lua @@ -1,4 +1,3 @@ -local lsp = require("lsp") local util = require("util") ---@type vim.lsp.Config @@ -9,8 +8,6 @@ return { "zsh", }, on_attach = function(client, bufnr) - lsp.on_attach(client, bufnr) - vim.keymap.set("n", "lf", function() util.format({ buf = bufnr, diff --git a/after/lsp/clangd.lua b/after/lsp/clangd.lua index 9edfb9e..f703962 100644 --- a/after/lsp/clangd.lua +++ b/after/lsp/clangd.lua @@ -1,5 +1,4 @@ local Linter = require("linter") -local lsp = require("lsp") ---@type vim.lsp.Config return { @@ -14,8 +13,6 @@ return { }, single_file_support = true, on_attach = function(client, bufnr) - lsp.on_attach(client, bufnr) - Linter.add(bufnr, { cmd = { "clang-tidy", diff --git a/after/lsp/gopls.lua b/after/lsp/gopls.lua index 6f6d6d9..be7d2d3 100644 --- a/after/lsp/gopls.lua +++ b/after/lsp/gopls.lua @@ -1,4 +1,3 @@ -local lsp = require("lsp") local util = require("util") ---@type vim.lsp.Config @@ -13,8 +12,6 @@ return { }, }, on_attach = function(client, bufnr) - lsp.on_attach(client, bufnr) - vim.keymap.set("n", "lf", function() util.format({ buf = bufnr, diff --git a/after/lsp/intelephense.lua b/after/lsp/intelephense.lua index b66e7b9..dd48328 100644 --- a/after/lsp/intelephense.lua +++ b/after/lsp/intelephense.lua @@ -1,5 +1,4 @@ local Linter = require("linter") -local lsp = require("lsp") local util = require("util") ---@type vim.lsp.Config @@ -13,8 +12,6 @@ return { }, }, on_attach = function(client, bufnr) - lsp.on_attach(client, bufnr) - Linter.add(bufnr, { cmd = { "phpcs", diff --git a/after/lsp/lua_ls.lua b/after/lsp/lua_ls.lua index c667cbd..56302f0 100644 --- a/after/lsp/lua_ls.lua +++ b/after/lsp/lua_ls.lua @@ -31,7 +31,6 @@ return { }, }, on_attach = function(client, bufnr) - require("lsp").on_attach(client, bufnr) local util = require("util") vim.keymap.set("n", "lf", function() diff --git a/after/lsp/ruff.lua b/after/lsp/ruff.lua index 5a5fd3b..d996894 100644 --- a/after/lsp/ruff.lua +++ b/after/lsp/ruff.lua @@ -1,11 +1,8 @@ -local lsp = require("lsp") local util = require("util") ---@type vim.lsp.Config return { on_attach = function(client, bufnr) - lsp.on_attach(client, bufnr) - vim.keymap.set("n", "lf", function() vim.lsp.buf.format() util.format({ diff --git a/init.lua b/init.lua index f3f946d..45993dc 100644 --- a/init.lua +++ b/init.lua @@ -25,10 +25,6 @@ local ts = require("ts") require("pack").setup({ "https://github.com/navarasu/onedark.nvim", "https://github.com/ibhagwan/fzf-lua", - { - "https://github.com/saghen/blink.cmp", - version = vim.version.range("^1"), - }, "https://github.com/neovim/nvim-lspconfig", "https://github.com/mason-org/mason.nvim", "https://github.com/owallb/mason-auto-install.nvim", diff --git a/lua/core/options.lua b/lua/core/options.lua index 1035d3c..2f9c527 100644 --- a/lua/core/options.lua +++ b/lua/core/options.lua @@ -26,14 +26,14 @@ vim.opt.foldlevel = 99 vim.opt.foldlevelstart = 99 vim.opt.foldmethod = "indent" vim.opt.foldignore = "" ----@diagnostic disable-next-line: undefined-field -vim.opt.completeopt:append({ +vim.opt.completeopt = { "menu", "menuone", - "preview", + "popup", "noinsert", "noselect", -}) +} +vim.opt.complete = { "o" } -- set nowrap vim.opt.matchpairs:append({ "<:>" }) -- Only relevant with wrap enabled (default) diff --git a/lua/lsp.lua b/lua/lsp.lua index 9af951b..f98ba09 100644 --- a/lua/lsp.lua +++ b/lua/lsp.lua @@ -1,8 +1,12 @@ ----@type fun(name: string, cfg: vim.lsp.Config) -vim.lsp.config = vim.lsp.config - local log = require("log") +local WORD_CHARS = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" + +local function fence(ft, text) + return string.format("```%s\n%s\n```", ft, text) +end + local M = {} M.diagnostic_signs = { @@ -45,6 +49,58 @@ function M.on_attach(client, buf) client.settings ) or client.settings + if + client:supports_method(vim.lsp.protocol.Methods.textDocument_completion) + then + local provider = client.server_capabilities.completionProvider + provider.triggerCharacters = provider.triggerCharacters or {} + if not provider._word_chars_added then + provider._word_chars_added = true + for c in WORD_CHARS:gmatch(".") do + table.insert(provider.triggerCharacters, c) + end + end + local group = vim.api.nvim_create_augroup( + "lsp_completion_" .. buf, + { clear = true } + ) + vim.api.nvim_create_autocmd("TextChangedI", { + buffer = buf, + group = group, + callback = function() + if vim.fn.pumvisible() ~= 0 then + return + end + local col = vim.fn.col(".") - 1 + if col <= 0 then + return + end + local char = vim.api.nvim_get_current_line():sub(col, col) + if char:match("[%w_]") then + vim.lsp.completion.get() + end + end, + }) + vim.lsp.completion.enable(true, client.id, buf, { + autotrigger = true, + convert = function(item) + local signature = vim.tbl_get( + item, + "labelDetails", + "description" + ) or item.detail or "" + return { + abbr = item.label:match("[^(]+") or item.label, + menu = "", + kind = "", + info = signature ~= "" + and fence(vim.bo[buf].filetype, signature) + or " ", + } + end, + }) + end + vim.api.nvim_create_autocmd("LspProgress", { buffer = buf, callback = function(ev) @@ -72,7 +128,7 @@ function M.setup() border = "rounded", focusable = true, format = function(diagnostic) - return string.format("%s", diagnostic.message) + return diagnostic.message end, width = 80, }, @@ -109,23 +165,170 @@ function M.setup() }) local capabilities = vim.lsp.protocol.make_client_capabilities() - capabilities = vim.tbl_deep_extend( - "force", - capabilities, - require("blink.cmp").get_lsp_capabilities({ - textDocument = { - completion = { - completionItem = { - snippetSupport = false, - }, - }, - }, - }, false) - ) + capabilities.textDocument.completion.completionItem.snippetSupport = false vim.lsp.config("*", { capabilities = capabilities, - on_attach = M.on_attach, }) + + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(ev) + local client = vim.lsp.get_client_by_id(ev.data.client_id) + if client then + M.on_attach(client, ev.buf) + end + end, + }) + + local function style_popup(winid, bufnr, width) + if + not winid + or winid <= 0 + or not vim.api.nvim_win_is_valid(winid) + or vim.api.nvim_win_get_config(winid).relative == "" + then + return + end + local cfg = { border = "rounded" } + if width then + cfg.width = math.min(width, 80) + end + if bufnr and vim.api.nvim_buf_is_valid(bufnr) then + vim.wo[winid].wrap = true + vim.wo[winid].linebreak = true + vim.wo[winid].conceallevel = 2 + pcall(vim.treesitter.start, bufnr, "markdown") + end + pcall(vim.api.nvim_win_set_config, winid, cfg) + end + + vim.api.nvim_create_autocmd("CompleteChanged", { + callback = function(ev) + local cinfo = vim.fn.complete_info({ + "selected", + "preview_winid", + "preview_bufnr", + }) + style_popup(cinfo.preview_winid, cinfo.preview_bufnr) + + local completed = vim.v.event.completed_item or {} + local lsp_item = vim.tbl_get( + completed, + "user_data", + "nvim", + "lsp", + "completion_item" + ) + local client_id = + vim.tbl_get(completed, "user_data", "nvim", "lsp", "client_id") + if not lsp_item or not client_id then + return + end + + local client = vim.lsp.get_client_by_id(client_id) + if + not client + or not client:supports_method( + vim.lsp.protocol.Methods.completionItem_resolve + ) + then + return + end + + local selected = cinfo.selected + local word = completed.word + client:request( + vim.lsp.protocol.Methods.completionItem_resolve, + lsp_item, + function(err, result) + if err or not result then + return + end + + local cur = vim.fn.complete_info({ + "selected", + "completed", + "preview_winid", + }) + if + cur.selected ~= selected + or (vim.tbl_get(cur, "completed", "word") or "") + ~= word + then + return + end + + local signature = vim.tbl_get( + result, + "labelDetails", + "description" + ) or result.detail or "" + local doc = result.documentation + if type(doc) == "table" then + doc = doc.value + end + doc = doc or "" + + local ft = vim.bo[ev.buf].filetype + local code_parts = {} + if result.additionalTextEdits then + for _, edit in ipairs(result.additionalTextEdits) do + local text = (edit.newText or ""):gsub("%s+$", "") + if text ~= "" then + table.insert(code_parts, fence(ft, text)) + end + end + end + if signature ~= "" then + table.insert(code_parts, fence(ft, signature)) + end + local sections = {} + if #code_parts > 0 then + table.insert(sections, table.concat(code_parts, "\n\n")) + end + if doc ~= "" then + table.insert(sections, doc) + end + if #sections == 0 then + if + cur.preview_winid + and cur.preview_winid > 0 + and vim.api.nvim_win_is_valid(cur.preview_winid) + then + pcall( + vim.api.nvim_win_close, + cur.preview_winid, + true + ) + end + return + end + + local max_w = 0 + for _, s in ipairs(sections) do + for _, line in + ipairs(vim.split(s, "\n", { plain = true })) + do + max_w = + math.max(max_w, vim.fn.strdisplaywidth(line)) + end + end + local sep = "\n" + .. string.rep("─", math.min(max_w, 80)) + .. "\n" + local combined = table.concat(sections, sep) + + local windata = vim.api.nvim__complete_set(selected, { + info = combined, + }) + if windata then + style_popup(windata.winid, windata.bufnr, max_w) + end + end, + ev.buf + ) + end, + }) + vim.lsp.log.set_level(vim.log.levels.WARN) end diff --git a/lua/plugins/blink.lua b/lua/plugins/blink.lua deleted file mode 100644 index fe6efb6..0000000 --- a/lua/plugins/blink.lua +++ /dev/null @@ -1,88 +0,0 @@ -require("blink.cmp").setup({ - completion = { - documentation = { - auto_show = true, - window = { - winhighlight = "", - }, - }, - ghost_text = { - enabled = true, - }, - list = { - selection = { - preselect = false, - auto_insert = false, - }, - }, - menu = { - border = "none", - draw = { - align_to = "simple_label", - columns = { - { "simple_label" }, - { "kind_icon", "label_description", gap = 1 }, - }, - components = { - simple_label = { - width = { fill = true, max = 60 }, - text = function(ctx) - return ctx.label - end, - highlight = function(ctx) - local highlights = { - { - 0, - #ctx.label, - group = ctx.deprecated - and "BlinkCmpLabelDeprecated" - or "BlinkCmpLabel", - }, - } - for _, idx in ipairs(ctx.label_matched_indices) do - table.insert(highlights, { - idx, - idx + 1, - group = "BlinkCmpLabelMatch", - }) - end - - return highlights - end, - }, - }, - }, - }, - }, - fuzzy = { - implementation = "prefer_rust_with_warning", - }, - signature = { - enabled = true, - window = { - winhighlight = "", - show_documentation = false, - }, - }, - sources = { - default = { - "lsp", - "path", - }, - transform_items = function(_, items) - return vim.tbl_filter(function(item) - return item.kind - ~= require("blink.cmp.types").CompletionItemKind.Snippet - end, items) - end, - }, - keymap = { - preset = "none", - [""] = { "insert_next", "fallback" }, - [""] = { "insert_prev", "fallback" }, - [""] = { "accept", "fallback" }, - [""] = { "show_signature", "hide_signature", "fallback" }, - [""] = { "snippet_forward", "fallback" }, - [""] = { "snippet_backward", "fallback" }, - }, -})