(hack-dir-local-get-variables-functions): New hook
Make it possible to provide more dir-local variables, such as done by the Editorconfig package. * lisp/files.el (hack-dir-local--get-variables): Make arg optional. (hack-dir-local-get-variables-functions): New hook. (hack-dir-local-variables): Run it instead of calling `hack-dir-local--get-variables`. * doc/lispref/variables.texi (Directory Local Variables): Document the new hook.
This commit is contained in:
parent
3ecc6b4f3c
commit
8253228d55
3 changed files with 95 additions and 16 deletions
|
@ -2277,6 +2277,35 @@ modification times of the associated directory local variables file
|
|||
updates this list.
|
||||
@end defvar
|
||||
|
||||
@defvar hack-dir-local-get-variables-functions
|
||||
This special hook holds the functions that gather the directory-local
|
||||
variables to use for a given buffer. By default it contains just the
|
||||
function that obeys the other settings described in the present section.
|
||||
But it can be used to add support for more sources of directory-local
|
||||
variables, such as those used by other text editors.
|
||||
|
||||
The functions on this hook are called with no argument, in the buffer to
|
||||
which we intend to apply the directory-local variables, after the
|
||||
buffer's major mode function has been run, so they can use sources of
|
||||
information such as @code{major-mode} or @code{buffer-file-name} to find
|
||||
the variables that should be applied.
|
||||
|
||||
It should return either a cons cell of the form @code{(@var{directory}
|
||||
. @var{alist})} or a list of such cons-cells. A @code{nil} return value
|
||||
means that it found no directory-local variables. @var{directory}
|
||||
should be a string: the name of the directory to which the variables
|
||||
apply. @var{alist} is a list of variables together with their values
|
||||
that apply to the current buffer, where every element is of the form
|
||||
@code{(@var{varname} . @var{value})}.
|
||||
|
||||
The various @var{alist} returned by these functions will be combined,
|
||||
and in case of conflicts, the settings coming from deeper directories
|
||||
will take precedence over those coming from higher directories in the
|
||||
directory hierarchy. Finally, since this hook is run every time we visit
|
||||
a file it is important to try and keep those functions efficient, which
|
||||
will usually require some kind of caching.
|
||||
@end defvar
|
||||
|
||||
@defvar enable-dir-local-variables
|
||||
If @code{nil}, directory-local variables are ignored. This variable
|
||||
may be useful for modes that want to ignore directory-locals while
|
||||
|
|
5
etc/NEWS
5
etc/NEWS
|
@ -2186,6 +2186,11 @@ completion candidate.
|
|||
|
||||
* Lisp Changes in Emacs 30.1
|
||||
|
||||
+++
|
||||
** New hook 'hack-dir-local-get-variables-functions'.
|
||||
This can be used to provide support for other directory-local settings
|
||||
beside '.dir-locals.el'.
|
||||
|
||||
+++
|
||||
** 'auto-coding-functions' can know the name of the file.
|
||||
The functions on this hook can now find the name of the file to
|
||||
|
|
|
@ -3494,6 +3494,8 @@ we don't actually set it to the same mode the buffer already has."
|
|||
;; Check for auto-mode-alist entry in dir-locals.
|
||||
(with-demoted-errors "Directory-local variables error: %s"
|
||||
;; Note this is a no-op if enable-local-variables is nil.
|
||||
;; We don't use `hack-dir-local-get-variables-functions' here, because
|
||||
;; modes are specific to Emacs.
|
||||
(let* ((mode-alist (cdr (hack-dir-local--get-variables
|
||||
(lambda (key) (eq key 'auto-mode-alist))))))
|
||||
(set-auto-mode--apply-alist mode-alist keep-mode-if-same t)))
|
||||
|
@ -4769,7 +4771,7 @@ Return the new class name, which is a symbol named DIR."
|
|||
|
||||
(defvar hack-dir-local-variables--warned-coding nil)
|
||||
|
||||
(defun hack-dir-local--get-variables (predicate)
|
||||
(defun hack-dir-local--get-variables (&optional predicate)
|
||||
"Read per-directory local variables for the current buffer.
|
||||
Return a cons of the form (DIR . ALIST), where DIR is the
|
||||
directory name (maybe nil) and ALIST is an alist of all variables
|
||||
|
@ -4799,6 +4801,16 @@ PREDICATE is passed to `dir-locals-collect-variables'."
|
|||
(dir-locals-get-class-variables class)
|
||||
dir-name nil predicate))))))
|
||||
|
||||
(defvar hack-dir-local-get-variables-functions
|
||||
(list #'hack-dir-local--get-variables)
|
||||
"Special hook to compute the set of dir-local variables.
|
||||
Every function is called without arguments and should return either
|
||||
a cons of the form (DIR . ALIST) or a (possibly empty) list of such conses,
|
||||
where ALIST is an alist of (VAR . VAL) settings.
|
||||
DIR should be a string (a directory name) and is used to obey
|
||||
`safe-local-variable-directories'.
|
||||
This hook is run after the major mode has been setup.")
|
||||
|
||||
(defun hack-dir-local-variables ()
|
||||
"Read per-directory local variables for the current buffer.
|
||||
Store the directory-local variables in `dir-local-variables-alist'
|
||||
|
@ -4806,21 +4818,54 @@ 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))))
|
||||
(let (items)
|
||||
(when (and enable-local-variables
|
||||
enable-dir-local-variables
|
||||
(or enable-remote-dir-locals
|
||||
(not (file-remote-p (or (buffer-file-name)
|
||||
default-directory)))))
|
||||
(run-hook-wrapped 'hack-dir-local-get-variables-functions
|
||||
(lambda (fun)
|
||||
(let ((res (funcall fun)))
|
||||
(cond
|
||||
((null res))
|
||||
((consp (car-safe res))
|
||||
(setq items (append res items)))
|
||||
(t (push res items))))
|
||||
nil)))
|
||||
;; Sort the entries from nearest dir to furthest dir.
|
||||
(setq items (sort (nreverse items)
|
||||
:key (lambda (x) (length (car-safe x))) :reverse t))
|
||||
;; Filter out duplicates, preferring the settings from the nearest dir
|
||||
;; and from the first hook function.
|
||||
(let ((seen nil))
|
||||
(dolist (item items)
|
||||
(when seen ;; Special case seen=nil since it's the most common case.
|
||||
(setcdr item (seq-filter (lambda (vv) (not (memq (car-safe vv) seen)))
|
||||
(cdr item))))
|
||||
(setq seen (nconc (seq-difference (mapcar #'car (cdr item))
|
||||
'(eval mode))
|
||||
seen))))
|
||||
;; Rather than a loop, maybe we should handle all the dirs
|
||||
;; "together", e.g. prompting the user only once. But if so, we'd
|
||||
;; probably want to also merge the prompt for file-local vars,
|
||||
;; which comes from the call to `hack-local-variables-filter' in
|
||||
;; `hack-local-variables'.
|
||||
(dolist (item items)
|
||||
(let ((dir-name (car item))
|
||||
(variables (cdr item)))
|
||||
(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.
|
||||
|
|
Loading…
Add table
Reference in a new issue