New version.
[bpt/emacs.git] / lisp / tar-mode.el
index 7375e54..9ac9eb9 100644 (file)
@@ -1,95 +1,95 @@
 ;;; tar-mode.el --- simple editing of tar files from GNU emacs
 
-;;; Copyright (C) 1990, 1991, 1993, 1994 Free Software Foundation, Inc.
+;; Copyright (C) 1990, 1991, 1993, 1994, 1995 Free Software Foundation, Inc.
 
 ;; Author: Jamie Zawinski <jwz@lucid.com>
 ;; Created: 04 Apr 1990
-;; Version: 1.21bis (some cleanup by ESR)
 ;; Keywords: unix
 
-;;; This file is part of GNU Emacs.
-;;;
-;;; GNU Emacs is free software; you can redistribute it and/or modify
-;;; it under the terms of the GNU General Public License as published by
-;;; the Free Software Foundation; either version 2, or (at your option)
-;;; any later version.
-;;;
-;;; GNU Emacs is distributed in the hope that it will be useful,
-;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;;; GNU General Public License for more details.
-;;;
-;;; You should have received a copy of the GNU General Public License
-;;; along with GNU Emacs; see the file COPYING.  If not, write to
-;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
 
 ;;; Commentary:
 
-;;; This package attempts to make dealing with Unix 'tar' archives easier.
-;;; When this code is loaded, visiting a file whose name ends in '.tar' will
-;;; cause the contents of that archive file to be displayed in a Dired-like
-;;; listing.  It is then possible to use the customary Dired keybindings to
-;;; extract sub-files from that archive, either by reading them into their own
-;;; editor buffers, or by copying them directly to arbitrary files on disk.
-;;; It is also possible to delete sub-files from within the tar file and write
-;;; the modified archive back to disk, or to edit sub-files within the archive
-;;; and re-insert the modified files into the archive.  See the documentation
-;;; string of tar-mode for more info.
-
-;;; This code now understands the extra fields that GNU tar adds to tar files.
-
-;;; This interacts correctly with "uncompress.el" in the Emacs library,
-;;; which you get with 
-;;;
-;;;  (autoload 'uncompress-while-visiting "uncompress")
-;;;  (setq auto-mode-alist (cons '("\\.Z$" . uncompress-while-visiting)
-;;;                       auto-mode-alist))
-;;;
-;;; Do not attempt to use tar-mode.el with crypt.el, you will lose.
-
-;;;    ***************   TO DO   *************** 
-;;;
-;;; o  chmod should understand "a+x,og-w".
-;;;
-;;; o  It's not possible to add a NEW file to a tar archive; not that 
-;;;    important, but still...
-;;;
-;;; o  The code is less efficient that it could be - in a lot of places, I
-;;;    pull a 512-character string out of the buffer and parse it, when I could
-;;;    be parsing it in place, not garbaging a string.  Should redo that.
-;;;
-;;; o  I'd like a command that searches for a string/regexp in every subfile
-;;;    of an archive, where <esc> would leave you in a subfile-edit buffer.
-;;;    (Like the Meta-R command of the Zmacs mail reader.)
-;;;
-;;; o  Sometimes (but not always) reverting the tar-file buffer does not 
-;;;    re-grind the listing, and you are staring at the binary tar data.
-;;;    Typing 'g' again immediately after that will always revert and re-grind
-;;;    it, though.  I have no idea why this happens.
-;;;
-;;; o  Tar-mode interacts poorly with crypt.el and zcat.el because the tar
-;;;    write-file-hook actually writes the file.  Instead it should remove the
-;;;    header (and conspire to put it back afterwards) so that other write-file
-;;;    hooks which frob the buffer have a chance to do their dirty work.  There
-;;;    might be a problem if the tar write-file-hook does not come *first* on
-;;;    the list.
-;;;
-;;; o  Block files, sparse files, continuation files, and the various header 
-;;;    types aren't editable.  Actually I don't know that they work at all.
-
-;;; Rationale:
-
-;;; Why does tar-mode edit the file itself instead of using tar?
-
-;;; That means that you can edit tar files which you don't have room for
-;;; on your local disk.
-
-;;; I don't know about recent features in gnu tar, but old versions of tar
-;;; can't replace a file in the middle of a tar file with a new version.
-;;; Tar-mode can.  I don't think tar can do things like chmod the subfiles.
-;;; An implementation which involved unpacking and repacking the file into
-;;; some scratch directory would be very wasteful, and wouldn't be able to
-;;; preserve the file owners.
+;; This package attempts to make dealing with Unix 'tar' archives easier.
+;; When this code is loaded, visiting a file whose name ends in '.tar' will
+;; cause the contents of that archive file to be displayed in a Dired-like
+;; listing.  It is then possible to use the customary Dired keybindings to
+;; extract sub-files from that archive, either by reading them into their own
+;; editor buffers, or by copying them directly to arbitrary files on disk.
+;; It is also possible to delete sub-files from within the tar file and write
+;; the modified archive back to disk, or to edit sub-files within the archive
+;; and re-insert the modified files into the archive.  See the documentation
+;; string of tar-mode for more info.
+
+;; This code now understands the extra fields that GNU tar adds to tar files.
+
+;; This interacts correctly with "uncompress.el" in the Emacs library,
+;; which you get with 
+;;
+;;  (autoload 'uncompress-while-visiting "uncompress")
+;;  (setq auto-mode-alist (cons '("\\.Z$" . uncompress-while-visiting)
+;;                        auto-mode-alist))
+;;
+;; Do not attempt to use tar-mode.el with crypt.el, you will lose.
+
+;;    ***************   TO DO   *************** 
+;;
+;; o  chmod should understand "a+x,og-w".
+;;
+;; o  It's not possible to add a NEW file to a tar archive; not that 
+;;    important, but still...
+;;
+;; o  The code is less efficient that it could be - in a lot of places, I
+;;    pull a 512-character string out of the buffer and parse it, when I could
+;;    be parsing it in place, not garbaging a string.  Should redo that.
+;;
+;; o  I'd like a command that searches for a string/regexp in every subfile
+;;    of an archive, where <esc> would leave you in a subfile-edit buffer.
+;;    (Like the Meta-R command of the Zmacs mail reader.)
+;;
+;; o  Sometimes (but not always) reverting the tar-file buffer does not 
+;;    re-grind the listing, and you are staring at the binary tar data.
+;;    Typing 'g' again immediately after that will always revert and re-grind
+;;    it, though.  I have no idea why this happens.
+;;
+;; o  Tar-mode interacts poorly with crypt.el and zcat.el because the tar
+;;    write-file-hook actually writes the file.  Instead it should remove the
+;;    header (and conspire to put it back afterwards) so that other write-file
+;;    hooks which frob the buffer have a chance to do their dirty work.  There
+;;    might be a problem if the tar write-file-hook does not come *first* on
+;;    the list.
+;;
+;; o  Block files, sparse files, continuation files, and the various header 
+;;    types aren't editable.  Actually I don't know that they work at all.
+
+;; Rationale:
+
+;; Why does tar-mode edit the file itself instead of using tar?
+
+;; That means that you can edit tar files which you don't have room for
+;; on your local disk.
+
+;; I don't know about recent features in gnu tar, but old versions of tar
+;; can't replace a file in the middle of a tar file with a new version.
+;; Tar-mode can.  I don't think tar can do things like chmod the subfiles.
+;; An implementation which involved unpacking and repacking the file into
+;; some scratch directory would be very wasteful, and wouldn't be able to
+;; preserve the file owners.
 
 ;;; Code:
 
@@ -277,7 +277,7 @@ write-date, checksum, link-type, and link-name."
   (if (null start) (setq start 0))
   (if (null end) (setq end (length string)))
   (if (= (aref string start) 0)
-      [0 0]
+      (list 0 0)
     (let ((lo 0)
          (hi 0))
       (while (< start end)
@@ -415,7 +415,7 @@ MODE should be an integer which is a file mode value."
 Place a dired-like listing on the front;
 then narrow to it, so that only that listing
 is visible (and the real data of the buffer is hidden)."
-  (message "parsing tar file...")
+  (message "Parsing tar file...")
   (let* ((result '())
         (pos 1)
         (bs (max 1 (- (buffer-size) 1024))) ; always 2+ empty blocks at end.
@@ -457,15 +457,20 @@ is visible (and the real data of the buffer is hidden)."
     ;; A tar file should end with a block or two of nulls,
     ;; but let's not get a fatal error if it doesn't.
     (if (eq tokens 'empty-tar-block)
-       (message "Parsing tar file...done.")
+       (message "Parsing tar file...done")
       (message "Warning: premature EOF parsing tar file")))
   (save-excursion
     (goto-char (point-min))
-    (let ((buffer-read-only nil))
-      (tar-dolist (tar-desc tar-parse-info)
-       (insert-string
-         (tar-header-block-summarize (tar-desc-tokens tar-desc)))
-       (insert-string "\n"))
+    (let ((buffer-read-only nil)
+         (summaries nil))
+      ;; Collect summary lines and insert them all at once since tar files
+      ;; can be pretty big.
+      (tar-dolist (tar-desc (reverse tar-parse-info))
+       (setq summaries
+             (cons (tar-header-block-summarize (tar-desc-tokens tar-desc))
+                   (cons "\n"
+                         summaries))))
+      (insert (apply 'concat summaries))
       (make-local-variable 'tar-header-offset)
       (setq tar-header-offset (point))
       (narrow-to-region 1 tar-header-offset)
@@ -514,7 +519,7 @@ is visible (and the real data of the buffer is hidden)."
 (define-key tar-mode-map [menu-bar immediate view]
   '("View This File" . tar-view))
 (define-key tar-mode-map [menu-bar immediate display]
-  '("Display in Other Window" . tar-display-file))
+  '("Display in Other Window" . tar-display-other-file))
 (define-key tar-mode-map [menu-bar immediate find-file-other-window]
   '("Find in Other Window" . tar-extract-other-window))
 (define-key tar-mode-map [menu-bar immediate find-file]
@@ -544,7 +549,7 @@ is visible (and the real data of the buffer is hidden)."
 (define-key tar-mode-map [menu-bar operate copy]
   '("Copy to..." . tar-copy))
 (define-key tar-mode-map [menu-bar operate expunge]
-  '("Expunge marked files" . tar-expunge))
+  '("Expunge Marked Files" . tar-expunge))
 \f
 ;; tar mode is suitable only for specially formatted data.
 (put 'tar-mode 'mode-class 'special)
@@ -579,10 +584,14 @@ See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
   (setq revert-buffer-function 'tar-mode-revert)
   (make-local-variable 'enable-local-variables)
   (setq enable-local-variables nil)
+  (make-local-variable 'next-line-add-newlines)
+  (setq next-line-add-newlines nil)
   (setq major-mode 'tar-mode)
   (setq mode-name "Tar")
   (use-local-map tar-mode-map)
   (auto-save-mode 0)
+  (make-local-variable 'write-contents-hooks)
+  (setq write-contents-hooks '(tar-mode-write-file))
   (widen)
   (if (and (boundp 'tar-header-offset) tar-header-offset)
       (narrow-to-region 1 tar-header-offset)
@@ -591,13 +600,11 @@ See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
   )
 
 
-;; This should be converted to use a minor mode keymap.
-
 (defun tar-subfile-mode (p)
   "Minor mode for editing an element of a tar-file.
-This mode redefines C-x C-s to save the current buffer back into its 
-associated tar-file buffer.  You must save that buffer to actually
-save your changes to disk."
+This mode arranges for \"saving\" this buffer to write the data
+into the tar-file buffer that it came from.  The changes will actually
+appear on disk when you save the tar-file's buffer."
   (interactive "P")
   (or (and (boundp 'tar-superior-buffer) tar-superior-buffer)
       (error "This buffer is not an element of a tar file"))
@@ -623,11 +630,19 @@ save your changes to disk."
 
 ;; Revert the buffer and recompute the dired-like listing.
 (defun tar-mode-revert (&optional no-autosave no-confirm)
-  (setq tar-header-offset nil)
-  (let ((revert-buffer-function nil))
-    (revert-buffer t no-confirm)
-    (widen))
-  (tar-mode))
+  (let ((revert-buffer-function nil)
+       (old-offset tar-header-offset)
+       success)
+    (setq tar-header-offset nil)
+    (unwind-protect
+       (and (revert-buffer t no-confirm)
+            (progn (widen)
+                   (setq success t)
+                   (tar-mode)))
+      ;; If the revert was canceled,
+      ;; put back the old value of tar-header-offset.
+      (or success
+         (setq tar-header-offset old-offset)))))
 
 
 (defun tar-next-line (p)
@@ -724,15 +739,16 @@ save your changes to disk."
                (make-local-variable 'tar-superior-descriptor)
                (setq tar-superior-buffer tar-buffer)
                (setq tar-superior-descriptor descriptor)
-               (tar-subfile-mode 1)            
-               (setq buffer-read-only read-only-p)
-               (set-buffer-modified-p nil))
+               (setq buffer-read-only read-only-p)             
+               (set-buffer-modified-p nil)
+               (tar-subfile-mode 1))
              (set-buffer tar-buffer))
          (narrow-to-region 1 tar-header-offset)))
       (if view-p
          (progn
            (view-buffer buffer)
            (and just-created
+                ;; This will be created by view.el
                 (setq view-exit-action 'kill-buffer)))
        (if (eq other-window-p 'display)
            (display-buffer buffer)
@@ -787,9 +803,20 @@ the current tar-entry."
         (name (tar-header-name tokens))
         (size (tar-header-size tokens))
         (start (+ (tar-desc-data-start descriptor) tar-header-offset -1))
-        (end (+ start size)))
+        (end (+ start size))
+        (inhibit-file-name-handlers inhibit-file-name-handlers)
+        (inhibit-file-name-operation inhibit-file-name-operation))
     (save-restriction
       (widen)
+      ;; Inhibit compressing a subfile again if *both* name and
+      ;; to-file are handled by jka-compr
+      (if (and (eq (find-file-name-handler name 'write-region) 'jka-compr-handler)
+              (eq (find-file-name-handler to-file 'write-region) 'jka-compr-handler))
+         (setq inhibit-file-name-handlers
+               (cons 'jka-compr-handler
+                     (and (eq inhibit-file-name-operation 'write-region)
+                          inhibit-file-name-handlers))
+               inhibit-file-name-operation 'write-region))
       (write-region start end to-file))
     (message "Copied tar entry %s to %s" name to-file)))
 
@@ -838,7 +865,7 @@ With a prefix argument, un-mark that many files backward."
       (let ((line-len (- (point) line-start)))
        (delete-region line-start (point))
        ;;
-       ;; decrement the header-pointer to be in synch...
+       ;; decrement the header-pointer to be in sync...
        (setq tar-header-offset (- tar-header-offset line-len))))
     ;;
     ;; delete the data pointer...
@@ -869,7 +896,7 @@ This does not modify the disk image; you must save the tar file itself
 for this to be permanent."
   (interactive)
   (if (or noconfirm
-         (y-or-n-p "expunge files marked for deletion? "))
+         (y-or-n-p "Expunge files marked for deletion? "))
       (let ((n 0))
        (save-excursion
          (goto-char 0)
@@ -883,16 +910,17 @@ for this to be permanent."
          (narrow-to-region 1 tar-header-offset)
          )
        (if (zerop n)
-           (message "nothing to expunge.")
-           (message "%s expunged.  Be sure to save this buffer." n)))))
+           (message "Nothing to expunge.")
+           (message "%s files expunged.  Be sure to save this buffer." n)))))
 
 
 (defun tar-clear-modification-flags ()
   "Remove the stars at the beginning of each line."
+  (interactive)
   (save-excursion
-    (goto-char 0)
+    (goto-char 1)
     (while (< (point) tar-header-offset)
-      (if (looking-at "*")
+      (if (not (eq (following-char) ?\ ))
          (progn (delete-char 1) (insert " ")))
       (forward-line 1))))
 
@@ -1123,7 +1151,7 @@ to make your changes permanent."
     (set-buffer-modified-p t)   ; mark the tar file as modified
     (set-buffer subfile)
     (set-buffer-modified-p nil) ; mark the tar subfile as unmodified
-    (message "saved into tar-buffer `%s' -- remember to save that buffer!"
+    (message "Saved into tar-buffer `%s'.  Be sure to save that buffer!"
             (buffer-name tar-superior-buffer))
     ;; Prevent ordinary saving from happening.
     t)))
@@ -1158,35 +1186,19 @@ Leaves the region wide."
 
 
 ;; Used in write-file-hook to write tar-files out correctly.
-(defun tar-mode-maybe-write-tar-file ()
-  ;;
-  ;; If the current buffer is in Tar mode and has its header-offset set,
-  ;; only write out the part of the file after the header-offset.
-  ;;
-  (if (and (eq major-mode 'tar-mode)
-          (and (boundp 'tar-header-offset) tar-header-offset))
-      (unwind-protect
-       (save-excursion
-         (tar-clear-modification-flags)
-         (widen)
-         ;; Doing this here confuses things - the region gets left too wide!
-         ;; I suppose this is run in a context where changing the buffer is bad.
-         ;; (tar-pad-to-blocksize)
-         (write-region tar-header-offset (1+ (buffer-size)) buffer-file-name nil t)
-         ;; return T because we've written the file.
-         t)
-       (narrow-to-region 1 tar-header-offset)
-       t)
-      ;; return NIL because we haven't.
-      nil))
-
+(defun tar-mode-write-file ()
+  (unwind-protect
+      (save-excursion
+       (widen)
+       ;; Doing this here confuses things - the region gets left too wide!
+       ;; I suppose this is run in a context where changing the buffer is bad.
+       ;; (tar-pad-to-blocksize)
+       (write-region tar-header-offset (point-max) buffer-file-name nil t)
+       (tar-clear-modification-flags))
+    (narrow-to-region 1 tar-header-offset))
+  ;; return T because we've written the file.
+  t)
 \f
-;;; Patch it in.
-
-(or (memq 'tar-mode-maybe-write-tar-file write-file-hooks)
-    (setq write-file-hooks
-         (cons 'tar-mode-maybe-write-tar-file write-file-hooks)))
-
 (provide 'tar-mode)
 
 ;;; tar-mode.el ends here