Hacker News new | ask | show | jobs
by spenczar5 390 days ago
Something I have always wondered about is how Nix interacts with my editor and language servers.

I use emacs. The emacs LSP mode starts up a language server process for a language like Python or Go. If I use Nix to manage development dependencies like my compiler, linting tools, and even language-specific dependencies, then how do I get the LSP and emacs to use the correct Nix-y set of dependencies? To make things extra complex, note that I often work on multiple interdependent projects concurrently; I imagine that makes things even gnarlier.

Is there a sane way to manage this sort of thing? Do I end up managing a per-project installation of emacs??

6 comments

For each project, configure a nix devshell to include the appropriate linters etc for that project. Then configure direnv to activate the nix devshell when you cd into the project directory. Then launch your editor from that shell. Since the editor's parent process is the devshell, its PATH var points to that project's dependencies.

Or at least that's how I do it. That will make `ruff` or `pyright-langserver` point to whichever version of the tool is bound to that project. So when I run `nix flake update` it updates the tools for only that project and I don't get any crosstalk between projects.

As for whether a given linter or language server should be configured for that project, helix looks for .helix/languages.toml in the CWD to decide which ones to load, so that bit of config I handle independently from nix, I just add that file to the repo. Presumably emacs has something similar.

So to answer your question: you only get a project-specific version of emacs if you add emacs to the project's devshell. Otherwise whatever was already on your PATH will be used.

I have never tried Nix, but read about it a lot, and I agree that this part is most of the time completely glossed over in tutorials. I get that nix-shell is cool, and I can get the shell with everything configured to run, let's say, Python with specific deps, very easily - but... then what? Can I run, let's say, IDEA or vscode from there and get all the PATH stuff right? Or does it require extra magic? Not everyone lives and breathes in terminal (even though I mostly do).
> Can I run, let's say, IDEA or vscode from there and get all the PATH stuff right?

Yes.

But if you use an editor for many projects at once, it'll work better if different projects can have different PATHs set, in which case you can use direnv + an appropriate direnv plugin for your editor. That way you don't have to worry about launching your editor from inside nix-shell or whatever.

IIRC IDEA is still defective in this way, not allowing per-project environment variables, but Emacs, Vim/Neovim, and VSCode all handle this nicely.

Easiest thing is to use direnv to load your nix environment (which is just ‘use nix’ or ‘use flake’, support is builtin to direnv), so that all the nix stuff is first in PATH. There are packages to help with this for emacs, like envrc-mode and direnv-mode, that will load the envrc file whenever you’re visiting a file in the project. Then, whenever emacs runs some external command, it’ll pick up the right one.

There are similar plugins for vscode, vim, etc

In most cases is enough to have emacs installed as part of OS with all required general cli tools and then open project specific shell with all project dev dependencies like compilers and libs.

However is some cases when it could be little more tricky, for instance I'm using LSP for Scala (https://scalameta.org/metals/) which on start trying to automatically detect JDK version for project from environment. So if my project depends on JDK 11 it might required for me manually start LSP from nix-shell to force it use right JVM version.

With Rust is in someway similar, I need to bring some shell with 'rust up' setup so it could install everything for rust-analyzer then emacs can use it.

Regardless emacs itself is few options to manage emacs packages in nix way but for me it not really gives anything so I just use it as on any other OS.

The "correct way" is flakes, but I don't know if it would cover every edge case. I don't use emacs, but I'd assume you'd arrange it a bit like this:

- Install nixpkgs emacs or emacs distribution to your user profile so it links into every shell you open.

- Configure system-wide settings with the default emacs config, or with home-manager (https://mynixos.com/home-manager/options/programs.emacs)

- Each project (or group of projects) gets a Nix flake with a derivation specifying which dependencies or LSP packages are needed.

When a devshell is spun up, you should have the nixpkgs version of Emacs with your system-wide config and project-specific modifications, handled via version-control. It is a hassle, but in my experience the ROI is very worthwhile. YMMV.

I use the envrc package to integrate my Nix-powered development environments via direnv: https://github.com/purcell/envrc

My Emacs then sets up an LSP per project, and just picks up the correct LSP implementation by PATH.

No per-project Emacs madness. (Though I guess you could do that if you wanted by including copies of Emacs with all the appropriate packages in your development environments.)