local protocol = require 'vim.lsp.protocol' local lsp_util = vim.lsp.util vim.lsp.set_log_level("off") local preview_location_callback = function(_, result) if result == nil or vim.tbl_isempty(result) then return nil end vim.lsp.util.preview_location(result[1]) end local peek_definition = function() local params = vim.lsp.util.make_position_params() return vim.lsp.buf_request(0, 'textDocument/definition', params, preview_location_callback) end _G.PeekDefinition = peek_definition protocol.CompletionItemKind = { '󰉿 Text' ; '󰡱 Method' ; '󰊕 Function' ; ' Constructor' ; ' Field' ; ' Variable' ; ' Class' ; ' Interface' ; '󰏗 Module' ; ' Property' ; ' Unit' ; '󰮸 Value' ; ' Enum' ; '󰌋 Keyword' ; '󰷈 Snippet' ; '󰌁 Color' ; '󰈙 File' ; '󰀾 Reference' ; ' Folder' ; '󰀬 EnumMember' ; ' Constant' ; ' Struct' ; '󰀾 Event' ; '󰆕 Operator' ; ' TypeParameter'; } local lsp_augroup_id = vim.api.nvim_create_augroup("LSP", { clear = true }) local lsp_key_mappings = { { "textDocument/definition" , 'n', 'pd' , 'lua PeekDefinition()' }, { "textDocument/definition" , 'n', 'gd' , 'lua vim.lsp.buf.definition()' }, --{ "textDocument/declaration" , 'n', 'gD' , 'lua vim.lsp.buf.declaration()' }, { "textDocument/typeDefinition" , 'n', '' , 'lua vim.lsp.buf.type_definition()' }, { "textDocument/references" , 'n', 'gr' , 'lua vim.lsp.buf.references()' }, { "textDocument/implementation" , 'n', 'gD' , 'lua vim.lsp.buf.implementation()' }, { "textDocument/documentSymbol" , 'n', '1gd' , 'lua vim.lsp.buf.document_symbol()' }, { "workspace/symbol" , 'n', '1gD' , 'lua vim.lsp.buf.workspace_symbol()' }, { "textDocument/hover" , 'n', 'k', 'lua vim.lsp.buf.hover()' }, { "textDocument/signatureHelp" , 'n', 'S', 'lua vim.lsp.buf.signature_help()' }, { "textDocument/rename" , 'n', 'gR' , 'lua vim.lsp.buf.rename()' }, { "textDocument/inlayHint" , 'n', 'gi' , 'lua vim.lsp.inlay_hint(0, nil)' }, { "textDocument/rangeFormatting", 'x', 'gq', 'lua vim.lsp.buf.format({async=true})' }, { "textDocument/formatting" , 'n', 'gq', 'lua vim.lsp.buf.format({async=true})' }, { "textDocument/codeAction", 'n', 'ga' , 'lua vim.lsp.buf.code_action()' }, { "textDocument/codeAction", 'v', 'ga' , 'lua vim.lsp.buf.code_action()' }, { "textDocument/codeAction", 'n', 'r', 'lua vim.lsp.buf.code_action{only = \'refactor\' }' }, { "textDocument/codeAction", 'v', 'r', 'lua vim.lsp.buf.code_action{only = \'refactor\' }' }, { "textDocument/codeLens", 'n', 'l', 'lua vim.lsp.codelens.run()' }, { "textDocument/codeLens", 'n', 'L', 'lua vim.lsp.codelens.clear()' }, } local CODE_ACTION_AVAILABLE = "CodeActionAvailable" local CODE_ACTION_GROUP = "code-action-group" if vim.tbl_isempty(vim.fn.sign_getdefined(CODE_ACTION_AVAILABLE)) then vim.fn.sign_define(CODE_ACTION_AVAILABLE, { text = "💡", texthl = "DiagnosticSignInfo" }) end local code_action_update_sign = function(old_line, new_line, bufnr) bufnr = bufnr or "%" if old_line then vim.fn.sign_unplace( CODE_ACTION_GROUP, { id = old_line, buffer = bufnr } ) vim.b.code_action_line = nil end if new_line and (vim.b.code_action_line ~= new_line) then vim.fn.sign_place( new_line, CODE_ACTION_GROUP, CODE_ACTION_AVAILABLE, bufnr, { lnum = new_line, priority = 10 } ) vim.b.code_action_line = new_line end end local cargo_reload_workspace = function() vim.lsp.buf_request(0, 'rust-analyzer/reloadWorkspace', nil, function(err) if err then vim.schedule(function() vim.notify('Cargo reload workspace gave error: ' .. tostring(err), vim.log.levels.ERROR) end) else vim.schedule(function() vim.notify 'Cargo workspace reloaded' end) end end) end local get_active_client_by_name = function(bufnr, servername) for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do if client.name == servername then return client end end end local switch_source_header = function() local clangd_client = get_active_client_by_name(0, 'clangd') local params = { uri = vim.uri_from_bufnr(0) } if clangd_client then clangd_client.request('textDocument/switchSourceHeader', params, function(err, result) if err then vim.schedule(function() vim.notify('Clangd: switchSourceHeader gave error: ' .. tostring(err), vim.log.levels.ERROR) end) end if not result then vim.schedule(function() vim.notify('Clangd: switchSourceHeader, corresponding file not determined', vim.log.levels.ERROR) end) return end vim.cmd.edit(vim.uri_to_fname(result)) end, 0) else vim.schedule(function() vim.notify('Clangd: switchSourceHeader is not supported', vim.log.levels.ERROR) end) end end local client_custom_setup = function(client) if client.name == 'clangd' then vim.api.nvim_create_user_command("ClangdSwitchSourceHeader", switch_source_header, {desc = 'Switch between source/header'}) end if client.name == 'rust-analyzer' then vim.api.nvim_create_user_command("CargoReload", cargo_reload_workspace, {desc = 'Reload current cargo workspace'}) end if client.name == 'tsserver' then -- Disable tsserver formatting, we want formatting via prettier client.server_capabilities.documentFormattingProvider = false client.server_capabilities.documentRangeFormattingProvider = false end end local client_custom_cleanup = function(client) if client.name == 'clangd' then vim.api.nvim_del_user_command("ClangdSwitchSourceHeader") end if client.name == 'rust-analyzer' then vim.api.nvim_del_user_command("CargoReload") end end local on_attach = function(client, bufnr) vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') if client.config.flags then client.config.flags.allow_incremental_sync = true client.config.flags.debounce_text_changes = 100 end local opts = { noremap=true, silent=true } client_custom_setup(client) for _, mappings in pairs(lsp_key_mappings) do local capability, mode, lhs, rhs = unpack(mappings) if client.supports_method(capability) then vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts) end end if client.supports_method('textDocument/codeAction') then vim.api.nvim_create_autocmd({"CursorHold", "CursorHoldI"}, { group = lsp_augroup_id, buffer = bufnr, callback = function() -- Taken from https://github.com/neovim/nvim-lspconfig/wiki/Code-Actions local context = { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() } local params = lsp_util.make_range_params() params.context = context vim.lsp.buf_request(0, 'textDocument/codeAction', params, function(err, result, ctx, config) if result and not vim.tbl_isempty(result) then local line = params.range.start.line code_action_update_sign(vim.b.code_action_line, line + 1, bufnr) else code_action_update_sign(vim.b.code_action_line, nil, bufnr) end end) end }) end if client.supports_method('textDocument/codeLens') then vim.api.nvim_create_autocmd({"CursorHold", "CursorHoldI", "InsertLeave"}, { group = lsp_augroup_id, buffer = bufnr, callback = vim.lsp.codelens.refresh, }) end if client.supports_method('textDocument/documentHighlight') then vim.api.nvim_create_autocmd("CursorHold", { group = lsp_augroup_id, buffer = bufnr, callback = vim.lsp.buf.document_highlight, }) vim.api.nvim_create_autocmd("CursorMoved", { group = lsp_augroup_id, buffer = bufnr, callback = vim.lsp.buf.clear_references, }) end if client.supports_method('textDocument/inlayHint') then vim.lsp.inlay_hint(bufnr, false) end end vim.api.nvim_create_autocmd("LspAttach", { group = lsp_augroup_id, callback = function(args) local bufnr = args.buf local client = vim.lsp.get_client_by_id(args.data.client_id) local opts = { noremap=true, silent=true } for _, mappings in pairs(lsp_key_mappings) do local capability, mode, lhs, rhs = unpack(mappings) if client.supports_method(capability) then vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts) end end on_attach(client, bufnr) end, }) vim.api.nvim_create_autocmd("LspDetach", { group = lsp_augroup_id, callback = function(args) local bufnr = args.buf local client = vim.lsp.get_client_by_id(args.data.client_id) for _, mappings in pairs(lsp_key_mappings) do local capability, mode, lhs, _ = unpack(mappings) if client.supports_method(capability) then vim.api.nvim_buf_del_keymap(bufnr, mode, lhs) end end client_custom_cleanup(client) end, }) vim.api.nvim_create_autocmd("LspProgress", { group = lsp_augroup_id, command = "redrawstatus" })