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
;; 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)))
(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
(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'
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.