nvim-config

Log | Files | Refs | Submodules | README

lsp_config.lua (13943B)


      1 vim.diagnostic.config {
      2   virtual_text = false,
      3   severity_sort = true,
      4   signs = true,
      5 }
      6 
      7 vim.lsp.log.set_level(vim.lsp.log_levels.WARN)
      8 
      9 vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(vim.lsp.handlers.hover, { border = 'single' })
     10 vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(vim.lsp.handlers.hover, { border = 'single' })
     11 
     12 local function on_attach(client, bufnr)
     13   local function set_keymap(lhs, func, desc)
     14     -- vim.api.nvim_echo({ {"Setup "}, {lhs} }, true, {})
     15     vim.keymap.set("n", lhs, "", {
     16       remap = false,
     17       silent = true,
     18       buffer = bufnr,
     19       callback = func,
     20       desc = desc
     21     })
     22   end
     23 
     24   local function set_autocmd(event, func)
     25     vim.api.nvim_create_autocmd(event, {
     26       buffer = bufnr,
     27       callback = function() pcall(func) end,
     28     })
     29   end
     30 
     31   -- Mappings.
     32   -- See `:help vim.lsp.*` for documentation on any of the below functions
     33   if client.server_capabilities and client.server_capabilities.hoverProvider then
     34     set_keymap('K', vim.lsp.buf.hover, "Hover / print docs")
     35   end
     36   set_keymap('<Leader>d', vim.lsp.buf.definition, "Go to definition")
     37   set_keymap('<Leader>gd', vim.lsp.buf.declaration, "Go to declaration")
     38   set_keymap('<Leader>td', vim.lsp.buf.type_definition, "Go to type definition")
     39   set_keymap('gr', vim.lsp.buf.rename, "Rename symbol at point")
     40   set_keymap('<Leader>a', vim.lsp.buf.code_action, "Code action at point")
     41   set_keymap('<Leader>=', vim.lsp.buf.format, "Format the whole buffer")
     42   set_keymap('gR', require "azy.builtins".lsp.references(), "Search for references")
     43   set_keymap('<Leader>s', require "azy.builtins".lsp.workspace_symbols(), "Search workspace symbols")
     44 
     45   vim.opt_local.tagfunc = "v:lua.vim.lsp.tagfunc"
     46 
     47   set_keymap('<Leader>e', require "azy.builtins".files(vim.tbl_filter(function(p)
     48     return #p > 0 and p ~= vim.fn.expand("$HOME")
     49   end, vim.lsp.buf.list_workspace_folders())))
     50 
     51   if client.supports_method('textDocument/documentHighlight') then
     52     set_autocmd("CursorHold", vim.lsp.buf.document_highlight)
     53     set_autocmd("CursorMoved", vim.lsp.buf.clear_references)
     54   end
     55 
     56   if client.supports_method('textDocument/codeLens') then
     57     set_autocmd({ "BufEnter", "CursorHoldI", "InsertLeave" }, vim.lsp.codelens.refresh)
     58     set_keymap('<Leader>lr', vim.lsp.codelens.run, "Run codelens")
     59   end
     60 
     61   -- if client.supports_method 'textDocument/inlayHint' then
     62   --   vim.lsp.inlay_hint(bufnr, true)
     63   -- end
     64 end
     65 
     66 -- TexLab and LTeX things
     67 
     68 local function texlab_attach(client, bufnr)
     69   vim.api.nvim_buf_set_keymap(bufnr, 'n', '<Leader>m', '<cmd>TexlabForward<CR>', { noremap = true, silent = true })
     70 
     71   vim.lsp.protocol.SymbolKind = {
     72     'file',
     73     'sec',
     74     'fold',
     75     '',
     76     'class',
     77     'float',
     78     'lib',
     79     'field',
     80     'label',
     81     'enum',
     82     'misc',
     83     'cmd',
     84     'thm',
     85     'equ',
     86     'strg',
     87     'arg',
     88     '',
     89     '',
     90     'PhD',
     91     '',
     92     '',
     93     'item',
     94     'book',
     95     'artl',
     96     'part',
     97     'coll',
     98   }
     99   vim.lsp.protocol.CompletionItemKind = {
    100     'string',
    101     '',
    102     '',
    103     '',
    104     'field',
    105     '',
    106     'class',
    107     'misc',
    108     '',
    109     'library',
    110     'thesis',
    111     'argument',
    112     '',
    113     '',
    114     'snippet',
    115     'color',
    116     'file',
    117     '',
    118     'folder',
    119     '',
    120     '',
    121     'book',
    122     'article',
    123     'part',
    124     'collect',
    125   }
    126   on_attach(client, bufnr)
    127 end
    128 
    129 local augroup = vim.api.nvim_create_augroup("User_LSP", {})
    130 
    131 local find_root = function(markers, file)
    132   local dirs = vim.fs.find(markers, { upward = true, path = vim.fs.dirname(file) })
    133   local source = vim.fn.fnamemodify(dirs[1] or file, ":p")
    134   if source:sub(-1) == "/" then
    135     source = vim.fs.dirname(source)
    136   end
    137   return vim.fs.dirname(source)
    138 end
    139 
    140 local function setup_lsp(name, filetypes, command, ucommands, config)
    141   vim.api.nvim_create_autocmd("Filetype", {
    142     pattern = filetypes,
    143     group = augroup,
    144     callback = function(args)
    145       local cpreffix = name:gsub("[-_ ]", "")
    146       cpreffix = cpreffix:sub(1, 1):upper() .. cpreffix:sub(2)
    147 
    148       -- Setup user commands if requested
    149       for cname, func in pairs(ucommands) do
    150         vim.api.nvim_create_user_command(cpreffix .. cname, function(...)
    151           local client = vim.lsp.get_clients { name = name }[1]
    152           func(client, args.buf, ...)
    153         end, {})
    154       end
    155       local cfg = vim.deepcopy(config)
    156       cfg.name = name
    157       cfg.cmd = command
    158 
    159       -- Find the root directory
    160       if not cfg.root_dir then
    161         cfg.root_dir = find_root(cfg.root_markers or { ".git" }, args.file)
    162       end
    163       vim.lsp.start(cfg, { bufnr = args.buf })
    164     end
    165   })
    166 end
    167 
    168 local function execute_command(client, command, args, bufnr, handler)
    169   return client.request("workspace/executeCommand", { command = command, arguments = args }, handler, bufnr)
    170 end
    171 
    172 -- System lsps
    173 
    174 local capabilities = vim.lsp.protocol.make_client_capabilities()
    175 capabilities.textDocument.completion.completionItem.snippetSupport = true
    176 capabilities.workspace.configuration = true
    177 
    178 local default_cfg = { capabilities = capabilities, on_attach = on_attach }
    179 
    180 -- A bit of notes for the future me
    181 -- filetypes (table) List of filetypes for this lsp
    182 -- command (table|nil) command to run to start the server, defaults to server name
    183 -- cfg (table) config as in vim.lsp.start_client()
    184 -- root_markers (list|nil) Files that are markers for the root directory
    185 -- ucommands (table) Mapping from valid command names to lua functions (as in
    186 --   nvim_create_user_command, with first argument being the client) to create in the buffer where the thing is started
    187 local system_lsps = {
    188   lemminx = {
    189     filetypes = { 'xml' },
    190   },
    191   hls = {
    192     command = { "haskell-language-server-wrapper", "--lsp" },
    193     filetypes = { "haskell" },
    194     root_markers = { "Setup.hs", "stack.yaml" },
    195   },
    196 
    197   zls = {
    198     filetypes = { "zig" },
    199     root_markers = { "build.zig", ".git" },
    200     cfg = {
    201       capabilities = capabilities,
    202       on_attach = on_attach,
    203       settings = {
    204         zls = {
    205           enable_autofix = false,
    206           enable_snippets = true,
    207           enable_inlay_hints = true,
    208           include_at_in_builtins = true,
    209           inlay_hints_hide_redundant_param_names_last_token = true,
    210           warn_style = true,
    211         }
    212       }
    213     }
    214   },
    215 
    216   htmlsp = {
    217     filetypes = { "html" },
    218     root_markers = { "index.html", ".git" },
    219     command = { "vscode-html-language-server", "--stdio" },
    220   },
    221 
    222   lean = {
    223     filetypes = { 'lean', },
    224     command = { 'lake', 'serve'},
    225   },
    226 
    227   cmakels = {
    228     command = { "cmake-language-server" },
    229     filetypes = { "cmake" },
    230     root_markers = { "CMakeLists.txt", ".git", }
    231   },
    232 
    233   tsserver = {
    234     command = { "typescript-language-server", "--stdio" },
    235     filetypes = { "typescript", "javascript" },
    236     root_markers = { "tsconfig.json", "package.json", ".git" }
    237   },
    238 
    239   ocamllsp = {
    240     filetypes = { "ocaml" },
    241     cfg = {
    242       capabilites = capabilities,
    243       on_attach = on_attach,
    244       settings = {
    245         ocaml = {
    246           server = {
    247             extraEnv = {
    248               OCAMLLSP_SEMANTIC_HIGHLIGHTING = "full",
    249             }
    250           }
    251         }
    252       }
    253     }
    254   },
    255 
    256   clangd = {
    257     command = { "clangd", "--limit-results=0", "--suggest-missing-includes", "--all-scopes-completion",
    258       "--compile-commands-dir=build/" },
    259     filetypes = { "c", "cpp" },
    260     root_markers = { "CMakeLists.txt", ".git" },
    261   },
    262 
    263   pylsp = {
    264     filetypes = { "python" },
    265   },
    266 
    267   ["rnix-lsp"] = {
    268     filetypes = { "nix" },
    269   },
    270 
    271   ["vim-language-server"] = {
    272     filetypes = { "vim" },
    273   },
    274 
    275   gopls = {
    276     filetypes = { "go" },
    277   },
    278 
    279   rust_analyzer = {
    280     filetypes = { "rust" },
    281     root_markers = { "Cargo.toml", ".git" },
    282     command = { "rustup", "run", "nightly", "rust-analyzer" },
    283   },
    284 
    285   codeql = {
    286     command = { 'codeql', 'execute', 'language-server', '--check-errors=ON_CHANGE' },
    287     filetypes = { 'ql' },
    288   },
    289 
    290   ["lua-language-server"] = (function()
    291     return {
    292       filetypes = { "lua" },
    293       cfg = {
    294         capabilities = capabilities,
    295         on_attach = on_attach,
    296         settings = {
    297           Lua = {
    298             runtime = {
    299               -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
    300               version = 'LuaJIT',
    301               -- Setup your lua path
    302               path = vim.split(package.path, ';'),
    303             },
    304             hint = {
    305               enable = true
    306             },
    307             diagnostics = {
    308               -- Get the language server to recognize the `vim` global
    309               globals = { 'vim' },
    310 
    311               workspaceDelay = -1,
    312 
    313               groupFileStatus = {
    314                 strict = "Opened",
    315                 strong = "Opened",
    316               },
    317 
    318               groupSeverity = {
    319                 strong = "Warning",
    320                 strict = "Warning",
    321               },
    322             },
    323             workspace = {
    324               -- Make the server aware of Neovim runtime files
    325               library = vim.api.nvim_get_runtime_file('', true),
    326               checkThirdParty = false,
    327             },
    328             -- Do not send telemetry data containing a randomized but unique identifier
    329             telemetry = {
    330               enable = false,
    331             },
    332             completion = {
    333               callSnippet = "Replace",
    334             }
    335           },
    336         },
    337       }
    338     }
    339   end)(),
    340 
    341   texlab = {
    342     command = { "texlab" },
    343     filetypes = { "tex", "bib", "latex" },
    344     root_markers = { ".latexmkrc", ".git" },
    345     ucommands = {
    346       Build = function(client, bufnr)
    347         local texlab_build_status = vim.tbl_add_reverse_lookup {
    348           Success = 0,
    349           Error = 1,
    350           Failure = 2,
    351           Cancelled = 3,
    352         }
    353 
    354         local params = vim.lsp.util.make_text_document_params(bufnr)
    355         client.request('textDocument/build', params, function(err, result)
    356           if err then
    357             error(tostring(err))
    358           end
    359           vim.notify('Build ' .. texlab_build_status[result.status])
    360         end, bufnr)
    361       end,
    362 
    363       Forward = function(client, bufnr)
    364         local texlab_forward_status = vim.tbl_add_reverse_lookup {
    365           Success = 0,
    366           Error = 1,
    367           Failure = 2,
    368           Unconfigured = 3,
    369         }
    370 
    371         local params = {
    372           textDocument = { uri = vim.uri_from_bufnr(bufnr) },
    373           position = { line = vim.fn.line '.' - 1, character = vim.fn.col '.' },
    374         }
    375 
    376         client.request('textDocument/forwardSearch', params, function(err, result)
    377           if err then
    378             error(tostring(err))
    379           end
    380           vim.notify('Search ' .. texlab_forward_status[result.status])
    381         end)
    382       end,
    383 
    384       CleanAuxiliary = function(client, bufnr)
    385         client.request("workspace/executeCommand",
    386           { command = "texlab.cleanAuxiliary", arguments = { document = vim.lsp.util.make_text_document_params(bufnr) } },
    387           function(...) end, bufnr)
    388       end,
    389 
    390       CleanArtifacts = function(client, bufnr)
    391         local uri = vim.uri_from_bufnr(bufnr)
    392         client.request("workspace/executeCommand",
    393           { command = "texlab.cleanArtifacts", arguments = { document = { uri = uri } } })
    394       end
    395     },
    396     cfg = {
    397       trace = "verbose",
    398       capabilities = capabilities,
    399       on_attach = texlab_attach,
    400       settings = {
    401         texlab = {
    402           build = {
    403             onSave = true,
    404             forwardSearchAfter = true,
    405             args = { "-interaction=nonstopmode", "-f", "-synctex=1", "-shell-escape", "-xelatex", "%f" }
    406           },
    407           forwardSearch = {
    408             executable = "zathura",
    409             args = { "-x",
    410               -- First level of escaping is for string.format, second level for escaping is for texlab
    411               string.format("nvim --server %s --remote-send '<cmd>edit +%%%%{line} %%%%{input}<cr>'",
    412                 vim.fn.serverlist()[1]),
    413               "--synctex-forward", "%l:1:%f", "%p" }
    414           },
    415         }
    416       },
    417     },
    418   }
    419 }
    420 
    421 for lname, config in pairs(system_lsps) do
    422   if not config.filetypes then
    423     vim.notify(string.format("No filetypes defined for %s", lname))
    424   else
    425     setup_lsp(lname, config.filetypes, config.command or { lname }, config.ucommands or {}, config.cfg or default_cfg)
    426   end
    427 end
    428 
    429 require 'ltex-ls'.setup {
    430   on_attach = on_attach,
    431   capabilities = capabilities,
    432   filetypes = { "latex", "tex", "bib", "markdown", "gitcommit", "text", "mail" },
    433   use_spellfile = true,
    434   handlers = {
    435     ["ltex/workspaceSpecificConfiguration"] = vim.lsp.with(require 'ltex-ls.handlers'.workspace_configuration,
    436       { debug = true })
    437   },
    438   settings = {
    439     ltex = {
    440       checkFrequency = "save",
    441       enabled = { "latex", "tex", "bib", "markdown", },
    442       language = "auto",
    443       diagnosticSeverity = "information",
    444       additionalRules = {
    445         enablePickyRules = true,
    446         motherTongue = "fr",
    447       },
    448       disabledRules = {
    449         en = { "REGARD", "PASSIVE_VOICE", "ACTUALLY", "REST" },
    450         fr = { "APOS_TYP", "FRENCH_WHITESPACE", "FR_SPELLING_RULE", "COMMA_PARENTHESIS_WHITESPACE" }
    451       },
    452       latex = {
    453         commands = {
    454           ["\\MaxMC"] = "dummy",
    455           ["\\SSAT"] = "dummy",
    456           ["\\SAT"] = "dummy",
    457           ["\\todo"] = "ignore",
    458         },
    459         environments = {
    460           quote = 'ignore',
    461         }
    462       },
    463       dictionary = (function()
    464         local files = {}
    465         for _, file in ipairs(vim.api.nvim_get_runtime_file("spell/*.add", true)) do
    466           local lang = vim.fn.fnamemodify(file, ":t:r:r") -- Because 'spellfile' is .{encoding}.add
    467           local fullpath = vim.fn.fnamemodify(file, ":p")
    468           files[lang] = { ":" .. fullpath }
    469         end
    470 
    471         if files.default then
    472           for lang, _ in pairs(files) do
    473             if lang ~= "default" then
    474               vim.list_extend(files[lang], files.default)
    475             end
    476           end
    477           files.default = nil
    478         end
    479         return files
    480       end)(),
    481       trace = { server = "verbose" },
    482     },
    483   },
    484 }