My take on language server protocol

It is a long time since I used anything more complicated than compiler, ctags and vim for my programming, but this week I decided to give language server protocol a shot.

The idea is quite sound and appealing -- instead of having each text editor implementing support for every language in existence, create a protocol that allows editor, which works as a client, to defer all real work -- parsing, compiling, linting -- to an external process. As the official site puts it, replace O(n * m) problem with O(n + m). This is really in the spirit of Unix, so I spent around 8 hours configuring it. It kinda works, somewhat.

Note that my experience is based on using tools with Nix. Maybe things work differently with traditional package managers.

I used an addon called LanguageServer. Among all options, its configuration was the simplest: just mapping from file mode into command used to start language server. So I added the following lines into my ~/.vimrc, as suggested by documentation.

nmap <silent> K  <Plug>(lcn-hover)
nmap <silent> gd <Plug>(lcn-definition)
nmap <silent> gr <Plug>(lcn-rename)
nmap <silent> g? <Plug>(lcn-references)
nmap <silent> gl <Plug>(lcn-symbols)
nmap <silent> g= <Plug>(lcn-format)
nmap <silent> g. <Plug>(lcn-explain-error)
nmap <silent> g' <Plug>(lcn-code-action)
nmap <silent> g; <Plug>(lcn-code-lens-action)
let g:LanguageClient_serverCommands = {
  \ 'c': ['ccls'],
  \ 'python': ['pyls'],
  \ 'dhall': ['dhall-lsp-server'],
  \ 'tex': ['texlab'],
  \ 'go': ['gopls'],
  \ 'nix': ['rnix-lsp'],
  \ 'haskell': ['haskell-language-server-wrapper', '--lsp']
  \ }

C language

Autocompletion works half of the time -- it auto-completed "printf" from <stdio.h>, but not "malloc" from <stdlib.h>. For anything more sophisticated, like an Autotools-based project, it can't find headers that are present in the project subdirectory. In theory, it is possible to configure it, but I failed.

On other hand, even without headers, it can highlight syntax errors, like a missing semicolon or variable name typo in almost real-time, which is quite nice. Function renaming works perfectly in the scope of a single file, across the the project -- not so fine, but I suspect that it is the fault of Vim, not of the language server.


Python language server is written in python, so it is expectedly slow. Another problem is that it parses, not executes modules imported, so anything generated dynamically will not be found (and will even trigger warning). For example:

class Foo:
    def do_with(self, name, **kwargs):

for name in ["alice", "bob", "charlie"]:
    setattr(Foo, name, partial(Foo.do_with, name=name))

obj = Foo()

None of names would be auto-completed and use of any of them triggers warning. Obliviously, actually running code opens another, much bigger can of worms, but
this is code I have to work on full time, I wanted miracle.

And third problem is PYTHONPATH. Python language server needs this variable to find modules, but this variable affects its own behaviour. It means that when PYTHONPATH is set by nix-shell for python2.7 project, "pyls" that uses python3
interpreter crashes with obscure import error. Can be worked-around by adding python-language-server into "shell.nix".

As with C, real-time lint warnings are nice, but signal/noice ratio is much lower.


The only thing that worked for me is type-checking. Slow type-checking.


Quite impressive. Macro auto-completion, macro renaming, package options, jump to definition. Unfortunately, some of these features do not work with plain TeX (e.g \def instead of \newcommand).

Go language

I didn't do much testing, since I barely know this language, but everything I tried worked like a charm.


Jump to definition, renaming and autocompletion only work in scope of single file, so they are not really better than basic tools of Vim -- regex substitution, regex search (wonderful "*" key) and word completion ("C-n"). Also it does not complete Nix builtin functions, so I would say that Nix language server is useless.


For some reason it hanged on one of my computers, while same binary (downloaded from worked fine on two other computers. When it works, it works fine -- type checking, completion, jump to definition (only within current project, though). Probably I would like to get types of sub-expressions,
but I am asking for much.

It is not perfect, but improvement over just vim and "cabal repl".

Last modified: 2021-03-13