The language server protocol was proposed by Microsoft as a way for different editors and development environments to share language analysis backends
This post describes how to configure Emacs, lsp-mode and the palantir python-language-server for improved code intelligence when working on Python projects. (I’m planning a companion post for Emacs, C++ and the cquery language server.)
Goal
Before starting, it is motivating to see what we are working towards.
With a correctly configured setup, Emacs will sport, amongst others, improved completion with interactive documentation, imenu navigation, documentation on hover, and really snazzy find definitions (M-.
) and find references.
See the following screenshots for some examples:
Pre-requisites on the Python side
Install the python-language-server
into the virtual environment, or user environment, that you’re planning to use.
These days, I tend to use pipenv
:
cd my_project pipenv install python-language-server[all]
The [all]
means that it installs all optional providers, e.g. yapf formatting.
Pre-requisites on the Emacs side
In Emacs, install the required and some optional packages using for example M-x package-install
:
lsp-mode
– the main language server protocol packagelsp-ui
– UI-related LSP extras, such as the sideline info, docs, flycheck, etc.company-lsp
– company-backend for LSP-based code completion.projectile
orfind-file-in-project
– we use a single function from here to determine the root directory of a project.
Emacs configuration
Add the following to your Emacs init.el
, and don’t forget to read the comments.
If you’re not yet using use-package
now would be a good time to upgrade.
(use-package lsp-mode :ensure t :config ;; make sure we have lsp-imenu everywhere we have LSP (require 'lsp-imenu) (add-hook 'lsp-after-open-hook 'lsp-enable-imenu) ;; get lsp-python-enable defined ;; NB: use either projectile-project-root or ffip-get-project-root-directory ;; or any other function that can be used to find the root directory of a project (lsp-define-stdio-client lsp-python "python" #'projectile-project-root '("pyls")) ;; make sure this is activated when python-mode is activated ;; lsp-python-enable is created by macro above (add-hook 'python-mode-hook (lambda () (lsp-python-enable))) ;; lsp extras (use-package lsp-ui :ensure t :config (setq lsp-ui-sideline-ignore-duplicate t) (add-hook 'lsp-mode-hook 'lsp-ui-mode)) (use-package company-lsp :config (push 'company-lsp company-backends)) ;; NB: only required if you prefer flake8 instead of the default ;; send pyls config via lsp-after-initialize-hook -- harmless for ;; other servers due to pyls key, but would prefer only sending this ;; when pyls gets initialised (:initialize function in ;; lsp-define-stdio-client is invoked too early (before server ;; start)) -- cpbotha (defun lsp-set-cfg () (let ((lsp-cfg `(:pyls (:configurationSources ("flake8"))))) ;; TODO: check lsp--cur-workspace here to decide per server / project (lsp--set-configuration lsp-cfg))) (add-hook 'lsp-after-initialize-hook 'lsp-set-cfg))
Putting it all together
Importantly, use pyvenv
or something similar to switch to the relevant virtualenv before opening the first Python file.
When you open the file, the pyls
should be automatically started up, and you can edit away with LSP-powered code intelligence.
This often gives better and more detailed results than elpy, probably because pyls uses a mix of static and dynamic (introspection-based) analysis.
Furthermore, the handling of LSP servers in Emacs can be unified, giving the same consistent level of support across a whole range of programming languages.