What is LSP
General overview
Language Server Protocol, or LSP for short, is a standardization between “development tools” (emacs, vim, vscode…) and “language servers” (clangd, typescript-language-server…)
The best source for explanation is to read the Overview page on the official protocol website. Keep in mind that:
- the “user” is you
- the “development tool” is emacs
The very short, and worse-than-official-doc version is :
- emacs is a “client” and needs to translate user-intent and emacs-lisp package calls from/into JSON-RPC payloads
- external language servers are installed on the machine and indexes the project’s code to send relevant JSON-RPC payloads
If the worse-than-official version was too short, please check the overview page, there are a lot more people that spent a lot of time to explain it.
Emacs role as a client (Emacs LSP stack)
As of 2020, Emacs is at least 35 years old; it had coding features way before LSP was a thing, through elisp packages. Keeping only currently popular implementations:
- completion is brought by
company
- syntax/error checking is brought by
flycheck
orflymake
- jumping to definitions is brought by
xref
- displaying symbol documentation on point (under the cursor) is brought by
eldoc
…
All those packages use data sources that are defined on a case by case basis; for example, elpy-company-backend
is a source that uses Jedi to bring python completions to company
As emacs is a “development tool”, theoretically emacs’ developers are responsible to handle the client part of the protocol. This means integrating LSP in all the existing packages so that they ask a language server over JSON-RPC for the relevant information as a data source.
Instead of implementing a company-lsp
source, and a flycheck-lsp
source, and a xref-lsp
source etc. that could be out of sync and create a lot of compatibilty issues, complete “LSP client” packages exist to act as an abstraction layer between the LSProtocol and the inner emacs packages that need a source.
There are 2 coexisting LSP clients in emacs, eglot
and lsp-mode
that provide exactly this :
- convert user intent (“I want completions” or “What is this symbol ?”) from
company
,xref
… requests into JSON-RPC messages - send and receive LSProtocol messages**
- dispatch the received messages to the relevant “source” so that other packages seamlessly use language servers as a source
You should not have both lsp-mode and eglot active at the same time.
Doom-emacs provides a few opinionated tweaks and configuration to how all those packages are plugged together (“LSP client” and all of company
eldoc
flycheck
etc.). All those different components playing together means that there are a few important places which when affected, can make the whole integration feel “broken”.
All of this is tentatively summarized in this graphic
This is serialization/deserialization, and the reason emacs 27 with jansson support is much more responsive when using LSP.
How to activate LSP in Doom Emacs
Doom supports LSP, but it is not enabled by default. To enable it, you must:
-
Uncomment
:tools lsp
in your$DOOMDIR/init.el
file. [1]If you want
eglot
instead, change that line to:tools (lsp +eglot)
instead. -
In the same file, add the
+lsp
flag to each language module you want to use LSP with::lang - go + (go +lsp) - python + (python +lsp)
-
Run
doom sync
on the command line, -
Restart Emacs.
-
Install any needed LSP servers (through your OS package manager or directly from the source).
Once done, Doom will automatically start the LSP server and connect to it when you open an eligible buffer.
Language modules without +lsp
support
Some of Doom’s language modules may lack LSP support, even though lsp-mode
(or another lsp Emacs plugin) already provides support for that language. This is usually because I’m not aware of it yet – let me know!).
To enable LSP for these languages, you only need to add the following line to $DOOMDIR/config.el
:
(add-hook 'MAJOR-MODE-local-vars-hook #'lsp!)
;; Where =MAJOR-MODE= is the major mode you're targeting. e.g.
;; lisp-mode-local-vars-hook
Installing a server
-
If you use
lsp-mode
, then many servers can be automatically installed withM-x lsp-install-server
. If it isn’t, or you’d prefer to install servers manually, lsp-mode’s documentation contains links to specific clients with their own documentation. -
If you use
eglot
(orlsp-mode
lacks an installer for the server you want), you must install the server yourself through your system package manager or directly from the source (and the server must be in your PATH).
A “server” is an external program installed on your machine.
Troubleshooting
How to know which component is affected ?
The connected server does not support…
Those messages tend to look like (error "The connected server(s) does not support method textDocument/codeLens To find out what capabilities support your server use ‘M-x lsp-describe-session’ and expand the capabilities section.")
and usually this is a problem with the language server you installed. This can not be fixed on Emacs side.
… has exited
When the server abruptly quits it usually has 2 possible sources:
- If it quits on start, try to start the server in a terminal in the same project folder and try to see if it works:
- If it works, it’s either a Doom or a LSP-mode/eglot issue.
- If it doesn’t work, your language server and your project don’t get along and need some counseling (you are the only counselor available).
- if it quits after a while, then everything is possible. One likely culprit is the language server trying to hog too much RAM and being killed by Emacs to prevent common memory issues, but at this point those are just wild guesses about hard bugs to reason about. Limiting resources usage might help.
If you cannot access the logs from Emacs, try starting the server in a terminal opened in your project folder to see what happens.
The wrong server starts for my language
You need to set the priority between available servers to tell it which one to choose.
Eglot just takes the first server that matches the correct major mode for the buffer. That means the last call to (add-to-list 'eglot-server-programs '(cc-mode . ("clangd"))
or the last call to (set-eglot-client! 'cc-mode '("clangd"))
will “win”
LSP mode has its own priority system, for any major mode the server with the highest priority is chosen (default priority is 0, builtin priority is -1). Therefore you can use (set-lsp-priority! 'clangd 10)
to use clangd when available.
How to use a custom server?
Server communication is the prerogative of the LSP client package, so the method depends on using eglot or lsp mode.
(set-eglot-client! 'cc-mode '("clangd" "--clang-tidy"))
You need to pass the symbol of the mode and the command line to start the server. For more complex cases, it is best to look at eglot documentation
Registering servers for lsp-mode
is a tad more complex:
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection '("lua-langserver" "--lang=EN"))
:major-modes '(lua-mode)
:priority -1
:server-id 'lua-langserver))
Again, for more complex cases, it is best to go look at the documentation
Checking lsp-mode//eglot issue tracker/PRs for your language
When you have an issue with LSP, it’s safe to assume you are not the only one. Trying to search the issue trackers of LSP mode or eglot might give pointers to what’s failing and how to fix it.
If the fix includes setting a lisp value, wrap it in (after! lsp-mode ...)
or (after! eglot ...)
and add it in your config.el
Why are there extra packages like ccls
and rustic
sometimes ?
LSP is a protocol that allows extensions, and for some languages and language server, there are additional Emacs packages that enable those extensions :
-
ccls
allows to query and see all the inheritance tree and overloads for functions/classes -
rustic
used to use theinline hints
feature supported byrust-analyzer
(the feature has been upstreamed tolsp-mode
since then)
So the most likely reason for an additional “LSP” package to be added for a language in Doom is to access LSP extensions.
How to limit cpu / ram usage ?
Sadly, this is a per-language server setting, and is heavily dependent on what you are using. Checking the readme of the Doom module for the language you are using, or the documentation on the language server you are using is your best bet. LSP-mode documentation might help as well.
If you find a setting to limit it that is not supported and have the time to contribute back, please make a PR to expose it as a setting for other users to tweak it more easily
Doom-specific settings
The simplest way to know more about LSP customization options for your workflow is to check :tools lsp's documentation and the sections regarding the programming languages that you use.
For example, you can search the “Learn > Configuration” category for posts tagged with LSP or Python (and even subscribe to those tags)
The crux of it is that you can check for the relevant variables in the documentation of eglot
or lsp-mode
, and apply those settings using (after! eglot (setq ...))
or (after! lsp-mode (setq ...))
-
By doing this you are enabling Doom modules. More information about Doom modules can be found in the documentation. ↩︎