DEV Community

Cover image for Neovim and its built in language server protocol
Cason Adams
Cason Adams

Posted on

Neovim and its built in language server protocol

I have been using coc for a few years now as my link to the various language servers out there. It is awesome! However, recently I have been tinkering with the built in LSP in neovim 0.5.0+. At the time of writing this, it is almost stable. I just wanted to share how I got things setup and working for me.

Requirements:

This brings in the tools that I use. I have included an upgrade.sh file that auto installs the needed language servers (macOS or fedora). There are a few more deps needed if one wants to run this. Most packages managers will have these.

Once everything is setup a few shortcut keys defined below will be as follows:

keys description
gd goto definition
gD goto declaration
gr show references
gi goto implementation
K show doc
SPACE rn rename
SPACE e show line diagnostics
[d diagnostic previous
]d diagnostic next

Alt Text

init.vim

  • ~/.config/nvim/init.vim
"------------------------------------------------ " Plugins START call plug#begin() Plug 'neovim/nvim-lspconfig' Plug 'nvim-lua/completion-nvim' Plug 'nvim-lua/lsp-status.nvim' Plug 'nvim-lua/diagnostic-nvim' Plug 'airblade/vim-gitgutter' Plug 'sbdchd/neoformat' call plug#end() " Plugins END "------------------------------------------------ " Loads lua config lua require('init') " Use <Tab> and <S-Tab> to navigate through popup menu inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>" inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>" "------------------------------------------------ " Settings START filetype plugin on set updatetime=300 set completeopt=menuone set completeopt+=noinsert set completeopt-=preview set shortmess+=c " Settings END "------------------------------------------------ "------------------------------------------------ " Status Line START set statusline=%<%f\ %h%m%r set statusline+=%=%-10.60{LspStatus()}\ %-.(%l,%c%V%)\ %P function! LspStatus() abort if luaeval('#vim.lsp.buf_get_clients() > 0') return luaeval("require('lsp-status').status()") endif return '' endfunction " Status Line END "------------------------------------------------ 
Enter fullscreen mode Exit fullscreen mode

init.lua

  • ~/.config/nvim/lua/init.lua
