Language Server Protocol Support for Emacs
Table of Contents
- Language Server Protocol Support for Emacs
Why?
- ❤️ Community Driven
- 💎 Fully featured - supports LSP core and Language Server non-standard extensions
- 🚀 Easy to configure - works out of the box and automatically upgrades if additional packages are present
- 🌟 Flexible - could be configured as full-blown IDE with flashy UI or minimal distraction free
Overview
Client for Language Server Protocol (v3.14). lsp-mode aims to provide IDE-like experience by providing optional integration with the most popular Emacs packages like company
, flycheck
and projectile
.
- Non-blocking asynchronous calls
- Real-time Diagnostics/linting (via builtin
flymake
when Emacs > 26 or flycheck/lsp-ui) - Code completion - using company-lsp or builtin
completion-at-point
- Hovers - using lsp-ui
- Code actions - using
lsp-execute-code-action
or lsp-ui - Code outline - using builtin imenu or
helm-imenu
- Code navigation - using builtin xref
- Code lens (references/implementations) - using builtin xref
- Highlights
- Formatting
- Debugger - dap-mode
- Helm integration - helm-lsp
- Treemacs integration - lsp-treemacs
Installation
Install via melpa
The recommended way to install lsp-mode
is via package.el
- the built-in package manager in Emacs. lsp-mode
is available on the two major package.el
community maintained repos - MELPA Stable and MELPA.
M-x package-install
[RET] lsp-mode
[RET]
Configuration
Install language server
Check the table bellow with the list of supported servers and the corresponding instructions on how to install the server.
Configure lsp-mode
Vanilla Emacs
You could go minimal and use lsp-mode
as it is without external packages with the built-in flymake
and completion-at-point
or you could install the following extensions for better experience:
- install lsp-ui for flycheck integration and higher level UI modules.
- install company-lsp if you want to use
company-mode
for completion. - install lsp-treemacs for project wide error overview.
- install helm-lsp provides on type completion for
xref-apropos
. - install dap-mode if your language is supported by the debugger.
(require 'lsp-mode)
(add-hook 'XXX-mode-hook #'lsp)
where XXX
could be major mode like python
, java
, c++
. Alternatively, if you want to minimize you configuration you may use prog-mode-hook
. In case you do that, lsp
will try to start for each programming mode and echo a message when there is no client registered for the current mode or if the corresponding server is not present. In addition, lsp-mode
will automatically detect and configure lsp-ui and company-lsp. To turn off that behavior you could set lsp-auto-configure
to nil
.
To defer LSP server startup (and DidOpen notifications) until the buffer is visible you can use lsp-deferred
instead of lsp
:
(add-hook 'XXX-mode-hook #'lsp-deferred)
Spacemacs
lsp-mode is included in spacemacs develop branch. Add lsp
to dotspacemacs-configuration-layers
and configure the language that you want to use to be backed by lsp
backend.
use-package
Replace (require 'lsp-mode)
with the following if you use use-package.
(use-package lsp-mode
:hook (XXX-mode . lsp)
:commands lsp)
;; optionally
(use-package lsp-ui :commands lsp-ui-mode)
(use-package company-lsp :commands company-lsp)
(use-package helm-lsp :commands helm-lsp-workspace-symbol)
(use-package lsp-treemacs :commands lsp-treemacs-errors-list)
;; optionally if you want to use debugger
(use-package dap-mode)
;; (use-package dap-LANGUAGE) to load the dap adapter for your language
To defer LSP server startup (and DidOpen notifications) until the buffer is visible you can use lsp-deferred
instead of lsp
:
(use-package lsp-mode
:hook (XXX-mode . lsp-deferred)
:commands (lsp lsp-deferred))
How does it work?
lsp-mode
has predefined list of server configurations (loaded in lsp-clients.el
) containing a mapping from major-mode
to the server configuration or by using activation function. In addition to the default server configuration located in lsp-clients.el
there are few languages servers which require separate package(check Supported languages). When you open a file from a particular project lsp-mode
and call lsp
command lsp-mode
will look for server registrations able to handle current file. If there is such client lsp-mode
will look for the project root. If you open a file from the project for the first time you will be prompted to define the current project root. Once the project root is selected it is saved in lsp-session
file and it will be loaded the next time you start Emacs so you no longer will be asked for a project root when you open a file from that project. Later if you want to change the project root you may use lsp-workspace-folder-remove
to remove the project and call lsp-workspace-folder-add
to add the root. If you want to force starting a particular language server in a file you may use C-u
M-x
lsp
which will prompt you to select language server to start.
Supported languages
Some of the servers are directly supported by lsp-mode
by requiring
lsp-clients.el
while others require installing additional packages which provide
server specific functionality.
Language | Language Server | Built-in | Installation command | Debugger |
---|---|---|---|---|
Angular | vscode-ng-language-service | Yes | Installation instructions | Not relevant |
Bash | bash-language-server | Yes | npm i -g bash-language-server | |
C++ | ccls | emacs-ccls | ccls | Yes (gdb or lldb) |
C++ | clangd | Yes | clangd | Yes (gdb or lldb) |
C++ | cquery | emacs-cquery | cquery | Yes (gdb or lldb) |
Clojure | clojure-lsp | Yes | clojure-lisp | |
CSS/LessCSS/SASS/SCSS | css | Yes | npm install -g vscode-css-languageserver-bin | |
Dart | dart_analysis_server | Yes | built into dart-sdk | |
Dockerfile | dockerfile-language-server-nodejs | Yes | npm install -g dockerfile-language-server-nodejs | |
Dart | dart_language_server | Yes | pub global activate dart_language_server | |
Elixir | elixir-ls | Yes | elixir-ls | Yes |
Elm | elmLS | Yes | npm i -g @elm-tooling/elm-language-server, or clone the repository and follow installation instructions | No |
Erlang | erlang_ls | Yes | erlang_ls | |
F# | fsautocomplete | Yes | Automatic by lsp-fsharp | No |
Fortran | fortran-language-server | Yes | pip install fortran-language-server | Yes |
Go | gopls | Yes | gopls go get golang.org/x/tools/gopls@latest | Yes |
Go | bingo | Yes | bingo | Yes |
Groovy | groovy-language-server | Yes | groovy-language-server | |
Hack | hhvm | Yes | hhvm | |
HTML | html | Yes | npm install -g vscode-html-languageserver-bin | |
Haskell | IDE engine | lsp-haskell | IDE engine | |
Java | Eclipse JDT LS | lsp-java | Automatic by lsp-java | Yes |
JavaScript/TypeScript | typescript-language-server (recommended) | Yes | npm i -g typescript-language-server; npm i -g typescript | Yes (Firefox/Chrome) |
JavaScript/TypeScript | javascript-typescript-stdio | Yes | npm i -g javascript-typescript-langserver | Yes (Firefox/Chrome) |
JavaScript Flow | flow (add-on if working on a Flow file) | Yes | flow | Yes (Firefox/Chrome) |
Julia | lsp-julia | lsp-julia | LanguageServer.jl | |
Kotlin | kotlin-language-server | Yes | kotlin-language-server | |
Ocaml | ocaml-language-server | Yes | ocaml-language-server | |
PHP(recommended) | intelephense | Yes | npm i intelephense -g | Yes |
PHP | php-language-server | Yes | php-language-server | Yes |
Powershell | PowerShellEditorServices | lsp-powershell | Automatic by lsp-powershell | |
Python | pyls | Yes | pip install ‘python-language-server[all]’ | Yes |
Python(Microsoft) | Microsoft Python Language Server | lsp-python-ms | lsp-python-ms | Yes |
Ruby | solargraph | Yes | gem install solargraph | Yes |
Rust | rls | Yes | rls | Yes |
Scala | Metals | Yes | Metals | |
Swift | sourcekit-LSP | lsp-sourcekit | sourcekit-LSP | Yes (via llvm debug adapter) |
Vue | vue-language-server | Yes | npm install -g vue-language-server | Yes (Firefox/Chrome) |
XML | lsp4xml | Yes | Download from lsp4xml releases |
Commands
lsp-describe-session
- Display session folders and running servers.lsp-describe-thing-at-point
- Display help for the thing at point.lsp-execute-code-action
- Execute code actionlsp-format-buffer
- Format current bufferlsp-organize-imports
- Organize library importslsp-goto-implementation
- Go to implementationlsp-goto-type-definition
- Go to type definitionlsp-rename
- Rename symbol at pointlsp-restart-workspace
- Restart projectlsp-symbol-highlight
- Highlight all relevant references to the symbol under point.lsp-workspace-folders-add
- Add workspace folderlsp-workspace-folders-remove
- Remove workspace folderlsp-workspace-folders-switch
- Switch workspace folderimenu
orhelm-imenu
- display document structure.completion-at-point
- display completion using built-in emacscompletion-at-point
framework.lsp-find-definition
- to find the definition for the symbol under point.lsp-find-references
- Find references for the symbol under point.lsp-disconnect
- Disconnect the buffer from the language server.lsp-lens-show
- Show lenses in the current filelsp-lens-hide
- Hide lenses in the current filelsp-lens-mode
(experimental) - Turn on/off lenses in the current file.
Settings
lsp-log-io
- If non-nil, print all messages to and from the language server to*lsp-log*
.lsp-print-performance
- If non-nil, print performance info. to*lsp-log*
.lsp-inhibit-message
- If non-nil, inhibit the message echo viainhibit-message
.lsp-report-if-no-buffer
- If non nil the errors will be reported even when the file is not open.lsp-keep-workspace-alive
- If non nil keep workspace alive when the last workspace buffer is closed.lsp-enable-snippet
- Enable/disable snippet completion support.lsp-auto-guess-root
- Automatically guess the project root using projectile/project.lsp-restart
- Defines how server exited event must be handled.lsp-session-file
- File where session information is stored.lsp-auto-configure
- Auto configurelsp-mode
. When set to tlsp-mode
will auto-configurelsp-ui
andcompany-lsp
.lsp-document-sync-method
- How to sync the document with the language server.lsp-auto-execute-action
- Auto-execute single action.lsp-eldoc-render-all
- Display all of the info returned bydocument/onHover
. If this is nil,eldoc
will show only the symbol information.lsp-signature-render-all
- Display all of the info returned bytextDocument/signatureHelp
. If this is nil,eldoc
will show only the active signature.lsp-enable-completion-at-point
- Enablecompletion-at-point
integration.lsp-enable-xref
- Enable xref integration.lsp-prefer-flymake
- If you prefer flycheck andlsp-ui-flycheck
is available,(setq lsp-prefer-flymake nil)
. If set to:none
neither of two will be enabled.lsp-enable-indentation
- Indent regions using the file formatting functionality provided by the language server.lsp-enable-on-type-formatting
- EnabletextDocument/onTypeFormatting
integration.lsp-before-save-edits
- If non-nil,lsp-mode
will apply edits suggested by the language server before saving a document.lsp-imenu-show-container-name
- Display the symbol’s container name in an imenu entry.lsp-imenu-container-name-separator
- Separator string to use to separate the container name from the symbol while displaying imenu entries.lsp-imenu-sort-methods
- How to sort the imenu items. The value is a list ofkind
,name
orposition
. Priorities are determined by the index of the element.lsp-response-timeout
- Number of seconds to wait for a response from the language server before timing out.lsp-enable-file-watchers
- If non-nil lsp-mode will watch the files in the workspace if the server has requested that.
Screenshots
- RUST Completion with company-lsp
- Typescript references using lsp-ui
- Debugging Python using dap-mode
- Call hierarchy via ccls
- Metals Doctor
Extensions
TRAMP
LSP mode has support for tramp buffers with the following requirements:
- The language server has to be present on the remote server.
- Having multi folder language server (like Eclipse JDT LS) cannot have local and remote workspace folders.
How does it work?
lsp-mode
detects whether a particular file is located on remote machine and looks for a client which matches current file and it is marked as :remote?
t. Then lsp-mode
starts the client through tramp.
Sample configuration
Here it is example how you can configure python language server to work when using TRAMP
. Note that if you are trying to convert existing language server configuration you should copy all of it’s properties(e. g. :request-handlers
, activation-fn
, etc).
(lsp-register-client
(make-lsp-client :new-connection (lsp-tramp-connection "binary-or-full-path")
:major-modes '(python-mode)
:remote? t
:server-id 'pyls-remote))
Dealing with stderr
With TRAMP, Emacs does not have an easy way to distinguish stdout and stderr, so when the underlying LSP process writes to stderr, it breaks the lsp-mode
parser. As a workaround, lsp-mode
is redirecting stderr to /tmp/<process-name>-<id>~stderr
.
Limitations
File watches
When some of the workspaces that are active in the current project requests file notifications via workspace/didChangeWatchedFiles
lsp-mode
will start monitoring each of the folders in the workspace for changes. In case your project contains a lot of files you might want to disable file monitoring via lsp-enable-file-watchers
(you may use dir-locals).
Contributions
Contributions are very much welcome.
Here is a throughput graph of the repository for the last few weeks:
Troubleshooting
- set
lsp-log-io
tot
to inspect communication between client and the server. lsp-describe-session
will show the current projects roots + the started severs and allows inspecting the server capabilities.
Adding support for languages
Registering server
Here it is the minimal configuration that is needed for new language server registration. Refer to the documentation of lsp-client.el
for the additional settings supported on registration time. lsp-language-id-configuration
must be updated to contain the corresponding mode -> language id - in this case (python-mode . "python")
(defvar lsp-language-id-configuration
'(...
(python-mode . "python")
...))
;; if you are adding the support for your language server in separate repo use
;; (add-to-list 'lsp-language-id-configuration '(python-mode . "python"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection "pyls")
:major-modes '(python-mode)
:server-id 'pyls))
Sections
lsp-mode
provides tools to bridge emacs defcustom
as a language configuration sections properties(see specification workspace/configuration). In addition you may use lsp-generate-settings
from Generate Settings script to generate defcustom
from package.json
VScode plugin manifest. Example:
(defcustom lsp-foo-language-server-property "bar"
"Demo property."
:group 'foo-ls
:risky t)
(lsp-register-custom-settings '(("foo.section.property" lsp-foo-language-server-property)))
(lsp-configuration-section "foo")
;; => (("foo" ("settings" ("property" . "bar"))))
FAQ
- How to configure a server with local variables?
- Add
lsp
server call tohack-local-variables-hook
which runs right after the local variables are loaded.(add-hook 'hack-local-variables-hook (lambda () (when (derived-mode-p 'XXX-mode) (lsp))))
- Add
- I have multiple language servers registered for language FOO. Which one will be used when opening a project?
- The one with highest priority wins.
lsp-clients.el
predefined servers have priority -1, lower than external packages (priority 0 if unspecified). If a server is registered with:add-on?
flag set tot
it will be started in parallel to the other servers that are registered for the current mode.
- The one with highest priority wins.
- I have multiple language servers for language
FOO
and I want to select the server per project, what can I do?- You may create
dir-local
for each of the projects and specify list oflsp-enabled-clients
. This will narrow the list of the clients that are going to be tested for the project.
- You may create
- The completion does not work fine and inserts arguments and placeholders, what I am doing wrong?
- Snippet support works only with
company-lsp
so if you are usingcompletion-at-point
the snippets won’t be expanded and you should either disable them by settinglsp-enable-snippet
tonil
or you should switch tocompany-lsp
. Note also thatcompany-tng
frontend does not support snippet expansion(see company-mode#891)
- Snippet support works only with
- How to automatically follow
lsp-ui-log
?- Go into the log buffer and execute the following snippet(source: Emacs auto scrolling log buffer)
(set (make-local-variable 'window-point-insertion-type) t)
See also
- company-box -
company
frontend with icons. - dap-mode - Debugger integration for
lsp-mode
. - eglot - An alternative minimal LSP implementation.