Python Programming in Emacs
Making a lightweight and fast Python IDE in Emacs.
Python is one of my programming languages I’m using for both work and home life – no doubt, I’d want to use Emacs as an IDE for Python programming. There is a certain set of features we’d expect to have in any IDE: 1) code completion, 2) code navigation, 3) error checking.
Thanks to the fact that both eglot
, the Emacs client for the
Language Server Protocol (LSP), and tree-sitter
, a powerful parsing
library, are now – since version 29 – the parts of Emacs, It should
not be too hard to construct a lightweight and fast Python IDE.
Choosing a Language Server
There are a number of the language servers for Python available these days – pylsp, pyls, pyright, jedi-language-server. After a series of tests, I’ve settled on pyright, an open syntax and type checker from Microsoft (sic!) used in Visual Studio Code, the fast and reliable one, as for me.
The Pyright documentation – a bit weird – claims that Pyright is just static type checker for Python. However, as we’ll see later, Pyright can do much more than just type checks.
A note on the Tree-Sitter package
The tree-sitter
package is optional – I’m using it for
pretty-looking source code fontification mainly. However, if you want
to use it you have to pay attention to the following quote from the
Mickey Petersen’s ‘Mastering Emacs’
book:
And in Emacs 29, support for tree-sitter is built in. Sort of. It’s an optional extra, so you must compile Emacs from source, or hope that someone else will do it for you.
Neither tree-sitter nor Emacs come installed with language grammars. Just like kids' toys and batteries, they’re sold separately. So you’re required to download and compile the sources for each language grammar you want to use.
– Mastering Emacs by Mickey Petersen.
If you are – like me – on Linux, the best way to setup language grammars is to compile them from the source code; you can find all grammars available on the tree-sitter repository. More details at How to Get Started with Tree-Sitter.
Python prerequisites
First of all, we need to install the following Python packages –
pyright
(the language server) and jsonrpc
(the transport layer for
the LSP).
[p3] $ pip install pyright
[p3] $ pip install jsonrpc
I’m running Python in the virtual environment, “p3” in my case.
Error checking with Pyright
Eglot automatically finds a suitable LSP – Pyright in our case – when you are opening a Python file. I’m using the default Pyright settings, no extra tuning required.
Pyright LSP with Flymake
Flymake is a minor Emacs mode performing on-the-fly syntax checks. I’m
using the flymake
built-in Emacs package as an Eglot front-end for
syntax checks.
(require 'flymake)
My custom my/flymake-toggle-diagnostics-buffer
function toggles the
Flymake diagnostics buffer window.
If there is the Flymake diagnostics buffer associated with a file in the current buffer, it shows the diagnostics buffer in the new window and switches to it. If the current buffer is the Flymake diagnostics buffer, it closes it. Otherwise it just shows the error message.
(defun my/flymake-toggle-diagnostics-buffer ()
(interactive)
;; Check if we are in the diagnostics buffer.
(if (string-search "*Flymake diagnostics" (buffer-name))
(delete-window)
(progn
;; Activate the Flymake diagnostics buffer.
;; and switch to it
(flymake-show-buffer-diagnostics)
(let ((name (flymake--diagnostics-buffer-name)))
(if (get-buffer name)
(switch-to-buffer-other-window name)
(error "No Flymake diagnostics buffer found")
)))))
I’ve bound this function to the F7
key globally since I’m using F7
in other programming modes as well.
(global-set-key [(f7)] #'my/flymake-toggle-diagnostics-buffer)
;; Additional bindings.
(global-set-key (kbd "C-c f b") #'flymake-show-buffer-diagnostics)
(global-set-key (kbd "C-c f p") #'flymake-show-project-diagnostics)
The screenshot below shows what happens if you press F7
while
editing a Python file managed by Eglot + Flymake.
As you can see, Pyright can do much more than just type checks.
Code completion
I’m using the company
package, a
modular in-buffer completion framework for Emacs, as an Eglot
front-end for code completion; it integrates with Eglot seamlessly.
;;;; `COMPANY'
(use-package company
:ensure t
:config
(setq company-idle-delay 0)
(setq company-minimum-prefix-length 2)
(setq company-show-numbers t)
;; To prevent default down-casing.
;; https://emacs.stackexchange.com/questions/10837/how-to-make-company-mode-be-case-sensitive-on-plain-text
(setq company-dabbrev-downcase nil)
;; 2023-01-13 From a Reddit post on mixed case issue.
(setq company-dabbrev-ignore-case nil)
(setq company-dabbrev-code-ignore-case nil))
;; Use `company' everywhere.
(add-hook 'after-init-hook 'global-company-mode)
Code navigation
I’m using the default key bindings for
xref-find-definitions
M-.
to find the definition of the identifier at point.xref-go-back
M-,
to return back to where you invoked thexref-find-definitions
command.xref-find-references
M-?
to find references to the identifier at point.
These functions are now powered by Eglot.
Final notes
I can recommend to force using Eglot for Python explicitly –
(require 'eglot)
(add-hook 'python-mode-hook 'eglot-ensure)
A note for Tree-Sitter users
If you are using Tree-Sitter for Python, it’s recommended to remap
the python-mode
to the Tree-Sitter specific python-ts-mode
.
(add-to-list 'major-mode-remap-alist '(python-mode . python-ts-mode))
If this is the case, do not forget to force using Eglot for this mode as well.
(add-hook 'python-ts-mode-hook 'eglot-ensure)
Happy emacsing!