AutoModeAlist

The auto-mode-alist variable is an AssociationList that associates MajorModes with a pattern to match a buffer filename when it is first opened. See SetAutoMode.

Something like this:

   (add-to-list 'auto-mode-alist '("\\.py\\'" . python-mode))

Where ‘python-mode’ is a function that is either AutoLoaded or already defined.

Note that \' matches the end of a string, whereas $ matches the empty string before a newline. Thus, $ may lead to unexpected behavior when dealing with filenames containing newlines. (Should be pretty rare… ;))

The \. matches ‘.’ (a period); ‘.’ must be escaped by a backslash because the period is a special character in Regular Expressions.

If you write your own major mode that associate with a file extension, here is what you do:

  1. Put some documentation in the commentary section.
  2. As part of the documentation, your users should:
    1. add the file to a directory in their LoadPath
    2. add the following lines to their InitFile:
    (autoload 'foo-mode "foo" "Some documentation." t)
    (add-to-list 'auto-mode-alist '("\\.foo\\'" . foo-mode))

The autoload expression makes sure that the library “foo” is loaded when the function ‘foo-mode’ is called. Use ‘M-x locate-library RET foo RET’ to figure out what file this will be. The last parameter says that foo-mode is interactive. Most major modes are.

The add-to-list expression associates the .foo extension with foo-mode. As mentioned above, the \\' matches the end of the filename; a $ would match newlines within filenames (true, this should be rare… ;)).

Specifying your own rules: basic considerations

Paying attention to precedence

The order in which the cons cells appear matters, as the first matching one “wins” (and the rest of the list is simply ignored). The normal case will be that you want your rule to have the higher precedence than what is already in auto-mode-alist, so you will put it at the beginning of the list:

    ;; add just one element if it isn't already in the list:
    (add-to-list 'auto-mode-alist 
                 '("\\.foo\\'" . foo-mode))
    ;; add several elements at once (may create double entries,
    ;; but that would not hurt)
    (setq auto-mode-alist
          (append '(("\\.foo\\'" . foo-mode)  ; note these are encapsulated in a '() list
                    ("\\.bar\\'" . bar-mode))
                   auto-mode-alist))

In some cases, however, it may be preferrable to let take precedence what may already be predefined in auto-mode-alist, so you will put your rule at the end of the list:

    ;; add just one element (at the end) if it isn't already in the list:
    (add-to-list 'auto-mode-alist 
                 '("\\.foo\\'" . my-quick-and-dirty-foo-mode) t) ; t for APPEND (at the end)
    ;; add several elements at the end (may create double entries,
    ;; but that would not hurt)
    (setq auto-mode-alist
          (append auto-mode-alist
                  '(("\\.foo\\'" . my-quick-and-dirty-foo-mode)
                    ("\\.bar\\'" . my-fallback-bar-mode))))

Building the regexp you actually mean

When building REGEXPs for auto-mode-alist, keep in mind that the string matched is the full pathname. In the examples below, the assignment to the list, as detailed above, has been left out so we can concentrate on the regexps.

Basically, when matching file names with a regexp, it is two characters and two anchors that are especially important, and if your regexp is constructed with special attention to these four, the rest is usually relatively easy-peasy:

Meaning Should be used to …Comment
/the slash characterenclose directory names, mark beginning of a base name
\\. or [.]the dot character precede any file name extension not . !
^beginning of stringstart any absolute filename
\\'end of string end any extension or complete base name

Note that ., the unescaped dot, is a metacharacter in regexps, and means “match any character”. Treacherously, using it to mean a literal dot (which happens all too easily) will even work (i.e. will match the dot), but can, at least in theory, cause subtle bugs, as demonstrated in the following example, where .txt\\' is erroneously used to match the file extension .txt:

Expression will match:Comment
.txt\\'file.txt , gettxt, atxt, -txt, etc.also matches the dot
\\.txt\\' or [.]txt\\'file.txt, a.txt, b.txt, etc.only matches the dot

Avoiding any confusion between regexps and shell file patterns

Because we are matching file names, one might be tempted to confuse the regexps used here with shell file patterns in yet other ways than using an unescaped dot to mean a dot: one might try to use ~ to specify the home directory, or * to match any file, for example, both of which will not work as intended. Also, regexps do not treat the literal . at the beginning of hidden file names specially, the way shell file patterns do.

If you feel you do need a close regexp equivalent of a shell file pattern, the following should help:

Shell file pattern Meaning Closest regexp equivalent
*all visible files in a directory[^./][^/]+\\'
/ as first character the root directory ^/
/*/all visible directories somewhere in a path name/[^./][^/]+/
?any character in a file name .
[...]any of the characters between the braces [...]
~the current user’s home directory none in regexp; (getenv "HOME") in Elisp

However, it is interesting to note that it is often not necessary to substitute shell patterns like this. This is because as long as a regexp does not both begin with a ^ and end with a \\', it need only match a part of the filename:

Regexp will match:
"^/usr/share/man/"any file name that begins with /usr/share/man/
"^/usr/share/man/.*"exactly the same files
"^/usr/share/man/.*\\'"still exactly the same files
"^/usr/share/man/[^./][^/]+\\'"the same files as shell pattern /usr/share/man/*

The first three are exactly equivalent; of note, they match not only any file directly in /usr/share/man, but also any file in any of its subdirectories (because the regexp does not end with \\', the file names it matches can continue in any way they damn well please after the match). The fourth example excludes files in subdirectories and is the only one that can be expressed in a shell file pattern. The first three are far more powerful simply because unlike shell patterns, regexps do not treat the / specially, so any number of them (= any level of subdirectories) can follow.

Similar examples could be constructed for the beginning of a path: If the regexp does not begin with ^, it is not necessary to start it with .* or the like. Or for the middle of a path, when the regexp starts neither with ^ nor ends with \\'.

The bottom line is that it is probably best to try to forget how shell patterns work when crafting a regular expression to match file names, not only because their special characters mostly mean different things (partly because the former were invented for file names only, the latter for any kind of string), but also because their use is actually different: Shell patterns are expanded, possibly (if they contain a special character) to a list of several file names (try, e.g., echo /usr/share/man/*), while a regexp is matched against one given file name. These are quite different things, if you look at it.

Recipes

In order to be gentle to the readers’ eyes, the examples here always (with a rare exception) omit the code needed to prepend or append it to the already existing auto-mode-alist. (See above for that.) Mostly, successive lines are meant as independent from one another (i.e. just a number of examples), but when indicated, their order matters and is supposed to mean that lines that come first also come first in auto-mode-alist.

Examining only the file name itself (the basename)

The most usual case: Match only the file name extension (enclosed by \\. or [.] and \\'):

    '("\\.sh\\'"               . shell-script-mode) ; match ".sh" extension, and only that
    '("\\.jpe?g\\'"            . image-mode)        ; match both ".jpg" and ".jpeg" extensions
    '("\\.bat\\'"              . bat-mode)          ; MS Windows/DOS batch files
    '("\\.[Bb][Aa][Tt]\\'"     . bat-mode)          ; match also the upper-case spelling
    '("\\.\\(BAT\\||bat\\)\\'" . bat-mode)          ; more precise: do not match .Bat, .bAt, BaT, and the like

Match a base name that ends or begins as specified but is not an extension:

    '("_EDITMSG\\'"         . log-entry-mode) ; git commit message filename
    '("/cvs[[:alnum:]]*\\'" . log-entry-mode) ; CVS commit message filename

Match the complete basename (enclosed by / and \\'):

    '("/mimetypes.list\\'" . conf-desktop-mode)

Examining also the directory part of the filename

Match one and only one specific file (enclosed by ^/ and \\'):

    '("^/etc/alias\\'"                                  . shell-script-mode)
    ("^~/.alias\\'"                                     . shell-script-mode) ; No! ~  and . are used incorrectly
    (cons (concat "^" (getenv "HOME") "/\\.alias\\'")    'shell-script-mode) ; but this will work
    (cons (format "^%s/[.]alias\\'" (getenv "HOME"))     'shell-script-mode) ; same thing

Match any file in or somewhere below one certain directory (enclosed by ^/ and /):

    '("^/etc/X11/*" . conf-mode) ; No! "/*" means: "zero or more slashes", so e.g. "/etc/X11R6" matches.
    '("^/etc/X11"   . conf-mode) ; Also bad, matches the same files as the first example.
    '("^/etc/X11/"  . conf-mode) ; this is perfect

Match any file in or somewhere below any directory called foo (enclosed by / and /):

    '("/foo/" . foo-mode) ; matches as soon as "/foo/" is somewhere in the path name

Match any file directly in a certain directory:

    '("^/etc/X11/[^/]+\\'" . conf-mode) ; with absolute path
    '("/foo/[^/]+\\'"      . foo-mode)  ; not absolute, directory can be anywhere in the tree

Both: specifying aspects of path and basename

Both approaches can, of course, be combined. Care must be taken not to miss or duplicate any slashes:

    '("^/tmp/cvs[[:alnum:]]*\\'"                                  . log-entry-mode)  ; CVS commit message
    (cons (concat temporary-file-directory "/cvs[[:alnum:]]*\\'")  'log-entry-mode)) ; using a variable
    '("/\\.config/conky/.*[.]conf\\'"                             . lua-mode)        ; Conky uses Lua

Considering both precedence and the file name

Whenever there is more than one rule that may match the same file name, the order in which they appear in auto-mode-alist obviously matters: Let’s say we use OrgMode and keep all our org files in the directory ~/organization. We give them the extension .txt because we essentially regard them as text files. We craft a regexp that matches .txt files in that directory and add the rule to the end of auto-mode-alist (see above):

    '("\\.te?xt\\'"                                                   . text-mode) ; this was already in `auto-mode-alist'
    (cons (concat "^" (getenv "HOME") "/organization/[^/]+\\.txt\\'")  'org-mode)  ; why doesn't this apply???

Now we open a new file called ~/organization/timetable.txt and … the file opens in text-mode. But it is not as if our complicated regexp didn’t match the file name (it flawlessly does). It is simply that the first rule already matches ~/organization/timetable.txt, and then Emacs simply stops looking. Thus, in this case, we have to make sure that our more specific rule comes first, and put it at the beginning of auto-mode-alist (see above on how to do that):

    (cons (concat "^" (getenv "HOME") "/organization/[^/]+\\.txt\\'")  'org-mode)  ; matches .txt files in this directory
    '("\\.te?xt\\'"                                                   . text-mode) ; will match any leftover .txt file

And now, ~/organization/timetable.txt will open in org-mode. This probably demonstrates a good rule of thumb: Make more specific rules come before more general ones, because the specific ones always leave something over for the general ones, while the other way round, the general ones will already match everything and leave nothing over for the more specific ones.

A (real) complex real-life example

If the example above seems slightly artificial (since you always use the .org extension for your Org files), issues such as the above easily arise with files without any extension. Classic examples are shell configuration files such as .alias, .profile and the like, but also Windows-style config files, which have become more widespread in Un*x systems. Thus, auto-mode-alist comes with a predefined rule such as follows (I have simplified it for focus):

    '("c\\(?:on\\)?f\\(?:i?g\\)?\\'" . conf-mode) ; matches cfg, .cfg, .config, config, .cfig, etc.

As it happens, the Fvwm window manager looks for its configuration file in ~/.fvwm, and its default base name is simply config. The pre-existing rule just laid out matches this file, but I would like to use FvwmMode to edit it, not conf-mode. Thus, I have to add a special rule and put it before the general one (note the second line is implicit, as it is predefined in auto-mode-alist):

    (cons (concat "^" (getenv "HOME") "/[.]fvwm/config\\'")  'fvwm-mode) ; just /home/me/.fvwm/config
    '("c\\(?:on\\)?f\\(?:i?g\\)?\\'"                        . conf-mode) ; other files matching cfg, .cfg, .config, config, .cfig, etc.

Success! My Fvwm configuration file opens in fvwm-mode, and any other config files in conf-mode. But after a while, I discover that Fvwm features a Read command, which you can use to read in additional files to make a complex configuration better organized. I do that, and I keep these files in ~/.fvwm as well and certainly want to edit them in fvwm-mode. I could, of course, give them all an .fvwm extension and add a rule for it. But I don’t like that – rather, my additional files have names such as menus, windows, functions, and the like. Thus, I change my rule to make it more comprehensive. Come to think of it, I don’t even have to specify config explicitly (note, again, the second line is implicit, as it is predefined in auto-mode-alist):

    (cons (concat "^" (getenv "HOME") "/[.]fvwm/")  'fvwm-mode) ; all files below /home/me/.fvwm/ (including "config")
    '("c\\(?:on\\)?f\\(?:i?g\\)?\\'"               . conf-mode) ; other files matching cfg, .cfg, .config, config, .cfig, etc.

Wonderful; ~/.fvwm/ is now fvwm-mode’s turf. Becoming even more proficient in Fvwm over the years, however, I start to use external scripts in languages such as Perl, Bash, for things such as creating menus on the fly. I do the obvious thing and keep those scripts in a subdirectory, or subdirectories, of ~/.fvwm, such as ~/.fvwm/scripts/pl and so on. But these scripts open in fvwm-mode because of my above rule, which I have put not only before the general “cfg, .cfg, .config, config etc.” rule, but also before any predefined rules for file extensions such as .pl or .sh. If I moved the rule down, ~/.fvwm/config (though not the other files) would be opened in conf-mode again.

So what now? Do I have to make additional rules for .pl files, .sh files below ~/.fvwm? Not necessarily. The following solution, which puts one rule before and one rule after the predefined auto-mode-alist, is more elegant (note, the second and third lines are again implicit, as they only demonstrate what is already in auto-mode-alist):

    (cons (concat "^" (getenv "HOME") "/[.]fvwm/config\\'")  'fvwm-mode) ; only /home/me/.fvwm/config
    '("c\\(?:on\\)?f\\(?:i?g\\)?\\'"                        . conf-mode) ; other files matching cfg, .cfg, .config, config, .cfig, etc.
    ;; other predefined rules in `auto-mode-alist' concerning .pl, .sh, and whatever else
    (cons (concat "^" (getenv "HOME") "/[.]fvwm/")           'fvwm-mode) ; any leftover files below /home/me/.fvwm/

This solution has two advantages: First, any files in ~/.fvwm for which a default rule exists will get opened according to that rule, config being the sole exception. Thus, I can now start putting all kinds of files there that need a different mode than fvwm-mode, e.g. yet other programming languages and all kinds of image files (such as for desktop backgrounds and icons), and always get the correct mode automatically, without changing anything else about the above setup. Second, it is quite elegant because I am adding only two additional rules, one before, one after the preexisting rules. To wrap it up, this would be the complete, verbatim code:

    (setq auto-mode-alist
      (append
        (list
          (cons (concat "^" (getenv "HOME") "/[.]fvwm/config\\'") 'fvwm-mode))
        auto-mode-alist
        (list
          (cons (concat "^" (getenv "HOME") "/[.]fvwm/")          'fvwm-mode))))

Or, with add-to-list:

    (add-to-list 'auto-mode-alist
          (cons (concat "^" (getenv "HOME") "/[.]fvwm/config\\'")  'fvwm-mode))
    (add-to-list 'auto-mode-alist
          (cons (concat "^" (getenv "HOME") "/[.]fvwm/")           'fvwm-mode) t) ; t for APPEND

Debugging

If Emacs keeps starting the wrong mode although the right recipe should be in auto-mode-alist, it should be possible to work out the reason by following these two steps:

  1. Check if some other method takes precedence before auto-mode-alist (see SetAutoMode).
  2. If that is not the case, find out where exactly auto-mode-alist matches. Because a lot of packages add to this list, and simply printing it out may be hard on the eyes, you can run the following command while visiting the file in question, or specify the file name with a prefix argument:

(defun explain-auto-mode (file)
  "Explain in which mode FILE gets visited according to `auto-mode-alist'.
With prefix arg, prompt the user for FILE; else, use function `buffer-file-name'."
  (interactive
   (list
    (if current-prefix-arg
	(read-file-name "Explain the automatic mode of (possibly non-existing) file: " )
      (buffer-file-name))))

 (if (equal "" file)
   (error "I need some file name to work with"))

 (let* ((file (expand-file-name file))
	(index 0)
	assoc)
   (setq assoc
	 (catch 'match
	   (while (setq assoc (nth index auto-mode-alist))
	     (if (string-match (car assoc) file)
		 (throw 'match assoc)
	       (setq index (1+ index))))
	   (setq assoc nil)))
   
   (if assoc
       (message "First match in `auto-mode-alist' is at position %d:
\"%s\"  <=>  \"%s\".
The corresponding mode is `%s'."
		(1+ index)
		file (car assoc)
		(cdr assoc))
     (message "No match in `auto-mode-alist' for %s." file))))

CategoryDotEmacs CategoryModes