summaryrefslogtreecommitdiff
path: root/lisp/files.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/files.el')
-rw-r--r--lisp/files.el1180
1 files changed, 753 insertions, 427 deletions
diff --git a/lisp/files.el b/lisp/files.el
index dada69c1457..77977f14116 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -391,6 +391,10 @@ constructed by taking the directory part of the replaced file-name,
concatenated with the buffer file name with all directory separators
changed to `!' to prevent clashes. This will not work
correctly if your filesystem truncates the resulting name.
+If UNIQUIFY is one of the members of `secure-hash-algorithms',
+Emacs constructs the nondirectory part of the auto-save file name
+by applying that `secure-hash' to the buffer file name. This
+avoids any risk of excessively long file names.
All the transforms in the list are tried, in the order they are listed.
When one transform applies, its result is final;
@@ -461,6 +465,31 @@ If `silently', don't ask the user before saving."
:type '(choice (const t) (const nil) (const silently))
:group 'abbrev)
+(defcustom lock-file-name-transforms nil
+ "Transforms to apply to buffer file name before making a lock file name.
+This has the same syntax as
+`auto-save-file-name-transforms' (which see), but instead of
+applying to auto-save file names, it's applied to lock file names.
+
+By default, a lock file is put into the same directory as the
+file it's locking, and it has the same name, but with \".#\" prepended."
+ :group 'files
+ :type '(repeat (list (regexp :tag "Regexp")
+ (string :tag "Replacement")
+ (boolean :tag "Uniquify")))
+ :version "28.1")
+
+(defcustom remote-file-name-inhibit-locks nil
+ "Whether to use file locks for remote files."
+ :group 'files
+ :version "28.1"
+ :type 'boolean)
+
+(define-minor-mode lock-file-mode
+ "Toggle file locking in the current buffer (Lock File mode)."
+ :version "28.1"
+ (setq-local create-lockfiles (and lock-file-mode t)))
+
(defcustom find-file-run-dired t
"Non-nil means allow `find-file' to visit directories.
To visit the directory, `find-file' runs `find-directory-functions'."
@@ -577,7 +606,9 @@ a -*- line.
The command \\[normal-mode], when used interactively,
always obeys file local variable specifications and the -*- line,
-and ignores this variable."
+and ignores this variable.
+
+Also see the `permanently-enabled-local-variables' variable."
:risky t
:type '(choice (const :tag "Query Unsafe" t)
(const :tag "Safe Only" :safe)
@@ -751,7 +782,7 @@ nil (meaning `default-directory') as the associated list element."
(let ((spath (substitute-env-vars search-path)))
(mapcar (lambda (f)
(if (equal "" f) nil
- (let ((dir (expand-file-name (file-name-as-directory f))))
+ (let ((dir (file-name-as-directory f)))
;; Previous implementation used `substitute-in-file-name'
;; which collapse multiple "/" in front. Do the same for
;; backward compatibility.
@@ -823,7 +854,9 @@ The path separator is colon in GNU and GNU-like systems."
(expand-file-name dir))
(locate-file dir cd-path nil
(lambda (f) (and (file-directory-p f) 'dir-ok)))
- (error "No such directory found via CDPATH environment variable"))))
+ (if (getenv "CDPATH")
+ (error "No such directory found via CDPATH environment variable: %s" dir)
+ (error "No such directory: %s" dir)))))
(defun directory-files-recursively (dir regexp
&optional include-directories predicate
@@ -906,6 +939,8 @@ See `file-symlink-p' to distinguish symlinks."
(read-file-name "Load file: " nil nil 'lambda))))
(load (expand-file-name file) nil nil t))
+(defvar comp-eln-to-el-h)
+
(defun locate-file (filename path &optional suffixes predicate)
"Search for FILENAME through PATH.
If found, return the absolute file name of FILENAME; otherwise
@@ -932,7 +967,10 @@ one or more of those symbols."
(logior (if (memq 'executable predicate) 1 0)
(if (memq 'writable predicate) 2 0)
(if (memq 'readable predicate) 4 0))))
- (locate-file-internal filename path suffixes predicate))
+ (let ((file (locate-file-internal filename path suffixes predicate)))
+ (if (and file (string-match "\\.eln\\'" file))
+ (gethash (file-name-nondirectory file) comp-eln-to-el-h)
+ file)))
(defun locate-file-completion-table (dirs suffixes string pred action)
"Do completion for file names passed to `locate-file'."
@@ -998,7 +1036,7 @@ Any directory whose name matches this regexp will be treated like
a kind of root directory by `locate-dominating-file', which will stop its
search when it bumps into it.
The default regexp prevents fruitless and time-consuming attempts to find
-special files in directories in which filenames are interpreted as hostnames,
+special files in directories in which file names are interpreted as host names,
or mount points potentially requiring authentication as a different user.")
(defun locate-dominating-file (file name)
@@ -1639,20 +1677,21 @@ called additional times).
This macro actually adds an auxiliary function that calls FUN,
rather than FUN itself, to `minibuffer-setup-hook'."
- (declare (indent 1) (debug t))
+ (declare (indent 1) (debug ([&or (":append" form) [&or symbolp form]] body)))
(let ((hook (make-symbol "setup-hook"))
(funsym (make-symbol "fun"))
(append nil))
(when (eq (car-safe fun) :append)
(setq append '(t) fun (cadr fun)))
`(let ((,funsym ,fun)
- ,hook)
- (setq ,hook
- (lambda ()
- ;; Clear out this hook so it does not interfere
- ;; with any recursive minibuffer usage.
- (remove-hook 'minibuffer-setup-hook ,hook)
- (funcall ,funsym)))
+ ;; Use a symbol to make sure `add-hook' doesn't waste time
+ ;; in `equal'ity testing (bug#46326).
+ (,hook (make-symbol "minibuffer-setup")))
+ (fset ,hook (lambda ()
+ ;; Clear out this hook so it does not interfere
+ ;; with any recursive minibuffer usage.
+ (remove-hook 'minibuffer-setup-hook ,hook)
+ (funcall ,funsym)))
(unwind-protect
(progn
(add-hook 'minibuffer-setup-hook ,hook ,@append)
@@ -1663,6 +1702,10 @@ rather than FUN itself, to `minibuffer-setup-hook'."
(list (read-file-name prompt nil default-directory mustmatch)
t))
+(defun file-name-history--add (file)
+ "Add FILE to `file-name-history'."
+ (add-to-history 'file-name-history (abbreviate-file-name file)))
+
(defun find-file (filename &optional wildcards)
"Edit file FILENAME.
Switch to a buffer visiting file FILENAME,
@@ -2119,29 +2162,75 @@ think it does, because \"free\" is pretty hard to define in practice."
:version "25.1"
:type '(choice integer (const :tag "Never issue warning" nil)))
+(defcustom query-about-changed-file t
+ "If non-nil, query the user when re-visiting a file that has changed.
+This happens if the file is already visited in a buffer, the
+file was changed externally, and the user re-visits the file.
+
+If nil, don't prompt the user, but instead provide instructions for
+reverting, after switching to the buffer with its contents before
+the external changes."
+ :group 'files
+ :group 'find-file
+ :version "28.1"
+ :type 'boolean)
+
(declare-function x-popup-dialog "menu.c" (position contents &optional header))
+(defun files--ask-user-about-large-file-help-text (op-type size)
+ "Format the text that explains the options to open large files in Emacs.
+OP-TYPE contains the kind of file operation that will be
+performed. SIZE is the size of the large file."
+ (format
+ "The file that you want to %s is large (%s), which exceeds the
+ threshold above which Emacs asks for confirmation (%s).
+
+ Large files may be slow to edit or navigate so Emacs asks you
+ before you try to %s such files.
+
+ You can press:
+ 'y' to %s the file.
+ 'n' to abort, and not %s the file.
+ 'l' (the letter ell) to %s the file literally, which means that
+ Emacs will %s the file without doing any format or character code
+ conversion and in Fundamental mode, without loading any potentially
+ expensive features.
+
+ You can customize the option `large-file-warning-threshold' to be the
+ file size, in bytes, from which Emacs will ask for confirmation. Set
+ it to nil to never request confirmation."
+ op-type
+ size
+ (funcall byte-count-to-string-function large-file-warning-threshold)
+ op-type
+ op-type
+ op-type
+ op-type
+ op-type))
+
(defun files--ask-user-about-large-file (size op-type filename offer-raw)
+ "Query the user about what to do with large files.
+Files are \"large\" if file SIZE is larger than `large-file-warning-threshold'.
+
+OP-TYPE specifies the file operation being performed on FILENAME.
+
+If OFFER-RAW is true, give user the additional option to open the
+file literally."
(let ((prompt (format "File %s is large (%s), really %s?"
(file-name-nondirectory filename)
(funcall byte-count-to-string-function size) op-type)))
(if (not offer-raw)
(if (y-or-n-p prompt) nil 'abort)
- (let* ((use-dialog (and (display-popup-menus-p)
- last-input-event
- (listp last-nonmenu-event)
- use-dialog-box))
- (choice
- (if use-dialog
- (x-popup-dialog t `(,prompt
- ("Yes" . ?y)
- ("No" . ?n)
- ("Open literally" . ?l)))
- (read-char-choice
- (concat prompt " (y)es or (n)o or (l)iterally ")
- '(?y ?Y ?n ?N ?l ?L)))))
- (cond ((memq choice '(?y ?Y)) nil)
- ((memq choice '(?l ?L)) 'raw)
+ (let ((choice
+ (car
+ (read-multiple-choice
+ prompt '((?y "yes")
+ (?n "no")
+ (?l "literally"))
+ (files--ask-user-about-large-file-help-text
+ op-type (funcall byte-count-to-string-function size))))))
+ (cond ((eq choice ?y) nil)
+ ((eq choice ?l) 'raw)
(t 'abort))))))
(defun abort-if-file-too-large (size op-type filename &optional offer-raw)
@@ -2241,7 +2330,8 @@ the various files."
;; Check to see if the file looks uncommonly large.
(when (not (or buf nowarn))
(when (eq (abort-if-file-too-large
- (file-attribute-size attributes) "open" filename t)
+ (file-attribute-size attributes) "open" filename
+ (not rawfile))
'raw)
(setf rawfile t))
(warn-maybe-out-of-memory (file-attribute-size attributes)))
@@ -2267,6 +2357,14 @@ the various files."
(message "Reverting file %s..." filename)
(revert-buffer t t)
(message "Reverting file %s...done" filename)))
+ ((not query-about-changed-file)
+ (message
+ (substitute-command-keys
+ "File %s changed on disk. \\[revert-buffer] to load new contents%s")
+ (file-name-nondirectory filename)
+ (if (buffer-modified-p buf)
+ " and discard your edits"
+ "")))
((yes-or-no-p
(if (string= (file-name-nondirectory filename)
(buffer-name buf))
@@ -2382,7 +2480,8 @@ Do you want to revisit the file normally now? ")))
(set-buffer-multibyte t))
(if rawfile
(condition-case ()
- (let ((inhibit-read-only t))
+ (let ((inhibit-read-only t)
+ (enable-local-variables nil))
(insert-file-contents-literally filename t))
(file-error
(when (and (file-exists-p filename)
@@ -2421,7 +2520,7 @@ Do you want to revisit the file normally now? ")))
(not (funcall backup-enable-predicate buffer-file-name))
(setq-local backup-inhibited t))
(if rawfile
- (progn
+ (let ((enable-local-variables nil))
(set-buffer-multibyte nil)
(setq buffer-file-coding-system 'no-conversion)
(set-buffer-major-mode buf)
@@ -2529,23 +2628,20 @@ unless NOMODES is non-nil."
(let* (not-serious
(msg
(cond
- ((not warn) nil)
- ((and error (file-attributes buffer-file-name))
+ ((and error (file-exists-p buffer-file-name))
(setq buffer-read-only t)
- (if (and (file-symlink-p buffer-file-name)
- (not (file-exists-p
- (file-chase-links buffer-file-name))))
- "Symbolic link that points to nonexistent file"
- "File exists, but cannot be read"))
+ "File exists, but cannot be read")
+ ((and error (file-symlink-p buffer-file-name))
+ "Symbolic link that points to nonexistent file")
((not buffer-read-only)
- (if (and warn
- ;; No need to warn if buffer is auto-saved
- ;; under the name of the visited file.
- (not (and buffer-file-name
- auto-save-visited-file-name))
- (file-newer-than-file-p (or buffer-auto-save-file-name
- (make-auto-save-file-name))
- buffer-file-name))
+ (if (and
+ ;; No need to warn if buffer is auto-saved
+ ;; under the name of the visited file.
+ (not (and buffer-file-name
+ auto-save-visited-file-name))
+ (file-newer-than-file-p (or buffer-auto-save-file-name
+ (make-auto-save-file-name))
+ buffer-file-name))
(format "%s has auto save data; consider M-x recover-this-file"
(file-name-nondirectory buffer-file-name))
(setq not-serious t)
@@ -2553,14 +2649,13 @@ unless NOMODES is non-nil."
((not error)
(setq not-serious t)
"Note: file is write protected")
- ((file-attributes (directory-file-name default-directory))
+ ((file-accessible-directory-p default-directory)
"File not found and directory write-protected")
- ((file-exists-p (file-name-directory buffer-file-name))
- (setq buffer-read-only nil))
(t
(setq buffer-read-only nil)
- "Use M-x make-directory RET RET to create the directory and its parents"))))
- (when msg
+ (unless (file-directory-p default-directory)
+ "Use M-x make-directory RET RET to create the directory and its parents")))))
+ (when (and warn msg)
(message "%s" msg)
(or not-serious (sit-for 1 t))))
(when (and auto-save-default (not noauto))
@@ -2726,6 +2821,7 @@ since only a single case-insensitive search through the alist is made."
("\\.scm\\.[0-9]*\\'" . scheme-mode)
("\\.[ckz]?sh\\'\\|\\.shar\\'\\|/\\.z?profile\\'" . sh-mode)
("\\.bash\\'" . sh-mode)
+ ("/PKGBUILD\\'" . sh-mode)
("\\(/\\|\\`\\)\\.\\(bash_\\(profile\\|history\\|log\\(in\\|out\\)\\)\\|z?log\\(in\\|out\\)\\)\\'" . sh-mode)
("\\(/\\|\\`\\)\\.\\(shrc\\|zshrc\\|m?kshrc\\|bashrc\\|t?cshrc\\|esrc\\)\\'" . sh-mode)
("\\(/\\|\\`\\)\\.\\([kz]shenv\\|xinitrc\\|startxrc\\|xsession\\)\\'" . sh-mode)
@@ -2913,7 +3009,7 @@ ARC\\|ZIP\\|LZH\\|LHA\\|ZOO\\|[JEW]AR\\|XPI\\|RAR\\|CBR\\|7Z\\|SQUASHFS\\)\\'" .
("\\.xmp\\'" . image-mode)
("\\.xwd\\'" . image-mode)
("\\.yuv\\'" . image-mode)))
- "Alist of filename patterns vs corresponding major mode functions.
+ "Alist of file name patterns vs corresponding major mode functions.
Each element looks like (REGEXP . FUNCTION) or (REGEXP FUNCTION NON-NIL).
\(NON-NIL stands for anything that is not nil; the value does not matter.)
Visiting a file whose name matches REGEXP specifies FUNCTION as the
@@ -3099,14 +3195,73 @@ If FUNCTION is nil, then it is not called.")
"Upper limit on `magic-mode-alist' regexp matches.
Also applies to `magic-fallback-mode-alist'.")
+(defun set-auto-mode--apply-alist (alist keep-mode-if-same dir-local)
+ "Helper function for `set-auto-mode'.
+This function takes an alist of the same form as
+`auto-mode-alist'. It then tries to find the appropriate match
+in the alist for the current buffer; setting the mode if
+possible.
+Return non-nil if the mode was set, nil otherwise.
+DIR-LOCAL non-nil means this call is via directory-locals, and
+extra checks should be done."
+ (if buffer-file-name
+ (let (mode
+ (name buffer-file-name)
+ (remote-id (file-remote-p buffer-file-name))
+ (case-insensitive-p (file-name-case-insensitive-p
+ buffer-file-name)))
+ ;; Remove backup-suffixes from file name.
+ (setq name (file-name-sans-versions name))
+ ;; Remove remote file name identification.
+ (when (and (stringp remote-id)
+ (string-match (regexp-quote remote-id) name))
+ (setq name (substring name (match-end 0))))
+ (while name
+ ;; Find first matching alist entry.
+ (setq mode
+ (if case-insensitive-p
+ ;; Filesystem is case-insensitive.
+ (let ((case-fold-search t))
+ (assoc-default name alist 'string-match))
+ ;; Filesystem is case-sensitive.
+ (or
+ ;; First match case-sensitively.
+ (let ((case-fold-search nil))
+ (assoc-default name alist 'string-match))
+ ;; Fallback to case-insensitive match.
+ (and auto-mode-case-fold
+ (let ((case-fold-search t))
+ (assoc-default name alist 'string-match))))))
+ (if (and mode
+ (consp mode)
+ (cadr mode))
+ (setq mode (car mode)
+ name (substring name 0 (match-beginning 0)))
+ (setq name nil)))
+ (when (and dir-local mode
+ (not (set-auto-mode--dir-local-valid-p mode)))
+ (message "Ignoring invalid mode `%s'" mode)
+ (setq mode nil))
+ (when mode
+ (set-auto-mode-0 mode keep-mode-if-same)
+ t))))
+
+(defun set-auto-mode--dir-local-valid-p (mode)
+ "Say whether MODE can be used in a .dir-local.el `auto-mode-alist'."
+ (and (symbolp mode)
+ (string-suffix-p "-mode" (symbol-name mode))
+ (commandp mode)
+ (not (provided-mode-derived-p mode 'special-mode))))
+
(defun set-auto-mode (&optional keep-mode-if-same)
"Select major mode appropriate for current buffer.
To find the right major mode, this function checks for a -*- mode tag
checks for a `mode:' entry in the Local Variables section of the file,
+checks if there an `auto-mode-alist' entry in `.dir-locals.el',
checks if it uses an interpreter listed in `interpreter-mode-alist',
matches the buffer beginning against `magic-mode-alist',
-compares the filename against the entries in `auto-mode-alist',
+compares the file name against the entries in `auto-mode-alist',
then matches the buffer beginning against `magic-fallback-mode-alist'.
If `enable-local-variables' is nil, or if the file name matches
@@ -3160,13 +3315,16 @@ we don't actually set it to the same mode the buffer already has."
(or (set-auto-mode-0 mode keep-mode-if-same)
;; continuing would call minor modes again, toggling them off
(throw 'nop nil))))))
- ;; hack-local-variables checks local-enable-local-variables etc, but
- ;; we might as well be explicit here for the sake of clarity.
+ ;; Check for auto-mode-alist entry in dir-locals.
+ (unless done
+ (with-demoted-errors "Directory-local variables error: %s"
+ ;; Note this is a no-op if enable-local-variables is nil.
+ (let* ((mode-alist (cdr (hack-dir-local--get-variables
+ (lambda (key) (eq key 'auto-mode-alist))))))
+ (setq done (set-auto-mode--apply-alist mode-alist
+ keep-mode-if-same t)))))
(and (not done)
- enable-local-variables
- local-enable-local-variables
- try-locals
- (setq mode (hack-local-variables t))
+ (setq mode (hack-local-variables t (not try-locals)))
(not (memq mode modes)) ; already tried and failed
(if (not (functionp mode))
(message "Ignoring unknown mode `%s'" mode)
@@ -3216,45 +3374,8 @@ we don't actually set it to the same mode the buffer already has."
(set-auto-mode-0 done keep-mode-if-same)))
;; Next compare the filename against the entries in auto-mode-alist.
(unless done
- (if buffer-file-name
- (let ((name buffer-file-name)
- (remote-id (file-remote-p buffer-file-name))
- (case-insensitive-p (file-name-case-insensitive-p
- buffer-file-name)))
- ;; Remove backup-suffixes from file name.
- (setq name (file-name-sans-versions name))
- ;; Remove remote file name identification.
- (when (and (stringp remote-id)
- (string-match (regexp-quote remote-id) name))
- (setq name (substring name (match-end 0))))
- (while name
- ;; Find first matching alist entry.
- (setq mode
- (if case-insensitive-p
- ;; Filesystem is case-insensitive.
- (let ((case-fold-search t))
- (assoc-default name auto-mode-alist
- 'string-match))
- ;; Filesystem is case-sensitive.
- (or
- ;; First match case-sensitively.
- (let ((case-fold-search nil))
- (assoc-default name auto-mode-alist
- 'string-match))
- ;; Fallback to case-insensitive match.
- (and auto-mode-case-fold
- (let ((case-fold-search t))
- (assoc-default name auto-mode-alist
- 'string-match))))))
- (if (and mode
- (consp mode)
- (cadr mode))
- (setq mode (car mode)
- name (substring name 0 (match-beginning 0)))
- (setq name nil))
- (when mode
- (set-auto-mode-0 mode keep-mode-if-same)
- (setq done t))))))
+ (setq done (set-auto-mode--apply-alist auto-mode-alist
+ keep-mode-if-same nil)))
;; Next try matching the buffer beginning against magic-fallback-mode-alist.
(unless done
(if (setq done (save-excursion
@@ -3348,13 +3469,27 @@ Major modes can use this to examine user-specified local variables
in order to initialize other data structure based on them.")
(defcustom safe-local-variable-values nil
- "List variable-value pairs that are considered safe.
+ "List of variable-value pairs that are considered safe.
Each element is a cons cell (VAR . VAL), where VAR is a variable
-symbol and VAL is a value that is considered safe."
+symbol and VAL is a value that is considered safe.
+
+Also see `ignored-local-variable-values'."
:risky t
:group 'find-file
:type 'alist)
+(defcustom ignored-local-variable-values nil
+ "List of variable-value pairs that should always be ignored.
+Each element is a cons cell (VAR . VAL), where VAR is a variable
+symbol and VAL is its value; if VAR is set to VAL by a file-local
+variables section, that setting should be ignored.
+
+Also see `safe-local-variable-values'."
+ :risky t
+ :group 'find-file
+ :type 'alist
+ :version "28.1")
+
(defcustom safe-local-eval-forms
;; This should be here at least as long as Emacs supports write-file-hooks.
'((add-hook 'write-file-hooks 'time-stamp)
@@ -3465,6 +3600,10 @@ function is allowed to change the contents of this alist.
This hook is called only if there is at least one file-local
variable to set.")
+(defvar permanently-enabled-local-variables '(lexical-binding)
+ "A list of file-local variables that are always enabled.
+This overrides any `enable-local-variables' setting.")
+
(defun hack-local-variables-confirm (all-vars unsafe-vars risky-vars dir-name)
"Get confirmation before setting up local variable values.
ALL-VARS is the list of all variables to be set up.
@@ -3501,7 +3640,9 @@ n -- to ignore the local variables list.")
(if offer-save
(insert "
! -- to apply the local variables list, and permanently mark these
- values (*) as safe (in the future, they will be set automatically.)\n\n")
+ values (*) as safe (in the future, they will be set automatically.)
+i -- to ignore the local variables list, and permanently mark these
+ values (*) as ignored\n\n")
(insert "\n\n"))
(dolist (elt all-vars)
(cond ((member elt unsafe-vars)
@@ -3525,16 +3666,24 @@ n -- to ignore the local variables list.")
(pop-to-buffer buf '(display-buffer--maybe-at-bottom))
(let* ((exit-chars '(?y ?n ?\s))
(prompt (format "Please type %s%s: "
- (if offer-save "y, n, or !" "y or n")
+ (if offer-save "y, n, ! or i" "y or n")
(if (< (line-number-at-pos (point-max))
(window-body-height))
""
", or C-v/M-v to scroll")))
char)
- (if offer-save (push ?! exit-chars))
+ (when offer-save
+ (push ?i exit-chars)
+ (push ?! exit-chars))
(setq char (read-char-choice prompt exit-chars))
- (when (and offer-save (= char ?!) unsafe-vars)
- (customize-push-and-save 'safe-local-variable-values unsafe-vars))
+ (when (and offer-save
+ (or (= char ?!) (= char ?i))
+ unsafe-vars)
+ (customize-push-and-save
+ (if (= char ?!)
+ 'safe-local-variable-values
+ 'ignored-local-variable-values)
+ unsafe-vars))
(prog1 (memq char '(?! ?\s ?y))
(quit-window t)))))))
@@ -3627,13 +3776,18 @@ If these settings come from directory-local variables, then
DIR-NAME is the name of the associated directory. Otherwise it is nil."
;; Find those variables that we may want to save to
;; `safe-local-variable-values'.
- (let (all-vars risky-vars unsafe-vars)
+ (let (all-vars risky-vars unsafe-vars ignored)
(dolist (elt variables)
(let ((var (car elt))
(val (cdr elt)))
(cond ((memq var ignored-local-variables)
;; Ignore any variable in `ignored-local-variables'.
nil)
+ ((seq-some (lambda (elem)
+ (and (eq (car elem) var)
+ (eq (cdr elem) val)))
+ ignored-local-variable-values)
+ nil)
;; Obey `enable-local-eval'.
((eq var 'eval)
(when enable-local-eval
@@ -3678,25 +3832,26 @@ DIR-NAME is the name of the associated directory. Otherwise it is nil."
;; TODO? Warn once per file rather than once per session?
(defvar hack-local-variables--warned-lexical nil)
-(defun hack-local-variables (&optional handle-mode)
+(defun hack-local-variables (&optional handle-mode inhibit-locals)
"Parse and put into effect this buffer's local variables spec.
For buffers visiting files, also puts into effect directory-local
variables.
-Uses `hack-local-variables-apply' to apply the variables.
-If HANDLE-MODE is nil, we apply all the specified local
-variables. If HANDLE-MODE is neither nil nor t, we do the same,
-except that any settings of `mode' are ignored.
+Uses `hack-local-variables-apply' to apply the variables.
-If HANDLE-MODE is t, all we do is check whether a \"mode:\"
-is specified, and return the corresponding mode symbol, or nil.
-In this case, we try to ignore minor-modes, and return only a
-major-mode.
+See `hack-local-variables--find-variables' for the meaning of
+HANDLE-MODE.
-If `enable-local-variables' or `local-enable-local-variables' is nil,
-this function does nothing. If `inhibit-local-variables-regexps'
+If `enable-local-variables' or `local-enable-local-variables' is
+nil, or INHIBIT-LOCALS is non-nil, this function disregards all
+normal local variables. If `inhibit-local-variables-regexps'
applies to the file in question, the file is not scanned for
-local variables, but directory-local variables may still be applied."
+local variables, but directory-local variables may still be
+applied.
+
+Variables present in `permanently-enabled-local-variables' will
+still be evaluated, even if local variables are otherwise
+inhibited."
;; We don't let inhibit-local-variables-p influence the value of
;; enable-local-variables, because then it would affect dir-local
;; variables. We don't want to search eg tar files for file local
@@ -3704,9 +3859,18 @@ local variables, but directory-local variables may still be applied."
;; to them. The real meaning of inhibit-local-variables-p is "do
;; not scan this file for local variables".
(let ((enable-local-variables
- (and local-enable-local-variables enable-local-variables))
- result)
- (unless (eq handle-mode t)
+ (and (not inhibit-locals)
+ local-enable-local-variables enable-local-variables)))
+ (if (eq handle-mode t)
+ ;; We're looking just for the major mode setting.
+ (and enable-local-variables
+ (not (inhibit-local-variables-p))
+ ;; If HANDLE-MODE is t, and the prop line specifies a
+ ;; mode, then we're done, and have no need to scan further.
+ (or (hack-local-variables-prop-line t)
+ ;; Look for the mode elsewhere in the buffer.
+ (hack-local-variables--find-variables t)))
+ ;; Normal handling of local variables.
(setq file-local-variables-alist nil)
(when (and (file-remote-p default-directory)
(fboundp 'hack-connection-local-variables)
@@ -3717,133 +3881,138 @@ local variables, but directory-local variables may still be applied."
(connection-local-criteria-for-default-directory))))
(with-demoted-errors "Directory-local variables error: %s"
;; Note this is a no-op if enable-local-variables is nil.
- (hack-dir-local-variables)))
- ;; This entire function is basically a no-op if enable-local-variables
- ;; is nil. All it does is set file-local-variables-alist to nil.
- (when enable-local-variables
- ;; This part used to ignore enable-local-variables when handle-mode
- ;; was t. That was inappropriate, eg consider the
- ;; (artificial) example of:
- ;; (setq local-enable-local-variables nil)
- ;; Open a file foo.txt that contains "mode: sh".
- ;; It correctly opens in text-mode.
- ;; M-x set-visited-file name foo.c, and it incorrectly stays in text-mode.
- (unless (or (inhibit-local-variables-p)
- ;; If HANDLE-MODE is t, and the prop line specifies a
- ;; mode, then we're done, and have no need to scan further.
- (and (setq result (hack-local-variables-prop-line
- handle-mode))
- (eq handle-mode t)))
- ;; Look for "Local variables:" line in last page.
- (save-excursion
- (goto-char (point-max))
- (search-backward "\n\^L" (max (- (point-max) 3000) (point-min))
- 'move)
- (when (let ((case-fold-search t))
- (search-forward "Local Variables:" nil t))
- (skip-chars-forward " \t")
- ;; suffix is what comes after "local variables:" in its line.
- ;; prefix is what comes before "local variables:" in its line.
- (let ((suffix
- (concat
- (regexp-quote (buffer-substring (point)
- (line-end-position)))
- "$"))
- (prefix
- (concat "^" (regexp-quote
- (buffer-substring (line-beginning-position)
- (match-beginning 0))))))
-
- (forward-line 1)
- (let ((startpos (point))
- endpos
- (thisbuf (current-buffer)))
- (save-excursion
- (unless (let ((case-fold-search t))
- (re-search-forward
- (concat prefix "[ \t]*End:[ \t]*" suffix)
- nil t))
- ;; This used to be an error, but really all it means is
- ;; that this may simply not be a local-variables section,
- ;; so just ignore it.
- (message "Local variables list is not properly terminated"))
- (beginning-of-line)
- (setq endpos (point)))
-
- (with-temp-buffer
- (insert-buffer-substring thisbuf startpos endpos)
- (goto-char (point-min))
- (subst-char-in-region (point) (point-max) ?\^m ?\n)
- (while (not (eobp))
- ;; Discard the prefix.
- (if (looking-at prefix)
- (delete-region (point) (match-end 0))
- (error "Local variables entry is missing the prefix"))
- (end-of-line)
- ;; Discard the suffix.
- (if (looking-back suffix (line-beginning-position))
- (delete-region (match-beginning 0) (point))
- (error "Local variables entry is missing the suffix"))
- (forward-line 1))
- (goto-char (point-min))
-
- (while (not (or (eobp)
- (and (eq handle-mode t) result)))
- ;; Find the variable name;
- (unless (looking-at hack-local-variable-regexp)
- (error "Malformed local variable line: %S"
- (buffer-substring-no-properties
- (point) (line-end-position))))
- (goto-char (match-end 1))
- (let* ((str (match-string 1))
- (var (intern str))
- val val2)
- (and (equal (downcase (symbol-name var)) "mode")
- (setq var 'mode))
- ;; Read the variable value.
- (skip-chars-forward "^:")
- (forward-char 1)
- ;; As a defensive measure, we do not allow
- ;; circular data in the file-local data.
- (let ((read-circle nil))
- (setq val (read (current-buffer))))
- (if (eq handle-mode t)
- (and (eq var 'mode)
- ;; Specifying minor-modes via mode: is
- ;; deprecated, but try to reject them anyway.
- (not (string-match
- "-minor\\'"
- (setq val2 (downcase (symbol-name val)))))
- (setq result (intern (concat val2 "-mode"))))
- (cond ((eq var 'coding))
- ((eq var 'lexical-binding)
- (unless hack-local-variables--warned-lexical
- (setq hack-local-variables--warned-lexical t)
- (display-warning
- 'files
- (format-message
- "%s: `lexical-binding' at end of file unreliable"
- (file-name-nondirectory
- ;; We are called from
- ;; 'with-temp-buffer', so we need
- ;; to use 'thisbuf's name in the
- ;; warning message.
- (or (buffer-file-name thisbuf) ""))))))
- ((and (eq var 'mode) handle-mode))
- (t
- (ignore-errors
- (push (cons (if (eq var 'eval)
- 'eval
- (indirect-variable var))
- val)
- result))))))
- (forward-line 1))))))))
- ;; Now we've read all the local variables.
- ;; If HANDLE-MODE is t, return whether the mode was specified.
- (if (eq handle-mode t) result
- ;; Otherwise, set the variables.
- (hack-local-variables-filter result nil)
- (hack-local-variables-apply)))))
+ (hack-dir-local-variables))
+ (let ((result (append (hack-local-variables--find-variables)
+ (hack-local-variables-prop-line))))
+ (if (and enable-local-variables
+ (not (inhibit-local-variables-p)))
+ (progn
+ ;; Set the variables.
+ (hack-local-variables-filter result nil)
+ (hack-local-variables-apply))
+ ;; Handle `lexical-binding' and other special local
+ ;; variables.
+ (dolist (variable permanently-enabled-local-variables)
+ (when-let ((elem (assq variable result)))
+ (push elem file-local-variables-alist)))
+ (hack-local-variables-apply))))))
+
+(defun hack-local-variables--find-variables (&optional handle-mode)
+ "Return all local variables in the ucrrent buffer.
+If HANDLE-MODE is nil, we gather all the specified local
+variables. If HANDLE-MODE is neither nil nor t, we do the same,
+except that any settings of `mode' are ignored.
+
+If HANDLE-MODE is t, all we do is check whether a \"mode:\"
+is specified, and return the corresponding mode symbol, or nil.
+In this case, we try to ignore minor-modes, and return only a
+major-mode."
+ (let ((result nil))
+ ;; Look for "Local variables:" line in last page.
+ (save-excursion
+ (goto-char (point-max))
+ (search-backward "\n\^L" (max (- (point-max) 3000) (point-min))
+ 'move)
+ (when (let ((case-fold-search t))
+ (search-forward "Local Variables:" nil t))
+ (skip-chars-forward " \t")
+ ;; suffix is what comes after "local variables:" in its line.
+ ;; prefix is what comes before "local variables:" in its line.
+ (let ((suffix
+ (concat
+ (regexp-quote (buffer-substring (point)
+ (line-end-position)))
+ "$"))
+ (prefix
+ (concat "^" (regexp-quote
+ (buffer-substring (line-beginning-position)
+ (match-beginning 0))))))
+
+ (forward-line 1)
+ (let ((startpos (point))
+ endpos
+ (thisbuf (current-buffer)))
+ (save-excursion
+ (unless (let ((case-fold-search t))
+ (re-search-forward
+ (concat prefix "[ \t]*End:[ \t]*" suffix)
+ nil t))
+ ;; This used to be an error, but really all it means is
+ ;; that this may simply not be a local-variables section,
+ ;; so just ignore it.
+ (message "Local variables list is not properly terminated"))
+ (beginning-of-line)
+ (setq endpos (point)))
+
+ (with-temp-buffer
+ (insert-buffer-substring thisbuf startpos endpos)
+ (goto-char (point-min))
+ (subst-char-in-region (point) (point-max) ?\^m ?\n)
+ (while (not (eobp))
+ ;; Discard the prefix.
+ (if (looking-at prefix)
+ (delete-region (point) (match-end 0))
+ (error "Local variables entry is missing the prefix"))
+ (end-of-line)
+ ;; Discard the suffix.
+ (if (looking-back suffix (line-beginning-position))
+ (delete-region (match-beginning 0) (point))
+ (error "Local variables entry is missing the suffix"))
+ (forward-line 1))
+ (goto-char (point-min))
+
+ (while (not (or (eobp)
+ (and (eq handle-mode t) result)))
+ ;; Find the variable name;
+ (unless (looking-at hack-local-variable-regexp)
+ (error "Malformed local variable line: %S"
+ (buffer-substring-no-properties
+ (point) (line-end-position))))
+ (goto-char (match-end 1))
+ (let* ((str (match-string 1))
+ (var (intern str))
+ val val2)
+ (and (equal (downcase (symbol-name var)) "mode")
+ (setq var 'mode))
+ ;; Read the variable value.
+ (skip-chars-forward "^:")
+ (forward-char 1)
+ ;; As a defensive measure, we do not allow
+ ;; circular data in the file-local data.
+ (let ((read-circle nil))
+ (setq val (read (current-buffer))))
+ (if (eq handle-mode t)
+ (and (eq var 'mode)
+ ;; Specifying minor-modes via mode: is
+ ;; deprecated, but try to reject them anyway.
+ (not (string-match
+ "-minor\\'"
+ (setq val2 (downcase (symbol-name val)))))
+ (setq result (intern (concat val2 "-mode"))))
+ (cond ((eq var 'coding))
+ ((eq var 'lexical-binding)
+ (unless hack-local-variables--warned-lexical
+ (setq hack-local-variables--warned-lexical t)
+ (display-warning
+ 'files
+ (format-message
+ "%s: `lexical-binding' at end of file unreliable"
+ (file-name-nondirectory
+ ;; We are called from
+ ;; 'with-temp-buffer', so we need
+ ;; to use 'thisbuf's name in the
+ ;; warning message.
+ (or (buffer-file-name thisbuf) ""))))))
+ ((and (eq var 'mode) handle-mode))
+ (t
+ (ignore-errors
+ (push (cons (if (eq var 'eval)
+ 'eval
+ (indirect-variable var))
+ val)
+ result))))))
+ (forward-line 1)))))))
+ result))
(defun hack-local-variables-apply ()
"Apply the elements of `file-local-variables-alist'.
@@ -3981,7 +4150,7 @@ already the major mode."
('eval
(pcase val
(`(add-hook ',hook . ,_) (hack-one-local-variable--obsolete hook)))
- (save-excursion (eval val)))
+ (save-excursion (eval val t)))
(_
(hack-one-local-variable--obsolete var)
;; Make sure the string has no text properties.
@@ -4027,10 +4196,13 @@ Returns the new list."
;; Need a new cons in case we setcdr later.
(push (cons variable value) variables)))))
-(defun dir-locals-collect-variables (class-variables root variables)
+(defun dir-locals-collect-variables (class-variables root variables
+ &optional predicate)
"Collect entries from CLASS-VARIABLES into VARIABLES.
ROOT is the root directory of the project.
-Return the new variables list."
+Return the new variables list.
+If PREDICATE is given, it is used to test a symbol key in the alist
+to see whether it should be considered."
(let* ((file-name (or (buffer-file-name)
;; Handle non-file buffers, too.
(expand-file-name default-directory)))
@@ -4049,9 +4221,11 @@ Return the new variables list."
(>= (length sub-file-name) (length key))
(string-prefix-p key sub-file-name))
(setq variables (dir-locals-collect-variables
- (cdr entry) root variables))))
- ((or (not key)
- (derived-mode-p key))
+ (cdr entry) root variables predicate))))
+ ((if predicate
+ (funcall predicate key)
+ (or (not key)
+ (derived-mode-p key)))
(let* ((alist (cdr entry))
(subdirs (assq 'subdirs alist)))
(if (or (not subdirs)
@@ -4301,6 +4475,9 @@ Return the new class name, which is a symbol named DIR."
(let ((read-circle nil))
(read (current-buffer)))
(end-of-file nil))))
+ (unless (listp newvars)
+ (message "Invalid data in %s: %s" file newvars)
+ (setq newvars nil))
(setq variables
;; Try and avoid loading `map' since that also loads cl-lib
;; which then might hamper bytecomp warnings (bug#30635).
@@ -4345,13 +4522,13 @@ Return the new class name, which is a symbol named DIR."
(defvar hack-dir-local-variables--warned-coding nil)
-(defun hack-dir-local-variables ()
+(defun hack-dir-local--get-variables (predicate)
"Read per-directory local variables for the current buffer.
-Store the directory-local variables in `dir-local-variables-alist'
-and `file-local-variables-alist', without applying them.
-
-This does nothing if either `enable-local-variables' or
-`enable-dir-local-variables' are nil."
+Return a cons of the form (DIR . ALIST), where DIR is the
+directory name (maybe nil) and ALIST is an alist of all variables
+that might apply. These will be filtered according to the
+buffer's directory, but not according to its mode.
+PREDICATE is passed to `dir-locals-collect-variables'."
(when (and enable-local-variables
enable-dir-local-variables
(or enable-remote-dir-locals
@@ -4370,21 +4547,33 @@ This does nothing if either `enable-local-variables' or
(setq dir-name (nth 0 dir-or-cache))
(setq class (nth 1 dir-or-cache))))
(when class
- (let ((variables
- (dir-locals-collect-variables
- (dir-locals-get-class-variables class) dir-name nil)))
- (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)))))))
+ (cons dir-name
+ (dir-locals-collect-variables
+ (dir-locals-get-class-variables class)
+ dir-name nil predicate))))))
+
+(defun hack-dir-local-variables ()
+ "Read per-directory local variables for the current buffer.
+Store the directory-local variables in `dir-local-variables-alist'
+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))))
(defun hack-dir-local-variables-non-file-buffer ()
"Apply directory-local variables to a non-file buffer.
@@ -4831,6 +5020,27 @@ extension, the value is \"\"."
(if period
"")))))
+(defun file-name-with-extension (filename extension)
+ "Set the EXTENSION of a FILENAME.
+The extension (in a file name) is the part that begins with the last \".\".
+
+Trims a leading dot from the EXTENSION so that either \"foo\" or
+\".foo\" can be given.
+
+Errors if the FILENAME or EXTENSION are empty, or if the given
+FILENAME has the format of a directory.
+
+See also `file-name-sans-extension'."
+ (let ((extn (string-trim-left extension "[.]")))
+ (cond ((string-empty-p filename)
+ (error "Empty filename: %s" filename))
+ ((string-empty-p extn)
+ (error "Malformed extension: %s" extension))
+ ((directory-name-p filename)
+ (error "Filename is a directory: %s" filename))
+ (t
+ (concat (file-name-sans-extension filename) "." extn)))))
+
(defun file-name-base (&optional filename)
"Return the base name of the FILENAME: no directory, no extension."
(declare (advertised-calling-convention (filename) "27.1"))
@@ -4857,7 +5067,7 @@ See also `backup-directory-alist'."
(function :tag "Function")))
(defcustom backup-directory-alist nil
- "Alist of filename patterns and backup directory names.
+ "Alist of file name patterns and backup directory names.
Each element looks like (REGEXP . DIRECTORY). Backups of files with
names matching REGEXP will be made in DIRECTORY. DIRECTORY may be
relative or absolute. If it is absolute, so that all matching files
@@ -4870,7 +5080,7 @@ For the common case of all backups going into one directory, the alist
should contain a single element pairing \".\" with the appropriate
directory name.
-If this variable is nil, or it fails to match a filename, the backup
+If this variable is nil, or it fails to match a file name, the backup
is made in the original file's directory.
On MS-DOS filesystems without long names this variable is always
@@ -4991,7 +5201,7 @@ The function `find-backup-file-name' also uses this."
(expand-file-name
(subst-char-in-string
?/ ?!
- (replace-regexp-in-string "!" "!!" file))
+ (string-replace "!" "!!" file))
backup-directory))
(expand-file-name (file-name-nondirectory file)
(file-name-as-directory abs-backup-directory))))))
@@ -5517,9 +5727,23 @@ be saved."
:group 'auto-save
;; FIXME nil should not be a valid option, let alone the default,
;; eg so that add-function can be used.
- :type '(choice (const :tag "Default" nil) function)
+ :type '(choice (const :tag "Default" nil)
+ (function :tag "Only in subdirs of root"
+ save-some-buffers-root)
+ (function :tag "Custom function"))
:version "26.1")
+(defun save-some-buffers-root ()
+ "A predicate to check whether the buffer is under the root directory.
+Can be used as a value of `save-some-buffers-default-predicate'
+to save buffers only under the project root or in subdirectories
+of the directory that was default during command invocation."
+ (let ((root (or (and (featurep 'project) (project-current)
+ (fboundp 'project-root)
+ (project-root (project-current)))
+ default-directory)))
+ (lambda () (file-in-directory-p default-directory root))))
+
(defun save-some-buffers (&optional arg pred)
"Save some modified file-visiting buffers. Asks user about each one.
You can answer `y' or SPC to save, `n' or DEL not to save, `C-r'
@@ -5548,6 +5772,11 @@ change the additional actions you can take on files."
(interactive "P")
(unless pred
(setq pred save-some-buffers-default-predicate))
+ ;; Allow `pred' to be a function that returns a predicate
+ ;; with lexical bindings in its original environment (bug#46374).
+ (let ((pred-fun (and (functionp pred) (funcall pred))))
+ (when (functionp pred-fun)
+ (setq pred pred-fun)))
(let* ((switched-buffer nil)
(save-some-buffers--switch-window-callback
(lambda (buffer)
@@ -6115,9 +6344,6 @@ This undoes all changes since the file was visited or saved.
With a prefix argument, offer to revert from latest auto-save file, if
that is more recent than the visited file.
-Reverting a buffer will try to preserve markers in the buffer;
-see the Info node `(elisp)Reverting' for details.
-
This command also implements an interface for special buffers
that contain text that doesn't come from a file, but reflects
some other data instead (e.g. Dired buffers, `buffer-list'
@@ -6143,7 +6369,12 @@ This function binds `revert-buffer-in-progress-p' non-nil while it operates.
This function calls the function that `revert-buffer-function' specifies
to do the work, with arguments IGNORE-AUTO and NOCONFIRM.
The default function runs the hooks `before-revert-hook' and
-`after-revert-hook'."
+`after-revert-hook'
+
+Reverting a buffer will try to preserve markers in the buffer,
+but it cannot always preserve all of them. For better results,
+use `revert-buffer-with-fine-grain', which tries harder to
+preserve markers and overlays, at the price of being slower."
;; I admit it's odd to reverse the sense of the prefix argument, but
;; there is a lot of code out there that assumes that the first
;; argument should be t to avoid consulting the auto-save file, and
@@ -6187,8 +6418,11 @@ Non-file buffers need a custom function."
(dolist (regexp revert-without-query)
(when (string-match regexp file-name)
(throw 'found t)))))
- (yes-or-no-p (format "Revert buffer from file %s? "
- file-name)))
+ (yes-or-no-p
+ (format (if (buffer-modified-p)
+ "Discard edits and reread from %s? "
+ "Revert buffer from file %s? ")
+ file-name)))
(run-hooks 'before-revert-hook)
;; If file was backed up but has changed since,
;; we should make another backup.
@@ -6237,11 +6471,6 @@ an auto-save file."
"Cannot revert unreadable file %s")
file-name))
(t
- ;; Bind buffer-file-name to nil
- ;; so that we don't try to lock the file.
- (let ((buffer-file-name nil))
- (or auto-save-p
- (unlock-buffer)))
(widen)
(let ((coding-system-for-read
;; Auto-saved file should be read by Emacs's
@@ -6323,7 +6552,8 @@ see `replace-buffer-contents'."
;; See comments in revert-buffer-with-fine-grain for an explanation.
(defun revert-buffer-with-fine-grain-success-p ()
success))
- (set-buffer-modified-p nil))))
+ (set-buffer-modified-p nil)
+ (set-visited-file-modtime))))
(defun revert-buffer-with-fine-grain (&optional ignore-auto noconfirm)
"Revert buffer preserving markers, overlays, etc.
@@ -6350,6 +6580,38 @@ details on the arguments, see `revert-buffer'."
(revert-buffer-with-fine-grain-success-p)
(fmakunbound 'revert-buffer-with-fine-grain-success-p)))))
+(defcustom revert-buffer-quick-short-answers nil
+ "How much confirmation to be done by the `revert-buffer-quick' command.
+If non-nil, use `y-or-n-p' instead of `yes-or-no-p'."
+ :version "28.1"
+ :type 'boolean)
+
+(defun revert-buffer-quick (&optional auto-save)
+ "Like `revert-buffer', but asks for less confirmation.
+If the current buffer is visiting a file, and the buffer is not
+modified, no confirmation is required.
+
+This command heeds the `revert-buffer-quick-short-answers' user option.
+
+If AUTO-SAVE (the prefix argument), offer to revert from latest
+auto-save file, if that is more recent than the visited file."
+ (interactive "P")
+ (cond
+ ;; If we've visiting a file, and we have no changes, don't ask for
+ ;; confirmation.
+ ((and buffer-file-name
+ (not (buffer-modified-p)))
+ (revert-buffer (not auto-save) t)
+ (message "Reverted buffer"))
+ ;; Heed `revert-buffer-quick-short-answers'.
+ (revert-buffer-quick-short-answers
+ (let ((use-short-answers t))
+ (revert-buffer (not auto-save))))
+ ;; Call `revert-buffer' normally.
+ (t
+ (revert-buffer (not auto-save)))))
+
+
(defun recover-this-file ()
"Recover the visited file--get contents from its last auto-save file."
(interactive)
@@ -6405,7 +6667,8 @@ details on the arguments, see `revert-buffer'."
(coding-system-for-read 'auto-save-coding))
(erase-buffer)
(insert-file-contents file-name nil)
- (set-buffer-file-coding-system coding-system))
+ (set-buffer-file-coding-system coding-system)
+ (set-buffer-auto-saved))
(after-find-file nil nil t))
(t (user-error "Recover-file canceled")))))
@@ -6524,6 +6787,7 @@ This command is used in the special Dired buffer created by
(message "No files can be recovered from this session now")))
(kill-buffer buffer))))
+
(defun kill-buffer-ask (buffer)
"Kill BUFFER if confirmed."
(when (yes-or-no-p (format "Buffer %s %s. Kill? "
@@ -6582,61 +6846,15 @@ Does not consider `auto-save-visited-file-name' as that variable is checked
before calling this function.
See also `auto-save-file-name-p'."
(if buffer-file-name
- (let ((handler (find-file-name-handler buffer-file-name
- 'make-auto-save-file-name)))
+ (let ((handler (find-file-name-handler
+ buffer-file-name 'make-auto-save-file-name)))
(if handler
(funcall handler 'make-auto-save-file-name)
- (let ((list auto-save-file-name-transforms)
- (filename buffer-file-name)
- result uniq)
- ;; Apply user-specified translations
- ;; to the file name.
- (while (and list (not result))
- (if (string-match (car (car list)) filename)
- (setq result (replace-match (cadr (car list)) t nil
- filename)
- uniq (car (cddr (car list)))))
- (setq list (cdr list)))
- (if result
- (if uniq
- (setq filename (concat
- (file-name-directory result)
- (subst-char-in-string
- ?/ ?!
- (replace-regexp-in-string "!" "!!"
- filename))))
- (setq filename result)))
- (setq result
- (if (and (eq system-type 'ms-dos)
- (not (msdos-long-file-names)))
- ;; We truncate the file name to DOS 8+3 limits
- ;; before doing anything else, because the regexp
- ;; passed to string-match below cannot handle
- ;; extensions longer than 3 characters, multiple
- ;; dots, and other atrocities.
- (let ((fn (dos-8+3-filename
- (file-name-nondirectory buffer-file-name))))
- (string-match
- "\\`\\([^.]+\\)\\(\\.\\(..?\\)?.?\\|\\)\\'"
- fn)
- (concat (file-name-directory buffer-file-name)
- "#" (match-string 1 fn)
- "." (match-string 3 fn) "#"))
- (concat (file-name-directory filename)
- "#"
- (file-name-nondirectory filename)
- "#")))
- ;; Make sure auto-save file names don't contain characters
- ;; invalid for the underlying filesystem.
- (if (and (memq system-type '(ms-dos windows-nt cygwin))
- ;; Don't modify remote filenames
- (not (file-remote-p result)))
- (convert-standard-filename result)
- result))))
-
+ (files--transform-file-name
+ buffer-file-name auto-save-file-name-transforms
+ "#" "#")))
;; Deal with buffers that don't have any associated files. (Mail
;; mode tends to create a good number of these.)
-
(let ((buffer-name (buffer-name))
(limit 0)
file-name)
@@ -6684,6 +6902,74 @@ See also `auto-save-file-name-p'."
(file-error nil))
file-name)))
+(defun files--transform-file-name (filename transforms prefix suffix)
+ "Transform FILENAME according to TRANSFORMS.
+See `auto-save-file-name-transforms' for the format of
+TRANSFORMS. PREFIX is prepended to the non-directory portion of
+the resulting file name, and SUFFIX is appended."
+ (save-match-data
+ (let (result uniq)
+ ;; Apply user-specified translations to the file name.
+ (while (and transforms (not result))
+ (if (string-match (car (car transforms)) filename)
+ (setq result (replace-match (cadr (car transforms)) t nil
+ filename)
+ uniq (car (cddr (car transforms)))))
+ (setq transforms (cdr transforms)))
+ (when result
+ (setq filename
+ (cond
+ ((memq uniq (secure-hash-algorithms))
+ (concat
+ (file-name-directory result)
+ (secure-hash uniq filename)))
+ (uniq
+ (concat
+ (file-name-directory result)
+ (subst-char-in-string
+ ?/ ?!
+ (string-replace
+ "!" "!!" filename))))
+ (t result))))
+ (setq result
+ (if (and (eq system-type 'ms-dos)
+ (not (msdos-long-file-names)))
+ ;; We truncate the file name to DOS 8+3 limits before
+ ;; doing anything else, because the regexp passed to
+ ;; string-match below cannot handle extensions longer
+ ;; than 3 characters, multiple dots, and other
+ ;; atrocities.
+ (let ((fn (dos-8+3-filename
+ (file-name-nondirectory buffer-file-name))))
+ (string-match
+ "\\`\\([^.]+\\)\\(\\.\\(..?\\)?.?\\|\\)\\'"
+ fn)
+ (concat (file-name-directory buffer-file-name)
+ prefix (match-string 1 fn)
+ "." (match-string 3 fn) suffix))
+ (concat (file-name-directory filename)
+ prefix
+ (file-name-nondirectory filename)
+ suffix)))
+ ;; Make sure auto-save file names don't contain characters
+ ;; invalid for the underlying filesystem.
+ (expand-file-name
+ (if (and (memq system-type '(ms-dos windows-nt cygwin))
+ ;; Don't modify remote filenames
+ (not (file-remote-p result)))
+ (convert-standard-filename result)
+ result)))))
+
+(defun make-lock-file-name (filename)
+ "Make a lock file name for FILENAME.
+By default, this just prepends \".#\" to the non-directory part
+of FILENAME, but the transforms in `lock-file-name-transforms'
+are done first."
+ (let ((handler (find-file-name-handler filename 'make-lock-file-name)))
+ (if handler
+ (funcall handler 'make-lock-file-name filename)
+ (files--transform-file-name filename lock-file-name-transforms ".#" ""))))
+
(defun auto-save-file-name-p (filename)
"Return non-nil if FILENAME can be yielded by `make-auto-save-file-name'.
FILENAME should lack slashes.
@@ -6692,7 +6978,7 @@ See also `make-auto-save-file-name'."
(defun wildcard-to-regexp (wildcard)
"Given a shell file name pattern WILDCARD, return an equivalent regexp.
-The generated regexp will match a filename only if the filename
+The generated regexp will match a file name only if the file name
matches that wildcard according to shell rules. Only wildcards known
by `sh' are supported."
(let* ((i (string-match "[[.*+\\^$?]" wildcard))
@@ -6745,7 +7031,7 @@ by `sh' are supported."
(prog1 ; copy everything upto next `]'.
(substring wildcard
i
- (setq j (string-match
+ (setq j (string-search
"]" wildcard i)))
(setq i (if j (1- j) (1- len)))))))
((eq ch ?.) "\\.")
@@ -6871,7 +7157,7 @@ need to be passed verbatim to shell commands."
;; DOS/Windows don't allow `"' in file names. So if the
;; argument has quotes, we can safely assume it is already
;; quoted by the caller.
- (if (or (string-match "[\"]" pattern)
+ (if (or (string-search "\"" pattern)
;; We quote [&()#$`'] in case their shell is a port of a
;; Unixy shell. We quote [,=+] because stock DOS and
;; Windows shells require that in some cases, such as
@@ -7177,7 +7463,7 @@ normally equivalent short `-D' option is just passed on to
(unless (equal switches "")
;; Split the switches at any spaces so we can
;; pass separate options as separate args.
- (split-string-and-unquote switches)))
+ (split-string-shell-command switches)))
;; Avoid lossage if FILE starts with `-'.
'("--")
(list file))))))
@@ -7412,7 +7698,7 @@ If the current frame has no client, kill Emacs itself using
With prefix ARG, silently save all file-visiting buffers, then kill.
-If emacsclient was started with a list of filenames to edit, then
+If emacsclient was started with a list of file names to edit, then
only these files will be asked to be saved."
(interactive "P")
(if (frame-parameter nil 'client)
@@ -7433,12 +7719,11 @@ only these files will be asked to be saved."
;; operations, which return a file name. See Bug#29579.
(defun file-name-non-special (operation &rest arguments)
- (let (;; In general, we don't want any file name handler. For some
- ;; few cases, operations with two file name arguments which
- ;; might be bound to different file name handlers, we still
- ;; need this.
- (saved-file-name-handler-alist file-name-handler-alist)
- file-name-handler-alist
+ (let ((inhibit-file-name-handlers
+ (cons 'file-name-non-special
+ (and (eq inhibit-file-name-operation operation)
+ inhibit-file-name-handlers)))
+ (inhibit-file-name-operation operation)
;; Some operations respect file name handlers in
;; `default-directory'. Because core function like
;; `call-process' don't care about file name handlers in
@@ -7520,69 +7805,73 @@ only these files will be asked to be saved."
(when (car pair)
(setcar pair (file-name-unquote (car pair) t))))
(setq file-arg-indices (cdr file-arg-indices))))
- (pcase method
- ('identity (car arguments))
- ('add (file-name-quote (apply operation arguments) t))
- ('buffer-file-name
- (let ((buffer-file-name (file-name-unquote buffer-file-name t)))
- (apply operation arguments)))
- ('insert-file-contents
- (let ((visit (nth 1 arguments)))
- (unwind-protect
- (apply operation arguments)
- (when (and visit buffer-file-name)
- (setq buffer-file-name (file-name-quote buffer-file-name t))))))
- ('unquote-then-quote
- ;; We can't use `cl-letf' with `(buffer-local-value)' here
- ;; because it wouldn't work during bootstrapping.
- (let ((buffer (current-buffer)))
- ;; `unquote-then-quote' is used only for the
- ;; `verify-visited-file-modtime' action, which takes a buffer
- ;; as only optional argument.
- (with-current-buffer (or (car arguments) buffer)
- (let ((buffer-file-name (file-name-unquote buffer-file-name t)))
- ;; Make sure to hide the temporary buffer change from the
- ;; underlying operation.
- (with-current-buffer buffer
- (apply operation arguments))))))
- ('local-copy
- (let* ((file-name-handler-alist saved-file-name-handler-alist)
- (source (car arguments))
- (target (car (cdr arguments)))
- (prefix (expand-file-name
- "file-name-non-special" temporary-file-directory))
- tmpfile)
- (cond
- ;; If source is remote, we must create a local copy.
- ((file-remote-p source)
- (setq tmpfile (make-temp-name prefix))
- (apply operation source tmpfile (cddr arguments))
- (setq source tmpfile))
- ;; If source is quoted, and the unquoted source looks
- ;; remote, we must create a local copy.
- ((file-name-quoted-p source t)
- (setq source (file-name-unquote source t))
- (when (file-remote-p source)
+ ;; In general, we don't want any file name handler, see Bug#47625,
+ ;; Bug#48349. For some few cases, operations with two file name
+ ;; arguments which might be bound to different file name handlers,
+ ;; we still need this.
+ (let ((tramp-mode (and tramp-mode (eq method 'local-copy))))
+ (pcase method
+ ('identity (car arguments))
+ ('add (file-name-quote (apply operation arguments) t))
+ ('buffer-file-name
+ (let ((buffer-file-name (file-name-unquote buffer-file-name t)))
+ (apply operation arguments)))
+ ('insert-file-contents
+ (let ((visit (nth 1 arguments)))
+ (unwind-protect
+ (apply operation arguments)
+ (when (and visit buffer-file-name)
+ (setq buffer-file-name (file-name-quote buffer-file-name t))))))
+ ('unquote-then-quote
+ ;; We can't use `cl-letf' with `(buffer-local-value)' here
+ ;; because it wouldn't work during bootstrapping.
+ (let ((buffer (current-buffer)))
+ ;; `unquote-then-quote' is used only for the
+ ;; `verify-visited-file-modtime' action, which takes a
+ ;; buffer as only optional argument.
+ (with-current-buffer (or (car arguments) buffer)
+ (let ((buffer-file-name (file-name-unquote buffer-file-name t)))
+ ;; Make sure to hide the temporary buffer change from
+ ;; the underlying operation.
+ (with-current-buffer buffer
+ (apply operation arguments))))))
+ ('local-copy
+ (let ((source (car arguments))
+ (target (car (cdr arguments)))
+ (prefix (expand-file-name
+ "file-name-non-special" temporary-file-directory))
+ tmpfile)
+ (cond
+ ;; If source is remote, we must create a local copy.
+ ((file-remote-p source)
(setq tmpfile (make-temp-name prefix))
- (let (file-name-handler-alist)
- (apply operation source tmpfile (cddr arguments)))
- (setq source tmpfile))))
- ;; If target is quoted, and the unquoted target looks remote,
- ;; we must disable the file name handler.
- (when (file-name-quoted-p target t)
- (setq target (file-name-unquote target t))
- (when (file-remote-p target)
- (setq file-name-handler-alist nil)))
- ;; Do it.
- (setcar arguments source)
- (setcar (cdr arguments) target)
- (apply operation arguments)
- ;; Cleanup.
- (when (and tmpfile (file-exists-p tmpfile))
- (if (file-directory-p tmpfile)
- (delete-directory tmpfile 'recursive) (delete-file tmpfile)))))
- (_
- (apply operation arguments)))))
+ (apply operation source tmpfile (cddr arguments))
+ (setq source tmpfile))
+ ;; If source is quoted, and the unquoted source looks
+ ;; remote, we must create a local copy.
+ ((file-name-quoted-p source t)
+ (setq source (file-name-unquote source t))
+ (when (file-remote-p source)
+ (setq tmpfile (make-temp-name prefix))
+ (let (file-name-handler-alist)
+ (apply operation source tmpfile (cddr arguments)))
+ (setq source tmpfile))))
+ ;; If target is quoted, and the unquoted target looks
+ ;; remote, we must disable the file name handler.
+ (when (file-name-quoted-p target t)
+ (setq target (file-name-unquote target t))
+ (when (file-remote-p target)
+ (setq file-name-handler-alist nil)))
+ ;; Do it.
+ (setcar arguments source)
+ (setcar (cdr arguments) target)
+ (apply operation arguments)
+ ;; Cleanup.
+ (when (and tmpfile (file-exists-p tmpfile))
+ (if (file-directory-p tmpfile)
+ (delete-directory tmpfile 'recursive) (delete-file tmpfile)))))
+ (_
+ (apply operation arguments))))))
(defsubst file-name-quoted-p (name &optional top)
"Whether NAME is quoted with prefix \"/:\".
@@ -7638,6 +7927,9 @@ If CHAR is in [Xugo], the value is taken from FROM (or 0 if omitted)."
;; Rights relative to the previous file modes.
((= char ?X) (if (= (logand from #o111) 0) 0 #o0111))
((= char ?u) (let ((uright (logand #o4700 from)))
+ ;; FIXME: These divisions/shifts seem to be right
+ ;; for the `7' part of the #o4700 mask, but not
+ ;; for the `4' part. Same below for `g' and `o'.
(+ uright (/ uright #o10) (/ uright #o100))))
((= char ?g) (let ((gright (logand #o2070 from)))
(+ gright (/ gright #o10) (* gright #o10))))
@@ -7672,11 +7964,28 @@ as in \"og+rX-w\"."
op char-right)))
num-rights))
-(defun file-modes-number-to-symbolic (mode)
+(defun file-modes-number-to-symbolic (mode &optional filetype)
+ "Return a string describing a file's MODE.
+For instance, if MODE is #o700, then it produces `-rwx------'.
+FILETYPE if provided should be a character denoting the type of file,
+such as `?d' for a directory, or `?l' for a symbolic link and will override
+the leading `-' char."
(string
- (if (zerop (logand 8192 mode))
- (if (zerop (logand 16384 mode)) ?- ?d)
- ?c) ; completeness
+ (or filetype
+ (pcase (lsh mode -12)
+ ;; POSIX specifies that the file type is included in st_mode
+ ;; and provides names for the file types but values only for
+ ;; the permissions (e.g., S_IWOTH=2).
+
+ ;; (#o017 ??) ;; #define S_IFMT 00170000
+ (#o014 ?s) ;; #define S_IFSOCK 0140000
+ (#o012 ?l) ;; #define S_IFLNK 0120000
+ ;; (8 ??) ;; #define S_IFREG 0100000
+ (#o006 ?b) ;; #define S_IFBLK 0060000
+ (#o004 ?d) ;; #define S_IFDIR 0040000
+ (#o002 ?c) ;; #define S_IFCHR 0020000
+ (#o001 ?p) ;; #define S_IFIFO 0010000
+ (_ ?-)))
(if (zerop (logand 256 mode)) ?- ?r)
(if (zerop (logand 128 mode)) ?- ?w)
(if (zerop (logand 64 mode))
@@ -7732,7 +8041,7 @@ based on existing mode bits, as in \"og+rX-w\"."
(default
(and (stringp modestr)
(string-match "^.\\(...\\)\\(...\\)\\(...\\)$" modestr)
- (replace-regexp-in-string
+ (string-replace
"-" ""
(format "u=%s,g=%s,o=%s"
(match-string 1 modestr)
@@ -7748,6 +8057,7 @@ based on existing mode bits, as in \"og+rX-w\"."
(define-obsolete-variable-alias 'cache-long-line-scans
'cache-long-scans "24.4")
+
;; Trashcan handling.
(defcustom trash-directory nil
"Directory for `move-file-to-trash' to move files and directories to.
@@ -7863,9 +8173,24 @@ Otherwise, trash FILENAME using the freedesktop.org conventions,
;; Make a .trashinfo file. Use O_EXCL, as per trash-spec 1.0.
(let* ((files-base (file-name-nondirectory fn))
- (info-fn (expand-file-name
- (concat files-base ".trashinfo")
- trash-info-dir)))
+ (is-directory (file-directory-p fn))
+ (overwrite nil)
+ info-fn)
+ ;; We're checking further down whether the info file
+ ;; exists, but the file name may exist in the trash
+ ;; directory even if there is no info file for it.
+ (when (file-exists-p
+ (file-name-concat trash-files-dir files-base))
+ (setq overwrite t
+ files-base (file-name-nondirectory
+ (make-temp-file
+ (file-name-concat
+ trash-files-dir files-base)
+ is-directory))))
+ (setq info-fn (file-name-concat
+ trash-info-dir
+ (concat files-base ".trashinfo")))
+ ;; Re-check the existence (sort of).
(condition-case nil
(write-region nil nil info-fn nil 'quiet info-fn 'excl)
(file-already-exists
@@ -7873,16 +8198,17 @@ Otherwise, trash FILENAME using the freedesktop.org conventions,
;; like Emacs-style backup file names. E.g.:
;; https://bugs.kde.org/170956
(setq info-fn (make-temp-file
- (expand-file-name files-base trash-info-dir)
+ (file-name-concat trash-info-dir files-base)
nil ".trashinfo"))
(setq files-base (substring (file-name-nondirectory info-fn)
0 (- (length ".trashinfo"))))
(write-region nil nil info-fn nil 'quiet info-fn)))
;; Finally, try to move the file to the trashcan.
(let ((delete-by-moving-to-trash nil)
- (new-fn (expand-file-name files-base trash-files-dir)))
- (rename-file fn new-fn)))))))))
+ (new-fn (file-name-concat trash-files-dir files-base)))
+ (rename-file fn new-fn overwrite)))))))))
+
(defsubst file-attribute-type (attributes)
"The type field in ATTRIBUTES returned by `file-attributes'.
The value is either t for directory, string (name linked to) for