Emacs-direnv: Help needed to make it non-blocking

I use direnv in emacs to load my nix-shell environment. The mode
I am using for this is emacs-direnv.
Emacs-direnv however blocks emacs, which can be quite long if nix-shell needs to
download stuff. The author has no intention of fixing this. I started hacking on this in my own fork.
The code sort of works now to load the environment correctly. There are two bugs remaining however:

  • direnv several times in the background in parallel instead once
  • the summary text that is shown when direnv is loaded, no longer popup

My emacs-fu is quite weak. If somebody has more elisp experience, I would be grateful about help.


You might want to try GitHub - purcell/envrc: Emacs support for direnv which operates buffer-locally.

1 Like

This is not really a solution to your original problem, but you could use either lorri [1] or flakes [2] to cache your shell evaluations, so instead of having to instantiate a huge number of derivations each time, it will just give you the last build.

[1] GitHub - target/lorri: Your project's nix-env
[2] Nix Flakes edition | $ zimbatm

Lorri is not usable to me because of GitHub - nix-community/nix-direnv: A fast, persistent use_nix/use_flake implementation for direnv [maintainer=@Mic92]

nix develop does neither caches derivation nor it caches the flake sources it caches, this is an urban myth - I added gcroots in nix-direnv for this reason.

1 Like

envrc.el also blocks here: envrc/envrc.el at 1dc5aad14d2c27211c7c288d2d9dffeb2e27cb2d · purcell/envrc · GitHub
The code looks every similar to emacs-direnv.

Wow, nix-direnv is actually awesome! Thanks for showing this to me.

Friendship with lorri ended, nix-direnv is my new best friend now.


One small note first. You have extracted direnv--process-json which is probably a good thing. But I don’t understand why it is now taking a callback argument. We usually pass a callback when the function is async but this one isn’t. I think it would be simpler to call the callback from direnv--export directly instead of passing it as argument to direnv--process-json.

To avoid direnv to be called several times with the same directory, you could use a hash table mapping directory names (the ones containing the .envrc) to their direnv process. Each time you want to start a new process, you make sure that the hash table doesn’t already contain the directory. When a direnv process finishes, you should remove its directory from the hash table.

That being said and after reading the code of direnv, I’m not sure it makes sense to make emacs-direnv async. The fundamental problem you will face IMO is that emacs-direnv acts globally: when direnv finishes loading, Emacs whole environment must be changed. This is problematic because the user might have switched to a different project while direnv was building the derivation. I think a better target for making Emacs + direnv integration async is to improve envrc whose scope is the buffer.

I pass the callback to set-process-sentinel, which makes it non-blocking. That the same needs to be done for envrc.el. Having one instance per buffer make sense. I still have a hard time understanding all the elisp function.

The author of envrc.el is willing to support this feature: Asynchronous direnv calls · Issue #6 · purcell/envrc · GitHub so it would make sense to just switch to this.