2020-02-18
I have an old post describing how
to spin up an IDE-like Python development environment in Emacs with
Eglot and some .dir-locals.el
help. Now a year later, I've converged on what I think is a better
setup.
My main driver for installing different versions of Python and
spinning up virtual environments is pyenv. I use the automatic
installer on all machines where I install pyenv, and I manually
modify my shell's initialization such that I have to execute a
setupPyenv
function to enable its usage (I also give myself the
ability to activate an environment via a single argument):
function setupPyenv() {
VENV=$1
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
[ -n "$VENV" ] && pyenv activate $VENV
}
To activate various Python environments in Emacs I turn to
pyvenv. Since the pyenv
installer puts itself in the user's home
directory, we can configure pyvenv
to find virtual environments
in ~/.pyenv/versions
via the WORKON_ON
environment variable.
I lean on use-package
to initialize pyvenv
and set the
environment variable:
(use-package pyvenv
:ensure t
:init
(setenv "WORKON_HOME" "~/.pyenv/versions"))
By setting the WORKON_HOME
environment variable we can select
which pyenv
virtual environment we want to use by calling M-x pyvenv-workon
. One can also call M-x pyvenv-activate
to choose
an environment via manual filesystem navigation.
With a pyvenv
environment activated in Emacs, all we have to do
is call M-x lsp
(after setting it up of course); lsp-mode can be
configured in an init.el
file with something as simple as:
(use-package lsp-mode
:ensure t
:commands lsp)
See the GitHub project for more details. Completion (with
company-mode) and static checks (with Flymake, an Emacs builtin,
or Flycheck) are easy to setup with lsp-mode
.
The working virtual environment will have to have a language
server installed. The easiest and fastest way to get started (a
simple pip install
) is to use pyls. Alternatively, one can use
Microsoft's python-language-server with lsp-mode via
lsp-python-ms; upon first use a prompt will ask if the user would
like to download mspyls
. I've used both; while mspyls
has
better performance, pyls
support is built into lsp-mode
and
the server can be installed like any other Python package (the
Microsoft implementation is a C# program). In my opinion those
pros neutralize the performance con (which is not too bad).
Just about all of my Python development happens inside of a
projectile project. I have a simple interactive function that will
automatically activate the environment associated with a project
and spin up lsp-mode. I bind this helper function to C-c C-a
in
the python-mode-map
.
(defun dd/py-workon-project-venv ()
"Call pyenv-workon with the current projectile project name.
This will return the full path of the associated virtual
environment found in $WORKON_HOME, or nil if the environment does
not exist."
(let ((pname (projectile-project-name)))
(pyvenv-workon pname)
(if (file-directory-p pyvenv-virtual-env)
pyvenv-virtual-env
(pyvenv-deactivate))))
(defun dd/py-auto-lsp ()
"Turn on lsp mode in a Python project with some automated logic.
Try to automatically determine which pyenv virtual environment to
activate based on the project name, using
`dd/py-workon-project-venv'. If successful, call `lsp'. If we
cannot determine the virtualenv automatically, first call the
interactive `pyvenv-workon' function before `lsp'"
(interactive)
(let ((pvenv (dd/py-workon-project-venv)))
(if pvenv
(lsp)
(progn
(call-interactively #'pyvenv-workon)
(lsp)))))
(bind-key (kbd "C-c C-a") #'dd/py-auto-lsp python-mode-map)