-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
356 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,356 @@ | ||
--- | ||
title: neovim config - native lsp support instead of coc | ||
categories: | ||
- programming | ||
- linux | ||
tags: | ||
- vim | ||
--- | ||
|
||
# Migrating language server, linting and formatting away from [coc](https://github.com/neoclide/coc.nvim) | ||
|
||
## Context | ||
|
||
I am a big [vim (neovim)](https://neovim.io) fan and do basically all my | ||
programming and writing in it, the exception being work things that need MS | ||
Office. Luckily, most of those things are possible with the browser-based | ||
version of office, the exception being macros and obscure things like | ||
Powerpoint footer changes. Anyways, that is a completely different story. | ||
|
||
Naked, vanilla vim is nice and present in basically every environment. So, it's | ||
good to be able to use it, especially for remote | ||
configuration/documentation/troubleshooting work. The real power is unleashed | ||
with more advanced capabilities and extensions, which extend the barebone | ||
editor with modern creature comforts, such as _linting_, _automatic fixing_, | ||
_code completion_, _auto formatting_, _syntax highlighting_, _test execution_, | ||
_debugging_ and _intelligent navigation_. | ||
|
||
My _code completion_ and _intelligent navigation_ setup started with | ||
[YouCompleteMe](https://github.com/ycm-core/YouCompleteMe) in 2017. I needed it | ||
mainly for `C`, and it did a good job. | ||
|
||
A bit later, Microsoft's _Language Server Protocol_ gained much more traction, | ||
with more and more really usable [_language | ||
servers_](https://microsoft.github.io/language-server-protocol/implementors/servers/) | ||
being really usable and taking off, mainly n the context of _visual studio | ||
code_. Most of the Language servers where installed and integrated into | ||
_vscode_ through dedicated _plugins_. | ||
|
||
In comes the neovim plugin [coc (conquer of | ||
completion)](https://github.com/neoclide/coc.nvim). This plugin acts as a | ||
_language server client_, meaning that it can use any _language server_ that | ||
implements the lsp standard, and it can integrate slightly adapted _vscode | ||
pluins_ as _coc plugins_. This meant that the managed installation of language | ||
servers and the integration thereof became really simple and straightforward, | ||
with a very active ecosystem and community around it. I migrated to _coc_ in | ||
2019, adding support and better integration for many more languages into my | ||
setup. | ||
|
||
_coc_ with a set of plugins and a handful of "manually" integrated language | ||
servers as well as with a set of linters and formatters, integrated with | ||
[diagnostic-languageserver](https://github.com/iamcco/diagnostic-languageserver), | ||
was my default setup for 5 years. I changed the supported languaged all the | ||
time, based on what I needed, but besides that and [rewriting the config to | ||
lua]({% link _posts/2023-12-01-nvim-config-lua.md %}), my setup stayed like | ||
this for 5 years. | ||
|
||
In the meantime, _neovim_ gained native _language server client_ support, | ||
removing the need for a dedicated plugin for lsp support. However, _coc_ still | ||
provided the easy integration and awesome out of the box functionality. | ||
|
||
The awesome and very capable community embraced the feature, though. There is | ||
[mason](https://github.com/williamboman/mason.nvim), an nvim native package/lsp | ||
manager, and many integration layer plugins between neovim and mason, making | ||
the integration of language servers slimmer (no extra layer), extremely | ||
flexible and manageable, with concise and powerful configuration. | ||
|
||
Powered by all of that, projects like [lazyvim](https://www.lazyvim.org/) | ||
started, providing much more out of the box functionality and ease of use than | ||
coc had. Lazyvim is basically a full-featured one size fits all | ||
preconfiguration of nvim, with very sane defaults and a smart plugin and | ||
configuration system that makes it feature rich without bloat. I know a few | ||
people who recently went down the rabbithole of vim, and they started with | ||
lazyvim right away. I respect it a lot, even though I want to have a bit more | ||
control over my setup, so not for me. The ecosystem grew and grew and is now | ||
more active and powerful than coc's ecosystem. | ||
|
||
I was very interested in the principle of the powerful and manageable | ||
modularization, but still, the normal out of the box experience was a lot worse | ||
than with coc, as it was basically not a box, but a collection of elements you | ||
had to build your box with yourself. The capabilities of coc are split into | ||
separate and independent components for completion (with lsp as source amongst | ||
others), dedicated components for formatters, linters, dedicated configuration | ||
of all components and system integration. | ||
|
||
Interesting, but it's a lot. Especially because my current setup just worked | ||
and did mostly what I wanted it to do, and I had no real functioning reason to | ||
migrate besides the itch. So, I put a migration on the back of my to do list, | ||
to do some other time. | ||
|
||
## Some other time is _now_ | ||
|
||
That is until for the first time ever I had to do things in `C#`. There is a | ||
coc plugin for `C#`, | ||
[coc-omnisharp](https://github.com/coc-extensions/coc-omnisharp), which in the | ||
README greets you with the message: | ||
|
||
```text | ||
⛔ This project is lacking proper maintanence. I would recommend csharp-ls at this moment. | ||
``` | ||
|
||
This leaves manual integration of the | ||
[csharp-language-server](https://github.com/razzmatazz/csharp-language-server). | ||
Awesome, I did it right away. | ||
|
||
But there was a problem. | ||
|
||
With the coc integration, I was unable to find a way to replace the default | ||
sync handler for system libraries only available as binaries, used e.g. for | ||
navigation, such as _go_to_definition_, with a buffer showing the decompiled | ||
sourcecode. This is explicitly (and easily) possible with the flexible | ||
configuration of native language servers in nvim, as | ||
[documented](https://github.com/Decodetalkers/csharpls-extended-lsp.nvim). | ||
|
||
So, last weekend, I did it. | ||
|
||
I migrated my configuration. | ||
|
||
### Structure | ||
|
||
I use [lazy](https://github.com/folke/lazy.nvim) as plugin manager, which makes | ||
it really simple to split up plugin configuration. I decided to split my | ||
language server config into a dedicated file for the abstract definition of the | ||
behaviour of lsps, e.g. with key mappings, sources of the completion system and | ||
so on, a dedicated file for the specific definition of supported languages, and | ||
a dedicated file for the separate configuration of linters and fixers, while | ||
also revamping the test execution and debugging setup. | ||
|
||
```text | ||
/home/gierdo/.dotfiles/.config/nvim/lua/plugins | ||
❯ tree | ||
. | ||
├── debugging.lua | ||
├── filetree.lua | ||
├── fuzzyness.lua | ||
├── git.lua | ||
├── init.lua | ||
├── linters.lua | ||
├── lsp-behaviour.lua | ||
├── lsp-languages.lua | ||
├── neoai.lua | ||
├── testing.lua | ||
├── theme.lua | ||
└── treesitter.lua | ||
``` | ||
|
||
Copy-pasting the few hundred lines of lua here probably makes this post even | ||
less readable, but I'll point out some things here that are not just obvious | ||
configuration. | ||
|
||
If you want to see the full configuration, check out my | ||
[dotfiles.](https://github.com/gierdo/dotfiles/tree/master/.config/nvim/lua/plugins) | ||
|
||
#### The original motivation: C# decompiler integration | ||
|
||
```lua | ||
local cs_config = { | ||
capabilities = lsp_capabilities, | ||
handlers = { | ||
["textDocument/definition"] = require("csharpls_extended").handler, | ||
["textDocument/typeDefinition"] = require("csharpls_extended").handler, | ||
}, | ||
} | ||
lspconfig.csharp_ls.setup(cs_config) | ||
``` | ||
|
||
#### Fuzzy and fancy selection for multiple navigation target hits | ||
|
||
```lua | ||
vim.keymap.set( | ||
"n", | ||
"gd", | ||
"<cmd>lua require('telescope.builtin').lsp_definitions()<cr>", | ||
{ buffer = event.buf, desc = "Go to definition." } | ||
) | ||
vim.keymap.set( | ||
"n", | ||
"gi", | ||
"<cmd>lua require('telescope.builtin').lsp_implementations()<cr>", | ||
{ buffer = event.buf, desc = "Go to implementation." } | ||
) | ||
vim.keymap.set( | ||
"n", | ||
"go", | ||
"<cmd>lua require('telescope.builtin').lsp_type_definitions()<cr>", | ||
{ buffer = event.buf, desc = "Go to type definition." } | ||
) | ||
vim.keymap.set( | ||
"n", | ||
"gr", | ||
"<cmd>lua require('telescope.builtin').lsp_references()<cr>", | ||
{ buffer = event.buf, desc = "Show references." } | ||
) | ||
``` | ||
|
||
#### Extended json- and yaml schema support | ||
|
||
```lua | ||
lspconfig.jsonls.setup({ | ||
capabilities = lsp_capabilities, | ||
settings = { | ||
json = { | ||
schemas = require("schemastore").json.schemas(), | ||
validate = { enable = true }, | ||
schemaDownload = { enable = false }, | ||
}, | ||
}, | ||
}) | ||
|
||
lspconfig.yamlls.setup({ | ||
capabilities = lsp_capabilities, | ||
settings = { | ||
yaml = { | ||
schemaStore = { | ||
enable = false, | ||
url = "", | ||
}, | ||
schemas = require("schemastore").yaml.schemas({ | ||
extra = { | ||
{ | ||
name = "Cloudformation", | ||
description = "Cloudformation Template", | ||
fileMatch = { "*.template.y*ml", "*-template.y*ml" }, | ||
url = "https://raw.githubusercontent.com/awslabs/goformation/master/schema/cloudformation.schema.json", | ||
}, | ||
}, | ||
}), | ||
customTags = { | ||
-- Cloudformation tags | ||
"!And scalar", | ||
"!If scalar", | ||
"!Not", | ||
"!Equals scalar", | ||
"!Or scalar", | ||
"!FindInMap scalar", | ||
"!Base64", | ||
"!Cidr", | ||
"!Ref", | ||
"!Sub", | ||
"!GetAtt sequence", | ||
"!GetAZs", | ||
"!ImportValue sequence", | ||
"!Select sequence", | ||
"!Split sequence", | ||
"!Join sequence", | ||
}, | ||
}, | ||
}, | ||
}) | ||
``` | ||
|
||
#### imho smart test execution and debugging keymaps | ||
|
||
```lua | ||
local wk = require("which-key") | ||
|
||
wk.add({ | ||
{ "<leader>t", group = " Test" }, | ||
{ "<leader>tr", neotest.run.run, desc = "Run nearest test." }, | ||
{ | ||
"<leader>tf", | ||
function() | ||
neotest.run.run(vim.fn.expand("%")) | ||
end, | ||
desc = "Run current file.", | ||
}, | ||
{ | ||
"<leader>td", | ||
function() | ||
neotest.run.run({ strategy = "dap" }) | ||
end, | ||
desc = "Debug nearest test.", | ||
}, | ||
{ | ||
"<leader>ts", | ||
neotest.summary.toggle, | ||
desc = "Show/hide summary.", | ||
}, | ||
}) | ||
|
||
wk.add({ | ||
{ "<leader>d", group = " Debug" }, | ||
{ | ||
"<leader>dd", | ||
telescope.extensions.dap.commands, | ||
desc = "Show debug commands.", | ||
}, | ||
{ | ||
"<leader>dt", | ||
dap.toggle_breakpoint, | ||
desc = "Toggle line breakpoint on the current line.", | ||
}, | ||
{ | ||
"<leader>dbs", | ||
dap.set_breakpoint, | ||
desc = "Set breakpoint.", | ||
}, | ||
{ | ||
"<leader>dbl", | ||
telescope.extensions.dap.list_breakpoints, | ||
desc = "List breakpoints.", | ||
}, | ||
{ | ||
"<leader>dv", | ||
telescope.extensions.dap.variables, | ||
desc = "Show variables.", | ||
}, | ||
{ | ||
"<leader>dbc", | ||
dap.clear_breakpoints, | ||
desc = "Clear breakpoints.", | ||
}, | ||
{ | ||
"<leader>dbe", | ||
dap.set_exception_breakpoints, | ||
desc = "Set Exception breakpoints.", | ||
}, | ||
{ | ||
"<leader>dc", | ||
dap.continue, | ||
desc = "When debugging, continue. Otherwise start debugging.", | ||
}, | ||
{ | ||
"<leader>dfd", | ||
dap.down, | ||
desc = "Move down a frame in the current call stack.", | ||
}, | ||
{ | ||
"<leader>dfu", | ||
dap.up, | ||
desc = "Move up a frame in the current call stack.", | ||
}, | ||
{ "<leader>dp", dap.pause, desc = "Pause debuggee." }, | ||
{ | ||
"<leader>dr", | ||
dap.run_to_cursor, | ||
desc = "Continues execution to the current cursor.", | ||
}, | ||
{ | ||
"<leader>dre", | ||
dap.restart, | ||
desc = "Restart debugging with the same configuration.", | ||
}, | ||
}) | ||
``` | ||
|
||
## tl;dr | ||
|
||
- language servers and such + vim are great | ||
- there are many ways to integrate vim with language servers | ||
- [coc](https://github.com/neoclide/coc.nvim) is not that slim and flexible | ||
with good out of the box experience | ||
- native nvim lsp support is slim and flexible with worse out of the box | ||
experience | ||
- with existing plugins and thanks to the community, benefiting from the | ||
advantages of native nvim lsp support is possible in a sane way |