diff options
| author | Stefan Monnier | 2024-06-04 11:00:32 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2024-06-06 19:25:41 -0400 |
| commit | 71ac0af5d0191cdf8ebc56066c7ca58dd93487cf (patch) | |
| tree | 2a308c7e5b2c44212fe6cf025935235f23f0bb92 | |
| parent | d12c9bc2a4c8383abdf7fa32b67f1ca0379227e3 (diff) | |
| download | emacs-scratch/editorconfig.tar.gz emacs-scratch/editorconfig.zip | |
(hack-dir-local-get-variables-functions): New hookscratch/editorconfig
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.
| -rw-r--r-- | doc/lispref/variables.texi | 29 | ||||
| -rw-r--r-- | etc/NEWS | 4 | ||||
| -rw-r--r-- | lisp/files.el | 76 |
3 files changed, 93 insertions, 16 deletions
diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index e05d3bb0f81..c63057e31ea 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi | |||
| @@ -2277,6 +2277,35 @@ modification times of the associated directory local variables file | |||
| 2277 | updates this list. | 2277 | updates this list. |
| 2278 | @end defvar | 2278 | @end defvar |
| 2279 | 2279 | ||
| 2280 | @defvar hack-dir-local-get-variables-functions | ||
| 2281 | This special hook holds the functions that gather the directory-local | ||
| 2282 | variables to use for a given buffer. By default it contains just the | ||
| 2283 | function that obeys the other settings described in the present section. | ||
| 2284 | But it can be used to add support for more sources of directory-local | ||
| 2285 | variables, such as those used by other text editors. | ||
| 2286 | |||
| 2287 | The functions on this hook are called with no argument, in the buffer to | ||
| 2288 | which we intend to apply the directory-local variables, after the | ||
| 2289 | buffer's major mode function has been run, so it can use sources of | ||
| 2290 | information such as @code{major-mode} or @code{buffer-file-name} to find | ||
| 2291 | the variables that should be applied. | ||
| 2292 | |||
| 2293 | It should return either a cons cell of the form @code{(@var{directory} | ||
| 2294 | . @var{alist})} or a list of such cons-cells. A @code{nil} return value | ||
| 2295 | means that it found no directory-local variables. @var{directory} | ||
| 2296 | should be a string: the name of the directory to which the variables | ||
| 2297 | apply. @var{alist} is a list of variables together with their values | ||
| 2298 | that apply to the current buffer, where every element is of the form | ||
| 2299 | @code{(@var{varname} . @var{value})}. | ||
| 2300 | |||
| 2301 | The various @var{alist} returned by these functions will be combined, | ||
| 2302 | and in case of conflicts, the settings coming from deeper directories | ||
| 2303 | will take precedence over those coming from higher directories in the | ||
| 2304 | directory hierarchy. Finally, since this hook is run every time we visit | ||
| 2305 | a file it is important to try and keep those functions efficient, which | ||
| 2306 | will usually require some of caching. | ||
| 2307 | @end defvar | ||
| 2308 | |||
| 2280 | @defvar enable-dir-local-variables | 2309 | @defvar enable-dir-local-variables |
| 2281 | If @code{nil}, directory-local variables are ignored. This variable | 2310 | If @code{nil}, directory-local variables are ignored. This variable |
| 2282 | may be useful for modes that want to ignore directory-locals while | 2311 | may be useful for modes that want to ignore directory-locals while |
| @@ -2163,6 +2163,10 @@ completion candidate. | |||
| 2163 | 2163 | ||
| 2164 | * Lisp Changes in Emacs 30.1 | 2164 | * Lisp Changes in Emacs 30.1 |
| 2165 | 2165 | ||
| 2166 | ** New hook 'hack-dir-local-get-variables-functions'. | ||
| 2167 | This can be used to provide support for other directory-local settings | ||
| 2168 | beside '.dir-locals.el'. | ||
| 2169 | |||
| 2166 | +++ | 2170 | +++ |
| 2167 | ** 'auto-coding-functions' can know the name of the file. | 2171 | ** 'auto-coding-functions' can know the name of the file. |
| 2168 | The functions on this hook take can now find the name of the file to | 2172 | The functions on this hook take can now find the name of the file to |
diff --git a/lisp/files.el b/lisp/files.el index 210cd0fa7ad..a36ac6b1318 100644 --- a/lisp/files.el +++ b/lisp/files.el | |||
| @@ -3494,6 +3494,8 @@ we don't actually set it to the same mode the buffer already has." | |||
| 3494 | ;; Check for auto-mode-alist entry in dir-locals. | 3494 | ;; Check for auto-mode-alist entry in dir-locals. |
| 3495 | (with-demoted-errors "Directory-local variables error: %s" | 3495 | (with-demoted-errors "Directory-local variables error: %s" |
| 3496 | ;; Note this is a no-op if enable-local-variables is nil. | 3496 | ;; Note this is a no-op if enable-local-variables is nil. |
| 3497 | ;; We don't use `hack-dir-local-get-variables-functions' here, because | ||
| 3498 | ;; modes are specific to Emacs. | ||
| 3497 | (let* ((mode-alist (cdr (hack-dir-local--get-variables | 3499 | (let* ((mode-alist (cdr (hack-dir-local--get-variables |
| 3498 | (lambda (key) (eq key 'auto-mode-alist)))))) | 3500 | (lambda (key) (eq key 'auto-mode-alist)))))) |
| 3499 | (set-auto-mode--apply-alist mode-alist keep-mode-if-same t))) | 3501 | (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." | |||
| 4769 | 4771 | ||
| 4770 | (defvar hack-dir-local-variables--warned-coding nil) | 4772 | (defvar hack-dir-local-variables--warned-coding nil) |
| 4771 | 4773 | ||
| 4772 | (defun hack-dir-local--get-variables (predicate) | 4774 | (defun hack-dir-local--get-variables (&optional predicate) |
| 4773 | "Read per-directory local variables for the current buffer. | 4775 | "Read per-directory local variables for the current buffer. |
| 4774 | Return a cons of the form (DIR . ALIST), where DIR is the | 4776 | Return a cons of the form (DIR . ALIST), where DIR is the |
| 4775 | directory name (maybe nil) and ALIST is an alist of all variables | 4777 | directory name (maybe nil) and ALIST is an alist of all variables |
| @@ -4799,6 +4801,16 @@ PREDICATE is passed to `dir-locals-collect-variables'." | |||
| 4799 | (dir-locals-get-class-variables class) | 4801 | (dir-locals-get-class-variables class) |
| 4800 | dir-name nil predicate)))))) | 4802 | dir-name nil predicate)))))) |
| 4801 | 4803 | ||
| 4804 | (defvar hack-dir-local-get-variables-functions | ||
| 4805 | (list #'hack-dir-local--get-variables) | ||
| 4806 | "Special hook to compute the set of dir-local variables. | ||
| 4807 | Every function is called without arguments and should return either | ||
| 4808 | a cons of the form (DIR . ALIST) or a (possibly empty) list of such conses, | ||
| 4809 | where ALIST is an alist of (VAR . VAL) settings. | ||
| 4810 | DIR should be a string (a directory name) and is used to obey | ||
| 4811 | `safe-local-variable-directories'. | ||
| 4812 | This hook is run after the major mode has been setup.") | ||
| 4813 | |||
| 4802 | (defun hack-dir-local-variables () | 4814 | (defun hack-dir-local-variables () |
| 4803 | "Read per-directory local variables for the current buffer. | 4815 | "Read per-directory local variables for the current buffer. |
| 4804 | Store the directory-local variables in `dir-local-variables-alist' | 4816 | Store the directory-local variables in `dir-local-variables-alist' |
| @@ -4806,21 +4818,53 @@ and `file-local-variables-alist', without applying them. | |||
| 4806 | 4818 | ||
| 4807 | This does nothing if either `enable-local-variables' or | 4819 | This does nothing if either `enable-local-variables' or |
| 4808 | `enable-dir-local-variables' are nil." | 4820 | `enable-dir-local-variables' are nil." |
| 4809 | (let* ((items (hack-dir-local--get-variables nil)) | 4821 | (let (items) |
| 4810 | (dir-name (car items)) | 4822 | (when (and enable-local-variables |
| 4811 | (variables (cdr items))) | 4823 | enable-dir-local-variables |
| 4812 | (when variables | 4824 | (or enable-remote-dir-locals |
| 4813 | (dolist (elt variables) | 4825 | (not (file-remote-p (or (buffer-file-name) |
| 4814 | (if (eq (car elt) 'coding) | 4826 | default-directory))))) |
| 4815 | (unless hack-dir-local-variables--warned-coding | 4827 | (run-hook-wrapped 'hack-dir-local-get-variables-functions |
| 4816 | (setq hack-dir-local-variables--warned-coding t) | 4828 | (lambda (fun) |
| 4817 | (display-warning 'files | 4829 | (let ((res (funcall fun))) |
| 4818 | "Coding cannot be specified by dir-locals")) | 4830 | (cond |
| 4819 | (unless (memq (car elt) '(eval mode)) | 4831 | ((null res)) |
| 4820 | (setq dir-local-variables-alist | 4832 | ((consp (car-safe res)) |
| 4821 | (assq-delete-all (car elt) dir-local-variables-alist))) | 4833 | (setq items (append res items))) |
| 4822 | (push elt dir-local-variables-alist))) | 4834 | (t (push res items))))))) |
| 4823 | (hack-local-variables-filter variables dir-name)))) | 4835 | ;; Sort the entries from nearest dir to furthest dir. |
| 4836 | (setq items (sort (nreverse items) | ||
| 4837 | :key (lambda (x) (length (car-safe x))) :reverse t)) | ||
| 4838 | ;; Filter out duplicates, preferring the settings from the nearest dir | ||
| 4839 | ;; and from the first hook function. | ||
| 4840 | (let ((seen nil)) | ||
| 4841 | (dolist (item items) | ||
| 4842 | (when seen ;; Special case seen=nil since it's the most common case. | ||
| 4843 | (setcdr item (seq-filter (lambda (vv) (not (memq (car-safe vv) seen))) | ||
| 4844 | (cdr item)))) | ||
| 4845 | (setq seen (nconc (seq-difference (mapcar #'car (cdr item)) | ||
| 4846 | '(eval mode)) | ||
| 4847 | seen)))) | ||
| 4848 | ;; Rather than a loop, maybe we should handle all the dirs | ||
| 4849 | ;; "together", e.g. prompting the user only once. But if so, we'd | ||
| 4850 | ;; probably want to also merge the prompt for file-local vars, | ||
| 4851 | ;; which comes from the call to `hack-local-variables-filter' in | ||
| 4852 | ;; `hack-local-variables'. | ||
| 4853 | (dolist (item items) | ||
| 4854 | (let ((dir-name (car items)) | ||
| 4855 | (variables (cdr items))) | ||
| 4856 | (when variables | ||
| 4857 | (dolist (elt variables) | ||
| 4858 | (if (eq (car elt) 'coding) | ||
| 4859 | (unless hack-dir-local-variables--warned-coding | ||
| 4860 | (setq hack-dir-local-variables--warned-coding t) | ||
| 4861 | (display-warning 'files | ||
| 4862 | "Coding cannot be specified by dir-locals")) | ||
| 4863 | (unless (memq (car elt) '(eval mode)) | ||
| 4864 | (setq dir-local-variables-alist | ||
| 4865 | (assq-delete-all (car elt) dir-local-variables-alist))) | ||
| 4866 | (push elt dir-local-variables-alist))) | ||
| 4867 | (hack-local-variables-filter variables dir-name)))))) | ||
| 4824 | 4868 | ||
| 4825 | (defun hack-dir-local-variables-non-file-buffer () | 4869 | (defun hack-dir-local-variables-non-file-buffer () |
| 4826 | "Apply directory-local variables to a non-file buffer. | 4870 | "Apply directory-local variables to a non-file buffer. |