15

Suppose I have a directory with these files.

/foo/bar/baz/.dir-locals.el
/foo/bar/.dir-locals.el
/foo/.dir-locals.el

When I go to create a file in /foo/bar/baz/, I'd like to daisy chain them together such that /foo/.dir-locals.el applies first, and then /foo/bar/.dir-locals.el, and then /foo/bar/baz/.dir-locals.el

  • Related thread: How can I have a second .dir-locals?. – Dan Dec 18 '14 at 13:13
  • There is no option that would do that (I looked at the code pretty closely), but it should be (almost certainly is) possible with some extra code. I have a use for it too, so I might look into this... – Constantine Dec 18 '14 at 15:31
  • With elisp, all things are possible. :) – Eric Johnson Dec 18 '14 at 15:36
7

Based on the answer here, we do this by advising hack-dir-local-variables to look one directory up and load check if that .dir-locals.el file is readable. It will keep going up until it finds a directory with no readable .dir-locals.el.

Depending on the value of walk-dir-locals-upward the files can be read from the current directory upward or from the last .dir-locals.el found downward. Downward is the default so that subdirectories can clobber the settings of their parents.

(defvar walk-dir-locals-upward nil
  "If non-nil, evaluate .dir-locals.el files starting in the
  current directory and going up. Otherwise they will be
  evaluated from the top down to the current directory.")

(defadvice hack-dir-local-variables (around walk-dir-locals-file activate)
  (let* ((dir-locals-list (list dir-locals-file))
         (walk-dir-locals-file (first dir-locals-list)))
    (while (file-readable-p (concat "../" walk-dir-locals-file))
      (progn
        (setq walk-dir-locals-file (concat "../" walk-dir-locals-file))
        (add-to-list 'dir-locals-list walk-dir-locals-file
                     walk-dir-locals-upward)
        ))
    (dolist (file dir-locals-list)
      (let ((dir-locals-file (expand-file-name file)))
        (message dir-locals-file)
        ad-do-it
        )))
  )
  • This seems to expect that every directory in the tree (up to some level up from the current path) has a .dir-locals.el. Will it work if I have a tree of directories a/b/c and there exist a/.dir-locals.el and a/b/c/.dir-locals.el, but no a/b/.dir-locals.el (assume that I'm visiting a/b/c/foo.el and I want settings from a/.dir-locals.el to be applied)? – Constantine Dec 18 '14 at 17:48
  • 1
    Yes, that's what I'm assuming. The missing dir-locals in a/b/ breaks the chain. It has to stop somewhere and if you want it to keep going you can add an empty dir-locals files. – erikstokes Dec 18 '14 at 17:58
  • 2
    BTW, I'd welcome a patch for Emacs to support chaining dir-locals out of the box. – Stefan Apr 10 '15 at 4:54
5

Here's a different way of doing this.

I define a function that produces the list of all the directories in the current directory hierarchy.

(defun file-name-directory-nesting-helper (name previous-name accumulator)
  (if (string= name previous-name)
      accumulator                       ; stop when names stop changing (at the top)
      (file-name-directory-nesting-helper
       (directory-file-name (file-name-directory name))
       name
       (cons name accumulator))))

(defun file-name-directory-nesting (name)
  (file-name-directory-nesting-helper (expand-file-name name) "" ()))

An example is in order:

(file-name-directory-nesting "/foo/bar/baz/quux/foo.el")
;; => ("/" "/foo" "/foo/bar" "/foo/bar/baz" "/foo/bar/baz/quux" "/foo/bar/baz/quux/foo.el")

Now I can add advice to hack-dir-local-variables to make it "pretend" that we're visiting a file at the very top of the tree, apply directory-local settings, then step one level down, apply settings again, and so on.

(defun hack-dir-local-variables-chained-advice (orig)
  "Apply dir-local settings from the whole directory hierarchy,
from the top down."
  (let ((original-buffer-file-name (buffer-file-name))
        (nesting (file-name-directory-nesting (or (buffer-file-name)
                                                  default-directory))))
    (unwind-protect
        (dolist (name nesting)
          ;; make it look like we're in a directory higher up in the
          ;; hierarchy; note that the file we're "visiting" does not
          ;; have to exist
          (setq buffer-file-name (expand-file-name "ignored" name))
          (funcall orig))
      ;; cleanup
      (setq buffer-file-name original-buffer-file-name))))

(advice-add 'hack-dir-local-variables :around
            #'hack-dir-local-variables-chained-advice)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.