(catch 'exit
(unless enable-local-variables
(throw 'exit (message "Directory-local variables are disabled")))
- (let ((variables-file (or (and (buffer-file-name)
- (not (file-remote-p (buffer-file-name)))
- (dir-locals-find-file (buffer-file-name)))
- dir-locals-file))
+ (let ((variables-file (and (buffer-file-name)
+ (not (file-remote-p (buffer-file-name)))
+ (dir-locals-find-file (buffer-file-name))))
variables)
- (if (consp variables-file) ; result from cache
- ;; If cache element has an mtime, assume it came from a file.
- ;; Otherwise, assume it was set directly.
- (setq variables-file (if (nth 2 variables-file)
- (expand-file-name dir-locals-file
- (car variables-file))
- (cadr variables-file))))
+ (setq variables-file
+ ;; If there are several .dir-locals, the user probably
+ ;; wants to edit the last one (the highest priority).
+ (cond ((stringp variables-file)
+ (car (last (file-expand-wildcards variables-file))))
+ ((consp variables-file) ; result from cache
+ ;; If cache element has an mtime, assume it came from a file.
+ ;; Otherwise, assume it was set directly.
+ (if (nth 2 variables-file)
+ (let ((default-directory (car variables-file)))
+ (car (last (file-expand-wildcards dir-locals-file 'full))))
+ (cadr variables-file)))
+ (t dir-locals-file)))
;; I can't be bothered to handle this case right now.
;; Dir locals were set directly from a class. You need to
;; directly modify the class in dir-locals-class-alist.
;;; Code:
+(require 'map)
+(require 'seq)
(defvar font-lock-keywords)
(defgroup backup nil
(error
;; The file's content might be invalid (e.g. have a merge conflict), but
;; that shouldn't prevent the user from opening the file.
- (message ".dir-locals error: %s" (error-message-string err))
+ (message "%s error: %s" dir-locals-file (error-message-string err))
nil))))
(defun dir-locals-set-directory-class (directory class &optional mtime)
applied by recursively following these rules."
(setf (alist-get class dir-locals-class-alist) variables))
-(defconst dir-locals-file ".dir-locals.el"
+(defconst dir-locals-file ".dir-locals*.el"
"File that contains directory-local variables.
It has to be constant to enforce uniform values
across different environments and users.")
This function returns either nil (no directory local variables found),
or the matching entry from `dir-locals-directory-cache' (a list),
or the full path to the `dir-locals-file' (a string) in the case
-of no valid cache entry."
- (setq file (expand-file-name file))
- (let* ((dir-locals-file-name
- (if (eq system-type 'ms-dos)
- (dosified-file-name dir-locals-file)
- dir-locals-file))
- (locals-file (locate-dominating-file file dir-locals-file-name))
- (dir-elt nil))
+of no valid cache entry. If `dir-locals-file' contains
+wildcards, then the return value is not a proper filename, it is
+an absolute version of `dir-locals-file' which is guaranteed to
+expand to at least one file."
+ (setq file (file-name-directory (expand-file-name file)))
+ (let* ((dir-locals-file-name (if (eq system-type 'ms-dos)
+ (dosified-file-name dir-locals-file)
+ dir-locals-file))
+ (locals-dir (locate-dominating-file
+ file (lambda (dir)
+ (let ((default-directory dir))
+ (file-expand-wildcards dir-locals-file-name 'full)))))
+ locals-file dir-elt)
;; `locate-dominating-file' may have abbreviated the name.
- (and locals-file
- (setq locals-file (expand-file-name dir-locals-file-name locals-file)))
+ (when locals-dir
+ (setq locals-dir (expand-file-name locals-dir))
+ (setq locals-file (expand-file-name dir-locals-file-name locals-dir)))
;; Let dir-locals-read-from-file inform us via demoted-errors
;; about unreadable files, etc.
;; Maybe we'd want to keep searching though - that is
;; Find the best cached value in `dir-locals-directory-cache'.
(dolist (elt dir-locals-directory-cache)
(when (and (string-prefix-p (car elt) file
- (memq system-type
- '(windows-nt cygwin ms-dos)))
- (> (length (car elt)) (length (car dir-elt))))
- (setq dir-elt elt)))
+ (memq system-type
+ '(windows-nt cygwin ms-dos)))
+ (> (length (car elt)) (length (car dir-elt))))
+ (setq dir-elt elt)))
(if (and dir-elt
- (or (null locals-file)
- (<= (length (file-name-directory locals-file))
- (length (car dir-elt)))))
- ;; Found a potential cache entry. Check validity.
- ;; A cache entry with no MTIME is assumed to always be valid
- ;; (ie, set directly, not from a dir-locals file).
- ;; Note, we don't bother to check that there is a matching class
- ;; element in dir-locals-class-alist, since that's done by
- ;; dir-locals-set-directory-class.
- (if (or (null (nth 2 dir-elt))
- (let ((cached-file (expand-file-name dir-locals-file-name
- (car dir-elt))))
- (and (file-readable-p cached-file)
- (equal (nth 2 dir-elt)
- (nth 5 (file-attributes cached-file))))))
- ;; This cache entry is OK.
- dir-elt
- ;; This cache entry is invalid; clear it.
- (setq dir-locals-directory-cache
- (delq dir-elt dir-locals-directory-cache))
- ;; Return the first existing dir-locals file. Might be the same
- ;; as dir-elt's, might not (eg latter might have been deleted).
- locals-file)
+ (or (null locals-dir)
+ (<= (length locals-dir)
+ (length (car dir-elt)))))
+ ;; Found a potential cache entry. Check validity.
+ ;; A cache entry with no MTIME is assumed to always be valid
+ ;; (ie, set directly, not from a dir-locals file).
+ ;; Note, we don't bother to check that there is a matching class
+ ;; element in dir-locals-class-alist, since that's done by
+ ;; dir-locals-set-directory-class.
+ (if (or (null (nth 2 dir-elt))
+ (let* ((default-directory (car dir-elt))
+ (cached-files (seq-filter #'file-readable-p
+ (file-expand-wildcards dir-locals-file))))
+ ;; The entry MTIME should match the most recent
+ ;; MTIME among matching files.
+ (and cached-files
+ (= (time-to-seconds (nth 2 dir-elt))
+ (apply #'max (mapcar (lambda (f) (time-to-seconds (nth 5 (file-attributes f))))
+ cached-files))))))
+ ;; This cache entry is OK.
+ dir-elt
+ ;; This cache entry is invalid; clear it.
+ (setq dir-locals-directory-cache
+ (delq dir-elt dir-locals-directory-cache))
+ ;; Return the first existing dir-locals file. Might be the same
+ ;; as dir-elt's, might not (eg latter might have been deleted).
+ locals-file)
;; No cache entry.
locals-file)))
(defun dir-locals-read-from-file (file)
"Load a variables FILE and register a new class and instance.
-FILE is the name of the file holding the variables to apply.
+FILE is the absolute name of the file holding the variables to
+apply. It may contain wildcards.
The new class name is the same as the directory in which FILE
is found. Returns the new class name."
- (with-temp-buffer
+ (let* ((dir-name (file-name-directory file))
+ (class-name (intern dir-name))
+ (files (sort (file-expand-wildcards "/home/artur/Git/emacs/.dir-locals*.el") #'string<))
+ (read-circle nil)
+ (variables))
(with-demoted-errors "Error reading dir-locals: %S"
- (insert-file-contents file)
- (unless (zerop (buffer-size))
- (let* ((dir-name (file-name-directory file))
- (class-name (intern dir-name))
- (variables (let ((read-circle nil))
- (read (current-buffer)))))
- (dir-locals-set-class-variables class-name variables)
- (dir-locals-set-directory-class dir-name class-name
- (nth 5 (file-attributes file)))
- class-name)))))
+ (dolist (file files)
+ (with-temp-buffer
+ (insert-file-contents file)
+ (condition-case-unless-debug nil
+ (setq variables
+ (map-merge-with 'list (lambda (a b) (map-merge 'list a b))
+ variables
+ (read (current-buffer))))
+ (end-of-file nil)))))
+ (dir-locals-set-class-variables class-name variables)
+ (dir-locals-set-directory-class
+ dir-name class-name
+ (seconds-to-time (apply #'max (mapcar (lambda (file)
+ (time-to-seconds (nth 5 (file-attributes file))))
+ files))))
+ class-name))
(defcustom enable-remote-dir-locals nil
"Non-nil means dir-local variables will be applied to remote files."
(not (file-remote-p (or (buffer-file-name)
default-directory)))))
;; Find the variables file.
- (let ((variables-file (dir-locals-find-file
- (or (buffer-file-name) default-directory)))
+ (let ((file-pattern-or-cache (dir-locals-find-file
+ (or (buffer-file-name) default-directory)))
(class nil)
(dir-name nil))
(cond
- ((stringp variables-file)
- (setq dir-name (file-name-directory variables-file)
- class (dir-locals-read-from-file variables-file)))
- ((consp variables-file)
- (setq dir-name (nth 0 variables-file))
- (setq class (nth 1 variables-file))))
+ ((stringp file-pattern-or-cache)
+ (setq dir-name (file-name-directory file-pattern-or-cache)
+ class (dir-locals-read-from-file file-pattern-or-cache)))
+ ((consp file-pattern-or-cache)
+ (setq dir-name (nth 0 file-pattern-or-cache))
+ (setq class (nth 1 file-pattern-or-cache))))
(when class
(let ((variables
(dir-locals-collect-variables
(buffer-file-name buffer)))
(dir-locals-find-file
(buffer-file-name buffer))))
- (dir-file t))
+ (is-directory nil))
(princ (substitute-command-keys
" This variable's value is directory-local"))
- (if (null file)
- (princ ".\n")
- (princ ", set ")
- (if (consp file) ; result from cache
- ;; If the cache element has an mtime, we
- ;; assume it came from a file.
- (if (nth 2 file)
- (setq file (expand-file-name
- dir-locals-file (car file)))
- ;; Otherwise, assume it was set directly.
- (setq file (car file)
- dir-file nil)))
- (princ (substitute-command-keys
- (if dir-file
- "by the file\n `"
- "for the directory\n `")))
+ (when (consp file) ; result from cache
+ ;; If the cache element has an mtime, we
+ ;; assume it came from a file.
+ (if (nth 2 file)
+ (setq file (expand-file-name
+ dir-locals-file (car file)))
+ ;; Otherwise, assume it was set directly.
+ (setq file (car file)
+ is-directory t)))
+ (if (null file)
+ (princ ".\n")
+ (princ ", set ")
+ (let ((files (file-expand-wildcards file)))
+ (princ (substitute-command-keys
+ (cond
+ (is-directory "for the directory\n `")
+ ;; Many files matched.
+ ((cdr files)
+ (setq file (file-name-directory (car files)))
+ (format "by a file\n matching `%s' in the directory\n `"
+ dir-locals-file))
+ (t (setq file (car files))
+ "by the file\n `"))))
(with-current-buffer standard-output
(insert-text-button
file 'type 'help-dir-local-var-def
- 'help-args (list variable file)))
+ 'help-args (list variable file))))
(princ (substitute-command-keys "'.\n"))))
(princ (substitute-command-keys
" This variable's value is file-local.\n"))))