E(pi)glot-tal start
· 5 min read · October 29, 2025 · #tech #linux #emacs #nixosFollowing on from an earlier post about Tree-sitter, Eglot is the Emacs Language Server Protocol (LSP) client. Being also now included in Emacs from v29, it works well with Tree-sitter, integrating servers that can provide rich semantic analysis and tooling for a wide range of languages.
Basic Eglot configuration
I split my Emacs configuration for Eglot across some default configuration plus language specific modes, as far as possible. Default configuration first:
This sets up Eglot to run for all programming modes (prog-mode) except emacs-lisp-mode because there is no LSP for that, it being rather integral to Emacs itself! I will also configure it for a few non-programming modes in other relevant use-package stanzas. Finally, I configure calling Eglot’s (language specific) format function before saving, and a few useful key-bindings. See below for workspace configuration.
I also add eglot-booster as that enables Eglot to use the emacs-lsp-booster wrapper which should speed up LSP server interactions:
Configuring for specific languages
Simple: TOML and Bash
Configuring for a particular language is then relatively straightforward for some. For example, for TOML I use Tombi in the tree-sitter TOML mode, toml-ts-mode:
This replaces the default toml-mode with the tree-sitter toml-ts-mode, and ensures the Tombi TOML language server is running when in that mode.
Bash is similarly straightforward using the bash-language-server:
This stops use-package complaining that there is no bash-ts-mode package, sets tabs to be just two spaces (YMMV), and remaps the default bash-/sh- modes to use this bash-ts-mode. I don’t need to set eglot-server-programs in this case because the pre-configured default is set to use bash-language-server appropriately.
Slightly more complex: Nix
Nix is a little more complex as I would like to use the (non-preconfigured default) nixd LSP server and configure it to use nixfmt for formatting. In this case I do so by passing in :initializationOptions:
As with bash-ts-mode, this remaps the default nix-mode to nix-ts-mode, but then goes on to specify the language server command line to run, and an initialisation option to use nixfmt for formatting.
More complex: Python, LaTeX, Typst
Python is more complex as I use ruff for formatting but it doesn’t support much beyond that at present so I use basedpyright for type hinting and the rest. After installing the NixOS basedpyright, ruff, and python313Packages.python-lsp-server packages, this results in:
This essentially does three things: defines two reformatter functions using ruff, one to reformat the code and the other to sort imports; and sets basedpyright as the language server to use, by shadowing the default entries for Python in eglot-server-programs.
I also turn on uv-mode as I use uv for all Python package management:
Finally, for the purposes of this blog post, for document processing I use LaTeX historically and am now trying to use typst. LaTeX first using the eglot-ltex-plus server:
See my init.el for latex package configuration details.
Finally finally, typst:
Both of the above have to set various other options as well as configuring Eglot.
But wait! There’s more! Specifically, for Bash, LaTeX, and Python, I need to provide a default workspace configuration to set some sensible options.
Workspace configuration
This took me a little while to figure out. LSP servers assume a per-project “workspace”, apparently normally set from the project’s configuration file (either editor-independent but language-specific, or the Emacs specific but language-independent .dir-locals.el). As a now only occasional coder I prefer to set a global default and mostly rely on that.
However, the extra little fillip here is that you can’t set it locally in a buffer – and that includes via a major-mode hook (discussed here). I investigated using the :initializationOptions argument when setting eglot-server-programs so that I could keep all language-specific configuration together, but that appears to be supported by only some language servers :( So, for example, the nixd example uses it to configure use of nixfmt for formatting and it seems to work fine, but the equivalent for both Bash and Python did not.
So, I ended up just setting it in the Eglot configuration stanza using setq-default:
The final little nugget that I had to figure out to make the above work was what is the magic string that identifies the server so that it will process the correct configuration blob when it’s sent. Turns out this really is just a magic string and I needed to peruse docs and ultimately source to find it. So for example, for bash-language-server it turns out to be bashIde.
Possibly good general places to start looking are in the *EGLOT* logs from the server, your *MESSAGES* buffer, the server test for processing onDidChangeConfigration or (if one exists, and it probably does given the prevalence of LSPs in VSCode-land) in the vscode-client packaging, or some suitable declaration – in the case of bash-language-server, this turns out to be setting the variable CONFIGURATION_SECTION.
Anyway. That mostly concludes my Emacs polishing. For now. Probably.