Adding linting support to Satyrn

Satyrn is a modern Jupyter notebook client for macOS. It starts up faster than VS Code or JupyterLab, has a virtual environment management UI, LLM integrations for Anthropic and OpenAI (bring your own API key), and now builtin support for linting. You can download Satyrn and try it out. Activate linting from the Command Palette or Settings.

Linting in Satyrn

Adding linting to Satyrn was pretty tricky because of the architectural decisions I made when I started the project.

Satyrn is currently built on Electron (previously Swift), which means the state of the app is split into a backend “main process” and frontend “renderer” process. To keep things simple I initially put all the active notebook state into the renderer. This posed a challenge when adding linting support for Satyrn because I needed to synchronize the notebook state with the Ruff language server, and the renderer can’t communicate directly with any process except for the electron backend.

To make matters more difficult, Jupyter’s cell-level undo/redo mechanism (you can press z while in command mode to undo/redo adding/deleting cells) is quite simple to replicate if you don’t care about synchronizing your state with an external process. Adapting this Gist I was able to implement undo/redo by keeping copies of previous/future states. But this wouldn’t do for a remote server.

Given these challenges, and my goals to implement other cool features like Tabs, Jump-to-definition, and Copilot, I decided to shift state management to the backend and implement a more sophisticated undo/redo mechanism.

Before I started this upgrade, I did a lot of prototyping to figure out exactly what needed to be done to integrate linting well. I created an open source project to demonstrate how to integrate Ruff linting into a simple code editor. After that project I thought it would be easy sailing to get it working for a notebook, but it really was not. At one point I had to fork Ruff and add lots of my own debug statements to figure out where I was going wrong.

In the end I managed to get the Ruff integration working very nicely. Now Satyrn comes with Ruff bundled and it works out of the box. This is exciting because it opens the door for many other cool features based on the Language Server Protocol. I’m looking forward to adding more of them soon.