Beta: Emacs Multi-LSP support for Python and Typescript frameworks
Contents
- Introduction
- Why rass?
- Python: ty + Ruff in One Buffer
- TypeScript: Multi-Framework LSP Support
- Per-Project Configuration
- Installation
- Feedback Welcome
- Links
Introduction
Back in January I announced eglot-python-preset, an Emacs package that simplifies Python LSP configuration with Eglot. At the time, I mentioned rass support as a future direction. That future has arrived, and it brought a companion package along with it.
Both eglot-python-preset↗ and the new eglot-typescript-preset↗ now support rassumfrassum↗ (rass), an LSP multiplexer written by Eglot’s author. This means you can run multiple language servers simultaneously through a single Eglot connection, such as a type checker alongside a linter or a language server alongside a formatter, with minimal configuration.
The eglot-typescript-preset package covers Astro, Vue, Svelte, Deno, and CSS/Tailwind out of the box. On the linting side, it integrates with ESLint, oxlint, Biome, and oxfmt through rass, so you can combine a type checker with one or more linters in the same Eglot session.
This is a beta release. The rass backend works well in my day-to-day use,
but the MELPA packages don’t support rass yet. I have pending PRs with MELPA for
both packages, and eglot-typescript-preset isn’t on MELPA at all yet. For now,
manual installation (cloning the repos) is the way to go if you want to try rass
support.
Why rass?
Eglot is designed around one language server per major mode. If you want type checking from ty and linting from Ruff in the same Python buffer, or TypeScript diagnostics alongside ESLint, you’d normally have to choose one or run a separate tool outside the LSP workflow.
rass solves this by acting as a stdio multiplexer: it presents itself as a single LSP server to Eglot while dispatching requests to multiple backend tools. From Eglot’s perspective, nothing changes. From your perspective, you get diagnostics, completions, and code actions from all your tools in one place.
The preset packages handle the rass configuration for you. You declare which
tools you want, and the package generates the appropriate rass preset file
automatically. When your project has tools installed locally (in .venv or
node_modules), those executables are preferred over global installations.
Python: ty + Ruff in One Buffer
To use rass with eglot-python-preset, set the LSP server to rass and list
your tools:
(use-package eglot-python-preset
:ensure t
:after eglot
:custom
(eglot-python-preset-lsp-server 'rass)
(eglot-python-preset-rass-tools '(ty ruff))
:config
(eglot-python-preset-setup))
This gives you ty’s type checking and Ruff’s linting diagnostics in every Python buffer, with zero per-project setup for standard projects. PEP-723 scripts work too: the generated rass preset includes the script’s cached uv environment so both tools resolve imports correctly.
You can also use basedpyright instead of ty, or pass literal command vectors for custom tools:
(setopt eglot-python-preset-rass-tools
'(basedpyright
ruff
["custom-lsp" "--stdio"]))
TypeScript: Multi-Framework LSP Support
eglot-typescript-preset is brand new. It configures Eglot for TypeScript,
JavaScript, CSS, and several frontend frameworks, with rass support for
combining tools like ESLint, Biome, oxlint, and oxfmt alongside the TypeScript
language server.
The default setup requires no rass and works out of the box:
(use-package eglot-typescript-preset
:ensure t
:after eglot
:config
(eglot-typescript-preset-setup))
This configures typescript-language-server for TS/JS files, astro-ls for
Astro files, and vscode-css-language-server with tailwindcss-language-server
for CSS, all automatically.
Astro + ESLint + oxlint
One setup I’m particularly happy with is a fully-working Astro project with both ESLint and oxlint. The idea is that oxlint handles the fast, low-level lint rules while ESLint covers the Astro-specific and framework-aware rules. To avoid duplicate diagnostics, the eslint-plugin-oxlint↗ package disables ESLint rules that oxlint already covers.
On the Emacs side, the configuration is straightforward:
(setopt eglot-typescript-preset-lsp-server 'rass)
(setopt eglot-typescript-preset-rass-tools
'(typescript-language-server eslint oxlint))
(setopt eglot-typescript-preset-astro-lsp-server 'rass)
(setopt eglot-typescript-preset-astro-rass-tools
'(astro-ls eslint oxlint))
All three tools contribute diagnostics to the same Eglot session, and the preset
handles executable resolution from your project’s node_modules automatically.
Supported Frameworks
Beyond plain TypeScript and JavaScript, eglot-typescript-preset supports:
- Astro via
astro-ls, with automatic TypeScript SDK detection - CSS support via
vscode-css-language-server, optionally combined withtailwindcss-language-server - Deno as an alternative backend for Deno-based projects
- Svelte via
svelte-language-server, with TypeScript SDK path forwarding - Vue via
vue-language-serverin hybrid mode, where Vue handles template features andtypescript-language-serverwith@vue/typescript-pluginprovides type checking
Per-Project Configuration
Both packages support per-project overrides, so different projects can use different tools. There are two approaches.
In-repo: .dir-locals.el
Drop a .dir-locals.el in the project root. The preset packages declare their
variables as safe with appropriate values, so Emacs applies them without
prompting:
;;; .dir-locals.el for a Python project using basedpyright + ruff
((python-ts-mode
. ((eglot-python-preset-lsp-server . rass)
(eglot-python-preset-rass-tools . (basedpyright ruff)))))
;;; .dir-locals.el for a TypeScript project using biome instead of eslint
((typescript-ts-mode
. ((eglot-typescript-preset-lsp-server . rass)
(eglot-typescript-preset-rass-tools
. (typescript-language-server biome)))))
Out-of-repo: dir-locals-set-directory-class
If you’d rather not add Emacs-specific files to a project, configure it from your init file:
(dir-locals-set-class-variables
'my-python-project
'((python-ts-mode
. ((eglot-python-preset-lsp-server . rass)
(eglot-python-preset-rass-tools . (ty ruff))))))
(dir-locals-set-directory-class
(expand-file-name "~/devel/my-python-project") 'my-python-project)
(dir-locals-set-class-variables
'my-astro-project
'((typescript-ts-mode
. ((eglot-typescript-preset-lsp-server . rass)
(eglot-typescript-preset-rass-tools
. (typescript-language-server eslint oxlint))))
(astro-ts-mode
. ((eglot-typescript-preset-astro-lsp-server . rass)
(eglot-typescript-preset-astro-rass-tools
. (astro-ls eslint oxlint))))))
(dir-locals-set-directory-class
(expand-file-name "~/devel/my-astro-project") 'my-astro-project)
Installation
Of note:
- The rass backend works and has fairly extensive tests.
- MELPA packages don’t support rass yet. I have pending PRs with MELPA for both packages. Until those land, you need to install from source.
- The
eglot-typescript-presetpackage isn’t on MELPA at all yet. A submission is pending.
To install manually:
mkdir -p ~/devel
git clone https://github.com/mwolson/eglot-python-preset ~/devel/eglot-python-preset
git clone https://github.com/mwolson/eglot-typescript-preset ~/devel/eglot-typescript-preset
(add-to-list 'load-path (expand-file-name "~/devel/eglot-python-preset"))
(add-to-list 'load-path (expand-file-name "~/devel/eglot-typescript-preset"))
(require 'eglot-python-preset)
(setopt eglot-python-preset-lsp-server 'rass)
(setopt eglot-python-preset-rass-tools '(ty ruff))
(eglot-python-preset-setup)
(require 'eglot-typescript-preset)
(setopt eglot-typescript-preset-lsp-server 'rass)
(setopt eglot-typescript-preset-rass-tools '(typescript-language-server eslint))
(eglot-typescript-preset-setup)
Both packages require Emacs 30.2 or later and rass↗ v0.3.3 or later.
Feedback Welcome
If you try either package, I’d appreciate hearing about your experience. The best way to reach me is through GitHub Discussions on the respective repos:
In particular, I’d love to hear from people using the JS/TS frameworks we support (Astro, Vue, Svelte, and Deno) as well as anyone working with CSS and Tailwind. If there’s a commonly-used framework or tool you’d like to see supported, please open a discussion. The more real-world usage these packages get, the more confident I’ll be promoting them from beta.
Links
- eglot-python-preset↗ on GitHub
- eglot-typescript-preset↗ on GitHub
- rassumfrassum↗, the LSP multiplexer that makes multi-server setups work
- Announcing eglot-python-preset, the earlier post covering the initial release