local Item = require("lsp.completion.item") local Popup = require("lsp.completion.popup") local log = require("log") local session = require("lsp.completion.session") 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("textDocument/completion") then return end vim.api.nvim_clear_autocmds({ group = GROUP, buffer = buf }) vim.api.nvim_create_autocmd("InsertCharPre", { buffer = buf, group = GROUP, callback = session.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 --[[@as integer]], col = vim.v.event.col --[[@as integer]], width = vim.v.event.width --[[@as integer]], height = vim.v.event.height --[[@as integer]], scrollbar = vim.v.event.scrollbar --[[@as boolean]], } if client:supports_method("completionItem/resolve") then popup:dispatch_resolve( client, item, ft, pum, completed.word or "", 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 ---@param target ow.lsp.completion.Item 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, }, ---@param err lsp.ResponseError? ---@param ctx lsp.HandlerContext function(err, _, ctx) if err then log.warning( "client %d: %s failed for %s: %s", client.id, ctx.method, raw.command.title, err.message ) end end, 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 or "") if cached then apply(cached) elseif client:supports_method("completionItem/resolve") and not item.raw.additionalTextEdits and not item.raw.command then client:request( "completionItem/resolve", item.raw, ---@param err lsp.ResponseError? ---@param resolved lsp.CompletionItem? ---@param ctx lsp.HandlerContext function(err, resolved, ctx) if err then log.warning( "client %d: %s failed: %s", client.id, ctx.method, err.message ) end 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 }) vim.keymap.set("i", "", session.trigger) end return M