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.)
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
python-language-server into the virtual environment, or user environment, that you’re planning to use.
These days, I tend to use
cd my_project pipenv install python-language-server[all]
[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
lsp-mode– the main language server protocol package
lsp-ui– UI-related LSP extras, such as the sideline info, docs, flycheck, etc.
company-lsp– company-backend for LSP-based code completion.
find-file-in-project– we use a single function from here to determine the root directory of a project.
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
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.