Add auto-mode-alist functionality to .dir-locals.el
* doc/emacs/custom.texi (Directory Variables): Document auto-mode-alist in .dir-locals.el (Bug#18721) * doc/emacs/modes.texi (Choosing Modes): Update. * lisp/files.el (set-auto-mode--apply-alist): New function, from set-auto-mode. (set-auto-mode): Check directory locals for auto-mode-alist. (dir-locals-collect-variables): Add "predicate" parameter. (hack-dir-local--get-variables): New function, from hack-dir-local-variables. (hack-dir-local-variables): Call hack-dir-local--get-variables. * test/lisp/files-resources/.dir-locals.el: New file. * test/lisp/files-resources/whatever.quux: New file. * test/lisp/files-tests.el (files-tests-data-dir): New variable. (files-test-dir-locals-auto-mode-alist): New test.
This commit is contained in:
parent
6a3b89f9df
commit
ad5faa424a
7 changed files with 136 additions and 68 deletions
doc/emacs
etc
lisp
test/lisp
|
@ -1415,6 +1415,16 @@ meanings as they would have in file local variables. @code{coding}
|
|||
cannot be specified as a directory local variable. @xref{File
|
||||
Variables}.
|
||||
|
||||
The special key @code{auto-mode-alist} in a @file{.dir-locals.el} lets
|
||||
you set a file's major mode. It works much like the variable
|
||||
@code{auto-mode-alist} (@pxref{Choosing Modes}). For example, here is
|
||||
how you can tell Emacs that @file{.def} source files in this directory
|
||||
should be in C mode:
|
||||
|
||||
@example
|
||||
((auto-mode-alist . (("\\.def\\'" . c-mode))))
|
||||
@end example
|
||||
|
||||
@findex add-dir-local-variable
|
||||
@findex delete-dir-local-variable
|
||||
@findex copy-file-locals-to-dir-locals
|
||||
|
|
|
@ -357,8 +357,12 @@ preferences. If you personally want to use a minor mode for a
|
|||
particular file type, it is better to enable the minor mode via a
|
||||
major mode hook (@pxref{Major Modes}).
|
||||
|
||||
Second, Emacs checks whether the file's extension matches an entry
|
||||
in any directory-local @code{auto-mode-alist}. These are found using
|
||||
the @file{.dir-locals.el} facility (@pxref{Directory Variables}).
|
||||
|
||||
@vindex interpreter-mode-alist
|
||||
Second, if there is no file variable specifying a major mode, Emacs
|
||||
Third, if there is no file variable specifying a major mode, Emacs
|
||||
checks whether the file's contents begin with @samp{#!}. If so, that
|
||||
indicates that the file can serve as an executable shell command,
|
||||
which works by running an interpreter named on the file's first line
|
||||
|
@ -376,7 +380,7 @@ same is true for man pages which start with the magic string
|
|||
@samp{'\"} to specify a list of troff preprocessors.
|
||||
|
||||
@vindex magic-mode-alist
|
||||
Third, Emacs tries to determine the major mode by looking at the
|
||||
Fourth, Emacs tries to determine the major mode by looking at the
|
||||
text at the start of the buffer, based on the variable
|
||||
@code{magic-mode-alist}. By default, this variable is @code{nil} (an
|
||||
empty list), so Emacs skips this step; however, you can customize it
|
||||
|
@ -404,7 +408,7 @@ where @var{match-function} is a Lisp function that is called at the
|
|||
beginning of the buffer; if the function returns non-@code{nil}, Emacs
|
||||
set the major mode with @var{mode-function}.
|
||||
|
||||
Fourth---if Emacs still hasn't found a suitable major mode---it
|
||||
Fifth---if Emacs still hasn't found a suitable major mode---it
|
||||
looks at the file's name. The correspondence between file names and
|
||||
major modes is controlled by the variable @code{auto-mode-alist}. Its
|
||||
value is a list in which each element has this form,
|
||||
|
|
6
etc/NEWS
6
etc/NEWS
|
@ -2289,6 +2289,12 @@ This command, called interactively, toggles the local value of
|
|||
|
||||
** Miscellaneous
|
||||
|
||||
+++
|
||||
*** .dir-locals.el now supports setting 'auto-mode-alist'.
|
||||
The new 'auto-mode-alist' specification in .dir-local.el files can now
|
||||
be used to override the global 'auto-mode-alist' in the current
|
||||
directory tree.
|
||||
|
||||
---
|
||||
*** New utility function 'make-separator-line'.
|
||||
|
||||
|
|
169
lisp/files.el
169
lisp/files.el
|
@ -3195,11 +3195,62 @@ If FUNCTION is nil, then it is not called.")
|
|||
"Upper limit on `magic-mode-alist' regexp matches.
|
||||
Also applies to `magic-fallback-mode-alist'.")
|
||||
|
||||
(defun set-auto-mode--apply-alist (alist keep-mode-if-same dir-local)
|
||||
"Helper function for `set-auto-mode'.
|
||||
This function takes an alist of the same form as
|
||||
`auto-mode-alist'. It then tries to find the appropriate match
|
||||
in the alist for the current buffer; setting the mode if
|
||||
possible. Returns non-`nil' if the mode was set, `nil'
|
||||
otherwise. DIR-LOCAL is a boolean which, if true, says that this
|
||||
call is via directory-locals and extra checks should be done."
|
||||
(if buffer-file-name
|
||||
(let (mode
|
||||
(name buffer-file-name)
|
||||
(remote-id (file-remote-p buffer-file-name))
|
||||
(case-insensitive-p (file-name-case-insensitive-p
|
||||
buffer-file-name)))
|
||||
;; Remove backup-suffixes from file name.
|
||||
(setq name (file-name-sans-versions name))
|
||||
;; Remove remote file name identification.
|
||||
(when (and (stringp remote-id)
|
||||
(string-match (regexp-quote remote-id) name))
|
||||
(setq name (substring name (match-end 0))))
|
||||
(while name
|
||||
;; Find first matching alist entry.
|
||||
(setq mode
|
||||
(if case-insensitive-p
|
||||
;; Filesystem is case-insensitive.
|
||||
(let ((case-fold-search t))
|
||||
(assoc-default alist 'string-match))
|
||||
;; Filesystem is case-sensitive.
|
||||
(or
|
||||
;; First match case-sensitively.
|
||||
(let ((case-fold-search nil))
|
||||
(assoc-default name alist 'string-match))
|
||||
;; Fallback to case-insensitive match.
|
||||
(and auto-mode-case-fold
|
||||
(let ((case-fold-search t))
|
||||
(assoc-default name alist 'string-match))))))
|
||||
(if (and mode
|
||||
(consp mode)
|
||||
(cadr mode))
|
||||
(setq mode (car mode)
|
||||
name (substring name 0 (match-beginning 0)))
|
||||
(setq name nil)))
|
||||
(when (and dir-local mode)
|
||||
(unless (string-suffix-p "-mode" (symbol-name mode))
|
||||
(message "Ignoring invalid mode `%s'" (symbol-name mode))
|
||||
(setq mode nil)))
|
||||
(when mode
|
||||
(set-auto-mode-0 mode keep-mode-if-same)
|
||||
t))))
|
||||
|
||||
(defun set-auto-mode (&optional keep-mode-if-same)
|
||||
"Select major mode appropriate for current buffer.
|
||||
|
||||
To find the right major mode, this function checks for a -*- mode tag
|
||||
checks for a `mode:' entry in the Local Variables section of the file,
|
||||
checks if there an `auto-mode-alist' entry in `.dir-locals.el',
|
||||
checks if it uses an interpreter listed in `interpreter-mode-alist',
|
||||
matches the buffer beginning against `magic-mode-alist',
|
||||
compares the file name against the entries in `auto-mode-alist',
|
||||
|
@ -3256,6 +3307,14 @@ we don't actually set it to the same mode the buffer already has."
|
|||
(or (set-auto-mode-0 mode keep-mode-if-same)
|
||||
;; continuing would call minor modes again, toggling them off
|
||||
(throw 'nop nil))))))
|
||||
;; Check for auto-mode-alist entry in dir-locals.
|
||||
(unless done
|
||||
(with-demoted-errors "Directory-local variables error: %s"
|
||||
;; Note this is a no-op if enable-local-variables is nil.
|
||||
(let* ((mode-alist (cdr (hack-dir-local--get-variables
|
||||
(lambda (key) (eq key 'auto-mode-alist))))))
|
||||
(setq done (set-auto-mode--apply-alist mode-alist
|
||||
keep-mode-if-same t)))))
|
||||
(and (not done)
|
||||
(setq mode (hack-local-variables t (not try-locals)))
|
||||
(not (memq mode modes)) ; already tried and failed
|
||||
|
@ -3307,45 +3366,8 @@ we don't actually set it to the same mode the buffer already has."
|
|||
(set-auto-mode-0 done keep-mode-if-same)))
|
||||
;; Next compare the filename against the entries in auto-mode-alist.
|
||||
(unless done
|
||||
(if buffer-file-name
|
||||
(let ((name buffer-file-name)
|
||||
(remote-id (file-remote-p buffer-file-name))
|
||||
(case-insensitive-p (file-name-case-insensitive-p
|
||||
buffer-file-name)))
|
||||
;; Remove backup-suffixes from file name.
|
||||
(setq name (file-name-sans-versions name))
|
||||
;; Remove remote file name identification.
|
||||
(when (and (stringp remote-id)
|
||||
(string-match (regexp-quote remote-id) name))
|
||||
(setq name (substring name (match-end 0))))
|
||||
(while name
|
||||
;; Find first matching alist entry.
|
||||
(setq mode
|
||||
(if case-insensitive-p
|
||||
;; Filesystem is case-insensitive.
|
||||
(let ((case-fold-search t))
|
||||
(assoc-default name auto-mode-alist
|
||||
'string-match))
|
||||
;; Filesystem is case-sensitive.
|
||||
(or
|
||||
;; First match case-sensitively.
|
||||
(let ((case-fold-search nil))
|
||||
(assoc-default name auto-mode-alist
|
||||
'string-match))
|
||||
;; Fallback to case-insensitive match.
|
||||
(and auto-mode-case-fold
|
||||
(let ((case-fold-search t))
|
||||
(assoc-default name auto-mode-alist
|
||||
'string-match))))))
|
||||
(if (and mode
|
||||
(consp mode)
|
||||
(cadr mode))
|
||||
(setq mode (car mode)
|
||||
name (substring name 0 (match-beginning 0)))
|
||||
(setq name nil))
|
||||
(when mode
|
||||
(set-auto-mode-0 mode keep-mode-if-same)
|
||||
(setq done t))))))
|
||||
(setq done (set-auto-mode--apply-alist auto-mode-alist
|
||||
keep-mode-if-same nil)))
|
||||
;; Next try matching the buffer beginning against magic-fallback-mode-alist.
|
||||
(unless done
|
||||
(if (setq done (save-excursion
|
||||
|
@ -4166,10 +4188,13 @@ Returns the new list."
|
|||
;; Need a new cons in case we setcdr later.
|
||||
(push (cons variable value) variables)))))
|
||||
|
||||
(defun dir-locals-collect-variables (class-variables root variables)
|
||||
(defun dir-locals-collect-variables (class-variables root variables
|
||||
&optional predicate)
|
||||
"Collect entries from CLASS-VARIABLES into VARIABLES.
|
||||
ROOT is the root directory of the project.
|
||||
Return the new variables list."
|
||||
Return the new variables list.
|
||||
If PREDICATE is given, it is used to test a symbol key in the alist
|
||||
to see whether it should be considered."
|
||||
(let* ((file-name (or (buffer-file-name)
|
||||
;; Handle non-file buffers, too.
|
||||
(expand-file-name default-directory)))
|
||||
|
@ -4188,9 +4213,11 @@ Return the new variables list."
|
|||
(>= (length sub-file-name) (length key))
|
||||
(string-prefix-p key sub-file-name))
|
||||
(setq variables (dir-locals-collect-variables
|
||||
(cdr entry) root variables))))
|
||||
((or (not key)
|
||||
(derived-mode-p key))
|
||||
(cdr entry) root variables predicate))))
|
||||
((if predicate
|
||||
(funcall predicate key)
|
||||
(or (not key)
|
||||
(derived-mode-p key)))
|
||||
(let* ((alist (cdr entry))
|
||||
(subdirs (assq 'subdirs alist)))
|
||||
(if (or (not subdirs)
|
||||
|
@ -4487,13 +4514,13 @@ Return the new class name, which is a symbol named DIR."
|
|||
|
||||
(defvar hack-dir-local-variables--warned-coding nil)
|
||||
|
||||
(defun hack-dir-local-variables ()
|
||||
(defun hack-dir-local--get-variables (predicate)
|
||||
"Read per-directory local variables for the current buffer.
|
||||
Store the directory-local variables in `dir-local-variables-alist'
|
||||
and `file-local-variables-alist', without applying them.
|
||||
|
||||
This does nothing if either `enable-local-variables' or
|
||||
`enable-dir-local-variables' are nil."
|
||||
Return a cons of the form (DIR . ALIST), where DIR is the
|
||||
directory name (maybe nil) and ALIST is an alist of all variables
|
||||
that might apply. These will be filtered according to the
|
||||
buffer's directory, but not according to its mode.
|
||||
PREDICATE is passed to `dir-locals-collect-variables'."
|
||||
(when (and enable-local-variables
|
||||
enable-dir-local-variables
|
||||
(or enable-remote-dir-locals
|
||||
|
@ -4512,21 +4539,33 @@ This does nothing if either `enable-local-variables' or
|
|||
(setq dir-name (nth 0 dir-or-cache))
|
||||
(setq class (nth 1 dir-or-cache))))
|
||||
(when class
|
||||
(let ((variables
|
||||
(dir-locals-collect-variables
|
||||
(dir-locals-get-class-variables class) dir-name nil)))
|
||||
(when variables
|
||||
(dolist (elt variables)
|
||||
(if (eq (car elt) 'coding)
|
||||
(unless hack-dir-local-variables--warned-coding
|
||||
(setq hack-dir-local-variables--warned-coding t)
|
||||
(display-warning 'files
|
||||
"Coding cannot be specified by dir-locals"))
|
||||
(unless (memq (car elt) '(eval mode))
|
||||
(setq dir-local-variables-alist
|
||||
(assq-delete-all (car elt) dir-local-variables-alist)))
|
||||
(push elt dir-local-variables-alist)))
|
||||
(hack-local-variables-filter variables dir-name)))))))
|
||||
(cons dir-name
|
||||
(dir-locals-collect-variables
|
||||
(dir-locals-get-class-variables class)
|
||||
dir-name nil predicate))))))
|
||||
|
||||
(defun hack-dir-local-variables ()
|
||||
"Read per-directory local variables for the current buffer.
|
||||
Store the directory-local variables in `dir-local-variables-alist'
|
||||
and `file-local-variables-alist', without applying them.
|
||||
|
||||
This does nothing if either `enable-local-variables' or
|
||||
`enable-dir-local-variables' are nil."
|
||||
(let* ((items (hack-dir-local--get-variables nil))
|
||||
(dir-name (car items))
|
||||
(variables (cdr items)))
|
||||
(when variables
|
||||
(dolist (elt variables)
|
||||
(if (eq (car elt) 'coding)
|
||||
(unless hack-dir-local-variables--warned-coding
|
||||
(setq hack-dir-local-variables--warned-coding t)
|
||||
(display-warning 'files
|
||||
"Coding cannot be specified by dir-locals"))
|
||||
(unless (memq (car elt) '(eval mode))
|
||||
(setq dir-local-variables-alist
|
||||
(assq-delete-all (car elt) dir-local-variables-alist)))
|
||||
(push elt dir-local-variables-alist)))
|
||||
(hack-local-variables-filter variables dir-name))))
|
||||
|
||||
(defun hack-dir-local-variables-non-file-buffer ()
|
||||
"Apply directory-local variables to a non-file buffer.
|
||||
|
|
2
test/lisp/files-resources/.dir-locals.el
Normal file
2
test/lisp/files-resources/.dir-locals.el
Normal file
|
@ -0,0 +1,2 @@
|
|||
;; This is used by files-tests.el.
|
||||
((auto-mode-alist . (("\\.quux\\'" . tcl-mode))))
|
2
test/lisp/files-resources/whatever.quux
Normal file
2
test/lisp/files-resources/whatever.quux
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Used by files-test.el.
|
||||
# Due to .dir-locals.el this should end up in Tcl mode.
|
|
@ -1534,5 +1534,10 @@ The door of all subtleties!
|
|||
(should-error (file-name-with-extension "Jack" "."))
|
||||
(should-error (file-name-with-extension "/is/a/directory/" "css")))
|
||||
|
||||
(ert-deftest files-test-dir-locals-auto-mode-alist ()
|
||||
"Test an `auto-mode-alist' entry in `.dir-locals.el'"
|
||||
(find-file (ert-resource-file "whatever.quux"))
|
||||
(should (eq major-mode 'tcl-mode)))
|
||||
|
||||
(provide 'files-tests)
|
||||
;;; files-tests.el ends here
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue