]> code.delx.au - gnu-emacs/blobdiff - lisp/tar-mode.el
(Info-default-directory-list): Use configdir twice.
[gnu-emacs] / lisp / tar-mode.el
index ce8ed3b6184ac743a41955f6a70137f43d1c88ef..bfba9b402334b099dc55126308d278ac01c59200 100644 (file)
@@ -3,6 +3,7 @@
 ;; Copyright (C) 1990, 1991, 1993, 1994, 1995 Free Software Foundation, Inc.
 
 ;; Author: Jamie Zawinski <jwz@lucid.com>
+;; Maintainer: FSF
 ;; Created: 04 Apr 1990
 ;; Keywords: unix
 
 
 ;;; Code:
 
-(defvar tar-anal-blocksize 20
+(defgroup tar nil
+  "Simple editing of tar files."
+  :prefix "tar-"
+  :group 'data)
+
+(defcustom tar-anal-blocksize 20
   "*The blocksize of tar files written by Emacs, or nil, meaning don't care.
 The blocksize of a tar file is not really the size of the blocks; rather, it is
 the number of blocks written with one system call.  When tarring to a tape, 
 this is the size of the *tape* blocks, but when writing to a file, it doesn't
 matter much.  The only noticeable difference is that if a tar file does not
 have a blocksize of 20, tar will tell you that; all this really controls is
-how many null padding bytes go on the end of the tar file.")
+how many null padding bytes go on the end of the tar file."
+  :type '(choice integer (const nil))
+  :group 'tar)
 
-(defvar tar-update-datestamp nil
-  "*Non-nil means tar-mode should play fast and loose with sub-file datestamps.
+(defcustom tar-update-datestamp nil
+  "*Non-nil means Tar mode should play fast and loose with sub-file datestamps.
 If this is true, then editing and saving a tar file entry back into its
 tar file will update its datestamp.  If false, the datestamp is unchanged.
 You may or may not want this - it is good in that you can tell when a file
 in a tar archive has been changed, but it is bad for the same reason that
 editing a file in the tar archive at all is bad - the changed version of 
-the file never exists on disk.")
+the file never exists on disk."
+  :type 'boolean
+  :group 'tar)
 
-(defvar tar-mode-show-date nil
+(defcustom tar-mode-show-date nil
   "*Non-nil means Tar mode should show the date/time of each subfile.
-This information is useful, but it takes screen space away from file names.")
+This information is useful, but it takes screen space away from file names."
+  :type 'boolean
+  :group 'tar)
 
 (defvar tar-parse-info nil)
+;; Be sure that this variable holds byte position, not char position.
 (defvar tar-header-offset nil)
 (defvar tar-superior-buffer nil)
 (defvar tar-superior-descriptor nil)
@@ -241,6 +254,14 @@ write-date, checksum, link-type, and link-name."
                 link-p (if (or (= link-p 0) (= link-p ?0))
                            nil
                          (- link-p ?0)))
+          (setq linkname (substring string tar-link-offset link-end))
+          (if default-enable-multibyte-characters
+              (setq name
+                    (decode-coding-string name (or file-name-coding-system
+                                                   'undecided))
+                    linkname
+                    (decode-coding-string linkname (or file-name-coding-system
+                                                       'undecided))))
           (if (and (null link-p) (string-match "/$" name)) (setq link-p 5)) ; directory
           (make-tar-header
             name
@@ -251,7 +272,7 @@ write-date, checksum, link-type, and link-name."
             (tar-parse-octal-long-integer string tar-time-offset (1- tar-chk-offset))
             (tar-parse-octal-integer string tar-chk-offset (1- tar-linkp-offset))
             link-p
-            (substring string tar-link-offset link-end)
+            linkname
             uname-valid-p
             (and uname-valid-p (substring string tar-uname-offset uname-end))
             (and uname-valid-p (substring string tar-gname-offset gname-end))
@@ -294,7 +315,7 @@ write-date, checksum, link-type, and link-name."
     (tar-dotimes (i L)
        (if (or (< (aref string i) ?0)
               (> (aref string i) ?7))
-          (error "'%c' is not an octal digit"))))
+          (error "`%c' is not an octal digit"))))
   (tar-parse-octal-integer string))
 
 
@@ -373,7 +394,16 @@ MODE should be an integer which is a file mode value."
           (lastdigit (+ slash groupw sizew))
           (datestart (+ lastdigit 2))
           (namestart (+ datestart datew))
-          (string (make-string (+ namestart (length name) (if link-p (+ 5 (length link-name)) 0)) 32))
+          (multibyte (or (multibyte-string-p name)
+                         (multibyte-string-p link-name)))
+          ;; If multibyte, we can't use optimized method of aset,
+          ;; instead we must use concat.
+          (string (make-string (if multibyte
+                                   namestart
+                                 (+ namestart
+                                    (length name)
+                                    (if link-p (+ 5 (length link-name)) 0)))
+                               32))
           (type (tar-header-link-type tar-hblock)))
       (aset string 0 (if mod-p ?* ? ))
       (aset string 1
@@ -400,9 +430,14 @@ MODE should be an integer which is a file mode value."
       (tar-dotimes (i (min sizew (length size))) (aset string (- lastdigit i) (aref size (- (length size) i 1))))
       (if tar-mode-show-date
          (tar-dotimes (i (length time)) (aset string (+ datestart i) (aref time i))))
-      (tar-dotimes (i (length name)) (aset string (+ namestart i) (aref name i)))
+      (if multibyte
+         (setq string (concat string name))
+       (tar-dotimes (i (length name)) (aset string (+ namestart i) (aref name i))))
       (if (or (eq link-p 1) (eq link-p 2))
-         (progn
+         (if multibyte
+             (setq string (concat string
+                                  (if (= link-p 1) " ==> " " --> ")
+                                  link-name))
            (tar-dotimes (i 3) (aset string (+ namestart 1 (length name) i) (aref (if (= link-p 1) "==>" "-->") i)))
            (tar-dotimes (i (length link-name)) (aset string (+ namestart 5 (length name) i) (aref link-name i)))))
       (put-text-property namestart (length string)
@@ -415,6 +450,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)."
+  (set-buffer-multibyte nil)
   (message "Parsing tar file...")
   (let* ((result '())
         (pos 1)
@@ -470,10 +506,15 @@ is visible (and the real data of the buffer is hidden)."
              (cons (tar-header-block-summarize (tar-desc-tokens tar-desc))
                    (cons "\n"
                          summaries))))
-      (insert (apply 'concat summaries))
+      (let ((total-summaries (apply 'concat summaries)))
+       (if (multibyte-string-p total-summaries)
+           (set-buffer-multibyte t))
+       (insert total-summaries))
       (make-local-variable 'tar-header-offset)
       (setq tar-header-offset (point))
       (narrow-to-region 1 tar-header-offset)
+      (if enable-multibyte-characters
+         (setq tar-header-offset (position-bytes tar-header-offset)))
       (set-buffer-modified-p nil))))
 \f
 (defvar tar-mode-map nil "*Local keymap for Tar mode listings.")
@@ -483,7 +524,7 @@ is visible (and the real data of the buffer is hidden)."
   (setq tar-mode-map (make-keymap))
   (suppress-keymap tar-mode-map)
   (define-key tar-mode-map " " 'tar-next-line)
-  (define-key tar-mode-map "c" 'tar-copy)
+  (define-key tar-mode-map "C" 'tar-copy)
   (define-key tar-mode-map "d" 'tar-flag-deleted)
   (define-key tar-mode-map "\^D" 'tar-flag-deleted)
   (define-key tar-mode-map "e" 'tar-extract)
@@ -494,10 +535,13 @@ is visible (and the real data of the buffer is hidden)."
   (define-key tar-mode-map "h" 'describe-mode)
   (define-key tar-mode-map "n" 'tar-next-line)
   (define-key tar-mode-map "\^N" 'tar-next-line)
+  (define-key tar-mode-map [down] 'tar-next-line)
   (define-key tar-mode-map "o" 'tar-extract-other-window)
   (define-key tar-mode-map "p" 'tar-previous-line)
+  (define-key tar-mode-map "q" 'tar-quit)
   (define-key tar-mode-map "\^P" 'tar-previous-line)
-  (define-key tar-mode-map "r" 'tar-rename-entry)
+  (define-key tar-mode-map [up] 'tar-previous-line)
+  (define-key tar-mode-map "R" 'tar-rename-entry)
   (define-key tar-mode-map "u" 'tar-unflag)
   (define-key tar-mode-map "v" 'tar-view)
   (define-key tar-mode-map "x" 'tar-expunge)
@@ -519,7 +563,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-other-file))
+  '("Display in Other Window" . tar-display-other-window))
 (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]
@@ -582,10 +626,13 @@ See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
   (setq require-final-newline nil) ; binary data, dude...
   (make-local-variable 'revert-buffer-function)
   (setq revert-buffer-function 'tar-mode-revert)
-  (make-local-variable 'enable-local-variables)
-  (setq enable-local-variables nil)
+  (make-local-variable 'local-enable-local-variables)
+  (setq local-enable-local-variables nil)
   (make-local-variable 'next-line-add-newlines)
   (setq next-line-add-newlines nil)
+  ;; Prevent loss of data when saving the file.
+  (make-local-variable 'file-precious-flag)
+  (setq file-precious-flag t)
   (setq major-mode 'tar-mode)
   (setq mode-name "Tar")
   (use-local-map tar-mode-map)
@@ -594,8 +641,9 @@ See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
   (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)
-      (tar-summarize-buffer))
+      (narrow-to-region 1 (byte-to-position tar-header-offset))
+      (tar-summarize-buffer)
+      (tar-next-line 0))
   (run-hooks 'tar-mode-hook)
   )
 
@@ -621,7 +669,7 @@ appear on disk when you save the tar-file's buffer."
         (make-local-variable 'local-write-file-hooks)
         (setq local-write-file-hooks '(tar-subfile-save-buffer))
         ;; turn off auto-save.
-        (auto-save-mode nil)
+        (auto-save-mode -1)
         (setq buffer-auto-save-file-name nil)
         (run-hooks 'tar-subfile-mode-hook))
        (t
@@ -629,12 +677,20 @@ appear on disk when you save the tar-file's buffer."
 
 
 ;; 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))
+(defun tar-mode-revert (&optional no-auto-save no-confirm)
+  (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)
@@ -696,6 +752,7 @@ appear on disk when you save the tar-file's buffer."
         (start (+ (tar-desc-data-start descriptor) tar-header-offset -1))
         (end (+ start size)))
     (let* ((tar-buffer (current-buffer))
+          (tar-buffer-multibyte enable-multibyte-characters)
           (tarname (file-name-nondirectory (buffer-file-name)))
           (bufname (concat (file-name-nondirectory name)
                            " ("
@@ -711,14 +768,54 @@ appear on disk when you save the tar-file's buffer."
        (unwind-protect
            (progn
              (widen)
+             (set-buffer-multibyte nil)
              (save-excursion
                (set-buffer buffer)
-               (insert-buffer-substring tar-buffer start end)
+               (if enable-multibyte-characters
+                   (progn
+                     ;; We must avoid unibyte->multibyte conversion.
+                     (set-buffer-multibyte nil)
+                     (insert-buffer-substring tar-buffer start end)
+                     (set-buffer-multibyte t))
+                 (insert-buffer-substring tar-buffer start end))
                (goto-char 0)
                (setq buffer-file-name
-                     (expand-file-name (concat tarname ":" name)))
+                     ;; `:' is not allowed on Windows
+                     (expand-file-name (concat tarname "!" name)))
                (setq buffer-file-truename
                      (abbreviate-file-name buffer-file-name))
+               ;; We need to mimic the parts of insert-file-contents
+               ;; which determine the coding-system and decode the text.
+               (let ((coding
+                      (and set-auto-coding-function
+                           (save-excursion
+                             (funcall set-auto-coding-function
+                                      name (point-max)))))
+                     (multibyte enable-multibyte-characters)
+                     (detected (detect-coding-region
+                                1 (min 16384 (point-max)) t)))
+                 (if coding
+                     (or (numberp (coding-system-eol-type coding))
+                         (setq coding (coding-system-change-eol-conversion
+                                       coding
+                                       (coding-system-eol-type detected))))
+                   (setq coding
+                         (or (find-new-buffer-file-coding-system detected)
+                             (let ((file-coding
+                                    (find-operation-coding-system
+                                     'insert-file-contents buffer-file-name)))
+                               (if (consp file-coding)
+                                   (setq file-coding (car file-coding))
+                                 file-coding)))))
+                 (if (or (eq coding 'no-conversion)
+                         (eq (coding-system-type coding) 5))
+                     (setq multibyte (set-buffer-multibyte nil)))
+                 (or multibyte
+                     (setq coding
+                           (coding-system-change-text-conversion
+                            coding 'raw-text)))
+                 (decode-coding-region 1 (point-max) coding)
+                 (set-buffer-file-coding-system coding))
                ;; Set the default-directory to the dir of the
                ;; superior buffer. 
                (setq default-directory
@@ -735,13 +832,10 @@ appear on disk when you save the tar-file's buffer."
                (set-buffer-modified-p nil)
                (tar-subfile-mode 1))
              (set-buffer tar-buffer))
-         (narrow-to-region 1 tar-header-offset)))
+         (narrow-to-region 1 tar-header-offset)
+         (set-buffer-multibyte tar-buffer-multibyte)))
       (if view-p
-         (progn
-           (view-buffer buffer)
-           (and just-created
-                ;; This will be created by view.el
-                (setq view-exit-action 'kill-buffer)))
+         (view-buffer buffer (and just-created 'kill-buffer))
        (if (eq other-window-p 'display)
            (display-buffer buffer)
          (if other-window-p
@@ -796,6 +890,7 @@ the current tar-entry."
         (size (tar-header-size tokens))
         (start (+ (tar-desc-data-start descriptor) tar-header-offset -1))
         (end (+ start size))
+        (multibyte enable-multibyte-characters)
         (inhibit-file-name-handlers inhibit-file-name-handlers)
         (inhibit-file-name-operation inhibit-file-name-operation))
     (save-restriction
@@ -809,7 +904,11 @@ the current tar-entry."
                      (and (eq inhibit-file-name-operation 'write-region)
                           inhibit-file-name-handlers))
                inhibit-file-name-operation 'write-region))
-      (write-region start end to-file))
+      (unwind-protect
+         (let ((coding-system-for-write 'no-conversion))
+           (set-buffer-multibyte nil)
+           (write-region start end to-file))
+       (set-buffer-multibyte multibyte)))
     (message "Copied tar entry %s to %s" name to-file)))
 
 (defun tar-flag-deleted (p &optional unflag)
@@ -838,6 +937,7 @@ With a prefix argument, un-mark that many files backward."
   (tar-flag-deleted (- p) t))
 
 
+;; When this function is called, it is sure that the buffer is unibyte.
 (defun tar-expunge-internal ()
   "Expunge the tar-entry specified by the current line."
   (let* ((descriptor (tar-current-descriptor))
@@ -889,7 +989,9 @@ for this to be permanent."
   (interactive)
   (if (or noconfirm
          (y-or-n-p "Expunge files marked for deletion? "))
-      (let ((n 0))
+      (let ((n 0)
+           (multibyte enable-multibyte-characters))
+       (set-buffer-multibyte nil)
        (save-excursion
          (goto-char 0)
          (while (not (eobp))
@@ -899,8 +1001,8 @@ for this to be permanent."
                (forward-line 1)))
          ;; after doing the deletions, add any padding that may be necessary.
          (tar-pad-to-blocksize)
-         (narrow-to-region 1 tar-header-offset)
-         )
+         (narrow-to-region 1 tar-header-offset))
+       (set-buffer-multibyte multibyte)
        (if (zerop n)
            (message "Nothing to expunge.")
            (message "%s files expunged.  Be sure to save this buffer." n)))))
@@ -911,7 +1013,7 @@ for this to be permanent."
   (interactive)
   (save-excursion
     (goto-char 1)
-    (while (< (point) tar-header-offset)
+    (while (< (position-bytes (point)) tar-header-offset)
       (if (not (eq (following-char) ?\ ))
          (progn (delete-char 1) (insert " ")))
       (forward-line 1))))
@@ -1002,7 +1104,8 @@ for this to be permanent."
 
 (defun tar-alter-one-field (data-position new-data-string)
   (let* ((descriptor (tar-current-descriptor))
-        (tokens (tar-desc-tokens descriptor)))
+        (tokens (tar-desc-tokens descriptor))
+        (multibyte enable-multibyte-characters))
     (unwind-protect
        (save-excursion
          ;;
@@ -1012,9 +1115,10 @@ for this to be permanent."
            (forward-line 1)
            (delete-region p (point))
            (insert (tar-header-block-summarize tokens) "\n")
-           (setq tar-header-offset (point-max)))
+           (setq tar-header-offset (position-bytes (point-max))))
          
          (widen)
+         (set-buffer-multibyte nil)
          (let* ((start (+ (tar-desc-data-start descriptor) tar-header-offset -513)))
            ;;
            ;; delete the old field and insert a new one.
@@ -1037,7 +1141,9 @@ for this to be permanent."
                (buffer-substring start (+ start 512))
                chk (tar-header-name tokens))
              )))
-      (narrow-to-region 1 tar-header-offset))))
+      (narrow-to-region 1 tar-header-offset)
+      (set-buffer-multibyte multibyte)
+      (tar-next-line 0))))
 
 
 (defun tar-octal-time (timeval)
@@ -1060,8 +1166,14 @@ to make your changes permanent."
     (error "This buffer doesn't have an index into its superior tar file!"))
   (save-excursion
   (let ((subfile (current-buffer))
-       (subfile-size (buffer-size))
-       (descriptor tar-superior-descriptor))
+       (subfile-multibyte enable-multibyte-characters)
+       (coding buffer-file-coding-system)
+       (descriptor tar-superior-descriptor)
+       subfile-size)
+    ;; We must make the current buffer unibyte temporarily to avoid
+    ;; multibyte->unibyte conversion in `insert-buffer'.
+    (set-buffer-multibyte nil)
+    (setq subfile-size (buffer-size))
     (set-buffer tar-superior-buffer)
     (let* ((tokens (tar-desc-tokens descriptor))
           (start (tar-desc-data-start descriptor))
@@ -1069,12 +1181,14 @@ to make your changes permanent."
           (size (tar-header-size tokens))
           (size-pad (ash (ash (+ size 511) -9) 9))
           (head (memq descriptor tar-parse-info))
-          (following-descs (cdr head)))
+          (following-descs (cdr head))
+          (tar-buffer-multibyte enable-multibyte-characters))
       (if (not head)
        (error "Can't find this tar file entry in its parent tar file!"))
       (unwind-protect
        (save-excursion
        (widen)
+       (set-buffer-multibyte nil)
        ;; delete the old data...
        (let* ((data-start (+ start tar-header-offset -1))
               (data-end (+ data-start (ash (ash (+ size 511) -9) 9))))
@@ -1082,6 +1196,9 @@ to make your changes permanent."
          ;; insert the new data...
          (goto-char data-start)
          (insert-buffer subfile)
+         (setq subfile-size
+               (encode-coding-region
+                data-start (+ data-start subfile-size) coding))
          ;;
          ;; pad the new data out to a multiple of 512...
          (let ((subfile-size-pad (ash (ash (+ subfile-size 511) -9) 9)))
@@ -1133,22 +1250,32 @@ to make your changes permanent."
                (setq after (point))
                ;; Insert the new text after the old, before deleting,
                ;; to preserve the window start.
-               (insert-before-markers (tar-header-block-summarize tokens t) "\n")
+               (let ((line (tar-header-block-summarize tokens t)))
+                 (if (multibyte-string-p line)
+                     (insert-before-markers (string-as-unibyte line) "\n")
+                   (insert-before-markers line "\n")))
                (delete-region p after)
                (setq tar-header-offset (marker-position m)))
              )))
        ;; after doing the insertion, add any final padding that may be necessary.
        (tar-pad-to-blocksize))
-       (narrow-to-region 1 tar-header-offset)))
+       (narrow-to-region 1 tar-header-offset)
+       (set-buffer-multibyte tar-buffer-multibyte)))
     (set-buffer-modified-p t)   ; mark the tar file as modified
+    (tar-next-line 0)
     (set-buffer subfile)
+    ;; Restore the buffer multibyteness.
+    (set-buffer-multibyte subfile-multibyte)
     (set-buffer-modified-p nil) ; mark the tar subfile as unmodified
     (message "Saved into tar-buffer `%s'.  Be sure to save that buffer!"
             (buffer-name tar-superior-buffer))
+    ;; Prevent basic-save-buffer from changing our coding-system.
+    (setq last-coding-system-used buffer-file-coding-system)
     ;; Prevent ordinary saving from happening.
     t)))
 
 
+;; When this function is called, it is sure that the buffer is unibyte.
 (defun tar-pad-to-blocksize ()
   "If we are being anal about tar file blocksizes, fix up the current buffer.
 Leaves the region wide."
@@ -1185,11 +1312,24 @@ Leaves the region wide."
        ;; 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))
+       ;; tar-header-offset turns out to be null for files fetched with W3,
+       ;; at least.
+       (let ((coding-system-for-write 'no-conversion))
+         (write-region (or (byte-to-position tar-header-offset)
+                           (point-min))
+                       (point-max)
+                       buffer-file-name nil t))
+       (tar-clear-modification-flags)
+       (set-buffer-modified-p nil))
+    (narrow-to-region 1 (byte-to-position tar-header-offset)))
   ;; return T because we've written the file.
   t)
+
+(defun tar-quit ()
+  "Kill the current tar buffer."
+  (interactive)
+  (kill-buffer nil))
+
 \f
 (provide 'tar-mode)