wbolster / emacs-direnv

direnv integration for emacs
BSD 3-Clause "New" or "Revised" License
340 stars 38 forks source link

Feature: hook for when environment changes #28

Open shajra opened 6 years ago

shajra commented 6 years ago

I think it would be kind of nice to have a hook that is called only when a direnv environment changes. Sometimes services need to be updated/restarted for a buffer when the context shifts. The two services that I've run into are the anaconda-mode server for Python development, and the dante-mode server for Haskell development.

If we get this hook, it would be nice if it passed along enough information to know exactly which environment variables changed. One way of doing it would be to pass in both the old and new environment map.

I just thought of this idea, so I don't have code for a pull request yet. But I also thought it might be good to talk through too before writing code.

wbolster commented 6 years ago

what exactly is your use case? i think you want to detect "i changed between projects" events? anything on top of that? because maybe there's a simpler way that does not involve diffing old and new environments and so on?

shajra commented 6 years ago

Hi @wbolster, just getting to this. Thanks for the patience.

I completely get the idea of wanting to keep the design simple on your end. I still think my request has merit, but let me explain my thoughts with a concrete example:

Right now, for Haskell, I'm using Dante, and for Python I'm using anaconda-mode. These tools both run background services based on command-line tools assumed to be on the PATH. For me, Dante runs a Cabal REPL instance, based on tools defined in Nix. And anaconda-mode runs some Python server based on the Python interpreter the project is built against, and I use Nix to manage this too.

Getting all the dependencies and versions right for these servers per-project is exactly what Direnv is great for, so in that regard, direnv-mode works out really nicely. But these servers are kind of like live caches that can get invalidated. What if I change my Nix expression? Or my dependency list? Then I have to restart these servers. But before that, I need to do a direnv-update-environment call. Furthermore, I'm missing out on automated notification of when my environment has changed.

So right now, I'm making proxy commands for restarting services that make sure call direnv-update-environment, but also these restarts have to be called manually when I notice things are out of sync.

If I had a hook that would trigger when the environment changed in a buffer, I wouldn't have to call my service restarts manually, and I would also not need to call direnv-update-environment, because my hook would be automatically called after the environment update.

It just makes my life so much cleaner as a consumer. What do you think?

wbolster commented 5 years ago

what information would be needed in such a hook? both the complete old and new environments?

smaximov commented 5 years ago

Hello! I have a similar use-case.

I use Nix (and nix-shell) to provide development environments for my Elixir projects. I also use alchemist. To be able to jump to code in Erlang/Elixir sources (you can jump to definitions in your project's code and its dependencies by default), you need to tell alchemist where the Erlang/Elixir sources are located.

In shell.nix I expose the source directories of Erlang and Elixir as environment variables to the Nix shell and to Emacs (via direnv):

{ pkgs ? import ./pkgs.nix # pinned nixpkgs
}:
let
  inherit (pkgs) beam;

  erlang = pkgs.erlangR21.override {
    enableHipe = false;
    wxSupport = true;
  };

  elixir = (beam.packagesWith erlang).elixir_1_8;
in pkgs.mkShell {
  buildInputs = with pkgs; [
    elixir
    erlang
    pgcli
  ];

  ERL_AFLAGS = "-kernel shell_history enabled";

  ERLANG_SRC = "${erlang.src}";
  ELIXIR_SRC = "${elixir.src}";
}

These values are not static (i.e., they are not available system-wide) and may differ from project to project.

I tried to set alchemist-goto-{erlang|elixir}-source-dir inside an elixir-mode hook but that didn't work because the hook runs before direnv-update-directory-environment, so I came up with this workaround:

  (defun set-erlang/elixir-source (&rest args)
    (when (eq major-mode 'elixir-mode)
        (when-let* ((erlang-src (getenv "ERLANG_SRC")))
          (setq-local alchemist-goto-erlang-source-dir erlang-src))
        (when-let* ((elixir-src (getenv "ELIXIR_SRC")))
          (setq-local alchemist-goto-elixir-source-dir elixir-src))))

  (advice-add 'direnv-update-directory-environment :after #'set-erlang/elixir-source)

It feels kinda hackish to me, so I thought that having a hook that runs after direnv-update-... would be nice, then I stumbled upon this request.