(back_comment): Move the find_defun_start call
[bpt/emacs.git] / lisp / arc-mode.el
CommitLineData
665211a3
KH
1;;; arc-mode.el --- simple editing of archives
2
c38eb0a8 3;; Copyright (C) 1995, 1997 Free Software Foundation, Inc.
665211a3 4
0acdb863 5;; Author: Morten Welinder <terra@diku.dk>
665211a3
KH
6;; Keywords: archives msdog editing major-mode
7;; Favourite-brand-of-beer: None, I hate beer.
8
b578f267
EN
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
665211a3
KH
25
26;;; Commentary:
b578f267 27
665211a3
KH
28;; NAMING: "arc" is short for "archive" and does not refer specifically
29;; to files whose name end in ".arc"
30;;
31;; This code does not decode any files internally, although it does
32;; understand the directory level of the archives. For this reason,
33;; you should expect this code to need more fiddling than tar-mode.el
34;; (although it at present has fewer bugs :-) In particular, I have
35;; not tested this under Ms-Dog myself.
36;; -------------------------------------
37;; INTERACTION: arc-mode.el should play together with
38;;
39;; * ange-ftp.el: Remote archives (i.e., ones that ange-ftp has brought
40;; to you) are handled by doing all updates on a local
41;; copy. When you make changes to a remote file the
42;; changes will first take effect when the archive buffer
43;; is saved. You will be warned about this.
44;;
45;; * dos-fns.el: (Part of Emacs 19). You get automatic ^M^J <--> ^J
46;; conversion.
47;;
48;; arc-mode.el does not work well with crypt++.el; for the archives as
49;; such this could be fixed (but wouldn't be useful) by declaring such
50;; archives to be "remote". For the members this is a general Emacs
51;; problem that 19.29's file formats may fix.
52;; -------------------------------------
53;; ARCHIVE TYPES: Currently only the archives below are handled, but the
54;; structure for handling just about anything is in place.
55;;
56;; Arc Lzh Zip Zoo
57;; --------------------------------
58;; View listing Intern Intern Intern Intern
59;; Extract member Y Y Y Y
60;; Save changed member Y Y Y Y
61;; Add new member N N N N
62;; Delete member Y Y Y Y
63;; Rename member Y Y N N
64;; Chmod - Y Y -
65;; Chown - Y - -
66;; Chgrp - Y - -
67;;
68;; Special thanks to Bill Brodie <wbrodie@panix.com> for very useful tips
69;; on the first released version of this package.
70;;
71;; This code is partly based on tar-mode.el from Emacs.
72;; -------------------------------------
73;; ARCHIVE STRUCTURES:
74;; (This is mostly for myself.)
75;;
76;; ARC A series of (header,file). No interactions among members.
77;;
78;; LZH A series of (header,file). Headers are checksummed. No
79;; interaction among members.
80;;
81;; ZIP A series of (lheader,fil) followed by a "central directory"
82;; which is a series of (cheader) followed by an end-of-
83;; central-dir record possibly followed by junk. The e-o-c-d
84;; links to c-d. cheaders link to lheaders which are basically
85;; cut-down versions of the cheaders.
86;;
87;; ZOO An archive header followed by a series of (header,file).
88;; Each member header points to the next. The archive is
89;; terminated by a bogus header with a zero next link.
90;; -------------------------------------
665211a3
KH
91;; HOOKS: `foo' means one the the supported archive types.
92;;
93;; archive-mode-hook
94;; archive-foo-mode-hook
95;; archive-extract-hooks
96
97;;; Code:
98
99;; -------------------------------------------------------------------------
100;; Section: Configuration.
101
c38eb0a8
RS
102(defgroup archive nil
103 "Simple editing of archives."
104 :group 'data)
665211a3 105
c38eb0a8
RS
106(defgroup archive-arc nil
107 "ARC-specific options to archive."
108 :group 'archive)
109
110(defgroup archive-lzh nil
111 "LZH-specific options to archive."
112 :group 'archive)
113
114(defgroup archive-zip nil
115 "ZIP-specific options to archive."
116 :group 'archive)
117
118(defgroup archive-zoo nil
119 "ZOO-specific options to archive."
120 :group 'archive)
121
122
123(defcustom archive-dos-members t
124 "*If non-nil then recognize member files using ^M^J as line terminator."
125 :type 'boolean
126 :group 'archive)
127
128(defcustom archive-tmpdir
665211a3
KH
129 (expand-file-name
130 (make-temp-name (if (eq system-type 'ms-dos) "ar" "archive.tmp"))
131 (or (getenv "TMPDIR") (getenv "TMP") "/tmp"))
c38eb0a8
RS
132 "*Directory for temporary files made by arc-mode.el"
133 :type 'directory
134 :group 'archive)
665211a3 135
c38eb0a8 136(defcustom archive-remote-regexp "^/[^/:]*[^/:.]:"
eeba65ed
RS
137 "*Regexp recognizing archive files names that are not local.
138A non-local file is one whose file name is not proper outside Emacs.
c38eb0a8
RS
139A local copy of the archive will be used when updating."
140 :type 'regexp
141 :group 'archive)
142
143(defcustom archive-extract-hooks nil
144 "*Hooks to run when an archive member has been extracted."
145 :type 'hook
146 :group 'archive)
665211a3
KH
147;; ------------------------------
148;; Arc archive configuration
149
150;; We always go via a local file since there seems to be no reliable way
151;; to extract to stdout without junk getting added.
c38eb0a8 152(defcustom archive-arc-extract
665211a3 153 '("arc" "x")
eeba65ed
RS
154 "*Program and its options to run in order to extract an arc file member.
155Extraction should happen to the current directory. Archive and member
c38eb0a8
RS
156name will be added."
157 :type '(list (string :tag "Program")
158 (repeat :tag "Options"
159 :inline t
160 (string :format "%v")))
161 :group 'archive-arc)
162
163(defcustom archive-arc-expunge
665211a3
KH
164 '("arc" "d")
165 "*Program and its options to run in order to delete arc file members.
c38eb0a8
RS
166Archive and member names will be added."
167 :type '(list (string :tag "Program")
168 (repeat :tag "Options"
169 :inline t
170 (string :format "%v")))
171 :group 'archive-arc)
172
173(defcustom archive-arc-write-file-member
665211a3
KH
174 '("arc" "u")
175 "*Program and its options to run in order to update an arc file member.
c38eb0a8
RS
176Archive and member name will be added."
177 :type '(list (string :tag "Program")
178 (repeat :tag "Options"
179 :inline t
180 (string :format "%v")))
181 :group 'archive-arc)
665211a3
KH
182;; ------------------------------
183;; Lzh archive configuration
184
c38eb0a8 185(defcustom archive-lzh-extract
665211a3 186 '("lha" "pq")
eeba65ed
RS
187 "*Program and its options to run in order to extract an lzh file member.
188Extraction should happen to standard output. Archive and member name will
c38eb0a8
RS
189be added."
190 :type '(list (string :tag "Program")
191 (repeat :tag "Options"
192 :inline t
193 (string :format "%v")))
194 :group 'archive-lzh)
195
196(defcustom archive-lzh-expunge
665211a3
KH
197 '("lha" "d")
198 "*Program and its options to run in order to delete lzh file members.
c38eb0a8
RS
199Archive and member names will be added."
200 :type '(list (string :tag "Program")
201 (repeat :tag "Options"
202 :inline t
203 (string :format "%v")))
204 :group 'archive-lzh)
205
206(defcustom archive-lzh-write-file-member
665211a3
KH
207 '("lha" "a")
208 "*Program and its options to run in order to update an lzh file member.
c38eb0a8
RS
209Archive and member name will be added."
210 :type '(list (string :tag "Program")
211 (repeat :tag "Options"
212 :inline t
213 (string :format "%v")))
214 :group 'archive-lzh)
665211a3
KH
215;; ------------------------------
216;; Zip archive configuration
217
c38eb0a8 218(defcustom archive-zip-use-pkzip (memq system-type '(ms-dos windows-nt))
eeba65ed 219 "*If non-nil then pkzip option are used instead of zip options.
c38eb0a8
RS
220Only set to true for msdog systems!"
221 :type 'boolean
222 :group 'archive-zip)
665211a3 223
c38eb0a8 224(defcustom archive-zip-extract
665211a3 225 (if archive-zip-use-pkzip '("pkunzip" "-e") '("unzip" "-qq" "-c"))
eeba65ed
RS
226 "*Program and its options to run in order to extract a zip file member.
227Extraction should happen to standard output. Archive and member name will
228be added. If `archive-zip-use-pkzip' is non-nil then this program is
c38eb0a8
RS
229expected to extract to a file junking the directory part of the name."
230 :type '(list (string :tag "Program")
231 (repeat :tag "Options"
232 :inline t
233 (string :format "%v")))
234 :group 'archive-zip)
665211a3 235
e946e18c 236;; For several reasons the latter behaviour is not desirable in general.
665211a3
KH
237;; (1) It uses more disk space. (2) Error checking is worse or non-
238;; existent. (3) It tends to do funny things with other systems' file
239;; names.
240
c38eb0a8 241(defcustom archive-zip-expunge
665211a3
KH
242 (if archive-zip-use-pkzip '("pkzip" "-d") '("zip" "-d" "-q"))
243 "*Program and its options to run in order to delete zip file members.
c38eb0a8
RS
244Archive and member names will be added."
245 :type '(list (string :tag "Program")
246 (repeat :tag "Options"
247 :inline t
248 (string :format "%v")))
249 :group 'archive-zip)
250
251(defcustom archive-zip-update
665211a3
KH
252 (if archive-zip-use-pkzip '("pkzip" "-u") '("zip" "-q"))
253 "*Program and its options to run in order to update a zip file member.
254Options should ensure that specified directory will be put into the zip
c38eb0a8
RS
255file. Archive and member name will be added."
256 :type '(list (string :tag "Program")
257 (repeat :tag "Options"
258 :inline t
259 (string :format "%v")))
260 :group 'archive-zip)
261
262(defcustom archive-zip-update-case
665211a3 263 (if archive-zip-use-pkzip archive-zip-update '("zip" "-q" "-k"))
eeba65ed
RS
264 "*Program and its options to run in order to update a case fiddled zip member.
265Options should ensure that specified directory will be put into the zip file.
c38eb0a8
RS
266Archive and member name will be added."
267 :type '(list (string :tag "Program")
268 (repeat :tag "Options"
269 :inline t
270 (string :format "%v")))
271 :group 'archive-zip)
272
273(defcustom archive-zip-case-fiddle t
eeba65ed
RS
274 "*If non-nil then zip file members are case fiddled.
275Case fiddling will only happen for members created by a system that
c38eb0a8
RS
276uses caseless file names."
277 :type 'boolean
278 :group 'archive-zip)
665211a3
KH
279;; ------------------------------
280;; Zoo archive configuration
281
c38eb0a8 282(defcustom archive-zoo-extract
665211a3 283 '("zoo" "xpq")
eeba65ed
RS
284 "*Program and its options to run in order to extract a zoo file member.
285Extraction should happen to standard output. Archive and member name will
c38eb0a8
RS
286be added."
287 :type '(list (string :tag "Program")
288 (repeat :tag "Options"
289 :inline t
290 (string :format "%v")))
291 :group 'archive-zoo)
292
293(defcustom archive-zoo-expunge
665211a3
KH
294 '("zoo" "DqPP")
295 "*Program and its options to run in order to delete zoo file members.
c38eb0a8
RS
296Archive and member names will be added."
297 :type '(list (string :tag "Program")
298 (repeat :tag "Options"
299 :inline t
300 (string :format "%v")))
301 :group 'archive-zoo)
302
303(defcustom archive-zoo-write-file-member
665211a3
KH
304 '("zoo" "a")
305 "*Program and its options to run in order to update a zoo file member.
c38eb0a8
RS
306Archive and member name will be added."
307 :type '(list (string :tag "Program")
308 (repeat :tag "Options"
309 :inline t
310 (string :format "%v")))
311 :group 'archive-zoo)
665211a3
KH
312;; -------------------------------------------------------------------------
313;; Section: Variables
314
315(defvar archive-subtype nil "*Symbol describing archive type.")
316(defvar archive-file-list-start nil "*Position of first contents line.")
317(defvar archive-file-list-end nil "*Position just after last contents line.")
318(defvar archive-proper-file-start nil "*Position of real archive's start.")
319(defvar archive-read-only nil "*Non-nil if the archive is read-only on disk.")
320(defvar archive-remote nil "*Non-nil if the archive is outside file system.")
321(defvar archive-local-name nil "*Name of local copy of remote archive.")
322(defvar archive-mode-map nil "*Local keymap for archive mode listings.")
323(defvar archive-file-name-indent nil "*Column where file names start.")
324
325(defvar archive-alternate-display nil
326 "*Non-nil when alternate information is shown.")
327(make-variable-buffer-local 'archive-alternate-display)
328(put 'archive-alternate-display 'permanent-local t)
329
330(defvar archive-superior-buffer nil "*In archive members, points to archive.")
331(put 'archive-superior-buffer 'permanent-local t)
332
333(defvar archive-subfile-mode nil "*Non-nil in archive member buffers.")
334(make-variable-buffer-local 'archive-subfile-mode)
335(put 'archive-subfile-mode 'permanent-local t)
336
665211a3 337(defvar archive-subfile-dos nil
ab889912 338 "Negation of `buffer-file-type', which see.")
665211a3
KH
339(make-variable-buffer-local 'archive-subfile-dos)
340(put 'archive-subfile-dos 'permanent-local t)
341
c4de97b4
RS
342(defvar archive-files nil
343 "Vector of file descriptors.
344Each descriptor is a vector of the form
345 [EXT-FILE-NAME INT-FILE-NAME CASE-FIDDLED MODE ...]")
665211a3 346(make-variable-buffer-local 'archive-files)
43f657ea
KH
347
348(defvar archive-lemacs
349 (string-match "\\(Lucid\\|Xemacs\\)" emacs-version)
350 "*Non-nil when running under under Lucid Emacs or Xemacs.")
665211a3
KH
351;; -------------------------------------------------------------------------
352;; Section: Support functions.
353
354(defsubst archive-name (suffix)
355 (intern (concat "archive-" (symbol-name archive-subtype) "-" suffix)))
356
357(defun archive-l-e (str &optional len)
eeba65ed
RS
358 "Convert little endian string/vector to integer.
359Alternatively, first argument may be a buffer position in the current buffer
360in which case a second argument, length, should be supplied."
665211a3
KH
361 (if (stringp str)
362 (setq len (length str))
363 (setq str (buffer-substring str (+ str len))))
364 (let ((result 0)
365 (i 0))
366 (while (< i len)
367 (setq i (1+ i)
368 result (+ (ash result 8) (aref str (- len i)))))
369 result))
370
371(defun archive-int-to-mode (mode)
372 "Turn an integer like 0700 (i.e., 448) into a mode string like -rwx------"
373 (let ((str (make-string 10 ?-)))
374 (or (zerop (logand 16384 mode)) (aset str 0 ?d))
375 (or (zerop (logand 8192 mode)) (aset str 0 ?c)) ; completeness
376 (or (zerop (logand 256 mode)) (aset str 1 ?r))
377 (or (zerop (logand 128 mode)) (aset str 2 ?w))
378 (or (zerop (logand 64 mode)) (aset str 3 ?x))
379 (or (zerop (logand 32 mode)) (aset str 4 ?r))
380 (or (zerop (logand 16 mode)) (aset str 5 ?w))
381 (or (zerop (logand 8 mode)) (aset str 6 ?x))
382 (or (zerop (logand 4 mode)) (aset str 7 ?r))
383 (or (zerop (logand 2 mode)) (aset str 8 ?w))
384 (or (zerop (logand 1 mode)) (aset str 9 ?x))
385 (or (zerop (logand 1024 mode)) (aset str 3 (if (zerop (logand 64 mode))
386 ?S ?s)))
387 (or (zerop (logand 2048 mode)) (aset str 6 (if (zerop (logand 8 mode))
388 ?S ?s)))
389 str))
390
391(defun archive-calc-mode (oldmode newmode &optional error)
eeba65ed 392 "From the integer OLDMODE and the string NEWMODE calculate a new file mode.
665211a3
KH
393NEWMODE may be an octal number including a leading zero in which case it
394will become the new mode.\n
395NEWMODE may also be a relative specification like \"og-rwx\" in which case
396OLDMODE will be modified accordingly just like chmod(2) would have done.\n
397If optional third argument ERROR is non-nil an error will be signaled if
398the mode is invalid. If ERROR is nil then nil will be returned."
399 (cond ((string-match "^0[0-7]*$" newmode)
400 (let ((result 0)
401 (len (length newmode))
402 (i 1))
403 (while (< i len)
404 (setq result (+ (lsh result 3) (aref newmode i) (- ?0))
405 i (1+ i)))
406 (logior (logand oldmode 65024) result)))
407 ((string-match "^\\([agou]+\\)\\([---+=]\\)\\([rwxst]+\\)$" newmode)
408 (let ((who 0)
409 (result oldmode)
410 (op (aref newmode (match-beginning 2)))
411 (bits 0)
412 (i (match-beginning 3)))
413 (while (< i (match-end 3))
414 (let ((rwx (aref newmode i)))
415 (setq bits (logior bits (cond ((= rwx ?r) 292)
416 ((= rwx ?w) 146)
417 ((= rwx ?x) 73)
418 ((= rwx ?s) 3072)
419 ((= rwx ?t) 512)))
420 i (1+ i))))
421 (while (< who (match-end 1))
422 (let* ((whoc (aref newmode who))
423 (whomask (cond ((= whoc ?a) 4095)
424 ((= whoc ?u) 1472)
425 ((= whoc ?g) 2104)
426 ((= whoc ?o) 7))))
427 (if (= op ?=)
428 (setq result (logand result (lognot whomask))))
429 (if (= op ?-)
430 (setq result (logand result (lognot (logand whomask bits))))
431 (setq result (logior result (logand whomask bits)))))
432 (setq who (1+ who)))
433 result))
434 (t
435 (if error
436 (error "Invalid mode specification: %s" newmode)))))
437
438(defun archive-dosdate (date)
439 "Stringify dos packed DATE record."
440 (let ((year (+ 1980 (logand (ash date -9) 127)))
441 (month (logand (ash date -5) 15))
442 (day (logand date 31)))
443 (if (or (> month 12) (< month 1))
444 ""
445 (format "%2d-%s-%d"
446 day
447 (aref ["Jan" "Feb" "Mar" "Apr" "May" "Jun"
448 "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"] (1- month))
449 year))))
450
451(defun archive-dostime (time)
452 "Stringify dos packed TIME record."
453 (let ((hour (logand (ash time -11) 31))
454 (minute (logand (ash time -5) 53))
455 (second (* 2 (logand time 31)))) ; 2 seconds resolution
456 (format "%02d:%02d:%02d" hour minute second)))
457
458;;(defun archive-unixdate (low high)
459;; "Stringify unix (LOW HIGH) date."
460;; (let ((str (current-time-string (cons high low))))
461;; (format "%s-%s-%s"
462;; (substring str 8 9)
463;; (substring str 4 7)
464;; (substring str 20 24))))
465
466;;(defun archive-unixtime (low high)
467;; "Stringify unix (LOW HIGH) time."
468;; (let ((str (current-time-string (cons high low))))
469;; (substring str 11 19)))
470
471(defun archive-get-lineno ()
472 (if (>= (point) archive-file-list-start)
473 (count-lines archive-file-list-start
474 (save-excursion (beginning-of-line) (point)))
475 0))
476
477(defun archive-get-descr (&optional noerror)
eeba65ed
RS
478 "Return the descriptor vector for file at point.
479Does not signal an error if optional second argument NOERROR is non-nil."
665211a3
KH
480 (let ((no (archive-get-lineno)))
481 (if (and (>= (point) archive-file-list-start)
482 (< no (length archive-files)))
483 (let ((item (aref archive-files no)))
484 (if (vectorp item)
485 item
486 (if (not noerror)
487 (error "Entry is not a regular member of the archive"))))
488 (if (not noerror)
489 (error "Line does not describe a member of the archive")))))
490;; -------------------------------------------------------------------------
491;; Section: the mode definition
492
9199a670 493;;;###autoload
665211a3 494(defun archive-mode (&optional force)
eeba65ed
RS
495 "Major mode for viewing an archive file in a dired-like way.
496You can move around using the usual cursor motion commands.
665211a3
KH
497Letters no longer insert themselves.
498Type `e' to pull a file out of the archive and into its own buffer;
499or click mouse-2 on the file's line in the archive mode buffer.
500
501If you edit a sub-file of this archive (as with the `e' command) and
502save it, the contents of that buffer will be saved back into the
503archive.
504
505\\{archive-mode-map}"
506 ;; This is not interactive because you shouldn't be turning this
507 ;; mode on and off. You can corrupt things that way.
508 (if (zerop (buffer-size))
509 ;; At present we cannot create archives from scratch
510 (funcall default-major-mode)
511 (if (and (not force) archive-files) nil
512 (let* ((type (archive-find-type))
513 (typename (copy-sequence (symbol-name type))))
514 (aset typename 0 (upcase (aref typename 0)))
515 (kill-all-local-variables)
516 (make-local-variable 'archive-subtype)
517 (setq archive-subtype type)
518
519 ;; Buffer contains treated image of file before the file contents
520 (make-local-variable 'revert-buffer-function)
521 (setq revert-buffer-function 'archive-mode-revert)
522 (auto-save-mode 0)
2c1e5f9b
KH
523 (make-local-variable 'write-contents-hooks)
524 (add-hook 'write-contents-hooks 'archive-write-file)
665211a3
KH
525
526 ;; Real file contents is binary
527 (make-local-variable 'require-final-newline)
528 (setq require-final-newline nil)
529 (make-local-variable 'enable-local-variables)
530 (setq enable-local-variables nil)
ab889912
RS
531 (if (boundp 'default-buffer-file-type)
532 (setq buffer-file-type t))
665211a3
KH
533
534 (make-local-variable 'archive-read-only)
535 (setq archive-read-only (not (file-writable-p (buffer-file-name))))
536
537 ;; Should we use a local copy when accessing from outside Emacs?
538 (make-local-variable 'archive-local-name)
539 (make-local-variable 'archive-remote)
540 (setq archive-remote (string-match archive-remote-regexp
541 (buffer-file-name)))
542
543 (setq major-mode 'archive-mode)
544 (setq mode-name (concat typename "-Archive"))
545 ;; Run archive-foo-mode-hook and archive-mode-hook
546 (run-hooks (archive-name "mode-hook") 'archive-mode-hook)
547 (use-local-map archive-mode-map))
548
549 (make-local-variable 'archive-proper-file-start)
550 (make-local-variable 'archive-file-list-start)
551 (make-local-variable 'archive-file-list-end)
552 (make-local-variable 'archive-file-name-indent)
553 (archive-summarize)
554 (setq buffer-read-only t))))
555
556;; Archive mode is suitable only for specially formatted data.
557(put 'archive-mode 'mode-class 'special)
558;; -------------------------------------------------------------------------
559;; Section: Key maps
560
561(if archive-mode-map nil
562 (setq archive-mode-map (make-keymap))
563 (suppress-keymap archive-mode-map)
564 (define-key archive-mode-map " " 'archive-next-line)
565 (define-key archive-mode-map "a" 'archive-alternate-display)
566 ;;(define-key archive-mode-map "c" 'archive-copy)
567 (define-key archive-mode-map "d" 'archive-flag-deleted)
568 (define-key archive-mode-map "\C-d" 'archive-flag-deleted)
569 (define-key archive-mode-map "e" 'archive-extract)
570 (define-key archive-mode-map "f" 'archive-extract)
571 (define-key archive-mode-map "\C-m" 'archive-extract)
665211a3
KH
572 (define-key archive-mode-map "g" 'revert-buffer)
573 (define-key archive-mode-map "h" 'describe-mode)
574 (define-key archive-mode-map "m" 'archive-mark)
575 (define-key archive-mode-map "n" 'archive-next-line)
576 (define-key archive-mode-map "\C-n" 'archive-next-line)
577 (define-key archive-mode-map [down] 'archive-next-line)
578 (define-key archive-mode-map "o" 'archive-extract-other-window)
579 (define-key archive-mode-map "p" 'archive-previous-line)
580 (define-key archive-mode-map "\C-p" 'archive-previous-line)
581 (define-key archive-mode-map [up] 'archive-previous-line)
582 (define-key archive-mode-map "r" 'archive-rename-entry)
583 (define-key archive-mode-map "u" 'archive-unflag)
584 (define-key archive-mode-map "\M-\C-?" 'archive-unmark-all-files)
585 (define-key archive-mode-map "v" 'archive-view)
586 (define-key archive-mode-map "x" 'archive-expunge)
587 (define-key archive-mode-map "\177" 'archive-unflag-backwards)
588 (define-key archive-mode-map "E" 'archive-extract-other-window)
589 (define-key archive-mode-map "M" 'archive-chmod-entry)
590 (define-key archive-mode-map "G" 'archive-chgrp-entry)
591 (define-key archive-mode-map "O" 'archive-chown-entry)
43f657ea
KH
592
593 (if archive-lemacs
594 (progn
595 ;; Not a nice "solution" but it'll have to do
596 (define-key archive-mode-map "\C-xu" 'archive-undo)
597 (define-key archive-mode-map "\C-_" 'archive-undo))
598 (substitute-key-definition 'undo 'archive-undo
599 archive-mode-map global-map))
600
601 (define-key archive-mode-map
602 (if archive-lemacs 'button2 [mouse-2]) 'archive-mouse-extract)
603
604 (if archive-lemacs
605 () ; out of luck
606 ;; Get rid of the Edit menu bar item to save space.
607 (define-key archive-mode-map [menu-bar edit] 'undefined)
608
609 (define-key archive-mode-map [menu-bar immediate]
610 (cons "Immediate" (make-sparse-keymap "Immediate")))
611 (define-key archive-mode-map [menu-bar immediate alternate]
612 '("Alternate Display" . archive-alternate-display))
613 (put 'archive-alternate-display 'menu-enable
614 '(boundp (archive-name "alternate-display")))
615 (define-key archive-mode-map [menu-bar immediate view]
616 '("View This File" . archive-view))
617 (define-key archive-mode-map [menu-bar immediate display]
618 '("Display in Other Window" . archive-display-other-window))
619 (define-key archive-mode-map [menu-bar immediate find-file-other-window]
620 '("Find in Other Window" . archive-extract-other-window))
621 (define-key archive-mode-map [menu-bar immediate find-file]
622 '("Find This File" . archive-extract))
623
624 (define-key archive-mode-map [menu-bar mark]
625 (cons "Mark" (make-sparse-keymap "Mark")))
626 (define-key archive-mode-map [menu-bar mark unmark-all]
627 '("Unmark All" . archive-unmark-all-files))
628 (define-key archive-mode-map [menu-bar mark deletion]
629 '("Flag" . archive-flag-deleted))
630 (define-key archive-mode-map [menu-bar mark unmark]
631 '("Unflag" . archive-unflag))
632 (define-key archive-mode-map [menu-bar mark mark]
633 '("Mark" . archive-mark))
634
635 (define-key archive-mode-map [menu-bar operate]
636 (cons "Operate" (make-sparse-keymap "Operate")))
637 (define-key archive-mode-map [menu-bar operate chown]
638 '("Change Owner..." . archive-chown-entry))
639 (put 'archive-chown-entry 'menu-enable
640 '(fboundp (archive-name "chown-entry")))
641 (define-key archive-mode-map [menu-bar operate chgrp]
642 '("Change Group..." . archive-chgrp-entry))
643 (put 'archive-chgrp-entry 'menu-enable
644 '(fboundp (archive-name "chgrp-entry")))
645 (define-key archive-mode-map [menu-bar operate chmod]
646 '("Change Mode..." . archive-chmod-entry))
647 (put 'archive-chmod-entry 'menu-enable
648 '(fboundp (archive-name "chmod-entry")))
649 (define-key archive-mode-map [menu-bar operate rename]
650 '("Rename to..." . archive-rename-entry))
651 (put 'archive-rename-entry 'menu-enable
652 '(fboundp (archive-name "rename-entry")))
653 ;;(define-key archive-mode-map [menu-bar operate copy]
654 ;; '("Copy to..." . archive-copy))
655 (define-key archive-mode-map [menu-bar operate expunge]
656 '("Expunge Marked Files" . archive-expunge))
657 ))
665211a3
KH
658
659(let* ((item1 '(archive-subfile-mode " Archive"))
660 (item2 '(archive-subfile-dos " Dos"))
661 (items (if (memq system-type '(ms-dos windows-nt))
662 (list item1) ; msdog has its own indicator
663 (list item1 item2))))
664 (or (member item1 minor-mode-alist)
665 (setq minor-mode-alist (append items minor-mode-alist))))
666;; -------------------------------------------------------------------------
667(defun archive-find-type ()
668 (widen)
669 (goto-char (point-min))
670 ;; The funny [] here make it unlikely that the .elc file will be treated
671 ;; as an archive by other software.
672 (let (case-fold-search)
673 (cond ((looking-at "[P]K\003\004") 'zip)
674 ((looking-at "..-l[hz][0-9]-") 'lzh)
675 ((looking-at "....................[\334]\247\304\375") 'zoo)
676 ((and (looking-at "\C-z") ; signature too simple, IMHO
677 (string-match "\\.[aA][rR][cC]$"
678 (or buffer-file-name (buffer-name))))
679 'arc)
680 (t (error "Buffer format not recognized.")))))
681;; -------------------------------------------------------------------------
682(defun archive-summarize ()
683 "Parse the contents of the archive file in the current buffer.
684Place a dired-like listing on the front;
685then narrow to it, so that only that listing
686is visible (and the real data of the buffer is hidden)."
687 (widen)
688 (let (buffer-read-only)
689 (message "Parsing archive file...")
690 (buffer-disable-undo (current-buffer))
691 (setq archive-files (funcall (archive-name "summarize")))
692 (message "Parsing archive file...done.")
693 (setq archive-proper-file-start (point-marker))
694 (narrow-to-region (point-min) (point))
695 (set-buffer-modified-p nil)
696 (buffer-enable-undo))
697 (goto-char archive-file-list-start)
698 (archive-next-line 0))
699
700(defun archive-resummarize ()
701 "Recreate the contents listing of an archive."
702 (let ((modified (buffer-modified-p))
703 (no (archive-get-lineno))
704 buffer-read-only)
705 (widen)
706 (delete-region (point-min) archive-proper-file-start)
707 (archive-summarize)
708 (set-buffer-modified-p modified)
709 (goto-char archive-file-list-start)
710 (archive-next-line no)))
711
712(defun archive-summarize-files (files)
c4de97b4 713 "Insert a description of a list of files annotated with proper mouse face."
665211a3
KH
714 (setq archive-file-list-start (point-marker))
715 (setq archive-file-name-indent (if files (aref (car files) 1) 0))
716 ;; We don't want to do an insert for each element since that takes too
717 ;; long when the archive -- which has to be moved in memory -- is large.
718 (insert
719 (apply
720 (function concat)
721 (mapcar
43f657ea
KH
722 (function
723 (lambda (fil)
724 ;; Using `concat' here copies the text also, so we can add
725 ;; properties without problems.
726 (let ((text (concat (aref fil 0) "\n")))
727 (if archive-lemacs
728 () ; out of luck
729 (put-text-property (aref fil 1) (aref fil 2)
730 'mouse-face 'highlight
731 text))
732 text)))
665211a3
KH
733 files)))
734 (setq archive-file-list-end (point-marker)))
735
736(defun archive-alternate-display ()
eeba65ed
RS
737 "Toggle alternative display.
738To avoid very long lines some archive mode don't show all information.
739This function changes the set of information shown for each files."
665211a3
KH
740 (interactive)
741 (setq archive-alternate-display (not archive-alternate-display))
742 (archive-resummarize))
743;; -------------------------------------------------------------------------
744;; Section: Local archive copy handling
745
746(defun archive-maybe-copy (archive)
747 (if archive-remote
748 (let ((start (point-max)))
749 (setq archive-local-name (expand-file-name
750 (file-name-nondirectory archive)
751 archive-tmpdir))
752 (make-directory archive-tmpdir t)
753 (save-restriction
754 (widen)
755 (write-region start (point-max) archive-local-name nil 'nomessage))
756 archive-local-name)
757 (if (buffer-modified-p) (save-buffer))
758 archive))
759
760(defun archive-maybe-update (unchanged)
761 (if archive-remote
762 (let ((name archive-local-name)
763 (modified (buffer-modified-p))
764 buffer-read-only)
765 (if unchanged nil
766 (erase-buffer)
767 (insert-file-contents name)
768 (archive-mode t))
769 (archive-delete-local name)
770 (if (not unchanged)
771 (message "Archive file must be saved for changes to take effect"))
772 (set-buffer-modified-p (or modified (not unchanged))))))
773
774(defun archive-delete-local (name)
eeba65ed 775 "Delete file NAME and its parents up to and including `archive-tmpdir'."
665211a3
KH
776 (let ((again t)
777 (top (directory-file-name (file-name-as-directory archive-tmpdir))))
778 (condition-case nil
779 (delete-file name)
780 (error nil))
781 (while again
782 (setq name (directory-file-name (file-name-directory name)))
783 (condition-case nil
784 (delete-directory name)
785 (error nil))
786 (if (string= name top) (setq again nil)))))
787;; -------------------------------------------------------------------------
788;; Section: Member extraction
789
790(defun archive-mouse-extract (event)
791 "Extract a file whose name you click on."
792 (interactive "e")
43f657ea
KH
793 (mouse-set-point event)
794 (switch-to-buffer
795 (save-excursion
796 (archive-extract)
797 (current-buffer))))
665211a3
KH
798
799(defun archive-extract (&optional other-window-p)
800 "In archive mode, extract this entry of the archive into its own buffer."
801 (interactive)
802 (let* ((view-p (eq other-window-p 'view))
803 (descr (archive-get-descr))
804 (ename (aref descr 0))
805 (iname (aref descr 1))
806 (archive-buffer (current-buffer))
807 (arcdir default-directory)
808 (archive (buffer-file-name))
809 (arcname (file-name-nondirectory archive))
810 (bufname (concat (file-name-nondirectory iname) " (" arcname ")"))
811 (extractor (archive-name "extract"))
812 (read-only-p (or archive-read-only view-p))
813 (buffer (get-buffer bufname))
814 (just-created nil))
815 (if buffer
816 nil
817 (setq archive (archive-maybe-copy archive))
818 (setq buffer (get-buffer-create bufname))
819 (setq just-created t)
820 (save-excursion
821 (set-buffer buffer)
822 (setq buffer-file-name
823 (expand-file-name (concat arcname ":" iname)))
824 (setq buffer-file-truename
825 (abbreviate-file-name buffer-file-name))
826 ;; Set the default-directory to the dir of the superior buffer.
827 (setq default-directory arcdir)
828 (make-local-variable 'archive-superior-buffer)
829 (setq archive-superior-buffer archive-buffer)
830 (make-local-variable 'local-write-file-hooks)
831 (add-hook 'local-write-file-hooks 'archive-write-file-member)
832 (setq archive-subfile-mode descr)
ab889912
RS
833 (setq archive-subfile-dos nil)
834 (if (boundp 'default-buffer-file-type)
835 (setq buffer-file-type t))
665211a3
KH
836 (if (fboundp extractor)
837 (funcall extractor archive ename)
838 (archive-*-extract archive ename (symbol-value extractor)))
839 (if archive-dos-members (archive-check-dos))
840 (goto-char (point-min))
841 (rename-buffer bufname)
842 (setq buffer-read-only read-only-p)
843 (setq buffer-undo-list nil)
844 (set-buffer-modified-p nil)
845 (setq buffer-saved-size (buffer-size))
846 (normal-mode)
847 ;; Just in case an archive occurs inside another archive.
848 (if (eq major-mode 'archive-mode)
849 (setq archive-remote t))
850 (run-hooks 'archive-extract-hooks))
851 (archive-maybe-update t))
852 (if view-p
7a26bfc6 853 (view-buffer buffer (and just-created 'kill-buffer))
665211a3
KH
854 (if (eq other-window-p 'display)
855 (display-buffer buffer)
856 (if other-window-p
857 (switch-to-buffer-other-window buffer)
858 (switch-to-buffer buffer))))))
859
860(defun archive-*-extract (archive name command)
861 (let* ((default-directory (file-name-as-directory archive-tmpdir))
862 (tmpfile (expand-file-name (file-name-nondirectory name)
863 default-directory)))
864 (make-directory (directory-file-name default-directory) t)
865 (apply 'call-process
866 (car command)
867 nil
868 nil
869 nil
870 (append (cdr command) (list archive name)))
871 (insert-file-contents tmpfile)
872 (archive-delete-local tmpfile)))
873
874(defun archive-extract-by-stdout (archive name command)
875 (let ((binary-process-output t)) ; for Ms-Dos
876 (apply 'call-process
877 (car command)
878 nil
879 t
880 nil
881 (append (cdr command) (list archive name)))))
882
883(defun archive-extract-other-window ()
884 "In archive mode, find this member in another window."
885 (interactive)
886 (archive-extract t))
887
888(defun archive-display-other-window ()
889 "In archive mode, display this member in another window."
890 (interactive)
891 (archive-extract 'display))
892
893(defun archive-view ()
894 "In archive mode, view the member on this line."
895 (interactive)
896 (archive-extract 'view))
897
898(defun archive-add-new-member (arcbuf name)
eeba65ed 899 "Add current buffer to the archive in ARCBUF naming it NAME."
665211a3
KH
900 (interactive
901 (list (get-buffer
902 (read-buffer "Buffer containing archive: "
903 ;; Find first archive buffer and suggest that
904 (let ((bufs (buffer-list)))
905 (while (and bufs (not (eq (save-excursion
906 (set-buffer (car bufs))
907 major-mode)
908 'archive-mode)))
909 (setq bufs (cdr bufs)))
910 (if bufs
911 (car bufs)
912 (error "There are no archive buffers")))
913 t))
914 (read-string "File name in archive: "
915 (if buffer-file-name
916 (file-name-nondirectory buffer-file-name)
917 ""))))
918 (save-excursion
919 (set-buffer arcbuf)
920 (or (eq major-mode 'archive-mode)
921 (error "Buffer is not an archive buffer"))
922 (if archive-read-only
923 (error "Archive is read-only")))
924 (if (eq arcbuf (current-buffer))
925 (error "An archive buffer cannot be added to itself"))
926 (if (string= name "")
927 (error "Archive members may not be given empty names"))
928 (let ((func (save-excursion (set-buffer arcbuf)
929 (archive-name "add-new-member")))
930 (membuf (current-buffer)))
931 (if (fboundp func)
932 (save-excursion
933 (set-buffer arcbuf)
934 (funcall func buffer-file-name membuf name))
935 (error "Adding a new member is not supported for this archive type"))))
936;; -------------------------------------------------------------------------
937;; Section: IO stuff
938
939(defun archive-check-dos (&optional force)
eeba65ed 940 "*Possibly handle a buffer with ^M^J terminated lines."
665211a3
KH
941 (save-restriction
942 (widen)
943 (save-excursion
944 (goto-char (point-min))
945 (setq archive-subfile-dos
946 (or force (not (search-forward-regexp "[^\r]\n" nil t))))
ab889912
RS
947 (if (boundp 'default-buffer-file-type)
948 (setq buffer-file-type (not archive-subfile-dos)))
665211a3
KH
949 (if archive-subfile-dos
950 (let ((modified (buffer-modified-p)))
951 (buffer-disable-undo (current-buffer))
952 (goto-char (point-min))
953 (while (search-forward "\r\n" nil t)
954 (replace-match "\n"))
955 (buffer-enable-undo)
956 (set-buffer-modified-p modified))))))
957
958(defun archive-write-file-member ()
959 (if archive-subfile-dos
960 (save-restriction
961 (widen)
962 (save-excursion
963 (goto-char (point-min))
964 ;; We don't want our ^M^J <--> ^J changes to show in the undo list
965 (let ((undo-list buffer-undo-list))
966 (unwind-protect
967 (progn
968 (setq buffer-undo-list t)
969 (while (search-forward "\n" nil t)
970 (replace-match "\r\n"))
971 (setq archive-subfile-dos nil)
ab889912
RS
972 (if (boundp 'default-buffer-file-type)
973 (setq buffer-file-type t))
665211a3
KH
974 ;; OK, we're now have explicit ^M^Js -- save and re-unixfy
975 (archive-write-file-member))
976 (progn
977 (archive-check-dos t)
978 (setq buffer-undo-list undo-list))))
979 t))
980 (save-excursion
981 (save-restriction
982 (message "Updating archive...")
983 (widen)
984 (let ((writer (save-excursion (set-buffer archive-superior-buffer)
985 (archive-name "write-file-member")))
986 (archive (save-excursion (set-buffer archive-superior-buffer)
987 (buffer-file-name))))
988 (if (fboundp writer)
989 (funcall writer archive archive-subfile-mode)
990 (archive-*-write-file-member archive
991 archive-subfile-mode
992 (symbol-value writer))))
993 (set-buffer-modified-p nil)
994 (message "Updating archive...done")
995 (set-buffer archive-superior-buffer)
996 (revert-buffer)
997 t))))
998
999(defun archive-*-write-file-member (archive descr command)
1000 (let* ((ename (aref descr 0))
1001 (tmpfile (expand-file-name ename archive-tmpdir))
1002 (top (directory-file-name (file-name-as-directory archive-tmpdir)))
1003 (default-directory (file-name-as-directory top)))
1004 (unwind-protect
1005 (progn
1006 (make-directory (file-name-directory tmpfile) t)
1007 (write-region (point-min) (point-max) tmpfile nil 'nomessage)
1008 (if (aref descr 3)
1009 ;; Set the file modes, but make sure we can read it.
1010 (set-file-modes tmpfile (logior ?\400 (aref descr 3))))
1011 (let ((exitcode (apply 'call-process
1012 (car command)
1013 nil
1014 nil
1015 nil
1016 (append (cdr command) (list archive ename)))))
1017 (if (equal exitcode 0)
1018 nil
1019 (error "Updating was unsuccessful (%S)" exitcode))))
1020 (archive-delete-local tmpfile))))
1021
1022(defun archive-write-file ()
1023 (save-excursion
1024 (write-region archive-proper-file-start (point-max) buffer-file-name nil t)
1025 (set-buffer-modified-p nil)
1026 t))
1027;; -------------------------------------------------------------------------
1028;; Section: Marking and unmarking.
1029
1030(defun archive-flag-deleted (p &optional type)
1031 "In archive mode, mark this member to be deleted from the archive.
1032With a prefix argument, mark that many files."
1033 (interactive "p")
1034 (or type (setq type ?D))
1035 (beginning-of-line)
1036 (let ((sign (if (>= p 0) +1 -1))
1037 (modified (buffer-modified-p))
1038 buffer-read-only)
1039 (while (not (zerop p))
1040 (if (archive-get-descr t)
1041 (progn
1042 (delete-char 1)
1043 (insert type)))
1044 (forward-line sign)
1045 (setq p (- p sign)))
1046 (set-buffer-modified-p modified))
1047 (archive-next-line 0))
1048
1049(defun archive-unflag (p)
1050 "In archive mode, un-mark this member if it is marked to be deleted.
1051With a prefix argument, un-mark that many files forward."
1052 (interactive "p")
1053 (archive-flag-deleted p ? ))
1054
1055(defun archive-unflag-backwards (p)
1056 "In archive mode, un-mark this member if it is marked to be deleted.
1057With a prefix argument, un-mark that many members backward."
1058 (interactive "p")
1059 (archive-flag-deleted (- p) ? ))
1060
1061(defun archive-unmark-all-files ()
1062 "Remove all marks."
1063 (interactive)
1064 (let ((modified (buffer-modified-p))
1065 buffer-read-only)
1066 (save-excursion
1067 (goto-char archive-file-list-start)
1068 (while (< (point) archive-file-list-end)
1069 (or (= (following-char) ? )
1070 (progn (delete-char 1) (insert ? )))
1071 (forward-line 1)))
1072 (set-buffer-modified-p modified)))
1073
1074(defun archive-mark (p)
1075 "In archive mode, mark this member for group operations.
1076With a prefix argument, mark that many members.
1077Use \\[archive-unmark-all-files] to remove all marks."
1078 (interactive "p")
1079 (archive-flag-deleted p ?*))
1080
1081(defun archive-get-marked (mark &optional default)
1082 (let (files)
1083 (save-excursion
1084 (goto-char archive-file-list-start)
1085 (while (< (point) archive-file-list-end)
1086 (if (= (following-char) mark)
1087 (setq files (cons (archive-get-descr) files)))
1088 (forward-line 1)))
1089 (or (nreverse files)
1090 (and default
1091 (list (archive-get-descr))))))
1092;; -------------------------------------------------------------------------
1093;; Section: Operate
1094
1095(defun archive-next-line (p)
1096 (interactive "p")
1097 (forward-line p)
1098 (or (eobp)
1099 (forward-char archive-file-name-indent)))
1100
1101(defun archive-previous-line (p)
1102 (interactive "p")
1103 (archive-next-line (- p)))
1104
1105(defun archive-chmod-entry (new-mode)
eeba65ed 1106 "Change the protection bits associated with all marked or this member.
665211a3
KH
1107The new protection bits can either be specified as an octal number or
1108as a relative change like \"g+rw\" as for chmod(2)"
1109 (interactive "sNew mode (octal or relative): ")
1110 (if archive-read-only (error "Archive is read-only"))
1111 (let ((func (archive-name "chmod-entry")))
1112 (if (fboundp func)
1113 (progn
1114 (funcall func new-mode (archive-get-marked ?* t))
1115 (archive-resummarize))
1116 (error "Setting mode bits is not supported for this archive type"))))
1117
1118(defun archive-chown-entry (new-uid)
1119 "Change the owner of all marked or this member."
1120 (interactive "nNew uid: ")
1121 (if archive-read-only (error "Archive is read-only"))
1122 (let ((func (archive-name "chown-entry")))
1123 (if (fboundp func)
1124 (progn
1125 (funcall func new-uid (archive-get-marked ?* t))
1126 (archive-resummarize))
1127 (error "Setting owner is not supported for this archive type"))))
1128
1129(defun archive-chgrp-entry (new-gid)
1130 "Change the group of all marked or this member."
1131 (interactive "nNew gid: ")
1132 (if archive-read-only (error "Archive is read-only"))
1133 (let ((func (archive-name "chgrp-entry")))
1134 (if (fboundp func)
1135 (progn
1136 (funcall func new-gid (archive-get-marked ?* t))
1137 (archive-resummarize))
1138 (error "Setting group is not supported for this archive type"))))
1139
1140(defun archive-expunge ()
1141 "Do the flagged deletions."
1142 (interactive)
1143 (let (files)
1144 (save-excursion
1145 (goto-char archive-file-list-start)
1146 (while (< (point) archive-file-list-end)
1147 (if (= (following-char) ?D)
1148 (setq files (cons (aref (archive-get-descr) 0) files)))
1149 (forward-line 1)))
1150 (setq files (nreverse files))
1151 (and files
1152 (or (not archive-read-only)
1153 (error "Archive is read-only"))
1154 (or (yes-or-no-p (format "Really delete %d member%s? "
1155 (length files)
1156 (if (null (cdr files)) "" "s")))
1157 (error "Operation aborted"))
1158 (let ((archive (archive-maybe-copy (buffer-file-name)))
1159 (expunger (archive-name "expunge")))
1160 (if (fboundp expunger)
1161 (funcall expunger archive files)
1162 (archive-*-expunge archive files (symbol-value expunger)))
1163 (archive-maybe-update nil)
1164 (if archive-remote
1165 (archive-resummarize)
1166 (revert-buffer))))))
1167
1168(defun archive-*-expunge (archive files command)
1169 (apply 'call-process
1170 (car command)
1171 nil
1172 nil
1173 nil
1174 (append (cdr command) (cons archive files))))
1175
1176(defun archive-rename-entry (newname)
1177 "Change the name associated with this entry in the tar file."
1178 (interactive "sNew name: ")
1179 (if archive-read-only (error "Archive is read-only"))
1180 (if (string= newname "")
1181 (error "Archive members may not be given empty names"))
1182 (let ((func (archive-name "rename-entry"))
1183 (descr (archive-get-descr)))
1184 (if (fboundp func)
1185 (progn
1186 (funcall func (buffer-file-name) newname descr)
1187 (archive-resummarize))
1188 (error "Renaming is not supported for this archive type"))))
1189
1190;; Revert the buffer and recompute the dired-like listing.
1191(defun archive-mode-revert (&optional no-autosave no-confirm)
1192 (let ((no (archive-get-lineno)))
1193 (setq archive-files nil)
1194 (let ((revert-buffer-function nil))
1195 (revert-buffer t t))
1196 (archive-mode)
1197 (goto-char archive-file-list-start)
1198 (archive-next-line no)))
1199
1200(defun archive-undo ()
1201 "Undo in an archive buffer.
1202This doesn't recover lost files, it just undoes changes in the buffer itself."
1203 (interactive)
1204 (let (buffer-read-only)
1205 (undo)))
1206;; -------------------------------------------------------------------------
1207;; Section: Arc Archives
1208
1209(defun archive-arc-summarize ()
1210 (let ((p 1)
1211 (totalsize 0)
1212 (maxlen 8)
1213 files
1214 visual)
1215 (while (and (< (+ p 29) (point-max))
1216 (= (char-after p) ?\C-z)
1217 (> (char-after (1+ p)) 0))
1218 (let* ((namefld (buffer-substring (+ p 2) (+ p 2 13)))
1219 (fnlen (or (string-match "\0" namefld) 13))
1220 (efnname (substring namefld 0 fnlen))
1221 (csize (archive-l-e (+ p 15) 4))
1222 (moddate (archive-l-e (+ p 19) 2))
1223 (modtime (archive-l-e (+ p 21) 2))
1224 (ucsize (archive-l-e (+ p 25) 4))
1225 (fiddle (string= efnname (upcase efnname)))
1226 (ifnname (if fiddle (downcase efnname) efnname))
1227 (text (format " %8d %-11s %-8s %s"
1228 ucsize
1229 (archive-dosdate moddate)
1230 (archive-dostime modtime)
1231 ifnname)))
1232 (setq maxlen (max maxlen fnlen)
1233 totalsize (+ totalsize ucsize)
1234 visual (cons (vector text
1235 (- (length text) (length ifnname))
1236 (length text))
1237 visual)
1238 files (cons (vector efnname ifnname fiddle nil (1- p))
1239 files)
1240 p (+ p 29 csize))))
1241 (goto-char (point-min))
1242 (let ((dash (concat "- -------- ----------- -------- "
1243 (make-string maxlen ?-)
1244 "\n")))
1245 (insert "M Length Date Time File\n"
1246 dash)
1247 (archive-summarize-files (nreverse visual))
1248 (insert dash
1249 (format " %8d %d file%s"
1250 totalsize
1251 (length files)
1252 (if (= 1 (length files)) "" "s"))
1253 "\n"))
1254 (apply 'vector (nreverse files))))
1255
1256(defun archive-arc-rename-entry (archive newname descr)
1257 (if (string-match "[:\\\\/]" newname)
1258 (error "File names in arc files may not contain a path"))
1259 (if (> (length newname) 12)
1260 (error "File names in arc files are limited to 12 characters"))
1261 (let ((name (concat newname (substring "\0\0\0\0\0\0\0\0\0\0\0\0\0"
1262 (length newname))))
1263 buffer-read-only)
1264 (save-restriction
1265 (save-excursion
1266 (widen)
1267 (goto-char (+ archive-proper-file-start (aref descr 4) 2))
1268 (delete-char 13)
1269 (insert name)))))
1270;; -------------------------------------------------------------------------
1271;; Section: Lzh Archives
1272
1273(defun archive-lzh-summarize ()
1274 (let ((p 1)
1275 (totalsize 0)
1276 (maxlen 8)
1277 files
1278 visual)
1279 (while (progn (goto-char p) (looking-at "..-l[hz][0-9]-"))
1280 (let* ((hsize (char-after p))
1281 (csize (archive-l-e (+ p 7) 4))
1282 (ucsize (archive-l-e (+ p 11) 4))
1283 (modtime (archive-l-e (+ p 15) 2))
1284 (moddate (archive-l-e (+ p 17) 2))
1285 (fnlen (char-after (+ p 21)))
1286 (efnname (buffer-substring (+ p 22) (+ p 22 fnlen)))
1287 (fiddle (string= efnname (upcase efnname)))
1288 (ifnname (if fiddle (downcase efnname) efnname))
1289 (p2 (+ p 22 fnlen))
1290 (creator (if (>= (- hsize fnlen) 24) (char-after (+ p2 2)) 0))
1291 (mode (if (= creator ?U) (archive-l-e (+ p2 8) 2) ?\666))
1292 (modestr (if mode (archive-int-to-mode mode) "??????????"))
1293 (uid (if (= creator ?U) (archive-l-e (+ p2 10) 2)))
1294 (gid (if (= creator ?U) (archive-l-e (+ p2 12) 2)))
1295 (text (if archive-alternate-display
1296 (format " %8d %5S %5S %s"
1297 ucsize
1298 (or uid "?")
1299 (or gid "?")
1300 ifnname)
1301 (format " %10s %8d %-11s %-8s %s"
1302 modestr
1303 ucsize
1304 (archive-dosdate moddate)
1305 (archive-dostime modtime)
1306 ifnname))))
1307 (setq maxlen (max maxlen fnlen)
1308 totalsize (+ totalsize ucsize)
1309 visual (cons (vector text
1310 (- (length text) (length ifnname))
1311 (length text))
1312 visual)
1313 files (cons (vector efnname ifnname fiddle mode (1- p))
1314 files)
1315 p (+ p hsize 2 csize))))
1316 (goto-char (point-min))
1317 (let ((dash (concat (if archive-alternate-display
1318 "- -------- ----- ----- "
1319 "- ---------- -------- ----------- -------- ")
1320 (make-string maxlen ?-)
1321 "\n"))
1322 (header (if archive-alternate-display
1323 "M Length Uid Gid File\n"
1324 "M Filemode Length Date Time File\n"))
1325 (sumline (if archive-alternate-display
1326 " %8d %d file%s"
1327 " %8d %d file%s")))
1328 (insert header dash)
1329 (archive-summarize-files (nreverse visual))
1330 (insert dash
1331 (format sumline
1332 totalsize
1333 (length files)
1334 (if (= 1 (length files)) "" "s"))
1335 "\n"))
1336 (apply 'vector (nreverse files))))
1337
1338(defconst archive-lzh-alternate-display t)
1339
1340(defun archive-lzh-extract (archive name)
1341 (archive-extract-by-stdout archive name archive-lzh-extract))
1342
1343(defun archive-lzh-resum (p count)
1344 (let ((sum 0))
1345 (while (> count 0)
1346 (setq count (1- count)
1347 sum (+ sum (char-after p))
1348 p (1+ p)))
1349 (logand sum 255)))
1350
1351(defun archive-lzh-rename-entry (archive newname descr)
1352 (save-restriction
1353 (save-excursion
1354 (widen)
1355 (let* ((p (+ archive-proper-file-start (aref descr 4)))
1356 (oldhsize (char-after p))
1357 (oldfnlen (char-after (+ p 21)))
1358 (newfnlen (length newname))
1359 (newhsize (+ oldhsize newfnlen (- oldfnlen)))
1360 buffer-read-only)
1361 (if (> newhsize 255)
1362 (error "The file name is too long"))
1363 (goto-char (+ p 21))
1364 (delete-char (1+ oldfnlen))
1365 (insert newfnlen newname)
1366 (goto-char p)
1367 (delete-char 2)
1368 (insert newhsize (archive-lzh-resum p newhsize))))))
1369
1370(defun archive-lzh-ogm (newval files errtxt ofs)
1371 (save-restriction
1372 (save-excursion
1373 (widen)
1374 (while files
1375 (let* ((fil (car files))
1376 (p (+ archive-proper-file-start (aref fil 4)))
1377 (hsize (char-after p))
1378 (fnlen (char-after (+ p 21)))
1379 (p2 (+ p 22 fnlen))
1380 (creator (if (>= (- hsize fnlen) 24) (char-after (+ p2 2)) 0))
1381 buffer-read-only)
1382 (if (= creator ?U)
1383 (progn
1384 (or (numberp newval)
1385 (setq newval (funcall newval (archive-l-e (+ p2 ofs) 2))))
1386 (goto-char (+ p2 ofs))
1387 (delete-char 2)
1388 (insert (logand newval 255) (lsh newval -8))
1389 (goto-char (1+ p))
1390 (delete-char 1)
1391 (insert (archive-lzh-resum (1+ p) hsize)))
1392 (message "Member %s does not have %s field"
1393 (aref fil 1) errtxt)))
1394 (setq files (cdr files))))))
1395
1396(defun archive-lzh-chown-entry (newuid files)
1397 (archive-lzh-ogm newuid files "an uid" 10))
1398
1399(defun archive-lzh-chgrp-entry (newgid files)
1400 (archive-lzh-ogm newgid files "a gid" 12))
1401
1402(defun archive-lzh-chmod-entry (newmode files)
1403 (archive-lzh-ogm
1404 ;; This should work even though newmode will be dynamically accessed.
43f657ea 1405 (function (lambda (old) (archive-calc-mode old newmode t)))
665211a3
KH
1406 files "a unix-style mode" 8))
1407;; -------------------------------------------------------------------------
1408;; Section: Zip Archives
1409
1410(defun archive-zip-summarize ()
1411 (goto-char (- (point-max) (- 22 18)))
1412 (search-backward-regexp "[P]K\005\006")
1413 (let ((p (1+ (archive-l-e (+ (point) 16) 4)))
1414 (maxlen 8)
1415 (totalsize 0)
1416 files
1417 visual)
1418 (while (string= "PK\001\002" (buffer-substring p (+ p 4)))
1419 (let* ((creator (char-after (+ p 5)))
1420 (method (archive-l-e (+ p 10) 2))
1421 (modtime (archive-l-e (+ p 12) 2))
1422 (moddate (archive-l-e (+ p 14) 2))
1423 (ucsize (archive-l-e (+ p 24) 4))
1424 (fnlen (archive-l-e (+ p 28) 2))
1425 (exlen (archive-l-e (+ p 30) 2))
845720b9 1426 (fclen (archive-l-e (+ p 32) 2))
665211a3
KH
1427 (lheader (archive-l-e (+ p 42) 4))
1428 (efnname (buffer-substring (+ p 46) (+ p 46 fnlen)))
1429 (isdir (and (= ucsize 0)
1430 (string= (file-name-nondirectory efnname) "")))
1431 (mode (cond ((memq creator '(2 3)) ; Unix + VMS
1432 (archive-l-e (+ p 40) 2))
1433 ((memq creator '(0 5 6 7 10 11)) ; Dos etc.
1434 (logior ?\444
1435 (if isdir (logior 16384 ?\111) 0)
1436 (if (zerop
1437 (logand 1 (char-after (+ p 38))))
1438 ?\222 0)))
1439 (t nil)))
1440 (modestr (if mode (archive-int-to-mode mode) "??????????"))
1441 (fiddle (and archive-zip-case-fiddle
1442 (not (not (memq creator '(0 2 4 5 9))))))
1443 (ifnname (if fiddle (downcase efnname) efnname))
1444 (text (format " %10s %8d %-11s %-8s %s"
1445 modestr
1446 ucsize
1447 (archive-dosdate moddate)
1448 (archive-dostime modtime)
1449 ifnname)))
1450 (setq maxlen (max maxlen fnlen)
1451 totalsize (+ totalsize ucsize)
1452 visual (cons (vector text
1453 (- (length text) (length ifnname))
1454 (length text))
1455 visual)
1456 files (cons (if isdir
1457 nil
1458 (vector efnname ifnname fiddle mode
1459 (list (1- p) lheader)))
1460 files)
845720b9 1461 p (+ p 46 fnlen exlen fclen))))
665211a3
KH
1462 (goto-char (point-min))
1463 (let ((dash (concat "- ---------- -------- ----------- -------- "
1464 (make-string maxlen ?-)
1465 "\n")))
1466 (insert "M Filemode Length Date Time File\n"
1467 dash)
1468 (archive-summarize-files (nreverse visual))
1469 (insert dash
1470 (format " %8d %d file%s"
1471 totalsize
1472 (length files)
1473 (if (= 1 (length files)) "" "s"))
1474 "\n"))
1475 (apply 'vector (nreverse files))))
1476
1477(defun archive-zip-extract (archive name)
1478 (if archive-zip-use-pkzip
1479 (archive-*-extract archive name archive-zip-extract)
1480 (archive-extract-by-stdout archive name archive-zip-extract)))
1481
1482(defun archive-zip-write-file-member (archive descr)
1483 (archive-*-write-file-member
1484 archive
1485 descr
1486 (if (aref descr 2) archive-zip-update-case archive-zip-update)))
1487
1488(defun archive-zip-chmod-entry (newmode files)
1489 (save-restriction
1490 (save-excursion
1491 (widen)
1492 (while files
1493 (let* ((fil (car files))
1494 (p (+ archive-proper-file-start (car (aref fil 4))))
1495 (creator (char-after (+ p 5)))
1496 (oldmode (aref fil 3))
1497 (newval (archive-calc-mode oldmode newmode t))
1498 buffer-read-only)
1499 (cond ((memq creator '(2 3)) ; Unix + VMS
1500 (goto-char (+ p 40))
1501 (delete-char 2)
1502 (insert (logand newval 255) (lsh newval -8)))
1503 ((memq creator '(0 5 6 7 10 11)) ; Dos etc.
1504 (goto-char (+ p 38))
1505 (insert (logior (logand (char-after (point)) 254)
1506 (logand (logxor 1 (lsh newval -7)) 1)))
1507 (delete-char 1))
1508 (t (message "Don't know how to change mode for this member"))))
1509 (setq files (cdr files))))))
1510;; -------------------------------------------------------------------------
1511;; Section: Zoo Archives
1512
1513(defun archive-zoo-summarize ()
1514 (let ((p (1+ (archive-l-e 25 4)))
1515 (maxlen 8)
1516 (totalsize 0)
1517 files
1518 visual)
1519 (while (and (string= "\334\247\304\375" (buffer-substring p (+ p 4)))
1520 (> (archive-l-e (+ p 6) 4) 0))
1521 (let* ((next (1+ (archive-l-e (+ p 6) 4)))
1522 (moddate (archive-l-e (+ p 14) 2))
1523 (modtime (archive-l-e (+ p 16) 2))
1524 (ucsize (archive-l-e (+ p 20) 4))
1525 (namefld (buffer-substring (+ p 38) (+ p 38 13)))
83c4abcb
RS
1526 (dirtype (char-after (+ p 4)))
1527 (lfnlen (if (= dirtype 2) (char-after (+ p 56)) 0))
1528 (ldirlen (if (= dirtype 2) (char-after (+ p 57)) 0))
9913653a 1529 (fnlen (or (string-match "\0" namefld) 13))
83c4abcb
RS
1530 (efnname (concat
1531 (if (> ldirlen 0)
1532 (concat (buffer-substring
1533 (+ p 58 lfnlen) (+ p 58 lfnlen ldirlen -1))
1534 "/")
1535 "")
1536 (if (> lfnlen 0)
1537 (buffer-substring (+ p 58) (+ p 58 lfnlen -1))
1538 (substring namefld 0 fnlen))))
1539 (fiddle (and (= lfnlen 0) (string= efnname (upcase efnname))))
665211a3
KH
1540 (ifnname (if fiddle (downcase efnname) efnname))
1541 (text (format " %8d %-11s %-8s %s"
1542 ucsize
1543 (archive-dosdate moddate)
1544 (archive-dostime modtime)
1545 ifnname)))
9913653a 1546 (setq maxlen (max maxlen (length ifnname))
665211a3
KH
1547 totalsize (+ totalsize ucsize)
1548 visual (cons (vector text
1549 (- (length text) (length ifnname))
1550 (length text))
1551 visual)
1552 files (cons (vector efnname ifnname fiddle nil (1- p))
1553 files)
1554 p next)))
1555 (goto-char (point-min))
1556 (let ((dash (concat "- -------- ----------- -------- "
1557 (make-string maxlen ?-)
1558 "\n")))
1559 (insert "M Length Date Time File\n"
1560 dash)
1561 (archive-summarize-files (nreverse visual))
1562 (insert dash
1563 (format " %8d %d file%s"
1564 totalsize
1565 (length files)
1566 (if (= 1 (length files)) "" "s"))
1567 "\n"))
1568 (apply 'vector (nreverse files))))
1569
1570(defun archive-zoo-extract (archive name)
1571 (archive-extract-by-stdout archive name archive-zoo-extract))
1572;; -------------------------------------------------------------------------
1573(provide 'archive-mode)
1574
1575;; arc-mode.el ends here.