fakedrake
fakedrake

Reputation: 6856

Setting a mode for a particular file using .dir-locals.el

I made a small minor mode for navigating GHC profile files and I would like it to automatically take effect in .prof files that are in haskell projects. I am happy to use .dir-locals.el and I remember finding a relevant feature a while back but I can't seem to find it now.

Upvotes: 4

Views: 1573

Answers (1)

Thomas
Thomas

Reputation: 17422

There's a number of different ways you can achieve this. Here are three:

  1. File local variables
  2. Directory local variables
  3. Standard mode handling

Every one of them has some pros and cons to them, so you can decide which one suits your needs the best.

File local variables

Emacs provides a mechanism that allows you to specify the value of certain variables directly in a file using a special syntax. When visiting such a file, Emacs will parse the first line and set the specified variables to the given values.

Now, a minor mode is not a variable, of course. But the file local mechanism comes with a way to execute arbitrary code by using eval as the name of a "variable". In practice, it looks like this. There are two ways to specify file local variables, i.e., configure the loading of your minor mode.

Special first line

You can include the following line as the very first line of your .prof file:

-*- eval: (my-prof-minor-mode) -*-

Here, you see the use of the reserved eval keyword that allows the execution of arbitrary code - in our code, that code is just an invocation of your minor mode (of course, the example uses a placeholder, you would have to put in the name of your actual minor mode function).

Local variable list

If you don't like to mess with the beginning of your file, you can also include the eval declaration at (or towards) the end of your file but the syntax is slightly different:

## Local Variables:
## eval: (my-prof-minor-mode)

You notice the use of ## here: this may differ from file to file, as it should simply be the standard comment syntax that is commonly used in files of the type. Unfortunately, I could not determine what comment syntax .prof files use, if any.

However, the main disadvantage of the file local variables method is this: users are actually discouraged to use it for minor modes. The rationale is that minor modes are typically an expression of a specific user's personal preference when editing. In a shared environment, these preferences might differ from user to user, so fixing them in the file itself would be problematic.

Also, messing with the file contents might be problematic for other reasons, e.g., if you cannot easily do that without messing with the expected syntax of the file. If, for instance, the Haskell profiler cannot read the .prof file properly any more after you added one of the two options above, the whole exercise is pointless.

For more information on file local variables, see here, here, and here.

Directory local variables

Instead of messing with the file in question directly, directory local variables give you the option to have certain variable values be set whenever you visit a file in a certain directory. And, just like before, this mechanism supports the use of a special eval "variable" to execute arbitrary code, including the invocation of a minor mode.

In order to employ this method, you would create a file by the name of .dir-locals.el in the same directory as you .prof file. The contents of that file are as follows:

(fundamental-mode . ((eval . (my-prof-minor-mode)))))

The entry starts with a major mode specifier: everything that follows is only applied to files whose buffer's major mode is "fundamental-mode". If you have configured your Emacs to apply a different major mode to .prof files, you'd have to change that specification accordingly.

A bigger problem, however, is the above specification would turn your minor mode on for all files in that directory that match the given major mode. However, it is not hard to fix the .dir-locals.el file to do the right thing, by adding a filename test:

((fundamental-mode . ((eval . (when (string-match "\\.prof$" (buffer-file-name))
                                (my-prof-minor-mode))))))

More information on directory local variables can be found here, here, and here.

Standard mode handling

Let's go through this step by step.

The standard way to turn on major modes for certain file types is via auto-mode-alist:

(setq auto-mode-alist (append '(("\\.prof$" . my-prof-major-mode))
                              auto-mode-alist))  

The standard way to turn on minor modes for certain major modes is to register it through the major mode hook:

(add-hook 'text-mode-hook 'my-prof-minor-mode)

Now the question is: do you edit .prof files that are not inside Haskell projects and for which you don't want your minor mode to be turned on? If not, than the above mechanism(s) might already be enough. Let's assume that this is the case for a moment.

Which major mode are you using when editing .prof files? If it is fundamental-mode, you may consider turning your minor mode into a major mode and using auto-mode-alist to turn it on for all .prof files as shown in the first code example above.

If it is some other mode, say text-mode (or anything else) then you could use the second code example to activate your minor mode. However, that is only really useful if that major mode is exclusively used for .prof files. For text-mode, that is certainly not the case. But following the above way to have the minor mode be activate via the major mode hook would turn it on for all text files, not just .prof file. We can fix that through a small helper function though:

(add-hook 'text-mode-hook (lambda ()
                            (when (string-match "\\.prof$" (or (buffer-file-name) (buffer-name)))
                              (my-prof-minor-mode))))

With this, your minor mode is turned on every time a buffer's major mode is set to text-mode and the associated file ends in .prof. It might look slightly familiar if you have read the section on directory local variables above. It is slightly more complicated to make sure it also works with buffers not associated with a file.

Now, let's look at the case where you have all sorts of .prof files lying around but you only want to turn your minor mode on for those that reside inside a Haskell project. Well, then let's go for another auxiliary function, this time one that tells us whether or not a file is inside a Haskell project:

(defun file-inside-haskell-project-p (filename)
  "This function returns t if the file with the given `filename`
is located inside a Haskell project, otherwise nil."
  (when filename
    (locate-dominating-file (file-name-directory (expand-file-name filename))
                            (lambda (dir) (directory-files dir nil "\\.cabal$")))))

(Sorry, I don't know anything about Haskell, so I don't really know how to check whether a file is inside a Haskell project. What this function does is the following: given a file, it checks whether there's a .cabal file in the same directory. If not, it checks the parent directory, and that the grand-parent directory, and so on, all the way up to the root of the file system. If such a file is found, the function returns the containing directory, otherwise it returns nil. You may have to replace this function with a better one.)

This test function can now be used inside the aforementioned lambda expression to get the desired result:

(add-hook 'text-mode-hook (lambda ()
                            (let ((buffer-file-name (buffer-file-name)))
                              (when (and (string-match "\\.prof$" (or buffer-file-name ""))
                                         (file-inside-haskell-project-p buffer-file-name))
                                (my-prof-minor-mode)))))

Note that unlike previously discussed methods, this one does not work with buffers that aren't associated with a file. The reason for that is clear, though: if we only want to apply the minor mode to files that reside inside a Haskell project directory, a buffer that is not associated with any file at all cannot qualify.

Upvotes: 3

Related Questions