Associate a file/buffer with a particular major mode

When Emacs visits a file, it determines the major mode to activate based on:

  1. The file path,
  2. The contents of the file (shebang lines or text patterns near the beginning of the file),
  3. Or any file or directory-local settings for mode.

By file path

The auto-mode-alist variable contains an association-list mapping file name patterns (regular expressions) to the function that activates a major mode. Typically, the file name patterns test for file extensions, such as .el and .c. For example, to associate *.js files with typescript-mode instead of js2-mode:

;; add to ~/.doom.d/config.el
(add-to-list 'auto-mode-alist '("\\.js\\'" . typescript-mode))

Or any file in any */templates/* directory to be associated with web-mode:

;; add to ~/.doom.d/config.el
(add-to-list 'auto-mode-alist '("/templates/" . web-mode))

By shebang line

At the top of most shell scripts you’ll find a special line that looks like: #!/bin/bash.

This is a shebang line. It tells the OS what program to launch your script with.

Emacs acn determine the major mode from the shebang line, based on the rules set in interpreter-mode-alist; an association list mapping shebang line patterns (regex) to major modes (functions).

For example, to associate python3 with python-mode:

(add-to-list 'interpreter-mode-alist '("python3" . python-mode))

Let’s look at a more advanced example: nix-shell.

#!/usr/bin/env nix-shell
#! nix-shell -i bash -p maim slop

...

nix-shell functions as a wrapper around another program (bash, in this case). In order to handle this something more specialized is needed:

(defun +nix-shell-init-mode ()
  (save-excursion
    (goto-char (point-min))
    (save-match-data
      (if (not (re-search-forward "#! *nix-shell +-i +\\([^ \n]+\\)" 256 t))
          (message "Couldn't determine mode for this script")
        (let* ((interp (match-string 1))
               (mode
                (assoc-default
                 interp
                 (mapcar (lambda (e)
                           (cons (format "\\`%s\\'" (car e))
                                 (cdr e)))
                         interpreter-mode-alist)
                 #'string-match-p)))
          (when mode
            (prog1 (set-auto-mode-0 mode)
              (when (eq major-mode 'sh-mode)
                (sh-set-shell interp)))))))))

(add-to-list 'interpreter-mode-alist '("nix-shell" . +nix-shell-init-mode))

This searches for the #! nix-shell -i bash ... line, extracts the interpreter from the -i switch, and then searches for bash in interpreter-mode-alist to acquire the desired major-mode.

By text patterns in the file’s contents

The major mode for files without file extensions or shebang lines may be deduced by searching for common patterns within its contents. This is where magic-mode-alist comes in; an association list mapping regex patterns to major modes (or functions). It is consulted before auto-mode-alist.

For example, to use xml-mode when the file starts with <?xml:

(add-to-list 'magic-mode-alist '("^<\\?xml" . xml-mode))

There’s also magic-fallback-mode-alist, which is consulted if no mode can be determined from magic-mode-alist or auto-mode-alist.

By file or Directory local settings

  • ;; -*- mode: emacs-lisp; -*- at the top of the file or this at the bottom:

    ;; Local Variables:
    ;; mode: emacs-lisp
    ;; End:
    

    Will cause Emacs to open that file in emacs-lisp-mode.

  • Additionally, a .dir-locals.el file can be used to force a major mode:

    ((nil . ((mode . c)))
    

    All buffers opened under that directory will be opened in c-mode.

:pushpin: See “File and directory local settings” for more information.

4 Likes