]> git.eshelyaron.com Git - emacs.git/commitdiff
(hack-dir-local-get-variables-functions): New hook
authorStefan Monnier <monnier@iro.umontreal.ca>
Tue, 4 Jun 2024 15:00:32 +0000 (11:00 -0400)
committerEshel Yaron <me@eshelyaron.com>
Sun, 9 Jun 2024 09:52:44 +0000 (11:52 +0200)
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.

(cherry picked from commit 8253228d55b368ad7ea4d66d802059e8afff2b12)

doc/lispref/variables.texi
etc/NEWS
lisp/files.el

index e05d3bb0f8116b0b005f963c0e29ab3df04f216c..0ed1936cd84536445567189380e0a2b8bcb5d6b5 100644 (file)
@@ -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
index e5159b5d08ff2cf3f7e8c46666f30bb2ee7512d4..51f59112efbbed3305375e6610c916a1f85e8bfb 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2281,6 +2281,11 @@ unibyte string.
 \f
 * 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
index 65499c178659040010849c0a9fc23f68626f8910..fb8ef78f671a48aaa4df4cc583c76a6496fd27e2 100644 (file)
@@ -3515,6 +3515,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)))
@@ -4790,7 +4792,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
@@ -4820,6 +4822,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'
@@ -4827,21 +4839,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.