Journal

2018-09-23 add-dir-local-variable and dotted pairs

Last time I wrote about directory variables. I mentioned that the eval keyword apparently didn’t work with them.

Well, I was very wrong, and my mistake was a popular one. Since it is easy to fall for it, I decided to warn here about the pitfall.

Assume that you want Emacs to say “Hello” every time you visit some file. You say M-x add-file-local-variable, type eval, then (message "Hello"), and get this in your file:

# Local Variables:
# eval: (message "Hello")
# End:

(Of course, the comment syntax may depend on the major mode.) All well.

Now assume that you want Emacs to say “Hello” every time you visit a file in some directory. You say M-x add-dir-local-variable, type the mode name (optionally), type eval and then (message "Hello"). Emacs created a .dir-locals.el file with these contents:

;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")

((some-mode
  (eval message "Hello")))

What I did is I changed the last two lines to

((some-mode
  (eval (message "Hello"))))

thinking… Well, I’m not sure what and whether I was thinking. ;-)

But what I should have done was either leave it alone, or change it into the equivalent

((some-mode
  (eval . (message "Hello"))))

i.e., the “dotted pair” syntax. Of course, the docs say this (even if implicitly), and even give examples, although you have to look into the Elisp manual to learn the whole truth.

By the way, the workaround I found by trial and error (the one with anonymous functions) worked because, as the docs kindly say, “Anonymous functions are valid wherever function names are.” You can check it by evalling e.g. this:

((lambda (x) (message "Hello %s!" x)) "world")

Normally you wouldn’t want to do this, of course, but it works.

Last but not least, the thread I mentioned last week contains an interesting discussion on making add-dir-local-variable output the dotted-pair syntax for alists, and the consensus was that it can’t be done (at least not easily). A bit disappointing – but only a bit, since this is not a big deal for me anyway.

Take care and beware of dotted pairs and alists!

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2018-09-17 Emacs directory variables

Many people know and use Emacs file variables. I think less people know about a similar concept of directory variables.

Basically, the idea is that some variable should be specific to every single file in a directory (and its subdirectories), like it were defined (as a file-local variable) in every of its files. Alternatively, a directory variable may be specific only to files which are opened in certain major mode. Directory-local variables are defined in a file called .dir-locals.el (or .dir-locals-2.el, which see).

A caveat which I was only able to find through experimentation is that apparently, the eval “keyword” (described in the docs for file variables), while claimed by the manual to work the same way in the case of directory variables, in fact does not. While in the context of file variables it accepts a Lisp expression, its directory version needs a function (or a lambda expression). I asked about this on the mailing list. (Edit: it seems I was wrong here, see the linked thread. I wrote a follow-up about this.)

One use-case for those (apart from the obvious case of specifying some kind of behavior to all files – or all files of some mode – even the newly created, empty ones) is when for some reason you don’t want to put file-local variables in some file. For instance, that file may be under version control, and other people on your team (or some kind of broken software) do not like the Emacsy stuff you put into it. One caveat is that directory variables cannot be specified for a single file. No wonder, since this is what file variables are for – but read on.

Interestingly, there is a way of specifying directory variables without putting the .dir-locals.el file in the directory in question (this is useful if e.g. you don’t have write permissions in that directory) – see the docs for the details – but it seems that you can’t do the same thing with file-local variables. This is not a serious limitation, though, because in such a case you may always write a function which examines the name of the visited file (to be found in the buffer-file-name variable) and sets the appropriate variables and put it into find-file-hook. Such a function will even be called after processing file-local variables, so you can override (or examine) them with it. (A quick experiment showed that file-local variables are processed after the directory-local ones, so functions in find-file-hook have access to all of them.)

Be sure to at least skim the documentation on file- and directory-local variables – the details of how to specify them are a bit complicated, although there do exist some helper commands which can create them for you.

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2018-09-10 Persisting Emacs variables

Some time ago I coded a certain Elisp tool (for internal use at Wiadomości Matematyczne) and I needed to preserve the value of some variable between Emacs sessions. I asked how to do this on the Emacs mailing list, and I received a whole lot of very interesting and worthwhile responses. I decided to stay simple, though, and used the snippet of code I mentioned there to just add some variable and its value to init.el. For your convenience, here is the code – although I strongly suggest reading the mentioned thread before using it. My use-case is quite specific, and for most cases the solution described there will be better. Use this at your own risk!

(defun make-variable-persistent (variable-name)
  "Save VARIABLE-NAME to `custom-file' or `init-file'."
  (when (y-or-n-p (format "I am going to save variable `%s' in %s.  Should I proceed? " variable-name user-init-file))
    (with-temp-file user-init-file
      (let ((sentinel-text (format "\n;; Persistent variable `%s'.  Do not edit manually!\n" variable-name)))
	(insert-file-contents user-init-file)
	(if (search-forward sentinel-text nil t)
	    (if (not (looking-at-p (format "^(setq %s" variable-name)))
		(error "Broken init file")
	      (kill-sexp)
	      (when (eq (char-after (point)) ?\n)
		(delete-char 1)))
	  (goto-char (point-max))
	  (insert sentinel-text)))
      (insert (format "(setq %s %S)\n" variable-name (symbol-value variable-name))))))

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

More...