Skip to content

RaminHAL9001/dir-local-env

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 

Repository files navigation

dir-local-env.el

(Warning: beta-quality software. Feature complete, but tested only by the developer. Feedback is welcome, encouraged.)

This package allows you to use the Emacs project.el system by assigning properties to project directories which can be inherited as buffer-local variables by all files within the project directory. It is similar to the .dir-locals.el mechanism already built-in to Emacs, but with some important differences, which make it more similar to the direnv CLI utility than .dir-locals.el.

The key differences are:

  1. Any directory registered with dir-local-env will automatically be detected as a project directory by the project.el system (though you can register directories opting-out of project.el).
  2. There is no need to create a .dir-locals.el file in every single project directory.
  3. Unlike the dir-locals-class-alist variable built-in to Emacs, the dir-local-env.el mechanism can *assign a unique set of properties directly to each project,* rather than assigning properties on a per-class or per-major-mode basis.
  4. Updates to the dir-local-env for a directory are applied immediately (with some caveats), so no need to first update the class variables alist and then re-assign the class to a directory, every time a property is modified.

The dir-locals-class-alist, which can be set by the dir-locals-set-class-variables function, is the mechanism built-in to Emacs for assigning directory-local variables to directories that cannot store a .dir-locals.el file. This class-based mechanism can set variables for a directory inherited by all files within that directory, and can set additional variables depending on the major-mode of each file loaded from within that directory. You may even assign multiple classes to each directory.

However this class-based mechanism still requires the creation of a class, and it requires the additional step of assigning the class to a directory. So the directory-local variables mechanism built-in to Emacs is not suitable for use cases where:

  • you have many project directories, and you need each directory to have it’s own unique set of directory-local variables, since you would need to create one unique class for each project directory, populate each class with the correct properties, then assign each class to it’s respective directory.
  • you need to make changes to the directory-local properties of a project often. This would require creating an additional class for the oft-changed properties, assigning that class to a directory, calling the hack-dir-local-variables function, and then calling the hack-local-variables function on each already-open buffer to inherit the updates.

With dir-local-env.el, there are no classes of variables and no filters based on major mode, you simply assign variables directly to a directory, and all files within that directory inherit those variables as buffer-local variables. This prevents the need for hacky solutions like generating a unique class name from a directory path.

The dir-local-env mechanism complements (not replaces) the dir-local-env.el, so directory-local variables may be set by both or either. So it is still possible to use the dir-locals-class-alist and dir-locals-set-class-variables function while using dir-local-env.el.

Features

  • Provides a global minor mode dir-local-env-global-minor-mode
  • Compatible with Emacs’s project.el. When a directory local environment is registered for a directory, the project.el commands automatically see this directory as a project directory (disable by setting project-dir-disabled to non-~nil~).
  • sets “advice” on Emacs built-in process execution functions like make-process so as to alter the process-environment and exec-path variables on a per-project basis, especially useful for setting M-x compile executable on a per-project basis.
  • Can be used to complement the .dir-locals.el system, rather than replace it.
  • Configuration syntax similar to the Emacs Lisp (let* ...) macro, for example:
    (defdir-local-env
    
      "/home/abcdef/projects/sort-of-cool-raytracer"
      ((exec-path '("/usr/local/optimized-gpu-thing/compiler", "/usr/bin")))
    
      "/home/abcdef/projects/really-cool-compiler"
      ((project-dir-disabled t)
       ;; ^ this directory is thus ignored by `project.el' commands
       (favorite-color "red")
       (lucky-number 10001)))
        
  • Easy to temporarily disable by setting the local disable-dir-local-env variable to non-~nil~.
  • “Idempotent” declaration semantics, meaning if you evaluate the (defdir-local-env ...) macro twice by accident, only the first invocation has any effect. To update the configuration, unregister the directory local environment and then re-evaluate the modified (defdir-local-env ...) macro for that directory.
  • Provides Emacs commands to register directory-local environments, and set variables, without using the declarative defdir-local-env macro:
    • dir-local-env-register registers a directory to have it’s own directory-local environment.
    • dir-local-env-unregister deletes a directory-local environment.
    • dir-local-env-set sets an environment variable in a registered directory-local environment.
    • dir-local-env-show-all shows a directory’s registered local environment and all variables set for that environment.

An Emacs Lisp implementation of direnv

Features for extracting environment variables from a shell process is still experimental, and not at all easy to do (yet). But the dlenv--split-null-delimited-string function is provided so that you might parse the output of the printenv -0; shell command and produce an environment data structure suitable for use with Emacs’s process-environment variable. Setting this variable in a dir-local environment is similar to using direnv in the command line.

This also applies to projects computed by a functional package manager such as:

  • Nix nix-shell, and
  • Guix guix-shell programs

This is intended to be helpful when using the Emacs built-in M-x compile command, when one would like to execute a compiler via project-specific directory PATH environment variable defined by directory-local environment variable mechanisms such as, direnv. You can cache the environment provided by direnv into Emacs’s process-environment variable just for a particular project directory, so that M-x compile always executes a compiler in the PATH provided by direnv.

This can theoretically also be helpful if you choose to install a Language Server Protocol (LSP) server using the Nix or Guix package managers, and would like to direct the eglot or lsp-mode systems to use a LSP server specific to a particular project directory.

Once the shell environment has been computed and you have a shell, you may extract the environment using a command like printenv -0; and cache the result in a directory local process-environment variable. From that point on, any time the M-x compile command is called on a file within that directory, the compiler and environment variables defined by the Nix/Guix shell environment will be called.

Again, this is not exactly easy to do at this time, but it is hoped that soon functionality to automate the process of extracting computed process environments from functional package managers like Nix or Guix, or from direnv, will be implemented.

Caveats

The defdir-local-env macro and other commands like dir-local-env-set can apply changes to the directory local variables immediately. Changes to process-environment and exec-path will be seen immediately since dir-local-env-global-minor-mode checks these variables on each invocation of make-process.

However for all other directory local variables it is still necessary to call hack-dir-local-variables on each buffer affected by changes to the directory local environment after making updates to other variables. This process might be automated in a later version by adding advice functions to switch-to-buffer that automatically call hack-dir-local-variables whenever user focus switches to a directory. The architecture for how updates to the dir-local variables might be applied has not been fully investigated yet.

Prior art

There are, of course, other Emacs Lisp systems which allow you to assign properties to project directories in the manner of direnv.

  • Sidecar Locals allows you to declare properties for a directory in a file at the same level as the directory itself, rather than as a file within the directory. You specify a list of paths in the sidecar-local-paths-allow to files that can be trusted to assign directory-local properties.
  • buffer-env is essentially an Emacs Lisp implementation of the direnv tool, which loads project properties from a .envrc file, rather than from a .dir-locals.el file, and can be used to set properties such as process-environment and exec-path, which is very useful for changing the compiler tool chain you are using for a particular project.

About

Emacs system to configure directory local variables without .dir-local.el files

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published