(Info-directory-list): If path-separator isn't available, bind it here.
[bpt/emacs.git] / lisp / files.el
index 35a43d9..fd5268a 100644 (file)
@@ -130,7 +130,16 @@ If the buffer is visiting a new file, the value is nil.")
 
 (defconst file-precious-flag nil
   "*Non-nil means protect against I/O errors while saving files.
-Some modes set this non-nil in particular buffers.")
+Some modes set this non-nil in particular buffers.
+
+This feature works by writing the new contents into a temporary file
+and then renaming the temporary file to replace the original.
+In this way, any I/O error in writing leaves the original untouched,
+and there is never any instant where the file is nonexistent.
+
+Note that this feature forces backups to be made by copying.
+Yet, at the same time, saving a precious file
+breaks any hard links between it and other files.")
 
 (defvar version-control nil
   "*Control use of version numbers for backup files.
@@ -260,12 +269,15 @@ and ignores this variable.")
   "Value of the CDPATH environment variable, as a list.
 Not actually set up until the first time you you use it.")
 
+(defvar path-separator ":"
+  "Character used to separate concatenated paths.")
+
 (defun parse-colon-path (cd-path)
   "Explode a colon-separated list of paths into a string list."
   (and cd-path
        (let (cd-prefix cd-list (cd-start 0) cd-colon)
-        (setq cd-path (concat cd-path ":"))
-        (while (setq cd-colon (string-match ":" cd-path cd-start))
+        (setq cd-path (concat cd-path path-separator))
+        (while (setq cd-colon (string-match path-separator cd-path cd-start))
           (setq cd-list
                 (nconc cd-list
                        (list (if (= cd-start cd-colon)
@@ -343,7 +355,9 @@ Do not specify them in other calls."
   ;; to chase before getting an error.
   ;; PREV-DIRS can be a cons cell whose car is an alist
   ;; of truenames we've just recently computed.
-  (if (or (string= filename "~")
+
+  ;; The last test looks dubious, maybe `+' is meant here?  --simon.
+  (if (or (string= filename "") (string= filename "~")
          (and (string= (substring filename 0 1) "~")
               (string-match "~[^/]*" filename)))
       (progn
@@ -682,11 +696,19 @@ The buffer is not selected, just returned to the caller."
              (cond ((not (file-exists-p filename))
                     (error "File %s no longer exists!" filename))
                    ((yes-or-no-p
-                     (format
-                      (if (buffer-modified-p buf)
-    "File %s changed on disk.  Discard your edits? "
-    "File %s changed on disk.  Reread from disk? ")
-                      (file-name-nondirectory filename)))
+                     (if (string= (file-name-nondirectory filename)
+                                  (buffer-name buf))
+                         (format
+                          (if (buffer-modified-p buf)
+       "File %s changed on disk.  Discard your edits? "
+       "File %s changed on disk.  Reread from disk? ")
+                          (file-name-nondirectory filename))
+                       (format
+                        (if (buffer-modified-p buf)
+      "File %s changed on disk.  Discard your edits in %s? "
+      "File %s changed on disk.  Reread from disk into %s? ")
+                        (file-name-nondirectory filename)
+                        (buffer-name buf))))
                     (save-excursion
                       (set-buffer buf)
                       (revert-buffer t t)))))
@@ -834,6 +856,11 @@ run `normal-mode' explicitly."
                                  ("\\.hh\\'" . c++-mode)
                                  ("\\.C\\'" . c++-mode)
                                  ("\\.H\\'" . c++-mode)
+                                 ("\\.cpp\\'" . c++-mode)
+                                 ("\\.cxx\\'" . c++-mode)
+                                 ("\\.hxx\\'" . c++-mode)
+                                 ("\\.c\\+\\+\\'" . c++-mode)
+                                 ("\\.h\\+\\+\\'" . c++-mode)
 ;;;                              ("\\.mk\\'" . makefile-mode)
 ;;;                              ("[Mm]akefile" . makefile-mode)
 ;;; Less common extensions come here
@@ -909,9 +936,14 @@ The car of each element is compared with
 the name of the interpreter specified in the first line.
 If it matches, mode MODE is selected.")
 
-(defconst inhibit-first-line-modes-regexps '("\\.tar$")
+(defconst inhibit-first-line-modes-regexps '("\\.tar\\'")
   "List of regexps; if one matches a file name, don't look for `-*-'.")
 
+(defconst inhibit-first-line-modes-suffixes nil
+  "List of regexps for what to ignore, for `inhibit-first-line-modes-regexps'.
+When checking `inhibit-first-line-modes-regexps', we first discard
+from the end of the file name anything that matches one of these regexps.")
+
 (defvar user-init-file
   "" ; set by command-line
   "File name including directory of user's initialization file.")
@@ -937,10 +969,16 @@ If `enable-local-variables' is nil, this function does not check for a
           ;; Don't look for -*- if this file name matches any
           ;; of the regexps in inhibit-first-line-modes-regexps.
           (let ((temp inhibit-first-line-modes-regexps)
-                (name (file-name-sans-versions buffer-file-name)))
+                (name (if buffer-file-name
+                          (file-name-sans-versions buffer-file-name)
+                        (buffer-name))))
+            (while (let ((sufs inhibit-first-line-modes-suffixes))
+                     (while (and sufs (not (string-match (car sufs) name)))
+                       (setq sufs (cdr sufs)))
+                     sufs)
+              (setq name (substring name 0 (match-beginning 0))))
             (while (and temp
-                        (not (string-match (car temp)
-                                           name)))
+                        (not (string-match (car temp) name)))
               (setq temp (cdr temp)))
             (not temp))
           (search-forward "-*-" (save-excursion
@@ -977,10 +1015,11 @@ If `enable-local-variables' is nil, this function does not check for a
                       (forward-char -1)
                     (goto-char end))
                   (skip-chars-backward " \t")
-                  (funcall (intern (concat (downcase (buffer-substring beg (point))) "-mode"))))
+                  (funcall (intern (concat (downcase (buffer-substring beg (point))) "-mode")))
+                  (setq done t))
               ;; Simple -*-MODE-*- case.
-              (funcall (intern (concat (downcase (buffer-substring beg end)) "-mode"))))
-            (setq done t)))
+              (funcall (intern (concat (downcase (buffer-substring beg end)) "-mode")))
+              (setq done t))))
       ;; If we didn't find a mode from a -*- line, try using the file name.
       (if (and (not done) buffer-file-name)
          (let ((name buffer-file-name)
@@ -992,7 +1031,8 @@ If `enable-local-variables' is nil, this function does not check for a
              (let ((alist auto-mode-alist)
                    (mode nil))
                ;; Find first matching alist entry.
-               (let ((case-fold-search (eq system-type 'vax-vms)))
+               (let ((case-fold-search 
+                      (memq system-type '(vax-vms windows-nt))))
                  (while (and (not mode) alist)
                    (if (string-match (car (car alist)) name)
                        (if (and (consp (cdr (car alist)))
@@ -1048,9 +1088,12 @@ If `enable-local-variables' is nil, this function does not check for a
               (or (looking-at "[ \t]*\\([^ \t\n:]+\\)[ \t]*:[ \t]*")
                   (error "malformed -*- line"))
               (goto-char (match-end 0))
-              (let ((key (intern (downcase (buffer-substring
-                                            (match-beginning 1)
-                                            (match-end 1)))))
+              ;; There used to be a downcase here,
+              ;; but the manual didn't say so,
+              ;; and people want to set var names that aren't all lc.
+              (let ((key (intern (buffer-substring
+                                  (match-beginning 1)
+                                  (match-end 1))))
                     (val (save-restriction
                            (narrow-to-region (point) end)
                            (read (current-buffer)))))
@@ -1317,12 +1360,14 @@ if you wish to pass an empty string as the argument."
   (if buffer-file-name
       (set-buffer-modified-p t)))
 
-(defun write-file (filename)
+(defun write-file (filename &optional confirm)
   "Write current buffer into file FILENAME.
 Makes buffer visit that file, and marks it not modified.
 If the buffer is already visiting a file, you can specify
 a directory name as FILENAME, to write a file of the same
-old name in that directory."
+old name in that directory.
+If optional second arg CONFIRM is non-nil,
+ask for confirmation for overwriting an existing file."
 ;;  (interactive "FWrite file: ")
   (interactive
    (list (if buffer-file-name
@@ -1331,7 +1376,8 @@ old name in that directory."
           (read-file-name "Write file: "
                               (cdr (assq 'default-directory
                                          (buffer-local-variables)))
-                              nil nil (buffer-name)))))
+                              nil nil (buffer-name)))
+        t))
   (or (null filename) (string-equal filename "")
       (progn
        ;; If arg is just a directory,
@@ -1339,6 +1385,10 @@ old name in that directory."
        (if (and (file-directory-p filename) buffer-file-name)
            (setq filename (concat (file-name-as-directory filename)
                                   (file-name-nondirectory buffer-file-name))))
+       (and confirm
+            (file-exists-p filename)
+            (or (y-or-n-p (format "File `%s' exists; overwrite? " filename))
+                (error "Canceled")))
        (set-visited-file-name filename)))
   (set-buffer-modified-p t)
   (save-buffer))
@@ -1455,7 +1505,11 @@ we do not remove backup version numbers, only true file version numbers."
   (let ((handler (find-file-name-handler file 'file-ownership-preserved-p)))
     (if handler
        (funcall handler 'file-ownership-preserved-p file)
-      (= (nth 2 (file-attributes file)) (user-uid)))))
+      (let ((attributes (file-attributes file)))
+       ;; Return t if the file doesn't exist, since it's true that no
+       ;; information would be lost by an (attempted) delete and create.
+       (or (null attributes)
+           (= (nth 2 attributes) (user-uid)))))))
 
 (defun file-name-sans-extension (filename)
   "Return FILENAME sans final \"extension\".
@@ -1813,8 +1867,7 @@ With arg, set read-only iff arg is positive."
        (if (null arg)
             (not buffer-read-only)
             (> (prefix-numeric-value arg) 0)))
-  ;; Force mode-line redisplay
-  (set-buffer-modified-p (buffer-modified-p)))
+  (force-mode-line-update))
 
 (defun insert-file (filename)
   "Insert contents of file FILENAME into buffer after point.
@@ -1858,11 +1911,22 @@ saying what text to write."
 This function is useful for creating multiple shell process buffers
 or multiple mail buffers, etc."
   (interactive)
-  (let* ((new-buf (generate-new-buffer (buffer-name)))
-        (name (buffer-name new-buf)))
-    (kill-buffer new-buf)
-    (rename-buffer name)
-    (set-buffer-modified-p (buffer-modified-p)))) ; force mode line update
+  (save-match-data
+    (let* ((base-name (if (and (string-match "<[0-9]+>\\'" (buffer-name))
+                              (not (and buffer-file-name
+                                        (string= (buffer-name)
+                                                 (file-name-nondirectory
+                                                  buffer-file-name)))))
+                         ;; If the existing buffer name has a <NNN>,
+                         ;; which isn't part of the file name (if any),
+                         ;; then get rid of that.
+                         (substring (buffer-name) 0 (match-beginning 0))
+                       (buffer-name)))
+          (new-buf (generate-new-buffer base-name))
+          (name (buffer-name new-buf)))
+      (kill-buffer new-buf)
+      (rename-buffer name)
+      (force-mode-line-update))))
 
 (defun make-directory (dir &optional parents)
   "Create the directory DIR and any nonexistent parent dirs.
@@ -1902,13 +1966,27 @@ which are the arguments that `revert-buffer' received.")
 Gets two args, first the nominal file name to use,
 and second, t if reading the auto-save file.")
 
+(defvar before-revert-hook nil
+  "Normal hook for `revert-buffer' to run before reverting.
+If `revert-buffer-function' is used to override the normal revert
+mechanism, this hook is not used.")
+
+(defvar after-revert-hook nil
+  "Normal hook for `revert-buffer' to run after reverting.
+Note that the hook value that it runs is the value that was in effect
+before reverting; that makes a difference if you have buffer-local
+hook functions.
+
+If `revert-buffer-function' is used to override the normal revert
+mechanism, this hook is not used.")
+
 (defun revert-buffer (&optional ignore-auto noconfirm)
   "Replace the buffer text with the text of the visited file on disk.
 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.
 
-When called from lisp, The first argument is IGNORE-AUTO; only offer
+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
 sense of this argument is the reverse of the prefix argument, for the
 sake of backward compatibility.  IGNORE-AUTO is optional, defaulting
@@ -1928,7 +2006,7 @@ beginning and `after-revert-hook' at the end."
   ;; there's no straightforward way to encourage authors to notice a
   ;; reversal of the argument sense.  So I'm just changing the user
   ;; interface, but leaving the programmatic interface the same.
-  (interactive (list (not prefix-arg)))
+  (interactive (list (not current-prefix-arg)))
   (if revert-buffer-function
       (funcall revert-buffer-function ignore-auto noconfirm)
     (let* ((opoint (point))
@@ -1955,42 +2033,50 @@ beginning and `after-revert-hook' at the end."
             ;; Get rid of all undo records for this buffer.
             (or (eq buffer-undo-list t)
                 (setq buffer-undo-list nil))
-            (let ((buffer-read-only nil)
-                  ;; Don't make undo records for the reversion.
-                  (buffer-undo-list t))
-              (if revert-buffer-insert-file-contents-function
-                  (funcall revert-buffer-insert-file-contents-function
-                           file-name auto-save-p)
-                (if (not (file-exists-p file-name))
-                    (error "File %s no longer exists!" file-name))
-                ;; 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)
-                (insert-file-contents file-name (not auto-save-p)
-                                      nil nil t)))
-            (goto-char (min opoint (point-max)))
-            ;; Recompute the truename in case changes in symlinks
-            ;; have changed the truename.
-            (setq buffer-file-truename
-                  (abbreviate-file-name (file-truename buffer-file-name)))
-            (after-find-file nil nil t t)
-            (run-hooks 'after-revert-hook)
+            ;; Effectively copy the after-revert-hook status,
+            ;; since after-find-file will clobber it.
+            (let ((global-hook (default-value 'after-revert-hook))
+                  (local-hook-p (local-variable-p 'after-revert-hook))
+                  (local-hook (and (local-variable-p 'after-revert-hook)
+                                   after-revert-hook)))
+              (let (buffer-read-only
+                    ;; Don't make undo records for the reversion.
+                    (buffer-undo-list t))
+                (if revert-buffer-insert-file-contents-function
+                    (funcall revert-buffer-insert-file-contents-function
+                             file-name auto-save-p)
+                  (if (not (file-exists-p file-name))
+                      (error "File %s no longer exists!" file-name))
+                  ;; 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)
+                  (insert-file-contents file-name (not auto-save-p)
+                                        nil nil t)))
+              (goto-char (min opoint (point-max)))
+              ;; Recompute the truename in case changes in symlinks
+              ;; have changed the truename.
+              (setq buffer-file-truename
+                    (abbreviate-file-name (file-truename buffer-file-name)))
+              (after-find-file nil nil t t)
+              ;; Run after-revert-hook as it was before we reverted.
+              (setq-default revert-buffer-internal-hook global-hook)
+              (if local-hook-p
+                  (progn
+                    (make-local-variable 'revert-buffer-internal-hook)
+                    (setq revert-buffer-internal-hook local-hook))
+                (kill-local-variable 'revert-buffer-internal-hook))
+              (run-hooks 'revert-buffer-internal-hook))
             t)))))
 
 (defun recover-file (file)
   "Visit file FILE, but get contents from its last auto-save file."
-  (interactive
-   (let ((prompt-file buffer-file-name)
-        (file-name nil)
-        (file-dir nil))
-     (and prompt-file
-         (setq file-name (file-name-nondirectory prompt-file)
-               file-dir (file-name-directory prompt-file)))
-     (list (read-file-name "Recover file: "
-                              file-dir nil nil file-name))))
+  ;; Actually putting the file name in the minibuffer should be used
+  ;; only rarely.
+  ;; Not just because users often use the default.
+  (interactive "fRecover file: ")
   (setq file (expand-file-name file))
   (if (auto-save-file-name-p (file-name-nondirectory file))
       (error "%s is an auto-save file" file))
@@ -2013,6 +2099,49 @@ beginning and `after-revert-hook' at the end."
           (after-find-file nil nil t))
          (t (error "Recover-file cancelled.")))))
 
+(defun recover-session ()
+  "Recover auto save files from a previous Emacs session.
+This command first displays a Dired buffer showing you the
+previous sessions that you could recover from.
+To choose one, move point to the proper line and then type C-c C-c.
+Then you'll be asked about a number of files to recover."
+  (interactive)
+  (dired "~/.save*")
+  (goto-char (point-min))
+  (or (looking-at "Move to the session you want to recover,")
+      (let ((inhibit-read-only t))
+       (insert "Move to the session you want to recover,\n")
+       (insert "then type C-c C-c to select it.\n\n")))
+  (use-local-map (nconc (make-sparse-keymap) (current-local-map)))
+  (define-key (current-local-map) "\C-c\C-c" 'recover-session-finish))
+
+(defun recover-session-finish ()
+  "Choose one saved session to recover auto-save files from.
+This command is used in the special Dired buffer created by
+\\[recover-session]."
+  (interactive)
+  ;; Get the name of the session file to recover from.
+  (let ((file (dired-get-filename))
+       (buffer (get-buffer-create " *recover*")))
+    (unwind-protect
+       (save-excursion
+         ;; Read in the auto-save-list file.
+         (set-buffer buffer)
+         (erase-buffer)
+         (insert-file-contents file)
+         (map-y-or-n-p  "Recover %s? "
+                        (lambda (file) (save-excursion (recover-file file)))
+                        (lambda ()
+                          (if (eobp)
+                              nil
+                            (prog1
+                                (buffer-substring-no-properties
+                                 (point) (progn (end-of-line) (point)))
+                              (while (and (eolp) (not (eobp)))
+                                (forward-line 2)))))
+                        '("file" "files" "recover")))
+      (kill-buffer buffer))))
+
 (defun kill-some-buffers ()
   "For each buffer, ask whether to kill it."
   (interactive)
@@ -2200,7 +2329,8 @@ If WILDCARD, it also runs the shell specified by `shell-file-name'."
                              (substring pattern (match-beginning 0)))
                      beg (1+ (match-end 0))))
              (call-process shell-file-name nil t nil
-                           "-c" (concat insert-directory-program
+                           "-c" (concat "\\"  ;; Disregard shell aliases!
+                                        insert-directory-program
                                         " -d "
                                         (if (stringp switches)
                                             switches
@@ -2231,7 +2361,10 @@ If WILDCARD, it also runs the shell specified by `shell-file-name'."
 
 (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.")
+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'.")
 
 (defun save-buffers-kill-emacs (&optional arg)
   "Offer to save each buffer, then kill this Emacs process.