diff options
Diffstat (limited to 'lisp/files.el')
-rw-r--r-- | lisp/files.el | 272 |
1 files changed, 235 insertions, 37 deletions
diff --git a/lisp/files.el b/lisp/files.el index aa6e7f1082a..bc74ecf4667 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -213,6 +213,15 @@ have fast storage with limited space, such as a RAM disk." ;; The system null device. (Should reference NULL_DEVICE from C.) (defvar null-device "/dev/null" "The system null device.") +(declare-function msdos-long-file-names "msdos.c") +(declare-function w32-long-file-name "w32proc.c") +(declare-function dired-get-filename "dired" (&optional localp no-error-if-not-filep)) +(declare-function dired-unmark "dired" (arg)) +(declare-function dired-do-flagged-delete "dired" (&optional nomessage)) +(declare-function dos-8+3-filename "dos-fns" (filename)) +(declare-function vms-read-directory "vms-patch" (dirname switches buffer)) +(declare-function view-mode-disable "view" ()) + (defvar file-name-invalid-regexp (cond ((and (eq system-type 'ms-dos) (not (msdos-long-file-names))) (concat "^\\([^A-Z[-`a-z]\\|..+\\)?:\\|" ; colon except after drive @@ -450,7 +459,7 @@ use `before-save-hook'.") (defcustom enable-local-variables t "Control use of local variables in files you visit. -The value can be t, nil, :safe, :all or something else. +The value can be t, nil, :safe, :all, or something else. A value of t means file local variables specifications are obeyed if all the specified variable values are safe; if any values are @@ -626,9 +635,10 @@ Directories are separated by occurrences of `path-separator' (if (file-exists-p dir) (error "%s is not a directory" dir) (error "%s: no such directory" dir)) - (if (file-executable-p dir) - (setq default-directory dir) - (error "Cannot cd to %s: Permission denied" dir)))) + (unless (file-executable-p dir) + (error "Cannot cd to %s: Permission denied" dir)) + (setq default-directory dir) + (set (make-local-variable 'list-buffers-directory) dir))) (defun cd (dir) "Make DIR become the current buffer's default directory. @@ -647,7 +657,7 @@ The path separator is colon in GNU and GNU-like systems." (let ((trypath (parse-colon-path (getenv "CDPATH")))) (setq cd-path (or trypath (list "./"))))) (if (not (catch 'found - (mapcar + (mapc (function (lambda (x) (let ((f (expand-file-name (concat x dir)))) (if (file-directory-p f) @@ -715,6 +725,28 @@ PATH-AND-SUFFIXES is a pair of lists, (DIRECTORIES . SUFFIXES)." ((null action) (try-completion string names)) (t (test-completion string names)))))) +(defun locate-dominating-file (file regexp) + "Look up the directory hierarchy from FILE for a file matching REGEXP." + (while (and file (not (file-directory-p file))) + (setq file (file-name-directory (directory-file-name file)))) + (catch 'found + (let ((user (nth 2 (file-attributes file))) + ;; Abbreviate, so as to stop when we cross ~/. + (dir (abbreviate-file-name (file-name-as-directory file))) + files) + ;; As a heuristic, we stop looking up the hierarchy of directories as + ;; soon as we find a directory belonging to another user. This should + ;; save us from looking in things like /net and /afs. This assumes + ;; that all the files inside a project belong to the same user. + (while (and dir (equal user (nth 2 (file-attributes dir)))) + (if (setq files (directory-files dir 'full regexp)) + (throw 'found (car files)) + (if (equal dir + (setq dir (file-name-directory + (directory-file-name dir)))) + (setq dir nil)))) + nil))) + (defun executable-find (command) "Search for COMMAND in `exec-path' and return the absolute file name. Return nil if COMMAND is not found anywhere in `exec-path'." @@ -731,17 +763,28 @@ This is an interface to the function `load'." (cons load-path (get-load-suffixes))))) (load library)) -(defun file-remote-p (file) +(defun file-remote-p (file &optional identification connected) "Test whether FILE specifies a location on a remote system. Return an identification of the system if the location is indeed remote. The identification of the system may comprise a method to access the system and its hostname, amongst other things. For example, the filename \"/user@host:/foo\" specifies a location -on the system \"/user@host:\"." +on the system \"/user@host:\". + +IDENTIFICATION specifies which part of the identification shall +be returned as string. IDENTIFICATION can be the symbol +`method', `user' or `host'; any other value is handled like nil +and means to return the complete identification string. + +If CONNECTED is non-nil, the function returns an identification only +if FILE is located on a remote system, and a connection is established +to that remote system. + +`file-remote-p' will never open a connection on its own." (let ((handler (find-file-name-handler file 'file-remote-p))) (if handler - (funcall handler 'file-remote-p file) + (funcall handler 'file-remote-p file identification connected) nil))) (defun file-local-copy (file) @@ -1057,6 +1100,12 @@ Recursive uses of the minibuffer will not be affected." ,@body) (remove-hook 'minibuffer-setup-hook ,hook))))) +(defcustom find-file-confirm-nonexistent-file nil + "If non-nil, `find-file' requires confirmation before visiting a new file." + :group 'find-file + :version "23.1" + :type 'boolean) + (defun find-file-read-args (prompt mustmatch) (list (let ((find-file-default (and buffer-file-name @@ -1087,7 +1136,9 @@ suppress wildcard expansion by setting `find-file-wildcards' to nil. To visit a file without any kind of conversion and without automatically choosing a major mode, use \\[find-file-literally]." - (interactive (find-file-read-args "Find file: " nil)) + (interactive + (find-file-read-args "Find file: " + (if find-file-confirm-nonexistent-file 'confirm-only))) (let ((value (find-file-noselect filename nil nil wildcards))) (if (listp value) (mapcar 'switch-to-buffer (nreverse value)) @@ -1105,7 +1156,9 @@ type M-n to pull it into the minibuffer. Interactively, or if WILDCARDS is non-nil in a call from Lisp, expand wildcards (if any) and visit multiple files." - (interactive (find-file-read-args "Find file in other window: " nil)) + (interactive + (find-file-read-args "Find file in other window: " + (if find-file-confirm-nonexistent-file 'confirm-only))) (let ((value (find-file-noselect filename nil nil wildcards))) (if (listp value) (progn @@ -1126,7 +1179,9 @@ type M-n to pull it into the minibuffer. Interactively, or if WILDCARDS is non-nil in a call from Lisp, expand wildcards (if any) and visit multiple files." - (interactive (find-file-read-args "Find file in other frame: " nil)) + (interactive + (find-file-read-args "Find file in other frame: " + (if find-file-confirm-nonexistent-file 'confirm-only))) (let ((value (find-file-noselect filename nil nil wildcards))) (if (listp value) (progn @@ -1149,7 +1204,9 @@ file names with wildcards." "Edit file FILENAME but don't allow changes. Like \\[find-file], but marks buffer as read-only. Use \\[toggle-read-only] to permit editing." - (interactive (find-file-read-args "Find file read-only: " nil)) + (interactive + (find-file-read-args "Find file read-only: " + (if find-file-confirm-nonexistent-file 'confirm-only))) (unless (or (and wildcards find-file-wildcards (not (string-match "\\`/:" filename)) (string-match "[[*?]" filename)) @@ -1164,7 +1221,9 @@ Use \\[toggle-read-only] to permit editing." "Edit file FILENAME in another window but don't allow changes. Like \\[find-file-other-window], but marks buffer as read-only. Use \\[toggle-read-only] to permit editing." - (interactive (find-file-read-args "Find file read-only other window: " nil)) + (interactive + (find-file-read-args "Find file read-only other window: " + (if find-file-confirm-nonexistent-file 'confirm-only))) (unless (or (and wildcards find-file-wildcards (not (string-match "\\`/:" filename)) (string-match "[[*?]" filename)) @@ -1179,7 +1238,9 @@ Use \\[toggle-read-only] to permit editing." "Edit file FILENAME in another frame but don't allow changes. Like \\[find-file-other-frame], but marks buffer as read-only. Use \\[toggle-read-only] to permit editing." - (interactive (find-file-read-args "Find file read-only other frame: " nil)) + (interactive + (find-file-read-args "Find file read-only other frame: " + (if find-file-confirm-nonexistent-file 'confirm-only))) (unless (or (and wildcards find-file-wildcards (not (string-match "\\`/:" filename)) (string-match "[[*?]" filename)) @@ -1287,11 +1348,14 @@ killed." (defun create-file-buffer (filename) "Create a suitably named buffer for visiting FILENAME, and return it. FILENAME (sans directory) is used unchanged if that name is free; -otherwise a string <2> or <3> or ... is appended to get an unused name." +otherwise a string <2> or <3> or ... is appended to get an unused name. +Spaces at the start of FILENAME (sans directory) are removed." (let ((lastname (file-name-nondirectory filename))) (if (string= lastname "") (setq lastname filename)) - (generate-new-buffer lastname))) + (save-match-data + (string-match "^ *\\(.*\\)" lastname) + (generate-new-buffer (match-string 1 lastname))))) (defun generate-new-buffer (name) "Create and return a buffer with a name based on NAME. @@ -1915,6 +1979,7 @@ since only a single case-insensitive search through the alist is made." ("\\.ins\\'" . tex-mode) ;Installation files for TeX packages. ("\\.ltx\\'" . latex-mode) ("\\.dtx\\'" . doctex-mode) + ("\\.org\\'" . org-mode) ("\\.el\\'" . emacs-lisp-mode) ("\\.\\(scm\\|stk\\|ss\\|sch\\)\\'" . scheme-mode) ("\\.l\\'" . lisp-mode) @@ -1978,8 +2043,9 @@ since only a single case-insensitive search through the alist is made." ("\\.tar\\'" . tar-mode) ;; The list of archive file extensions should be in sync with ;; `auto-coding-alist' with `no-conversion' coding system. - ("\\.\\(arc\\|zip\\|lzh\\|lha\\|zoo\\|[jew]ar\\|xpi\\)\\'" . archive-mode) - ("\\.\\(ARC\\|ZIP\\|LZH\\|LHA\\|ZOO\\|[JEW]AR\\|XPI\\)\\'" . archive-mode) + ("\\.\\(\ +arc\\|zip\\|lzh\\|lha\\|zoo\\|[jew]ar\\|xpi\\|rar\\|\ +ARC\\|ZIP\\|LZH\\|LHA\\|ZOO\\|[JEW]AR\\|XPI\\|RAR\\)\\'" . archive-mode) ("\\.\\(sx[dmicw]\\|odt\\)\\'" . archive-mode) ; OpenOffice.org ;; Mailer puts message to be edited in ;; /tmp/Re.... or Message @@ -1994,7 +2060,6 @@ since only a single case-insensitive search through the alist is made." ("\\.dtd\\'" . sgml-mode) ("\\.ds\\(ss\\)?l\\'" . dsssl-mode) ("\\.js\\'" . java-mode) ; javascript-mode would be better - ("\\.x[bp]m\\'" . c-mode) ("\\.d?v\\'" . verilog-mode) ;; .emacs or .gnus or .viper following a directory delimiter in ;; Unix, MSDOG or VMS syntax. @@ -2012,6 +2077,7 @@ since only a single case-insensitive search through the alist is made." ("\\.\\(diffs?\\|patch\\|rej\\)\\'" . diff-mode) ("\\.\\(dif\\|pat\\)\\'" . diff-mode) ; for MSDOG ("\\.[eE]?[pP][sS]\\'" . ps-mode) + ("\\.\\(?:PDF\\|DVI\\|pdf\\|dvi\\)\\'" . doc-view-mode) ("configure\\.\\(ac\\|in\\)\\'" . autoconf-mode) ("BROWSE\\'" . ebrowse-tree-mode) ("\\.ebrowse\\'" . ebrowse-tree-mode) @@ -2388,7 +2454,11 @@ Otherwise, return nil; point may be changed." ;; put them in the first line of ;; such a file without screwing up ;; the interpreter invocation. - (and (looking-at "^#!") 2)) t) + ;; The same holds for + ;; '\" + ;; in man pages (preprocessor + ;; magic for the `man' program). + (and (looking-at "^\\(#!\\|'\\\\\"\\)") 2)) t) (progn (skip-chars-forward " \t") (setq beg (point)) @@ -2465,11 +2535,13 @@ asking you for confirmation." minor-mode-overriding-map-alist mode-line-buffer-identification mode-line-format + mode-line-client mode-line-modes mode-line-modified mode-line-mule-info mode-line-position mode-line-process + mode-line-remote mode-name outline-level overriding-local-map @@ -2577,7 +2649,7 @@ n -- to ignore the local variables list.") (if offer-save '(?! ?y ?n ?\s ?\C-g) '(?y ?n ?\s ?\C-g))) done) (while (not done) - (message prompt) + (message "%s" prompt) (setq char (read-event)) (if (numberp char) (cond ((eq char ?\C-v) @@ -3163,13 +3235,13 @@ BACKUPNAME is the backup file name, which is the old file renamed." (set-default-file-modes ?\700) (when (condition-case nil ;; Try to overwrite old backup first. - (copy-file from-name to-name t t) + (copy-file from-name to-name t t t) (error t)) (while (condition-case nil (progn (when (file-exists-p to-name) (delete-file to-name)) - (copy-file from-name to-name nil t) + (copy-file from-name to-name nil t t) nil) (file-already-exists t)) ;; The file was somehow created by someone else between @@ -4066,6 +4138,8 @@ or multiple mail buffers, etc." (defun make-directory (dir &optional parents) "Create the directory DIR and any nonexistent parent dirs. +If DIR already exists as a directory, do nothing. + Interactively, the default choice of directory to create is the current default directory for file names. That is useful when you have visited a file in a nonexistent directory. @@ -4147,10 +4221,12 @@ 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. -This command also works for special buffers that contain text which -doesn't come from a file, but reflects some other data base instead: -for example, Dired buffers and `buffer-list' buffers. In these cases, -it reconstructs the buffer contents from the appropriate data base. +This command also implements an interface for special buffers +that contain text which doesn't come from a file, but reflects +some other data instead (e.g. Dired buffers, `buffer-list' +buffers). This is done via the variable +`revert-buffer-function'. In these cases, it should reconstruct +the buffer contents from the appropriate data. When called from Lisp, the first argument is IGNORE-AUTO; only offer to revert from the auto-save file when this is nil. Note that the @@ -4437,6 +4513,14 @@ 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? " (buffer-name buffer) + (if (buffer-modified-p buffer) + "HAS BEEN EDITED" "is unmodified"))) + (kill-buffer buffer))) + (defun kill-some-buffers (&optional list) "Kill some buffers. Asks the user whether to kill each one of them. Non-interactively, if optional argument LIST is non-nil, it @@ -4451,13 +4535,20 @@ specifies the list of buffers to kill, asking for approval for each one." ; if we killed the base buffer. (not (string-equal name "")) (/= (aref name 0) ?\s) - (yes-or-no-p - (format "Buffer %s %s. Kill? " - name - (if (buffer-modified-p buffer) - "HAS BEEN EDITED" "is unmodified"))) - (kill-buffer buffer))) + (kill-buffer-ask buffer))) (setq list (cdr list)))) + +(defun kill-matching-buffers (regexp &optional internal-too) + "Kill buffers whose name matches the specified regexp. +The optional second argument indicates whether to kill internal buffers too." + (interactive "sKill buffers matching this regular expression: \nP") + (dolist (buffer (buffer-list)) + (let ((name (buffer-name buffer))) + (when (and name (not (string-equal name "")) + (or internal-too (/= (aref name 0) ?\s)) + (string-match regexp name)) + (kill-buffer-ask buffer))))) + (defun auto-save-mode (arg) "Toggle auto-saving of contents of current buffer. @@ -5228,9 +5319,8 @@ message to that effect instead of signaling an error." (defvar kill-emacs-query-functions nil "Functions to call with no arguments to query about killing Emacs. If any of these functions returns nil, killing Emacs is cancelled. -`save-buffers-kill-emacs' (\\[save-buffers-kill-emacs]) calls these functions, -but `kill-emacs', the low level primitive, does not. -See also `kill-emacs-hook'.") +`save-buffers-kill-emacs' calls these functions, but `kill-emacs', +the low level primitive, does not. See also `kill-emacs-hook'.") (defcustom confirm-kill-emacs nil "How to ask for confirmation when leaving Emacs. @@ -5269,6 +5359,22 @@ With prefix arg, silently save all file-visiting buffers, then kill." (or (null confirm-kill-emacs) (funcall confirm-kill-emacs "Really exit Emacs? ")) (kill-emacs))) + +(defun save-buffers-kill-terminal (&optional arg) + "Offer to save each buffer, then kill the current connection. +If the current frame has no client, kill Emacs itself. + +With prefix arg, silently save all file-visiting buffers, then kill. + +If emacsclient was started with a list of filenames to edit, then +only these files will be asked to be saved." + (interactive "P") + (let ((proc (frame-parameter (selected-frame) 'client)) + (frame (selected-frame))) + (if (null proc) + (save-buffers-kill-emacs) + (server-save-buffers-kill-terminal proc arg)))) + ;; We use /: as a prefix to "quote" a file name ;; so that magic file name handlers will not apply to it. @@ -5357,6 +5463,98 @@ With prefix arg, silently save all file-visiting buffers, then kill." (t (apply operation arguments))))) +;; Symbolic modes and read-file-modes. + +(defun file-modes-char-to-who (char) + "Convert CHAR to a who-mask from a symbolic mode notation. +CHAR is in [ugoa] and represents the users on which rights are applied." + (cond ((= char ?u) #o4700) + ((= char ?g) #o2070) + ((= char ?o) #o1007) + ((= char ?a) #o7777) + (t (error "%c: bad `who' character" char)))) + +(defun file-modes-char-to-right (char &optional from) + "Convert CHAR to a right-mask from a symbolic mode notation. +CHAR is in [rwxXstugo] and represents a right. +If CHAR is in [Xugo], the value is extracted from FROM (or 0 if nil)." + (or from (setq from 0)) + (cond ((= char ?r) #o0444) + ((= char ?w) #o0222) + ((= char ?x) #o0111) + ((= char ?s) #o1000) + ((= char ?t) #o6000) + ;; Rights relative to the previous file modes. + ((= char ?X) (if (= (logand from #o111) 0) 0 #o0111)) + ((= char ?u) (let ((uright (logand #o4700 from))) + (+ uright (/ uright #o10) (/ uright #o100)))) + ((= char ?g) (let ((gright (logand #o2070 from))) + (+ gright (/ gright #o10) (* gright #o10)))) + ((= char ?o) (let ((oright (logand #o1007 from))) + (+ oright (* oright #o10) (* oright #o100)))) + (t (error "%c: bad right character" char)))) + +(defun file-modes-rights-to-number (rights who-mask &optional from) + "Convert a right string to a right-mask from a symbolic modes notation. +RIGHTS is the right string, it should match \"([+=-][rwxXstugo]+)+\". +WHO-MASK is the mask number of the users on which the rights are to be applied. +FROM (or 0 if nil) is the orginal modes of the file to be chmod'ed." + (let* ((num-rights (or from 0)) + (list-rights (string-to-list rights)) + (op (pop list-rights))) + (while (memq op '(?+ ?- ?=)) + (let ((num-right 0) + char-right) + (while (memq (setq char-right (pop list-rights)) + '(?r ?w ?x ?X ?s ?t ?u ?g ?o)) + (setq num-right + (logior num-right + (file-modes-char-to-right char-right num-rights)))) + (setq num-right (logand who-mask num-right) + num-rights + (cond ((= op ?+) (logior num-rights num-right)) + ((= op ?-) (logand num-rights (lognot num-right))) + (t (logior (logand num-rights (lognot who-mask)) num-right))) + op char-right))) + num-rights)) + +(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 +\"[ugoa]*([+-=][rwxXstugo]+)+,...\". +See (info \"(coreutils)File permissions\") for more information on this +notation. +FROM (or 0 if nil) is the orginal modes of the file to be chmod'ed." + (save-match-data + (let ((case-fold-search nil) + (num-modes (or from 0))) + (while (/= (string-to-char modes) 0) + (if (string-match "^\\([ugoa]*\\)\\([+=-][rwxXstugo]+\\)+\\(,\\|\\)" modes) + (let ((num-who (apply 'logior 0 + (mapcar 'file-modes-char-to-who + (match-string 1 modes))))) + (when (= num-who 0) + (setq num-who (default-file-modes))) + (setq num-modes + (file-modes-rights-to-number (substring modes (match-end 1)) + num-who num-modes) + modes (substring modes (match-end 3)))) + (error "Parse error in modes near `%s'" (substring modes 0)))) + num-modes))) + +(defun read-file-modes (&optional prompt orig-file) + "Read file modes in octal or symbolic notation. +PROMPT is used as the prompt, default to `File modes (octal or symbolic): '. +ORIG-FILE is the original file of which modes will be change." + (let* ((modes (or (if orig-file (file-modes orig-file) 0) + (error "File not found"))) + (value (read-string (or prompt "File modes (octal or symbolic): ")))) + (save-match-data + (if (string-match "^[0-7]+" value) + (string-to-number value 8) + (file-modes-symbolic-to-number value modes))))) + + (define-key ctl-x-map "\C-f" 'find-file) (define-key ctl-x-map "\C-r" 'find-file-read-only) (define-key ctl-x-map "\C-v" 'find-alternate-file) @@ -5366,7 +5564,7 @@ With prefix arg, silently save all file-visiting buffers, then kill." (define-key ctl-x-map "i" 'insert-file) (define-key esc-map "~" 'not-modified) (define-key ctl-x-map "\C-d" 'list-directory) -(define-key ctl-x-map "\C-c" 'save-buffers-kill-emacs) +(define-key ctl-x-map "\C-c" 'save-buffers-kill-terminal) (define-key ctl-x-map "\C-q" 'toggle-read-only) (define-key ctl-x-4-map "f" 'find-file-other-window) |