diff options
Diffstat (limited to 'lisp/files.el')
-rw-r--r-- | lisp/files.el | 283 |
1 files changed, 208 insertions, 75 deletions
diff --git a/lisp/files.el b/lisp/files.el index 3e4ad7c0d44..c2c58dae934 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -405,7 +405,7 @@ editing a remote file. On MS-DOS filesystems without long names this variable is always ignored." :group 'auto-save - :type '(repeat (list (string :tag "Regexp") (string :tag "Replacement") + :type '(repeat (list (regexp :tag "Regexp") (string :tag "Replacement") (boolean :tag "Uniquify"))) :initialize 'custom-initialize-delay :version "21.1") @@ -430,7 +430,13 @@ idle for `auto-save-visited-interval' seconds." Unlike `auto-save-mode', this mode will auto-save buffer contents to the visited files directly and will also run all save-related -hooks. See Info node `Saving' for details of the save process." +hooks. See Info node `Saving' for details of the save process. + +You can also set the buffer-local value of the variable +`auto-save-visited-mode' to nil. A buffer where the buffer-local +value of this variable is nil is ignored for the purpose of +`auto-save-visited-mode', even if `auto-save-visited-mode' is +enabled." :group 'auto-save :global t (when auto-save--timer (cancel-timer auto-save--timer)) @@ -441,6 +447,7 @@ hooks. See Info node `Saving' for details of the save process." #'save-some-buffers :no-prompt (lambda () (and buffer-file-name + auto-save-visited-mode (not (and buffer-auto-save-file-name auto-save-visited-file-name)))))))) @@ -745,10 +752,16 @@ resulting list of directory names. For an empty path element (i.e., a leading or trailing separator, or two adjacent separators), return nil (meaning `default-directory') as the associated list element." (when (stringp search-path) - (mapcar (lambda (f) - (if (equal "" f) nil - (substitute-in-file-name (file-name-as-directory f)))) - (split-string search-path path-separator)))) + (let ((spath (substitute-env-vars search-path))) + (mapcar (lambda (f) + (if (equal "" f) nil + (let ((dir (expand-file-name (file-name-as-directory f)))) + ;; Previous implementation used `substitute-in-file-name' + ;; which collapse multiple "/" in front. Do the same for + ;; backward compatibility. + (if (string-match "\\`/+" dir) + (substring dir (1- (match-end 0))) dir)))) + (split-string spath path-separator))))) (defun cd-absolute (dir) "Change current directory to given absolute file name DIR." @@ -972,14 +985,6 @@ one or more of those symbols." (completion-table-with-context string-dir names string-file pred action))))) -(defun locate-file-completion (string path-and-suffixes action) - "Do completion for file names passed to `locate-file'. -PATH-AND-SUFFIXES is a pair of lists, (DIRECTORIES . SUFFIXES)." - (declare (obsolete locate-file-completion-table "23.1")) - (locate-file-completion-table (car path-and-suffixes) - (cdr path-and-suffixes) - string nil action)) - (defvar locate-dominating-stop-dir-regexp (purecopy "\\`\\(?:[\\/][\\/][^\\/]+[\\/]\\|/\\(?:net\\|afs\\|\\.\\.\\.\\)/\\)\\'") "Regexp of directory names that stop the search in `locate-dominating-file'. @@ -1094,6 +1099,8 @@ REMOTE is non-nil, search on the remote host indicated by (let ((default-directory (file-name-quote default-directory 'top))) (locate-file command exec-path exec-suffixes 1)))) +(declare-function read-library-name "find-func" nil) + (defun load-library (library) "Load the Emacs Lisp library named LIBRARY. LIBRARY should be a string. @@ -1103,12 +1110,7 @@ well as `load-file-rep-suffixes'). See Info node `(emacs)Lisp Libraries' for more details. See `load-file' for a different interface to `load'." - (interactive - (let (completion-ignored-extensions) - (list (completing-read "Load library: " - (apply-partially 'locate-file-completion-table - load-path - (get-load-suffixes)))))) + (interactive (list (read-library-name))) (load library)) (defun file-remote-p (file &optional identification connected) @@ -1390,7 +1392,7 @@ it means chase no more than that many links and then stop." newname)) ;; A handy function to display file sizes in human-readable form. -;; See http://en.wikipedia.org/wiki/Kibibyte for the reference. +;; See https://en.wikipedia.org/wiki/Kibibyte for the reference. (defun file-size-human-readable (file-size &optional flavor space unit) "Produce a string showing FILE-SIZE in human-readable form. @@ -1561,8 +1563,8 @@ use with M-x." (and (not (memq 'eight-bit-control charsets)) (not (memq 'eight-bit-graphic charsets))))) (setq from-coding (read-coding-system - (format "Recode filename %s from (default %s): " - filename default-coding) + (format-prompt "Recode filename %s from" + filename default-coding) default-coding)) (setq from-coding (read-coding-system (format "Recode filename %s from: " filename)))) @@ -1574,8 +1576,8 @@ use with M-x." (format "Recode filename %s from %s to: " filename from-coding))) (setq to-coding (read-coding-system - (format "Recode filename %s from %s to (default %s): " - filename from-coding default-coding) + (format-prompt "Recode filename %s from %s to" + default-coding filename from-coding) default-coding))) (list filename from-coding to-coding))) @@ -1917,6 +1919,8 @@ killed." (setq buffer-file-truename otrue) (setq dired-directory odir) (lock-buffer) + (if (get-buffer oname) + (kill-buffer oname)) (rename-buffer oname))) (unless (eq (current-buffer) obuf) (with-current-buffer obuf @@ -2660,6 +2664,13 @@ since only a single case-insensitive search through the alist is made." ("\\.ltx\\'" . latex-mode) ("\\.dtx\\'" . doctex-mode) ("\\.org\\'" . org-mode) + ;; .dir-locals.el is not really Elisp. Could use the + ;; `dir-locals-file' constant if it weren't defined below. + ("\\.dir-locals\\(?:-2\\)?\\.el\\'" . lisp-data-mode) + ("eww-bookmarks\\'" . lisp-data-mode) + ("tramp\\'" . lisp-data-mode) + ("places\\'" . lisp-data-mode) + ("\\.emacs-places\\'" . lisp-data-mode) ("\\.el\\'" . emacs-lisp-mode) ("Project\\.ede\\'" . emacs-lisp-mode) ("\\.\\(scm\\|stk\\|ss\\|sch\\)\\'" . scheme-mode) @@ -2670,8 +2681,6 @@ since only a single case-insensitive search through the alist is made." ("\\.p\\'" . pascal-mode) ("\\.pas\\'" . pascal-mode) ("\\.\\(dpr\\|DPR\\)\\'" . delphi-mode) - ("\\.ad[abs]\\'" . ada-mode) - ("\\.ad[bs]\\.dg\\'" . ada-mode) ("\\.\\([pP]\\([Llm]\\|erl\\|od\\)\\|al\\)\\'" . perl-mode) ("Imakefile\\'" . makefile-imake-mode) ("Makeppfile\\(?:\\.mk\\)?\\'" . makefile-makepp-mode) ; Put this before .mk @@ -3058,7 +3067,7 @@ If FUNCTION is nil, then it is not called. (That is a way of saying "\\(?:!DOCTYPE[ \t\r\n]+[^>]*>[ \t\r\n]*<[ \t\r\n]*" comment-re "*\\)?" "[Hh][Tt][Mm][Ll]")) . mhtml-mode) - ("<!DOCTYPE[ \t\r\n]+[Hh][Tt][Mm][Ll]" . mhtml-mode) + ("<![Dd][Oo][Cc][Tt][Yy][Pp][Ee][ \t\r\n]+[Hh][Tt][Mm][Ll]" . mhtml-mode) ;; These two must come after html, because they are more general: ("<\\?xml " . xml-mode) (,(let* ((incomment-re "\\(?:[^-]\\|-[^-]\\)") @@ -4674,6 +4683,7 @@ BACKUPNAME is the backup file name, which is the old file renamed." ;; Create temp files with strict access rights. It's easy to ;; loosen them later, whereas it's impossible to close the ;; time-window of loose permissions otherwise. + (let (nofollow-flag) (with-file-modes ?\700 (when (condition-case nil ;; Try to overwrite old backup first. @@ -4684,6 +4694,7 @@ BACKUPNAME is the backup file name, which is the old file renamed." (when (file-exists-p to-name) (delete-file to-name)) (copy-file from-name to-name nil t t) + (setq nofollow-flag 'nofollow) nil) (file-already-exists t)) ;; The file was somehow created by someone else between @@ -4696,7 +4707,7 @@ BACKUPNAME is the backup file name, which is the old file renamed." (with-demoted-errors (set-file-extended-attributes to-name extended-attributes))) (and modes - (set-file-modes to-name (logand modes #o1777))))) + (set-file-modes to-name (logand modes #o1777) nofollow-flag))))) (defvar file-name-version-regexp "\\(?:~\\|\\.~[-[:alnum:]:#@^._]+\\(?:~[[:digit:]]+\\)?~\\)" @@ -5555,10 +5566,28 @@ change the additional actions you can take on files." t (setq queried t) (if (buffer-file-name buffer) - (format "Save file %s? " - (buffer-file-name buffer)) - (format "Save buffer %s? " - (buffer-name buffer)))))) + (if (or + (equal (buffer-name buffer) + (file-name-nondirectory + (buffer-file-name buffer))) + (string-match + (concat "\\<" + (regexp-quote + (file-name-nondirectory + (buffer-file-name buffer))) + "<[^>]*>\\'") + (buffer-name buffer))) + ;; The buffer name is similar to the + ;; file name. + (format "Save file %s? " + (buffer-file-name buffer)) + ;; The buffer and file names are + ;; dissimilar; display both. + (format "Save file %s (buffer %s)? " + (buffer-file-name buffer) + (buffer-name buffer))) + ;; No file name + (format "Save buffer %s? " (buffer-name buffer)))))) (lambda (buffer) (with-current-buffer buffer (save-buffer))) @@ -5644,25 +5673,28 @@ like `write-region' does." (defun file-newest-backup (filename) "Return most recent backup file for FILENAME or nil if no backups exist." + (car (file-backup-file-names filename))) + +(defun file-backup-file-names (filename) + "Return a list of backup files for FILENAME. +The list will be sorted by modification time so that the most +recent files are first." ;; `make-backup-file-name' will get us the right directory for ;; ordinary or numeric backups. It might create a directory for ;; backups as a side-effect, according to `backup-directory-alist'. (let* ((filename (file-name-sans-versions (make-backup-file-name (expand-file-name filename)))) - (file (file-name-nondirectory filename)) - (dir (file-name-directory filename)) - (comp (file-name-all-completions file dir)) - (newest nil) - tem) - (while comp - (setq tem (pop comp)) - (cond ((and (backup-file-name-p tem) - (string= (file-name-sans-versions tem) file)) - (setq tem (concat dir tem)) - (if (or (null newest) - (file-newer-than-file-p tem newest)) - (setq newest tem))))) - newest)) + (dir (file-name-directory filename))) + (sort + (seq-filter + (lambda (candidate) + (and (backup-file-name-p candidate) + (string= (file-name-sans-versions candidate) filename))) + (mapcar + (lambda (file) + (concat dir file)) + (file-name-all-completions (file-name-nondirectory filename) dir))) + #'file-newer-than-file-p))) (defun rename-uniquely () "Rename current buffer to a similar name not already taken. @@ -5755,7 +5787,10 @@ If called interactively, then PARENTS is non-nil." (defconst directory-files-no-dot-files-regexp "[^.]\\|\\.\\.\\." - "Regexp matching any file name except \".\" and \"..\".") + "Regexp matching any file name except \".\" and \"..\". +More precisely, it matches parts of any nonempty string except those two. +It is useful as the regexp argument to `directory-files' and +`directory-files-and-attributes'.") (defun files--force (no-such fn &rest args) "Use NO-SUCH to affect behavior of function FN applied to list ARGS. @@ -5880,9 +5915,9 @@ last-modified time as the old ones. (This works on only some systems.) A prefix arg makes KEEP-TIME non-nil. -Noninteractively, the last argument PARENTS says whether to -create parent directories if they don't exist. Interactively, -this happens by default. +Noninteractively, the PARENTS argument says whether to create +parent directories if they don't exist. Interactively, this +happens by default. If NEWNAME is a directory name, copy DIRECTORY as a subdirectory there. However, if called from Lisp with a non-nil optional @@ -5902,7 +5937,8 @@ into NEWNAME instead." ;; If default-directory is a remote directory, make sure we find its ;; copy-directory handler. (let ((handler (or (find-file-name-handler directory 'copy-directory) - (find-file-name-handler newname 'copy-directory)))) + (find-file-name-handler newname 'copy-directory))) + (follow parents)) (if handler (funcall handler 'copy-directory directory newname keep-time parents copy-contents) @@ -5922,7 +5958,8 @@ into NEWNAME instead." (or parents (not (file-directory-p newname))) (setq newname (concat newname (file-name-nondirectory directory)))) - (make-directory (directory-file-name newname) parents))) + (make-directory (directory-file-name newname) parents)) + (t (setq follow t))) ;; Copy recursively. (dolist (file @@ -5942,9 +5979,10 @@ into NEWNAME instead." ;; Set directory attributes. (let ((modes (file-modes directory)) (times (and keep-time (file-attribute-modification-time - (file-attributes directory))))) - (if modes (set-file-modes newname modes)) - (if times (set-file-times newname times)))))) + (file-attributes directory)))) + (follow-flag (unless follow 'nofollow))) + (if modes (set-file-modes newname modes follow-flag)) + (if times (set-file-times newname times follow-flag)))))) ;; At time of writing, only info uses this. @@ -6216,6 +6254,82 @@ an auto-save file." (insert-file-contents file-name (not auto-save-p) nil nil t)))))) +(defvar revert-buffer-with-fine-grain-max-seconds 2.0 + "Maximum time that `revert-buffer-with-fine-grain' should use. +The command tries to preserve markers, properties and overlays. +If the operation takes more than this time, a single +delete+insert is performed. Actually, this value is passed as +the MAX-SECS argument to the function `replace-buffer-contents', +so it is not ensured that the whole execution won't take longer. +See `replace-buffer-contents' for more details.") + +(defun revert-buffer-insert-file-contents-delicately (file-name _auto-save-p) + "Optional function for `revert-buffer-insert-file-contents-function'. +The function `revert-buffer-with-fine-grain' uses this function by binding +`revert-buffer-insert-file-contents-function' to it. + +As with `revert-buffer-insert-file-contents--default-function', FILE-NAME is +the name of the file and AUTO-SAVE-P is non-nil if this is an auto-save file. +Since calling `replace-buffer-contents' can take a long time, depending of +the number of changes made to the buffer, it uses the value of the variable +`revert-buffer-with-fine-grain-max-seconds' as a maximum time to try delicately +reverting the buffer. If it fails, it does a delete+insert. For more details, +see `replace-buffer-contents'." + (cond + ((not (file-exists-p file-name)) + (error (if buffer-file-number + "File %s no longer exists" + "Cannot revert nonexistent file %s") + file-name)) + ((not (file-readable-p file-name)) + (error (if buffer-file-number + "File %s no longer readable" + "Cannot revert unreadable file %s") + file-name)) + (t + (let* ((buf (current-buffer)) ; current-buffer is the buffer to revert. + (success + (save-excursion + (save-restriction + (widen) + (with-temp-buffer + (insert-file-contents file-name) + (let ((temp-buf (current-buffer))) + (set-buffer buf) + (let ((buffer-file-name nil)) + (replace-buffer-contents + temp-buf + revert-buffer-with-fine-grain-max-seconds)))))))) + ;; 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)))) + +(defun revert-buffer-with-fine-grain (&optional ignore-auto noconfirm) + "Revert buffer preserving markers, overlays, etc. +This command is an alternative to `revert-buffer' because it tries to be as +non-destructive as possible, preserving markers, properties and overlays. +Binds `revert-buffer-insert-file-contents-function' to the function +`revert-buffer-insert-file-contents-delicately'. + +With a prefix argument, offer to revert from latest auto-save file. For more +details on the arguments, see `revert-buffer'." + ;; See revert-buffer for an explanation of this. + (interactive (list (not current-prefix-arg))) + ;; Simply bind revert-buffer-insert-file-contents-function to the specialized + ;; function, and call revert-buffer. + (let ((revert-buffer-insert-file-contents-function + #'revert-buffer-insert-file-contents-delicately)) + (revert-buffer ignore-auto noconfirm t) + ;; This closure is defined in revert-buffer-insert-file-contents-function. + ;; It is needed because revert-buffer--default always returns t after + ;; reverting, and it might be needed to report the success/failure of + ;; reverting delicately. + (when (fboundp 'revert-buffer-with-fine-grain-success-p) + (prog1 + (revert-buffer-with-fine-grain-success-p) + (fmakunbound 'revert-buffer-with-fine-grain-success-p))))) + (defun recover-this-file () "Recover the visited file--get contents from its last auto-save file." (interactive) @@ -6445,7 +6559,7 @@ Also rename any existing auto save file, if it was made in this session." (defun make-auto-save-file-name () "Return file name to use for auto-saves of current buffer. Does not consider `auto-save-visited-file-name' as that variable is checked -before calling this function. You can redefine this for customization. +before calling this function. See also `auto-save-file-name-p'." (if buffer-file-name (let ((handler (find-file-name-handler buffer-file-name @@ -6552,7 +6666,8 @@ See also `auto-save-file-name-p'." (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. You can redefine this for customization." +FILENAME should lack slashes. +See also `make-auto-save-file-name'." (string-match "\\`#.*#\\'" filename)) (defun wildcard-to-regexp (wildcard) @@ -6775,9 +6890,7 @@ We assume the output has the format of `df'. The value of this variable must be just a command name or file name; if you want to specify options, use `directory-free-space-args'. -A value of nil disables this feature. - -This variable is obsolete; Emacs no longer uses it." +A value of nil disables this feature." :type '(choice (string :tag "Program") (const :tag "None" nil)) :group 'dired) (make-obsolete-variable 'directory-free-space-program @@ -7031,6 +7144,8 @@ normally equivalent short `-D' option is just passed on to ((stringp switches) (concat switches " -d")) ((member "-d" switches) switches) (t (append switches '("-d")))))) + (if (string-match "\\`~" file) + (setq file (expand-file-name file))) (apply 'call-process insert-directory-program nil t nil (append @@ -7041,14 +7156,7 @@ normally equivalent short `-D' option is just passed on to (split-string-and-unquote switches))) ;; Avoid lossage if FILE starts with `-'. '("--") - (progn - (if (string-match "\\`~" file) - (setq file (expand-file-name file))) - (list - (if full-directory-p - ;; (concat (file-name-as-directory file) ".") - file - file)))))))) + (list file)))))) ;; If we got "//DIRED//" in the output, it means we got a real ;; directory listing, even if `ls' returned nonzero. @@ -7250,10 +7358,15 @@ if any returns nil. If `confirm-kill-emacs' is non-nil, calls it." (setq active t)) (setq processes (cdr processes))) (or (not active) - (with-displayed-buffer-window + (with-current-buffer-window (get-buffer-create "*Process List*") - '(display-buffer--maybe-at-bottom - (dedicated . t)) + `(display-buffer--maybe-at-bottom + (dedicated . t) + (window-height . fit-window-to-buffer) + (preserve-size . (nil . t)) + (body-function + . ,#'(lambda (_window) + (list-processes t)))) #'(lambda (window _value) (with-selected-window window (unwind-protect @@ -7261,8 +7374,7 @@ if any returns nil. If `confirm-kill-emacs' is non-nil, calls it." (setq confirm nil) (yes-or-no-p "Active processes exist; kill them and exit anyway? ")) (when (window-live-p window) - (quit-restore-window window 'kill))))) - (list-processes t))))) + (quit-restore-window window 'kill))))))))) ;; Query the user for other things, perhaps. (run-hook-with-args-until-failure 'kill-emacs-query-functions) (or (null confirm) @@ -7536,6 +7648,27 @@ as in \"og+rX-w\"." op char-right))) num-rights)) +(defun file-modes-number-to-symbolic (mode) + (string + (if (zerop (logand 8192 mode)) + (if (zerop (logand 16384 mode)) ?- ?d) + ?c) ; completeness + (if (zerop (logand 256 mode)) ?- ?r) + (if (zerop (logand 128 mode)) ?- ?w) + (if (zerop (logand 64 mode)) + (if (zerop (logand 2048 mode)) ?- ?S) + (if (zerop (logand 2048 mode)) ?x ?s)) + (if (zerop (logand 32 mode)) ?- ?r) + (if (zerop (logand 16 mode)) ?- ?w) + (if (zerop (logand 8 mode)) + (if (zerop (logand 1024 mode)) ?- ?S) + (if (zerop (logand 1024 mode)) ?x ?s)) + (if (zerop (logand 4 mode)) ?- ?r) + (if (zerop (logand 2 mode)) ?- ?w) + (if (zerop (logand 512 mode)) + (if (zerop (logand 1 mode)) ?- ?x) + (if (zerop (logand 1 mode)) ?T ?t)))) + (defun file-modes-symbolic-to-number (modes &optional from) "Convert symbolic file modes to numeric file modes. MODES is the string to convert, it should match @@ -7643,7 +7776,7 @@ Otherwise, trash FILENAME using the freedesktop.org conventions, (let (delete-by-moving-to-trash) (rename-file fn new-fn)))) ;; Otherwise, use the freedesktop.org method, as specified at - ;; http://freedesktop.org/wiki/Specifications/trash-spec + ;; https://freedesktop.org/wiki/Specifications/trash-spec (t (let* ((xdg-data-dir (directory-file-name |