local Item = require("lsp.completion.item") local Popup = require("lsp.completion.popup") local request = require("lsp.completion.request") local GROUP = vim.api.nvim_create_augroup("ow.lsp.completion", { clear = true }) local popup = Popup.new() ---@param client vim.lsp.Client ---@param buf integer local function on_attach(client, buf) if not client:supports_method( vim.lsp.protocol.Methods.textDocument_completion ) then return end vim.bo[buf].omnifunc = "v:lua.ow_lsp_completion.omnifunc" vim.api.nvim_clear_autocmds({ group = GROUP, buffer = buf }) vim.api.nvim_create_autocmd("InsertCharPre", { buffer = buf, group = GROUP, callback = request.on_insert_char_pre, }) end local M = {} function M.setup() vim.lsp.config("*", { capabilities = { textDocument = { completion = { completionItem = { snippetSupport = true }, }, }, }, }) vim.api.nvim_create_autocmd("LspAttach", { group = GROUP, callback = function(ev) local client = vim.lsp.get_client_by_id(ev.data.client_id) if client then on_attach(client, ev.buf) end end, }) vim.api.nvim_create_autocmd("CompleteChanged", { group = GROUP, callback = function(ev) local completed = vim.v.event.completed_item or {} local item = Item.from_user_data(completed) local client = item and vim.lsp.get_client_by_id(item.client_id) if not item or not client then vim.schedule(function() popup:close() end) return end local ft = vim.bo[ev.buf].filetype ---@type ow.lsp.completion.Pum local pum = { row = vim.v.event.row, col = vim.v.event.col, width = vim.v.event.width, height = vim.v.event.height, scrollbar = vim.v.event.scrollbar, } if client:supports_method( vim.lsp.protocol.Methods.completionItem_resolve ) then popup:dispatch_resolve( client, item, ft, pum, completed.word, ev.buf ) else vim.schedule(function() popup:show(item, ft, pum) end) end end, }) vim.api.nvim_create_autocmd({ "CompleteDonePre", "InsertLeave" }, { group = GROUP, callback = function() popup:close() end, }) vim.api.nvim_create_autocmd("CompleteDone", { group = GROUP, callback = function(ev) local completed = vim.v.completed_item or {} local item = Item.from_user_data(completed) if not item then return end local client = vim.lsp.get_client_by_id(item.client_id) if not client then return end local function apply(target) local raw = target.raw if raw.additionalTextEdits then vim.lsp.util.apply_text_edits( raw.additionalTextEdits, ev.buf, client.offset_encoding ) end if raw.command then client:request("workspace/executeCommand", { command = raw.command.command, arguments = raw.command.arguments, }, nil, ev.buf) end if target.snippet then local word = completed.word or "" local cursor = vim.api.nvim_win_get_cursor(0) local row = cursor[1] - 1 local col = cursor[2] vim.api.nvim_buf_set_text( ev.buf, row, col - #word, row, col, {} ) vim.snippet.expand(target.snippet) end end -- Prefer the resolved item when we have it. If we haven't cached -- one yet and the original is missing edits, resolve on the fly. local cached = popup:resolved_for(completed.word) if cached then apply(cached) elseif client:supports_method( vim.lsp.protocol.Methods.completionItem_resolve ) and not item.raw.additionalTextEdits and not item.raw.command then client:request( vim.lsp.protocol.Methods.completionItem_resolve, item.raw, function(err, resolved) if err or not resolved then return end item:apply_resolved(resolved) apply(item) end, ev.buf ) else apply(item) end end, }) ---@param key string ---@param action fun() local function scroll_map(key, action) vim.keymap.set("i", key, function() if popup:is_visible() then vim.schedule(action) return "" end return key end, { expr = true, replace_keycodes = true }) end scroll_map("", function() popup:scroll("down", Popup.HALF_PAGE) end) scroll_map("", function() popup:scroll("up", Popup.HALF_PAGE) end) scroll_map("", function() popup:scroll("down", 1) end) scroll_map("", function() popup:scroll("up", 1) end) vim.keymap.set("i", "", function() if popup:is_visible() then return "" end return "" end, { expr = true, replace_keycodes = true }) end return M