local nvim_lsp = require "lspconfig" local lsp_status = require("lsp-status") -- function to attach completion when setting up lsp local on_attach = function(client) lsp_status.register_progress() lsp_status.config( { status_symbol = "LSP ", indicator_errors = "E", indicator_warnings = "W", indicator_info = "I", indicator_hint = "H", indicator_ok = "ok" } ) require "completion".on_attach(client) local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end buf_set_option("omnifunc", "v:lua.vim.lsp.omnifunc") -- Mappings. local opts = {noremap = true, silent = true} buf_set_keymap("n", "gD", "<Cmd>lua vim.lsp.buf.declaration()<CR>", opts) buf_set_keymap("n", "gd", "<Cmd>lua vim.lsp.buf.definition()<CR>", opts) buf_set_keymap("n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", opts) buf_set_keymap("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", opts) buf_set_keymap("n", "K", "<Cmd>lua vim.lsp.buf.hover()<CR>", opts) buf_set_keymap("n", "<space>rn", "<cmd>lua vim.lsp.buf.rename()<CR>", opts) buf_set_keymap("n", "<space>e", "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>", opts) buf_set_keymap("n", "[d", "<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>", opts) buf_set_keymap("n", "]d", "<cmd>lua vim.lsp.diagnostic.goto_next()<CR>", opts) -- Set some keybinds conditional on server capabilities if client.resolved_capabilities.document_formatting then buf_set_keymap("n", "<space>f", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts) elseif client.resolved_capabilities.document_range_formatting then buf_set_keymap("n", "<space>f", "<cmd>lua vim.lsp.buf.range_formatting()<CR>", opts) end -- Set autocommands conditional on server_capabilities if client.resolved_capabilities.document_highlight then vim.api.nvim_exec([[ augroup lsp_document_highlight autocmd! * <buffer> autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() " autocmd CursorHold *.* :lua vim.lsp.diagnostic.show_line_diagnostics() autocmd BufWritePre * lua vim.lsp.buf.formatting_sync(nil, 300) augroup END ]], false ) else vim.api.nvim_exec([[ autocmd! autocmd BufWritePre * Neoformat augroup END ]], false) end end -- Use a loop to conveniently both setup defined servers -- and map buffer local keybindings when the language server attaches local servers = { "gopls", "dockerls", "tsserver", "bashls", "cmake", "pyright", "rust_analyzer", "clangd" } for _, lsp in ipairs(servers) do nvim_lsp[lsp].setup { on_attach = on_attach, capabilities = lsp_status.capabilities } end -- Setup diagnostics formaters and linters for non LSP provided files nvim_lsp.diagnosticls.setup { on_attach = on_attach, capabilities = lsp_status.capabilities, cmd = {"diagnostic-languageserver", "--stdio"}, filetypes = { "lua", "sh", "markdown", "json", "yaml", "toml" }, init_options = { linters = { shellcheck = { command = "shellcheck", debounce = 100, args = {"--format", "json", "-"}, sourceName = "shellcheck", parseJson = { line = "line", column = "column", endLine = "endLine", endColumn = "endColumn", message = "${message} [${code}]", security = "level" }, securities = { error = "error", warning = "warning", info = "info", style = "hint" } }, markdownlint = { command = "markdownlint", isStderr = true, debounce = 100, args = {"--stdin"}, offsetLine = 0, offsetColumn = 0, sourceName = "markdownlint", formatLines = 1, formatPattern = { "^.*?:\\s?(\\d+)(:(\\d+)?)?\\s(MD\\d{3}\\/[A-Za-z0-9-/]+)\\s(.*)$", { line = 1, column = 3, message = {4} } } } }, filetypes = { sh = "shellcheck", markdown = "markdownlint" }, formatters = { shfmt = { command = "shfmt", args = {"-i", "2", "-bn", "-ci", "-sr"} }, prettier = { command = "prettier", args = {"--stdin-filepath", "%filepath"}, } }, formatFiletypes = { sh = "shfmt", json = "prettier", yaml = "prettier", toml = "prettier", markdown = "prettier", lua = "prettier" } } } -- Enable diagnostics vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( vim.lsp.diagnostic.on_publish_diagnostics, { underline = true, virtual_text = false, signs = true, update_in_insert = true } ) 
Enter fullscreen mode Exit fullscreen mode

upgrade.sh

  • ~/.config/nvim/upgrade.sh
#!/usr/bin/env bash set -e system_type=$(uname -s) if [ "$system_type" = "Darwin" ]; then brew install shellcheck brew install shfmt brew install llvm fd 'clangd$' /usr/local/ --exec ln -s '{}' "$HOME"/.local/bin | : curl -L https://github.com/rust-analyzer/rust-analyzer/releases/download/nightly/rust-analyzer-mac.gz | gunzip -f > "$HOME"/.local/bin/rust-analyzer && chmod +x "$HOME"/.local/bin/rust-analyzer else sudo dnf install -y \ ShellCheck \ clang-tools-extra \ ; curl -L https://github.com/rust-analyzer/rust-analyzer/releases/download/nightly/rust-analyzer-linux.gz | gunzip -f > "$HOME"/.local/bin/rust-analyzer && chmod +x "$HOME"/.local/bin/rust-analyzer fi sudo npm install -g -f \ pyright \ lua-fmt \ prettier \ prettier-plugin-toml \ markdownlint \ diagnostic-languageserver \ typescript typescript-language-server \ dockerfile-language-server-nodejs \ ; pip install --upgrade \ cmake-language-server \ ; GO111MODULE=on go get golang.org/x/tools/gopls@latest 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)