diff options
| author | Richard M. Stallman | 1992-06-24 02:14:18 +0000 |
|---|---|---|
| committer | Richard M. Stallman | 1992-06-24 02:14:18 +0000 |
| commit | dd87891b706d3ca0b0b319fb09c7df195321973b (patch) | |
| tree | 34bedcdcead3212cde9dd01f1baad3f268bf2a52 | |
| parent | 492d2437106fe6c55d7c643a51ec1ae532b2ea8a (diff) | |
| download | emacs-dd87891b706d3ca0b0b319fb09c7df195321973b.tar.gz emacs-dd87891b706d3ca0b0b319fb09c7df195321973b.zip | |
Initial revision
| -rw-r--r-- | lisp/dired-aux.el | 1772 |
1 files changed, 1772 insertions, 0 deletions
diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el new file mode 100644 index 00000000000..216321d2259 --- /dev/null +++ b/lisp/dired-aux.el | |||
| @@ -0,0 +1,1772 @@ | |||
| 1 | ;; DIRED commands for Emacs. $Revision: 5.234 $ | ||
| 2 | ;; Copyright (C) 1985, 1986, 1992 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | ;; This file is part of GNU Emacs. | ||
| 5 | |||
| 6 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 7 | ;; it under the terms of the GNU General Public License as published by | ||
| 8 | ;; the Free Software Foundation; either version 1, or (at your option) | ||
| 9 | ;; any later version. | ||
| 10 | |||
| 11 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 12 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | ;; GNU General Public License for more details. | ||
| 15 | |||
| 16 | ;; You should have received a copy of the GNU General Public License | ||
| 17 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 18 | ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 19 | |||
| 20 | ;; Rewritten in 1990/1991 to add tree features, file marking and | ||
| 21 | ;; sorting by Sebastian Kremer <sk@thp.uni-koeln.de>. | ||
| 22 | ;; Finished up by rms in 1992. | ||
| 23 | |||
| 24 | ;;; 15K | ||
| 25 | ;;;###begin dired-cmd.el | ||
| 26 | ;; Diffing and compressing | ||
| 27 | |||
| 28 | ;;;###autoload | ||
| 29 | (defun dired-diff (file &optional switches) | ||
| 30 | "Compare file at point with file FILE using `diff'. | ||
| 31 | FILE defaults to the file at the mark. | ||
| 32 | The prompted-for file is the first file given to `diff'. | ||
| 33 | Prefix arg lets you edit the diff switches. See the command `diff'." | ||
| 34 | (interactive | ||
| 35 | (let ((default (if (mark) | ||
| 36 | (save-excursion (goto-char (mark)) | ||
| 37 | (dired-get-filename t t))))) | ||
| 38 | (list (read-file-name (format "Diff %s with: %s" | ||
| 39 | (dired-get-filename t) | ||
| 40 | (if default | ||
| 41 | (concat "(default " default ") ") | ||
| 42 | "")) | ||
| 43 | (dired-current-directory) default t) | ||
| 44 | (if (fboundp 'diff-read-switches) | ||
| 45 | (diff-read-switches "Options for diff: "))))) | ||
| 46 | (if switches ; Emacs 19's diff has but two | ||
| 47 | (diff file (dired-get-filename t) switches) ; args (yet ;-) | ||
| 48 | (diff file (dired-get-filename t)))) | ||
| 49 | |||
| 50 | ;;;###autoload | ||
| 51 | (defun dired-backup-diff (&optional switches) | ||
| 52 | "Diff this file with its backup file or vice versa. | ||
| 53 | Uses the latest backup, if there are several numerical backups. | ||
| 54 | If this file is a backup, diff it with its original. | ||
| 55 | The backup file is the first file given to `diff'. | ||
| 56 | Prefix arg lets you edit the diff switches. See the command `diff'." | ||
| 57 | (interactive (list (if (fboundp 'diff-read-switches) | ||
| 58 | (diff-read-switches "Diff with switches: ")))) | ||
| 59 | (let (bak ori (file (dired-get-filename))) | ||
| 60 | (if (backup-file-name-p file) | ||
| 61 | (setq bak file | ||
| 62 | ori (file-name-sans-versions file)) | ||
| 63 | (setq bak (or (dired-latest-backup-file file) | ||
| 64 | (error "No backup found for %s" file)) | ||
| 65 | ori file)) | ||
| 66 | (if switches | ||
| 67 | (diff bak ori switches) | ||
| 68 | (diff bak ori)))) | ||
| 69 | |||
| 70 | (defun dired-latest-backup-file (fn) ; actually belongs into files.el | ||
| 71 | "Return the latest existing backup of FILE, or nil." | ||
| 72 | ;; First try simple backup, then the highest numbered of the | ||
| 73 | ;; numbered backups. | ||
| 74 | ;; Ignore the value of version-control because we look for existing | ||
| 75 | ;; backups, which maybe were made earlier or by another user with | ||
| 76 | ;; a different value of version-control. | ||
| 77 | (setq fn (expand-file-name fn)) | ||
| 78 | (or | ||
| 79 | (let ((bak (make-backup-file-name fn))) | ||
| 80 | (if (file-exists-p bak) bak)) | ||
| 81 | (let* ((dir (file-name-directory fn)) | ||
| 82 | (base-versions (concat (file-name-nondirectory fn) ".~")) | ||
| 83 | (bv-length (length base-versions))) | ||
| 84 | (concat dir | ||
| 85 | (car (sort | ||
| 86 | (file-name-all-completions base-versions dir) | ||
| 87 | ;; bv-length is a fluid var for backup-extract-version: | ||
| 88 | (function | ||
| 89 | (lambda (fn1 fn2) | ||
| 90 | (> (backup-extract-version fn1) | ||
| 91 | (backup-extract-version fn2)))))))))) | ||
| 92 | |||
| 93 | (defun dired-do-chxxx (attribute-name program op-symbol arg) | ||
| 94 | ;; Change file attributes (mode, group, owner) of marked files and | ||
| 95 | ;; refresh their file lines. | ||
| 96 | ;; ATTRIBUTE-NAME is a string describing the attribute to the user. | ||
| 97 | ;; PROGRAM is the program used to change the attribute. | ||
| 98 | ;; OP-SYMBOL is the type of operation (for use in dired-mark-pop-up). | ||
| 99 | ;; ARG describes which files to use, as in dired-get-marked-files. | ||
| 100 | (let* ((files (dired-get-marked-files t arg)) | ||
| 101 | (new-attribute | ||
| 102 | (dired-mark-read-string | ||
| 103 | (concat "Change " attribute-name " of %s to: ") | ||
| 104 | nil op-symbol arg files)) | ||
| 105 | (operation (concat program " " new-attribute)) | ||
| 106 | failures) | ||
| 107 | (setq failures | ||
| 108 | (dired-bunch-files 10000 | ||
| 109 | (function dired-check-process) | ||
| 110 | (list operation program new-attribute) | ||
| 111 | files)) | ||
| 112 | (dired-do-redisplay arg);; moves point if ARG is an integer | ||
| 113 | (if failures | ||
| 114 | (dired-log-summary | ||
| 115 | (format "%s: error" operation) | ||
| 116 | nil)))) | ||
| 117 | |||
| 118 | ;;;###autoload | ||
| 119 | (defun dired-do-chmod (&optional arg) | ||
| 120 | "Change the mode of the marked (or next ARG) files. | ||
| 121 | This calls chmod, thus symbolic modes like `g+w' are allowed." | ||
| 122 | (interactive "P") | ||
| 123 | (dired-do-chxxx "Mode" "chmod" 'chmod arg)) | ||
| 124 | |||
| 125 | ;;;###autoload | ||
| 126 | (defun dired-do-chgrp (&optional arg) | ||
| 127 | "Change the group of the marked (or next ARG) files." | ||
| 128 | (interactive "P") | ||
| 129 | (dired-do-chxxx "Group" "chgrp" 'chgrp arg)) | ||
| 130 | |||
| 131 | ;;;###autoload | ||
| 132 | (defun dired-do-chown (&optional arg) | ||
| 133 | "Change the owner of the marked (or next ARG) files." | ||
| 134 | (interactive "P") | ||
| 135 | (dired-do-chxxx "Owner" dired-chown-program 'chown arg)) | ||
| 136 | |||
| 137 | ;; Process all the files in FILES in batches of a convenient size, | ||
| 138 | ;; by means of (FUNCALL FUNCTION ARGS... SOME-FILES...). | ||
| 139 | ;; Batches are chosen to need less than MAX chars for the file names, | ||
| 140 | ;; allowing 3 extra characters of separator per file name. | ||
| 141 | (defun dired-bunch-files (max function args files) | ||
| 142 | (let (pending | ||
| 143 | (pending-length 0) | ||
| 144 | failures) | ||
| 145 | ;; Accumulate files as long as they fit in MAX chars, | ||
| 146 | ;; then process the ones accumulated so far. | ||
| 147 | (while files | ||
| 148 | (let* ((thisfile (car files)) | ||
| 149 | (thislength (+ (length thisfile) 3)) | ||
| 150 | (rest (cdr files))) | ||
| 151 | ;; If we have at least 1 pending file | ||
| 152 | ;; and this file won't fit in the length limit, process now. | ||
| 153 | (if (and pending (> (+ thislength pending-length) max)) | ||
| 154 | (setq failures | ||
| 155 | (nconc (apply function (append args pending) pending) | ||
| 156 | failures) | ||
| 157 | pending nil | ||
| 158 | pending-length 0)) | ||
| 159 | ;; Do (setq pending (cons thisfile pending)) | ||
| 160 | ;; but reuse the cons that was in `files'. | ||
| 161 | (setcdr files pending) | ||
| 162 | (setq pending files) | ||
| 163 | (setq pending-length (+ thislength pending-length)) | ||
| 164 | (setq files rest))) | ||
| 165 | (nconc (apply function (append args pending) pending) | ||
| 166 | failures))) | ||
| 167 | |||
| 168 | ;;;###autoload | ||
| 169 | (defun dired-do-print (&optional arg) | ||
| 170 | "Print the marked (or next ARG) files. | ||
| 171 | Uses the shell command coming from variables `lpr-command' and | ||
| 172 | `lpr-switches' as default." | ||
| 173 | (interactive "P") | ||
| 174 | (let* ((file-list (dired-get-marked-files t arg)) | ||
| 175 | (command (dired-mark-read-string | ||
| 176 | "Print %s with: " | ||
| 177 | (apply 'concat lpr-command " " lpr-switches) | ||
| 178 | 'print arg file-list))) | ||
| 179 | (dired-run-shell-command (dired-shell-stuff-it command file-list nil)))) | ||
| 180 | |||
| 181 | ;; Read arguments for a marked-files command that wants a string | ||
| 182 | ;; that is not a file name, | ||
| 183 | ;; perhaps popping up the list of marked files. | ||
| 184 | ;; ARG is the prefix arg and indicates whether the files came from | ||
| 185 | ;; marks (ARG=nil) or a repeat factor (integerp ARG). | ||
| 186 | ;; If the current file was used, the list has but one element and ARG | ||
| 187 | ;; does not matter. (It is non-nil, non-integer in that case, namely '(4)). | ||
| 188 | |||
| 189 | (defun dired-mark-read-string (prompt initial op-symbol arg files) | ||
| 190 | ;; PROMPT for a string, with INITIAL input. | ||
| 191 | ;; Other args are used to give user feedback and pop-up: | ||
| 192 | ;; OP-SYMBOL of command, prefix ARG, marked FILES. | ||
| 193 | (dired-mark-pop-up | ||
| 194 | nil op-symbol files | ||
| 195 | (function read-string) | ||
| 196 | (format prompt (dired-mark-prompt arg files)) initial)) | ||
| 197 | |||
| 198 | ;;; Shell commands | ||
| 199 | ;;>>> install (move this function into simple.el) | ||
| 200 | (defun dired-shell-quote (filename) | ||
| 201 | "Quote a file name for inferior shell (see variable `shell-file-name')." | ||
| 202 | ;; Quote everything except POSIX filename characters. | ||
| 203 | ;; This should be safe enough even for really wierd shells. | ||
| 204 | (let ((result "") (start 0) end) | ||
| 205 | (while (string-match "[^---0-9a-zA-Z_./]" filename start) | ||
| 206 | (setq end (match-beginning 0) | ||
| 207 | result (concat result (substring filename start end) | ||
| 208 | "\\" (substring filename end (1+ end))) | ||
| 209 | start (1+ end))) | ||
| 210 | (concat result (substring filename start)))) | ||
| 211 | |||
| 212 | (defun dired-read-shell-command (prompt arg files) | ||
| 213 | ;; "Read a dired shell command prompting with PROMPT (using read-string). | ||
| 214 | ;;ARG is the prefix arg and may be used to indicate in the prompt which | ||
| 215 | ;; files are affected. | ||
| 216 | ;;This is an extra function so that you can redefine it, e.g., to use gmhist." | ||
| 217 | (dired-mark-pop-up | ||
| 218 | nil 'shell files | ||
| 219 | (function read-string) | ||
| 220 | (format prompt (dired-mark-prompt arg files)))) | ||
| 221 | |||
| 222 | ;; The in-background argument is only needed in Emacs 18 where | ||
| 223 | ;; shell-command doesn't understand an appended ampersand `&'. | ||
| 224 | ;;;###autoload | ||
| 225 | (defun dired-do-shell-command (&optional arg in-background) | ||
| 226 | "Run a shell command on the marked files. | ||
| 227 | If there is output, it goes to a separate buffer. | ||
| 228 | Normally the command is run on each file individually. | ||
| 229 | However, if there is a `*' in the command then it is run | ||
| 230 | just once with the entire file list substituted there. | ||
| 231 | |||
| 232 | If no files are marked or a specific numeric prefix arg is given, | ||
| 233 | the next ARG files are used. Just \\[universal-argument] means the current file. | ||
| 234 | The prompt mentions the file(s) or the marker, as appropriate. | ||
| 235 | |||
| 236 | No automatic redisplay is attempted, as the file names may have | ||
| 237 | changed. Type \\[dired-do-redisplay] to redisplay the marked files. | ||
| 238 | |||
| 239 | The shell command has the top level directory as working directory, so | ||
| 240 | output files usually are created there instead of in a subdir." | ||
| 241 | ;;Functions dired-run-shell-command and dired-shell-stuff-it do the | ||
| 242 | ;;actual work and can be redefined for customization. | ||
| 243 | (interactive "P") | ||
| 244 | (let* ((on-each (not (string-match "\\*" command))) | ||
| 245 | (prompt (concat (if in-background "& on " "! on ") | ||
| 246 | (if on-each "each " "") | ||
| 247 | "%s: ")) | ||
| 248 | (file-list (dired-get-marked-files t arg)) | ||
| 249 | ;; Want to give feedback whether this file or marked files are used: | ||
| 250 | (command (dired-read-shell-command | ||
| 251 | prompt arg file-list))) | ||
| 252 | (if on-each | ||
| 253 | (dired-bunch-files | ||
| 254 | (- 10000 (length command)) | ||
| 255 | (function (lambda (&rest files) | ||
| 256 | (dired-run-shell-command | ||
| 257 | (dired-shell-stuff-it command files t arg)) | ||
| 258 | in-background)) | ||
| 259 | nil | ||
| 260 | file-list) | ||
| 261 | ;; execute the shell command | ||
| 262 | (dired-run-shell-command | ||
| 263 | (dired-shell-stuff-it command file-list nil arg) | ||
| 264 | in-background)))) | ||
| 265 | |||
| 266 | ;; Might use {,} for bash or csh: | ||
| 267 | (defvar dired-mark-prefix "" | ||
| 268 | "Prepended to marked files in dired shell commands.") | ||
| 269 | (defvar dired-mark-postfix "" | ||
| 270 | "Appended to marked files in dired shell commands.") | ||
| 271 | (defvar dired-mark-separator " " | ||
| 272 | "Separates marked files in dired shell commands.") | ||
| 273 | |||
| 274 | (defun dired-shell-stuff-it (command file-list on-each &optional raw-arg) | ||
| 275 | ;; "Make up a shell command line from COMMAND and FILE-LIST. | ||
| 276 | ;; If ON-EACH is t, COMMAND should be applied to each file, else | ||
| 277 | ;; simply concat all files and apply COMMAND to this. | ||
| 278 | ;; FILE-LIST's elements will be quoted for the shell." | ||
| 279 | ;; Might be redefined for smarter things and could then use RAW-ARG | ||
| 280 | ;; (coming from interactive P and currently ignored) to decide what to do. | ||
| 281 | ;; Smart would be a way to access basename or extension of file names. | ||
| 282 | ;; See dired-trns.el for an approach to this. | ||
| 283 | ;; Bug: There is no way to quote a * | ||
| 284 | ;; On the other hand, you can never accidentally get a * into your cmd. | ||
| 285 | (let ((stuff-it | ||
| 286 | (if (string-match "\\*" command) | ||
| 287 | (function (lambda (x) | ||
| 288 | (dired-replace-in-string "\\*" x command))) | ||
| 289 | (function (lambda (x) (concat command " " x)))))) | ||
| 290 | (if on-each | ||
| 291 | (mapconcat stuff-it (mapcar 'dired-shell-quote file-list) ";") | ||
| 292 | (let ((fns (mapconcat 'dired-shell-quote | ||
| 293 | file-list dired-mark-separator))) | ||
| 294 | (if (> (length file-list) 1) | ||
| 295 | (setq fns (concat dired-mark-prefix fns dired-mark-postfix))) | ||
| 296 | (funcall stuff-it fns))))) | ||
| 297 | |||
| 298 | ;; This is an extra function so that it can be redefined by ange-ftp. | ||
| 299 | (defun dired-run-shell-command (command &optional in-background) | ||
| 300 | (if (not in-background) | ||
| 301 | (shell-command command) | ||
| 302 | ;; We need this only in Emacs 18 (19's shell command has `&'). | ||
| 303 | ;; comint::background is defined in emacs-19.el. | ||
| 304 | (comint::background command))) | ||
| 305 | |||
| 306 | ;; In Emacs 19 this will return program's exit status. | ||
| 307 | ;; This is a separate function so that ange-ftp can redefine it. | ||
| 308 | (defun dired-call-process (program discard &rest arguments) | ||
| 309 | ; "Run PROGRAM with output to current buffer unless DISCARD is t. | ||
| 310 | ;Remaining arguments are strings passed as command arguments to PROGRAM." | ||
| 311 | (apply 'call-process program nil (not discard) nil arguments)) | ||
| 312 | |||
| 313 | (defun dired-check-process (msg program &rest arguments) | ||
| 314 | ; "Display MSG while running PROGRAM, and check for output. | ||
| 315 | ;Remaining arguments are strings passed as command arguments to PROGRAM. | ||
| 316 | ; On error, insert output | ||
| 317 | ; in a log buffer and return the offending ARGUMENTS or PROGRAM. | ||
| 318 | ; Caller can cons up a list of failed args. | ||
| 319 | ;Else returns nil for success." | ||
| 320 | (let (err-buffer err (dir default-directory)) | ||
| 321 | (message "%s..." msg) | ||
| 322 | (save-excursion | ||
| 323 | ;; Get a clean buffer for error output: | ||
| 324 | (setq err-buffer (get-buffer-create " *dired-check-process output*")) | ||
| 325 | (set-buffer err-buffer) | ||
| 326 | (erase-buffer) | ||
| 327 | (setq default-directory dir ; caller's default-directory | ||
| 328 | err (/= 0 | ||
| 329 | (apply (function dired-call-process) program nil arguments))) | ||
| 330 | (if err | ||
| 331 | (progn | ||
| 332 | (dired-log (concat program " " (prin1-to-string arguments) "\n")) | ||
| 333 | (dired-log err-buffer) | ||
| 334 | (or arguments program t)) | ||
| 335 | (kill-buffer err-buffer) | ||
| 336 | (message "%s...done" msg) | ||
| 337 | nil)))) | ||
| 338 | |||
| 339 | ;; Commands that delete or redisplay part of the dired buffer. | ||
| 340 | |||
| 341 | ;;;###autoload | ||
| 342 | (defun dired-kill-line-or-subdir (&optional arg) | ||
| 343 | "Kill this line (but don't delete its file). | ||
| 344 | Optional prefix argument is a repeat factor. | ||
| 345 | If file is displayed as in situ subdir, kill that as well. | ||
| 346 | If on a subdir headerline, kill whole subdir." | ||
| 347 | (interactive "p") | ||
| 348 | (if (dired-get-subdir) | ||
| 349 | (dired-kill-subdir) | ||
| 350 | (dired-kill-line arg))) | ||
| 351 | |||
| 352 | (defun dired-kill-line (&optional arg) | ||
| 353 | (interactive "P") | ||
| 354 | (setq arg (prefix-numeric-value arg)) | ||
| 355 | (let (buffer-read-only file) | ||
| 356 | (while (/= 0 arg) | ||
| 357 | (setq file (dired-get-filename nil t)) | ||
| 358 | (if (not file) | ||
| 359 | (error "Can only kill file lines.") | ||
| 360 | (save-excursion (and file | ||
| 361 | (dired-goto-subdir file) | ||
| 362 | (dired-kill-subdir))) | ||
| 363 | (delete-region (progn (beginning-of-line) (point)) | ||
| 364 | (progn (forward-line 1) (point))) | ||
| 365 | (if (> arg 0) | ||
| 366 | (setq arg (1- arg)) | ||
| 367 | (setq arg (1+ arg)) | ||
| 368 | (forward-line -1)))) | ||
| 369 | (dired-move-to-filename))) | ||
| 370 | |||
| 371 | ;;;###autoload | ||
| 372 | (defun dired-do-kill-lines (&optional arg fmt) | ||
| 373 | "Kill all marked lines (not the files). | ||
| 374 | With a prefix arg, kill all lines not marked or flagged." | ||
| 375 | ;; Returns count of killed lines. FMT="" suppresses message. | ||
| 376 | (interactive "P") | ||
| 377 | (save-excursion | ||
| 378 | (goto-char (point-min)) | ||
| 379 | (let (buffer-read-only (count 0)) | ||
| 380 | (if (not arg) ; kill marked lines | ||
| 381 | (let ((regexp (dired-marker-regexp))) | ||
| 382 | (while (and (not (eobp)) | ||
| 383 | (re-search-forward regexp nil t)) | ||
| 384 | (setq count (1+ count)) | ||
| 385 | (delete-region (progn (beginning-of-line) (point)) | ||
| 386 | (progn (forward-line 1) (point))))) | ||
| 387 | ;; else kill unmarked lines | ||
| 388 | (while (not (eobp)) | ||
| 389 | (if (or (dired-between-files) | ||
| 390 | (not (looking-at "^ "))) | ||
| 391 | (forward-line 1) | ||
| 392 | (setq count (1+ count)) | ||
| 393 | (delete-region (point) (save-excursion | ||
| 394 | (forward-line 1) | ||
| 395 | (point)))))) | ||
| 396 | (or (equal "" fmt) | ||
| 397 | (message (or fmt "Killed %d line%s.") count (dired-plural-s count))) | ||
| 398 | count))) | ||
| 399 | |||
| 400 | ;;;###end dired-cmd.el | ||
| 401 | |||
| 402 | ;;; 30K | ||
| 403 | ;;;###begin dired-cp.el | ||
| 404 | |||
| 405 | (defun dired-compress () | ||
| 406 | ;; Compress or uncompress the current file. | ||
| 407 | ;; Return nil for success, offending filename else. | ||
| 408 | (let* (buffer-read-only | ||
| 409 | (from-file (dired-get-filename))) | ||
| 410 | (cond ((save-excursion (beginning-of-line) | ||
| 411 | (looking-at dired-re-sym)) | ||
| 412 | (dired-log (concat "Attempt to compress a symbolic link:\n" | ||
| 413 | from-file)) | ||
| 414 | (dired-make-relative from-file)) | ||
| 415 | ((string-match "\\.Z$" from-file) | ||
| 416 | (if (dired-check-process (concat "Uncompressing " from-file) | ||
| 417 | "uncompress" from-file) | ||
| 418 | (dired-make-relative from-file) | ||
| 419 | (dired-update-file-line (substring from-file 0 -2)))) | ||
| 420 | (t | ||
| 421 | (if (dired-check-process (concat "Compressing " from-file) | ||
| 422 | "compress" "-f" from-file) | ||
| 423 | ;; Errors from the process are already logged. | ||
| 424 | (dired-make-relative from-file) | ||
| 425 | (dired-update-file-line (concat from-file ".Z"))))) | ||
| 426 | nil)) | ||
| 427 | |||
| 428 | (defun dired-mark-confirm (op-symbol arg) | ||
| 429 | ;; Request confirmation from the user that the operation described | ||
| 430 | ;; by OP-SYMBOL is to be performed on the marked files. | ||
| 431 | ;; Confirmation consists in a y-or-n question with a file list | ||
| 432 | ;; pop-up unless OP-SYMBOL is a member of `dired-no-confirm'. | ||
| 433 | ;; The files used are determined by ARG (as in dired-get-marked-files). | ||
| 434 | (or (memq op-symbol dired-no-confirm) | ||
| 435 | (let ((files (dired-get-marked-files t arg))) | ||
| 436 | (dired-mark-pop-up nil op-symbol files (function y-or-n-p) | ||
| 437 | (concat (capitalize (symbol-name op-symbol)) " " | ||
| 438 | (dired-mark-prompt arg files) "? "))))) | ||
| 439 | |||
| 440 | (defun dired-map-over-marks-check (fun arg op-symbol &optional show-progress) | ||
| 441 | ; "Map FUN over marked files (with second ARG like in dired-map-over-marks) | ||
| 442 | ; and display failures. | ||
| 443 | |||
| 444 | ; FUN takes zero args. It returns non-nil (the offending object, e.g. | ||
| 445 | ; the short form of the filename) for a failure and probably logs a | ||
| 446 | ; detailed error explanation using function `dired-log'. | ||
| 447 | |||
| 448 | ; OP-SYMBOL is a symbol describing the operation performed (e.g. | ||
| 449 | ; `compress'). It is used with `dired-mark-pop-up' to prompt the user | ||
| 450 | ; (e.g. with `Compress * [2 files]? ') and to display errors (e.g. | ||
| 451 | ; `Failed to compress 1 of 2 files - type W to see why ("foo")') | ||
| 452 | |||
| 453 | ; SHOW-PROGRESS if non-nil means redisplay dired after each file." | ||
| 454 | (if (dired-mark-confirm op-symbol arg) | ||
| 455 | (let* ((total-list;; all of FUN's return values | ||
| 456 | (dired-map-over-marks (funcall fun) arg show-progress)) | ||
| 457 | (total (length total-list)) | ||
| 458 | (failures (delq nil total-list)) | ||
| 459 | (count (length failures))) | ||
| 460 | (if (not failures) | ||
| 461 | (message "%s: %d file%s." | ||
| 462 | (capitalize (symbol-name op-symbol)) | ||
| 463 | total (dired-plural-s total)) | ||
| 464 | ;; end this bunch of errors: | ||
| 465 | (dired-log-summary | ||
| 466 | (format "Failed to %s %d of %d file%s" | ||
| 467 | (symbol-name op-symbol) count total (dired-plural-s total)) | ||
| 468 | failures))))) | ||
| 469 | |||
| 470 | (defvar dired-query-alist | ||
| 471 | '((?\y . y) (?\040 . y) ; `y' or SPC means accept once | ||
| 472 | (?n . n) (?\177 . n) ; `n' or DEL skips once | ||
| 473 | (?! . yes) ; `!' accepts rest | ||
| 474 | (?q. no) (?\e . no) ; `q' or ESC skips rest | ||
| 475 | ;; None of these keys quit - use C-g for that. | ||
| 476 | )) | ||
| 477 | |||
| 478 | (defun dired-query (qs-var qs-prompt &rest qs-args) | ||
| 479 | ;; Query user and return nil or t. | ||
| 480 | ;; Store answer in symbol VAR (which must initially be bound to nil). | ||
| 481 | ;; Format PROMPT with ARGS. | ||
| 482 | ;; Binding variable help-form will help the user who types C-h. | ||
| 483 | (let* ((char (symbol-value qs-var)) | ||
| 484 | (action (cdr (assoc char dired-query-alist)))) | ||
| 485 | (cond ((eq 'yes action) | ||
| 486 | t) ; accept, and don't ask again | ||
| 487 | ((eq 'no action) | ||
| 488 | nil) ; skip, and don't ask again | ||
| 489 | (t;; no lasting effects from last time we asked - ask now | ||
| 490 | (let ((qprompt (concat qs-prompt | ||
| 491 | (if help-form | ||
| 492 | (format " [Type yn!q or %s] " | ||
| 493 | (key-description | ||
| 494 | (char-to-string help-char))) | ||
| 495 | " [Type y, n, q or !] "))) | ||
| 496 | result elt) | ||
| 497 | ;; Actually it looks nicer without cursor-in-echo-area - you can | ||
| 498 | ;; look at the dired buffer instead of at the prompt to decide. | ||
| 499 | (apply 'message qprompt qs-args) | ||
| 500 | (setq char (set qs-var (read-char))) | ||
| 501 | (while (not (setq elt (assoc char dired-query-alist))) | ||
| 502 | (message "Invalid char - type %c for help." help-char) | ||
| 503 | (ding) | ||
| 504 | (sit-for 1) | ||
| 505 | (apply 'message qprompt qs-args) | ||
| 506 | (setq char (set qs-var (read-char)))) | ||
| 507 | (memq (cdr elt) '(t y yes))))))) | ||
| 508 | |||
| 509 | ;;;###autoload | ||
| 510 | (defun dired-do-compress (&optional arg) | ||
| 511 | "Compress or uncompress marked (or next ARG) files." | ||
| 512 | (interactive "P") | ||
| 513 | (dired-map-over-marks-check (function dired-compress) arg 'compress t)) | ||
| 514 | |||
| 515 | ;; Commands for Emacs Lisp files - load and byte compile | ||
| 516 | |||
| 517 | (defun dired-byte-compile () | ||
| 518 | ;; Return nil for success, offending file name else. | ||
| 519 | (let* ((filename (dired-get-filename)) | ||
| 520 | (elc-file | ||
| 521 | (if (eq system-type 'vax-vms) | ||
| 522 | (concat (substring filename 0 (string-match ";" filename)) "c") | ||
| 523 | (concat filename "c"))) | ||
| 524 | buffer-read-only failure) | ||
| 525 | (condition-case err | ||
| 526 | (save-excursion (byte-compile-file filename)) | ||
| 527 | (error | ||
| 528 | (setq failure err))) | ||
| 529 | (if failure | ||
| 530 | (progn | ||
| 531 | (dired-log "Byte compile error for %s:\n%s\n" filename failure) | ||
| 532 | (dired-make-relative filename)) | ||
| 533 | (dired-remove-file elc-file) | ||
| 534 | (forward-line) ; insert .elc after its .el file | ||
| 535 | (dired-add-file elc-file) | ||
| 536 | nil))) | ||
| 537 | |||
| 538 | ;;;###autoload | ||
| 539 | (defun dired-do-byte-compile (&optional arg) | ||
| 540 | "Byte compile marked (or next ARG) Emacs Lisp files." | ||
| 541 | (interactive "P") | ||
| 542 | (dired-map-over-marks-check (function dired-byte-compile) arg 'byte-compile t)) | ||
| 543 | |||
| 544 | (defun dired-load () | ||
| 545 | ;; Return nil for success, offending file name else. | ||
| 546 | (let ((file (dired-get-filename)) failure) | ||
| 547 | (condition-case err | ||
| 548 | (load file nil nil t) | ||
| 549 | (error (setq failure err))) | ||
| 550 | (if (not failure) | ||
| 551 | nil | ||
| 552 | (dired-log "Load error for %s:\n%s\n" file failure) | ||
| 553 | (dired-make-relative file)))) | ||
| 554 | |||
| 555 | ;;;###autoload | ||
| 556 | (defun dired-do-load (&optional arg) | ||
| 557 | "Load the marked (or next ARG) Emacs Lisp files." | ||
| 558 | (interactive "P") | ||
| 559 | (dired-map-over-marks-check (function dired-load) arg 'load t)) | ||
| 560 | |||
| 561 | ;;;###autoload | ||
| 562 | (defun dired-do-redisplay (&optional arg test-for-subdir) | ||
| 563 | "Redisplay all marked (or next ARG) files. | ||
| 564 | If on a subdir line, redisplay that subdirectory. In that case, | ||
| 565 | a prefix arg lets you edit the `ls' switches used for the new listing." | ||
| 566 | ;; Moves point if the next ARG files are redisplayed. | ||
| 567 | (interactive "P\np") | ||
| 568 | (if (and test-for-subdir (dired-get-subdir)) | ||
| 569 | (dired-insert-subdir | ||
| 570 | (dired-get-subdir) | ||
| 571 | (if arg (read-string "Switches for listing: " dired-actual-switches))) | ||
| 572 | (message "Redisplaying...") | ||
| 573 | ;; message much faster than making dired-map-over-marks show progress | ||
| 574 | (dired-map-over-marks (let ((fname (dired-get-filename))) | ||
| 575 | (message "Redisplaying... %s" fname) | ||
| 576 | (dired-update-file-line fname)) | ||
| 577 | arg) | ||
| 578 | (dired-move-to-filename) | ||
| 579 | (message "Redisplaying...done"))) | ||
| 580 | |||
| 581 | (defun dired-update-file-line (file) | ||
| 582 | ;; Delete the current line, and insert an entry for FILE. | ||
| 583 | ;; If FILE is nil, then just delete the current line. | ||
| 584 | ;; Keeps any marks that may be present in column one (doing this | ||
| 585 | ;; here is faster than with dired-add-entry's optional arg). | ||
| 586 | ;; Does not update other dired buffers. Use dired-relist-entry for that. | ||
| 587 | (beginning-of-line) | ||
| 588 | (let ((char (following-char)) (opoint (point))) | ||
| 589 | (delete-region (point) (progn (forward-line 1) (point))) | ||
| 590 | (if file | ||
| 591 | (progn | ||
| 592 | (dired-add-entry file) | ||
| 593 | ;; Replace space by old marker without moving point. | ||
| 594 | ;; Faster than goto+insdel inside a save-excursion? | ||
| 595 | (subst-char-in-region opoint (1+ opoint) ?\040 char)))) | ||
| 596 | (dired-move-to-filename)) | ||
| 597 | |||
| 598 | (defun dired-fun-in-all-buffers (directory fun &rest args) | ||
| 599 | ;; In all buffers dired'ing DIRECTORY, run FUN with ARGS. | ||
| 600 | ;; Return list of buffers where FUN succeeded (i.e., returned non-nil). | ||
| 601 | (let ((buf-list (dired-buffers-for-dir directory)) | ||
| 602 | (obuf (current-buffer)) | ||
| 603 | buf success-list) | ||
| 604 | (while buf-list | ||
| 605 | (setq buf (car buf-list) | ||
| 606 | buf-list (cdr buf-list)) | ||
| 607 | (unwind-protect | ||
| 608 | (progn | ||
| 609 | (set-buffer buf) | ||
| 610 | (if (apply fun args) | ||
| 611 | (setq success-list (cons (buffer-name buf) success-list)))) | ||
| 612 | (set-buffer obuf))) | ||
| 613 | success-list)) | ||
| 614 | |||
| 615 | (defun dired-add-file (filename &optional marker-char) | ||
| 616 | (dired-fun-in-all-buffers | ||
| 617 | (file-name-directory filename) | ||
| 618 | (function dired-add-entry) filename marker-char)) | ||
| 619 | |||
| 620 | (defun dired-add-entry (filename &optional marker-char) | ||
| 621 | ;; Add a new entry for FILENAME, optionally marking it | ||
| 622 | ;; with MARKER-CHAR (a character, else dired-marker-char is used). | ||
| 623 | ;; Note that this adds the entry `out of order' if files sorted by | ||
| 624 | ;; time, etc. | ||
| 625 | ;; At least this version inserts in the right subdirectory (if present). | ||
| 626 | ;; And it skips "." or ".." (see `dired-trivial-filenames'). | ||
| 627 | ;; Hidden subdirs are exposed if a file is added there. | ||
| 628 | (setq filename (directory-file-name filename)) | ||
| 629 | ;; Entry is always for files, even if they happen to also be directories | ||
| 630 | (let ((opoint (point)) | ||
| 631 | (cur-dir (dired-current-directory)) | ||
| 632 | (directory (file-name-directory filename)) | ||
| 633 | reason) | ||
| 634 | (setq filename (file-name-nondirectory filename) | ||
| 635 | reason | ||
| 636 | (catch 'not-found | ||
| 637 | (if (string= directory cur-dir) | ||
| 638 | (progn | ||
| 639 | (skip-chars-forward "^\r\n") | ||
| 640 | (if (eq (following-char) ?\r) | ||
| 641 | (dired-unhide-subdir)) | ||
| 642 | ;; We are already where we should be, except when | ||
| 643 | ;; point is before the subdir line or its total line. | ||
| 644 | (let ((p (dired-after-subdir-garbage cur-dir))) | ||
| 645 | (if (< (point) p) | ||
| 646 | (goto-char p)))) | ||
| 647 | ;; else try to find correct place to insert | ||
| 648 | (if (dired-goto-subdir directory) | ||
| 649 | (progn;; unhide if necessary | ||
| 650 | (if (looking-at "\r");; point is at end of subdir line | ||
| 651 | (dired-unhide-subdir)) | ||
| 652 | ;; found - skip subdir and `total' line | ||
| 653 | ;; and uninteresting files like . and .. | ||
| 654 | ;; This better not moves into the next subdir! | ||
| 655 | (dired-goto-next-nontrivial-file)) | ||
| 656 | ;; not found | ||
| 657 | (throw 'not-found "Subdir not found"))) | ||
| 658 | ;; found and point is at The Right Place: | ||
| 659 | (let (buffer-read-only) | ||
| 660 | (beginning-of-line) | ||
| 661 | (dired-add-entry-do-indentation marker-char) | ||
| 662 | (dired-ls (dired-make-absolute filename directory);; don't expand `.' ! | ||
| 663 | (concat dired-actual-switches "d")) | ||
| 664 | (forward-line -1) | ||
| 665 | ;; We want to have the non-directory part, only: | ||
| 666 | (let* ((beg (dired-move-to-filename t)) ; error for strange output | ||
| 667 | (end (dired-move-to-end-of-filename))) | ||
| 668 | (setq filename (buffer-substring beg end)) | ||
| 669 | (delete-region beg end) | ||
| 670 | (insert (file-name-nondirectory filename))) | ||
| 671 | (if dired-after-readin-hook;; the subdir-alist is not affected... | ||
| 672 | (save-excursion;; ...so we can run it right now: | ||
| 673 | (save-restriction | ||
| 674 | (beginning-of-line) | ||
| 675 | (narrow-to-region (point) (save-excursion | ||
| 676 | (forward-line 1) (point))) | ||
| 677 | (run-hooks 'dired-after-readin-hook)))) | ||
| 678 | (dired-move-to-filename)) | ||
| 679 | ;; return nil if all went well | ||
| 680 | nil)) | ||
| 681 | (if reason ; don't move away on failure | ||
| 682 | (goto-char opoint)) | ||
| 683 | (not reason))) ; return t on succes, nil else | ||
| 684 | |||
| 685 | ;; This is a separate function for the sake of nested dired format. | ||
| 686 | (defun dired-add-entry-do-indentation (marker-char) | ||
| 687 | ;; two spaces or a marker plus a space: | ||
| 688 | (insert (if marker-char | ||
| 689 | (if (integerp marker-char) marker-char dired-marker-char) | ||
| 690 | ?\040) | ||
| 691 | ?\040)) | ||
| 692 | |||
| 693 | (defun dired-after-subdir-garbage (dir) | ||
| 694 | ;; Return pos of first file line of DIR, skipping header and total | ||
| 695 | ;; or wildcard lines. | ||
| 696 | ;; Important: never moves into the next subdir. | ||
| 697 | ;; DIR is assumed to be unhidden. | ||
| 698 | ;; Will probably be redefined for VMS etc. | ||
| 699 | (save-excursion | ||
| 700 | (or (dired-goto-subdir dir) (error "This cannot happen")) | ||
| 701 | (forward-line 1) | ||
| 702 | (while (and (not (eolp)) ; don't cross subdir boundary | ||
| 703 | (not (dired-move-to-filename))) | ||
| 704 | (forward-line 1)) | ||
| 705 | (point))) | ||
| 706 | |||
| 707 | (defun dired-remove-file (file) | ||
| 708 | (dired-fun-in-all-buffers | ||
| 709 | (file-name-directory file) (function dired-remove-entry) file)) | ||
| 710 | |||
| 711 | (defun dired-remove-entry (file) | ||
| 712 | (save-excursion | ||
| 713 | (and (dired-goto-file file) | ||
| 714 | (let (buffer-read-only) | ||
| 715 | (delete-region (progn (beginning-of-line) (point)) | ||
| 716 | (save-excursion (forward-line 1) (point))))))) | ||
| 717 | |||
| 718 | (defun dired-relist-file (file) | ||
| 719 | (dired-fun-in-all-buffers (file-name-directory file) | ||
| 720 | (function dired-relist-entry) file)) | ||
| 721 | |||
| 722 | (defun dired-relist-entry (file) | ||
| 723 | ;; Relist the line for FILE, or just add it if it did not exist. | ||
| 724 | ;; FILE must be an absolute pathname. | ||
| 725 | (let (buffer-read-only marker) | ||
| 726 | ;; If cursor is already on FILE's line delete-region will cause | ||
| 727 | ;; save-excursion to fail because of floating makers, | ||
| 728 | ;; moving point to beginning of line. Sigh. | ||
| 729 | (save-excursion | ||
| 730 | (and (dired-goto-file file) | ||
| 731 | (delete-region (progn (beginning-of-line) | ||
| 732 | (setq marker (following-char)) | ||
| 733 | (point)) | ||
| 734 | (save-excursion (forward-line 1) (point)))) | ||
| 735 | (setq file (directory-file-name file)) | ||
| 736 | (dired-add-entry file (if (eq ?\040 marker) nil marker))))) | ||
| 737 | |||
| 738 | ;;; Copy, move/rename, making hard and symbolic links | ||
| 739 | |||
| 740 | (defvar dired-backup-overwrite nil | ||
| 741 | "*Non-nil if Dired should ask about making backups before overwriting files. | ||
| 742 | Special value `always' suppresses confirmation.") | ||
| 743 | |||
| 744 | (defun dired-handle-overwrite (to) | ||
| 745 | ;; Save old version of a to be overwritten file TO. | ||
| 746 | ;; `overwrite-confirmed' and `overwrite-backup-query' are fluid vars | ||
| 747 | ;; from dired-create-files. | ||
| 748 | (if (and dired-backup-overwrite | ||
| 749 | overwrite-confirmed | ||
| 750 | (or (eq 'always dired-backup-overwrite) | ||
| 751 | (dired-query 'overwrite-backup-query | ||
| 752 | (format "Make backup for existing file `%s'? " to)))) | ||
| 753 | (let ((backup (car (find-backup-file-name to)))) | ||
| 754 | (rename-file to backup 0) ; confirm overwrite of old backup | ||
| 755 | (dired-relist-entry backup)))) | ||
| 756 | |||
| 757 | (defun dired-copy-file (from to ok-flag) | ||
| 758 | (dired-handle-overwrite to) | ||
| 759 | (copy-file from to ok-flag dired-copy-preserve-time)) | ||
| 760 | |||
| 761 | (defun dired-rename-file (from to ok-flag) | ||
| 762 | (dired-handle-overwrite to) | ||
| 763 | (rename-file from to ok-flag) ; error is caught in -create-files | ||
| 764 | ;; Silently rename the visited file of any buffer visiting this file. | ||
| 765 | (and (get-file-buffer from) | ||
| 766 | (save-excursion | ||
| 767 | (set-buffer (get-file-buffer from)) | ||
| 768 | (let ((modflag (buffer-modified-p))) | ||
| 769 | (set-visited-file-name to) | ||
| 770 | (set-buffer-modified-p modflag)))) | ||
| 771 | (dired-remove-file from) | ||
| 772 | ;; See if it's an inserted subdir, and rename that, too. | ||
| 773 | (dired-rename-subdir from to)) | ||
| 774 | |||
| 775 | (defun dired-rename-subdir (from-dir to-dir) | ||
| 776 | (setq from-dir (file-name-as-directory from-dir) | ||
| 777 | to-dir (file-name-as-directory to-dir)) | ||
| 778 | (dired-fun-in-all-buffers from-dir | ||
| 779 | (function dired-rename-subdir-1) from-dir to-dir) | ||
| 780 | ;; Update visited file name of all affected buffers | ||
| 781 | (let ((blist (buffer-list))) | ||
| 782 | (while blist | ||
| 783 | (save-excursion | ||
| 784 | (set-buffer (car blist)) | ||
| 785 | (if (and buffer-file-name | ||
| 786 | (dired-in-this-tree buffer-file-name from-dir)) | ||
| 787 | (let ((modflag (buffer-modified-p)) | ||
| 788 | (to-file (dired-replace-in-string | ||
| 789 | (concat "^" (regexp-quote from-dir)) | ||
| 790 | to-dir | ||
| 791 | buffer-file-name))) | ||
| 792 | (set-visited-file-name to-file) | ||
| 793 | (set-buffer-modified-p modflag)))) | ||
| 794 | (setq blist (cdr blist))))) | ||
| 795 | |||
| 796 | (defun dired-rename-subdir-1 (dir to) | ||
| 797 | ;; Rename DIR to TO in headerlines and dired-subdir-alist, if DIR or | ||
| 798 | ;; one of its subdirectories is expanded in this buffer. | ||
| 799 | (let ((alist dired-subdir-alist) | ||
| 800 | (elt nil)) | ||
| 801 | (while alist | ||
| 802 | (setq elt (car alist) | ||
| 803 | alist (cdr alist)) | ||
| 804 | (if (dired-in-this-tree (car elt) dir) | ||
| 805 | ;; ELT's subdir is affected by the rename | ||
| 806 | (dired-rename-subdir-2 elt dir to))) | ||
| 807 | (if (equal dir default-directory) | ||
| 808 | ;; if top level directory was renamed, lots of things have to be | ||
| 809 | ;; updated: | ||
| 810 | (progn | ||
| 811 | (dired-unadvertise dir) ; we no longer dired DIR... | ||
| 812 | (setq default-directory to | ||
| 813 | dired-directory (expand-file-name;; this is correct | ||
| 814 | ;; with and without wildcards | ||
| 815 | (file-name-nondirectory dired-directory) | ||
| 816 | to)) | ||
| 817 | (let ((new-name (file-name-nondirectory | ||
| 818 | (directory-file-name dired-directory)))) | ||
| 819 | ;; try to rename buffer, but just leave old name if new | ||
| 820 | ;; name would already exist (don't try appending "<%d>") | ||
| 821 | (or (get-buffer new-name) | ||
| 822 | (rename-buffer new-name))) | ||
| 823 | ;; ... we dired TO now: | ||
| 824 | (dired-advertise))))) | ||
| 825 | |||
| 826 | (defun dired-rename-subdir-2 (elt dir to) | ||
| 827 | ;; Update the headerline and dired-subdir-alist element of directory | ||
| 828 | ;; described by alist-element ELT to reflect the moving of DIR to TO. | ||
| 829 | ;; Thus, ELT describes either DIR itself or a subdir of DIR. | ||
| 830 | (save-excursion | ||
| 831 | (let ((regexp (regexp-quote (directory-file-name dir))) | ||
| 832 | (newtext (directory-file-name to)) | ||
| 833 | buffer-read-only) | ||
| 834 | (goto-char (dired-get-subdir-min elt)) | ||
| 835 | ;; Update subdir headerline in buffer | ||
| 836 | (if (not (looking-at dired-subdir-regexp)) | ||
| 837 | (error "%s not found where expected - dired-subdir-alist broken?" | ||
| 838 | dir) | ||
| 839 | (goto-char (match-beginning 1)) | ||
| 840 | (if (re-search-forward regexp (match-end 1) t) | ||
| 841 | (replace-match newtext t t) | ||
| 842 | (error "Expected to find `%s' in headerline of %s" dir (car elt)))) | ||
| 843 | ;; Update buffer-local dired-subdir-alist | ||
| 844 | (setcar elt | ||
| 845 | (dired-normalize-subdir | ||
| 846 | (dired-replace-in-string regexp newtext (car elt))))))) | ||
| 847 | |||
| 848 | ;; Cloning replace-match to work on strings instead of in buffer: | ||
| 849 | ;; The FIXEDCASE parameter of replace-match is not implemented. | ||
| 850 | ;;;###autoload | ||
| 851 | (defun dired-string-replace-match (regexp string newtext | ||
| 852 | &optional literal global) | ||
| 853 | "Replace first match of REGEXP in STRING with NEWTEXT. | ||
| 854 | If it does not match, nil is returned instead of the new string. | ||
| 855 | Optional arg LITERAL means to take NEWTEXT literally. | ||
| 856 | Optional arg GLOBAL means to replace all matches." | ||
| 857 | (if global | ||
| 858 | (let ((result "") (start 0) mb me) | ||
| 859 | (while (string-match regexp string start) | ||
| 860 | (setq mb (match-beginning 0) | ||
| 861 | me (match-end 0) | ||
| 862 | result (concat result | ||
| 863 | (substring string start mb) | ||
| 864 | (if literal | ||
| 865 | newtext | ||
| 866 | (dired-expand-newtext string newtext))) | ||
| 867 | start me)) | ||
| 868 | (if mb ; matched at least once | ||
| 869 | (concat result (substring string start)) | ||
| 870 | nil)) | ||
| 871 | ;; not GLOBAL | ||
| 872 | (if (not (string-match regexp string 0)) | ||
| 873 | nil | ||
| 874 | (concat (substring string 0 (match-beginning 0)) | ||
| 875 | (if literal newtext (dired-expand-newtext string newtext)) | ||
| 876 | (substring string (match-end 0)))))) | ||
| 877 | |||
| 878 | (defun dired-expand-newtext (string newtext) | ||
| 879 | ;; Expand \& and \1..\9 (referring to STRING) in NEWTEXT, using match data. | ||
| 880 | ;; Note that in Emacs 18 match data are clipped to current buffer | ||
| 881 | ;; size...so the buffer should better not be smaller than STRING. | ||
| 882 | (let ((pos 0) | ||
| 883 | (len (length newtext)) | ||
| 884 | (expanded-newtext "")) | ||
| 885 | (while (< pos len) | ||
| 886 | (setq expanded-newtext | ||
| 887 | (concat expanded-newtext | ||
| 888 | (let ((c (aref newtext pos))) | ||
| 889 | (if (= ?\\ c) | ||
| 890 | (cond ((= ?\& (setq c | ||
| 891 | (aref newtext | ||
| 892 | (setq pos (1+ pos))))) | ||
| 893 | (substring string | ||
| 894 | (match-beginning 0) | ||
| 895 | (match-end 0))) | ||
| 896 | ((and (>= c ?1) (<= c ?9)) | ||
| 897 | ;; return empty string if N'th | ||
| 898 | ;; sub-regexp did not match: | ||
| 899 | (let ((n (- c ?0))) | ||
| 900 | (if (match-beginning n) | ||
| 901 | (substring string | ||
| 902 | (match-beginning n) | ||
| 903 | (match-end n)) | ||
| 904 | ""))) | ||
| 905 | (t | ||
| 906 | (char-to-string c))) | ||
| 907 | (char-to-string c))))) | ||
| 908 | (setq pos (1+ pos))) | ||
| 909 | expanded-newtext)) | ||
| 910 | |||
| 911 | ;; The basic function for half a dozen variations on cp/mv/ln/ln -s. | ||
| 912 | (defun dired-create-files (file-creator operation fn-list name-constructor | ||
| 913 | &optional marker-char) | ||
| 914 | |||
| 915 | ;; Create a new file for each from a list of existing files. The user | ||
| 916 | ;; is queried, dired buffers are updated, and at the end a success or | ||
| 917 | ;; failure message is displayed | ||
| 918 | |||
| 919 | ;; FILE-CREATOR must accept three args: oldfile newfile ok-if-already-exists | ||
| 920 | |||
| 921 | ;; It is called for each file and must create newfile, the entry of | ||
| 922 | ;; which will be added. The user will be queried if the file already | ||
| 923 | ;; exists. If oldfile is removed by FILE-CREATOR (i.e, it is a | ||
| 924 | ;; rename), it is FILE-CREATOR's responsibility to update dired | ||
| 925 | ;; buffers. FILE-CREATOR must abort by signalling a file-error if it | ||
| 926 | ;; could not create newfile. The error is caught and logged. | ||
| 927 | |||
| 928 | ;; OPERATION (a capitalized string, e.g. `Copy') describes the | ||
| 929 | ;; operation performed. It is used for error logging. | ||
| 930 | |||
| 931 | ;; FN-LIST is the list of files to copy (full absolute pathnames). | ||
| 932 | |||
| 933 | ;; NAME-CONSTRUCTOR returns a newfile for every oldfile, or nil to | ||
| 934 | ;; skip. If it skips files for other reasons than a direct user | ||
| 935 | ;; query, it is supposed to tell why (using dired-log). | ||
| 936 | |||
| 937 | ;; Optional MARKER-CHAR is a character with which to mark every | ||
| 938 | ;; newfile's entry, or t to use the current marker character if the | ||
| 939 | ;; oldfile was marked. | ||
| 940 | |||
| 941 | (let (failures skipped (success-count 0) (total (length fn-list))) | ||
| 942 | (let (to overwrite-query | ||
| 943 | overwrite-backup-query) ; for dired-handle-overwrite | ||
| 944 | (mapcar | ||
| 945 | (function | ||
| 946 | (lambda (from) | ||
| 947 | (setq to (funcall name-constructor from)) | ||
| 948 | (if (equal to from) | ||
| 949 | (progn | ||
| 950 | (setq to nil) | ||
| 951 | (dired-log "Cannot %s to same file: %s\n" | ||
| 952 | (downcase operation) from))) | ||
| 953 | (if (not to) | ||
| 954 | (setq skipped (cons (dired-make-relative from) skipped)) | ||
| 955 | (let* ((overwrite (file-exists-p to)) | ||
| 956 | (overwrite-confirmed ; for dired-handle-overwrite | ||
| 957 | (and overwrite | ||
| 958 | (let ((help-form '(format "\ | ||
| 959 | Type SPC or `y' to overwrite file `%s', | ||
| 960 | DEL or `n' to skip to next, | ||
| 961 | ESC or `q' to not overwrite any of the remaining files, | ||
| 962 | `!' to overwrite all remaining files with no more questions." to))) | ||
| 963 | (dired-query 'overwrite-query | ||
| 964 | "Overwrite `%s'?" to)))) | ||
| 965 | ;; must determine if FROM is marked before file-creator | ||
| 966 | ;; gets a chance to delete it (in case of a move). | ||
| 967 | (actual-marker-char | ||
| 968 | (cond ((integerp marker-char) marker-char) | ||
| 969 | (marker-char (dired-file-marker from)) ; slow | ||
| 970 | (t nil)))) | ||
| 971 | (condition-case err | ||
| 972 | (progn | ||
| 973 | (funcall file-creator from to overwrite-confirmed) | ||
| 974 | (if overwrite | ||
| 975 | ;; If we get here, file-creator hasn't been aborted | ||
| 976 | ;; and the old entry (if any) has to be deleted | ||
| 977 | ;; before adding the new entry. | ||
| 978 | (dired-remove-file to)) | ||
| 979 | (setq success-count (1+ success-count)) | ||
| 980 | (message "%s: %d of %d" operation success-count total) | ||
| 981 | (dired-add-file to actual-marker-char)) | ||
| 982 | (file-error ; FILE-CREATOR aborted | ||
| 983 | (progn | ||
| 984 | (setq failures (cons (dired-make-relative from) failures)) | ||
| 985 | (dired-log "%s `%s' to `%s' failed:\n%s\n" | ||
| 986 | operation from to err)))))))) | ||
| 987 | fn-list)) | ||
| 988 | (cond | ||
| 989 | (failures | ||
| 990 | (dired-log-summary | ||
| 991 | (format "%s failed for %d of %d file%s" | ||
| 992 | operation (length failures) total | ||
| 993 | (dired-plural-s total)) | ||
| 994 | failures)) | ||
| 995 | (skipped | ||
| 996 | (dired-log-summary | ||
| 997 | (format "%s: %d of %d file%s skipped" | ||
| 998 | operation (length skipped) total | ||
| 999 | (dired-plural-s total)) | ||
| 1000 | skipped)) | ||
| 1001 | (t | ||
| 1002 | (message "%s: %s file%s" | ||
| 1003 | operation success-count (dired-plural-s success-count))))) | ||
| 1004 | (dired-move-to-filename)) | ||
| 1005 | |||
| 1006 | (defun dired-do-create-files (op-symbol file-creator operation arg | ||
| 1007 | &optional marker-char op1 | ||
| 1008 | how-to) | ||
| 1009 | ;; Create a new file for each marked file. | ||
| 1010 | ;; Prompts user for target, which is a directory in which to create | ||
| 1011 | ;; the new files. Target may be a plain file if only one marked | ||
| 1012 | ;; file exists. | ||
| 1013 | ;; OP-SYMBOL is the symbol for the operation. Function `dired-mark-pop-up' | ||
| 1014 | ;; will determine wether pop-ups are appropriate for this OP-SYMBOL. | ||
| 1015 | ;; FILE-CREATOR and OPERATION as in dired-create-files. | ||
| 1016 | ;; ARG as in dired-get-marked-files. | ||
| 1017 | ;; Optional arg OP1 is an alternate form for OPERATION if there is | ||
| 1018 | ;; only one file. | ||
| 1019 | ;; Optional arg MARKER-CHAR as in dired-create-files. | ||
| 1020 | ;; Optional arg HOW-TO determines how to treat target: | ||
| 1021 | ;; If HOW-TO is not given (or nil), and target is a directory, the | ||
| 1022 | ;; file(s) are created inside the target directory. If target | ||
| 1023 | ;; is not a directory, there must be exactly one marked file, | ||
| 1024 | ;; else error. | ||
| 1025 | ;; If HOW-TO is t, then target is not modified. There must be | ||
| 1026 | ;; exactly one marked file, else error. | ||
| 1027 | ;; Else HOW-TO is assumed to be a function of one argument, target, | ||
| 1028 | ;; that looks at target and returns a value for the into-dir | ||
| 1029 | ;; variable. The function dired-into-dir-with-symlinks is provided | ||
| 1030 | ;; for the case (common when creating symlinks) that symbolic | ||
| 1031 | ;; links to directories are not to be considered as directories | ||
| 1032 | ;; (as file-directory-p would if HOW-TO had been nil). | ||
| 1033 | (or op1 (setq op1 operation)) | ||
| 1034 | (let* ((fn-list (dired-get-marked-files nil arg)) | ||
| 1035 | (fn-count (length fn-list)) | ||
| 1036 | (target (expand-file-name | ||
| 1037 | (dired-mark-read-file-name | ||
| 1038 | (concat (if (= 1 fn-count) op1 operation) " %s to: ") | ||
| 1039 | (dired-dwim-target-directory) | ||
| 1040 | op-symbol arg (mapcar (function dired-make-relative) fn-list)))) | ||
| 1041 | (into-dir (cond ((null how-to) (file-directory-p target)) | ||
| 1042 | ((eq how-to t) nil) | ||
| 1043 | (t (funcall how-to target))))) | ||
| 1044 | (if (and (> fn-count 1) | ||
| 1045 | (not into-dir)) | ||
| 1046 | (error "Marked %s: target must be a directory: %s" operation target)) | ||
| 1047 | ;; rename-file bombs when moving directories unless we do this: | ||
| 1048 | (or into-dir (setq target (directory-file-name target))) | ||
| 1049 | (dired-create-files | ||
| 1050 | file-creator operation fn-list | ||
| 1051 | (if into-dir ; target is a directory | ||
| 1052 | ;; This function uses fluid vars into-dir and target when called | ||
| 1053 | ;; inside dired-create-files: | ||
| 1054 | (function (lambda (from) | ||
| 1055 | (expand-file-name (file-name-nondirectory from) target))) | ||
| 1056 | (function (lambda (from) target))) | ||
| 1057 | marker-char))) | ||
| 1058 | |||
| 1059 | ;; Read arguments for a marked-files command that wants a file name, | ||
| 1060 | ;; perhaps popping up the list of marked files. | ||
| 1061 | ;; ARG is the prefix arg and indicates whether the files came from | ||
| 1062 | ;; marks (ARG=nil) or a repeat factor (integerp ARG). | ||
| 1063 | ;; If the current file was used, the list has but one element and ARG | ||
| 1064 | ;; does not matter. (It is non-nil, non-integer in that case, namely '(4)). | ||
| 1065 | |||
| 1066 | (defun dired-mark-read-file-name (prompt dir op-symbol arg files) | ||
| 1067 | (dired-mark-pop-up | ||
| 1068 | nil op-symbol files | ||
| 1069 | (function read-file-name) | ||
| 1070 | (format prompt (dired-mark-prompt arg files)) dir)) | ||
| 1071 | |||
| 1072 | (defun dired-dwim-target-directory () | ||
| 1073 | ;; Try to guess which target directory the user may want. | ||
| 1074 | ;; If there is a dired buffer displayed in the next window, use | ||
| 1075 | ;; its current subdir, else use current subdir of this dired buffer. | ||
| 1076 | (let ((this-dir (and (eq major-mode 'dired-mode) | ||
| 1077 | (dired-current-directory)))) | ||
| 1078 | ;; non-dired buffer may want to profit from this function, e.g. vm-uudecode | ||
| 1079 | (if dired-dwim-target | ||
| 1080 | (let* ((other-buf (window-buffer (next-window))) | ||
| 1081 | (other-dir (save-excursion | ||
| 1082 | (set-buffer other-buf) | ||
| 1083 | (and (eq major-mode 'dired-mode) | ||
| 1084 | (dired-current-directory))))) | ||
| 1085 | (or other-dir this-dir)) | ||
| 1086 | this-dir))) | ||
| 1087 | |||
| 1088 | ;;;###autoload | ||
| 1089 | (defun dired-create-directory (directory) | ||
| 1090 | "Create a directory called DIRECTORY." | ||
| 1091 | (interactive | ||
| 1092 | (list (read-file-name "Create directory: " (dired-current-directory)))) | ||
| 1093 | (let ((expanded (directory-file-name (expand-file-name directory)))) | ||
| 1094 | (make-directory expanded) | ||
| 1095 | (dired-add-file expanded) | ||
| 1096 | (dired-move-to-filename))) | ||
| 1097 | |||
| 1098 | (defun dired-into-dir-with-symlinks (target) | ||
| 1099 | (and (file-directory-p target) | ||
| 1100 | (not (file-symlink-p target)))) | ||
| 1101 | ;; This may not always be what you want, especially if target is your | ||
| 1102 | ;; home directory and it happens to be a symbolic link, as is often the | ||
| 1103 | ;; case with NFS and automounters. Or if you want to make symlinks | ||
| 1104 | ;; into directories that themselves are only symlinks, also quite | ||
| 1105 | ;; common. | ||
| 1106 | |||
| 1107 | ;; So we don't use this function as value for HOW-TO in | ||
| 1108 | ;; dired-do-symlink, which has the minor disadvantage of | ||
| 1109 | ;; making links *into* a symlinked-dir, when you really wanted to | ||
| 1110 | ;; *overwrite* that symlink. In that (rare, I guess) case, you'll | ||
| 1111 | ;; just have to remove that symlink by hand before making your marked | ||
| 1112 | ;; symlinks. | ||
| 1113 | |||
| 1114 | ;;;###autoload | ||
| 1115 | (defun dired-do-copy (&optional arg) | ||
| 1116 | "Copy all marked (or next ARG) files, or copy the current file. | ||
| 1117 | This normally preserves the last-modified date when copying. | ||
| 1118 | When operating on just the current file, you specify the new name. | ||
| 1119 | When operating on multiple or marked files, you specify a directory | ||
| 1120 | and new symbolic links are made in that directory | ||
| 1121 | with the same names that the files currently have." | ||
| 1122 | (interactive "P") | ||
| 1123 | (dired-do-create-files 'copy (function dired-copy-file) | ||
| 1124 | (if dired-copy-preserve-time "Copy [-p]" "Copy") | ||
| 1125 | arg dired-keep-marker-copy)) | ||
| 1126 | |||
| 1127 | ;;;###autoload | ||
| 1128 | (defun dired-do-symlink (&optional arg) | ||
| 1129 | "Make symbolic links to current file or all marked (or next ARG) files. | ||
| 1130 | When operating on just the current file, you specify the new name. | ||
| 1131 | When operating on multiple or marked files, you specify a directory | ||
| 1132 | and new symbolic links are made in that directory | ||
| 1133 | with the same names that the files currently have." | ||
| 1134 | (interactive "P") | ||
| 1135 | (dired-do-create-files 'symlink (function make-symbolic-link) | ||
| 1136 | "Symlink" arg dired-keep-marker-symlink)) | ||
| 1137 | |||
| 1138 | ;;;###autoload | ||
| 1139 | (defun dired-do-hardlink (&optional arg) | ||
| 1140 | "Add names (hard links) current file or all marked (or next ARG) files. | ||
| 1141 | When operating on just the current file, you specify the new name. | ||
| 1142 | When operating on multiple or marked files, you specify a directory | ||
| 1143 | and new hard links are made in that directory | ||
| 1144 | with the same names that the files currently have." | ||
| 1145 | (interactive "P") | ||
| 1146 | (dired-do-create-files 'hardlink (function add-name-to-file) | ||
| 1147 | "Hardlink" arg dired-keep-marker-hardlink)) | ||
| 1148 | |||
| 1149 | ;;;###autoload | ||
| 1150 | (defun dired-do-rename (&optional arg) | ||
| 1151 | "Rename current file or all marked (or next ARG) files. | ||
| 1152 | When renaming just the current file, you specify the new name. | ||
| 1153 | When renaming multiple or marked files, you specify a directory." | ||
| 1154 | (interactive "P") | ||
| 1155 | (dired-do-create-files 'move (function dired-rename-file) | ||
| 1156 | "Move" arg dired-keep-marker-rename "Rename")) | ||
| 1157 | ;;;###end dired-cp.el | ||
| 1158 | |||
| 1159 | ;;; 5K | ||
| 1160 | ;;;###begin dired-re.el | ||
| 1161 | (defun dired-do-create-files-regexp | ||
| 1162 | (file-creator operation arg regexp newname &optional whole-path marker-char) | ||
| 1163 | ;; Create a new file for each marked file using regexps. | ||
| 1164 | ;; FILE-CREATOR and OPERATION as in dired-create-files. | ||
| 1165 | ;; ARG as in dired-get-marked-files. | ||
| 1166 | ;; Matches each marked file against REGEXP and constructs the new | ||
| 1167 | ;; filename from NEWNAME (like in function replace-match). | ||
| 1168 | ;; Optional arg WHOLE-PATH means match/replace the whole pathname | ||
| 1169 | ;; instead of only the non-directory part of the file. | ||
| 1170 | ;; Optional arg MARKER-CHAR as in dired-create-files. | ||
| 1171 | (let* ((fn-list (dired-get-marked-files nil arg)) | ||
| 1172 | (fn-count (length fn-list)) | ||
| 1173 | (operation-prompt (concat operation " `%s' to `%s'?")) | ||
| 1174 | (rename-regexp-help-form (format "\ | ||
| 1175 | Type SPC or `y' to %s one match, DEL or `n' to skip to next, | ||
| 1176 | `!' to %s all remaining matches with no more questions." | ||
| 1177 | (downcase operation) | ||
| 1178 | (downcase operation))) | ||
| 1179 | (regexp-name-constructor | ||
| 1180 | ;; Function to construct new filename using REGEXP and NEWNAME: | ||
| 1181 | (if whole-path ; easy (but rare) case | ||
| 1182 | (function | ||
| 1183 | (lambda (from) | ||
| 1184 | (let ((to (dired-string-replace-match regexp from newname)) | ||
| 1185 | ;; must bind help-form directly around call to | ||
| 1186 | ;; dired-query | ||
| 1187 | (help-form rename-regexp-help-form)) | ||
| 1188 | (if to | ||
| 1189 | (and (dired-query 'rename-regexp-query | ||
| 1190 | operation-prompt | ||
| 1191 | from | ||
| 1192 | to) | ||
| 1193 | to) | ||
| 1194 | (dired-log "%s: %s did not match regexp %s\n" | ||
| 1195 | operation from regexp))))) | ||
| 1196 | ;; not whole-path, replace non-directory part only | ||
| 1197 | (function | ||
| 1198 | (lambda (from) | ||
| 1199 | (let* ((new (dired-string-replace-match | ||
| 1200 | regexp (file-name-nondirectory from) newname)) | ||
| 1201 | (to (and new ; nil means there was no match | ||
| 1202 | (expand-file-name new | ||
| 1203 | (file-name-directory from)))) | ||
| 1204 | (help-form rename-regexp-help-form)) | ||
| 1205 | (if to | ||
| 1206 | (and (dired-query 'rename-regexp-query | ||
| 1207 | operation-prompt | ||
| 1208 | (dired-make-relative from) | ||
| 1209 | (dired-make-relative to)) | ||
| 1210 | to) | ||
| 1211 | (dired-log "%s: %s did not match regexp %s\n" | ||
| 1212 | operation (file-name-nondirectory from) regexp))))))) | ||
| 1213 | rename-regexp-query) | ||
| 1214 | (dired-create-files | ||
| 1215 | file-creator operation fn-list regexp-name-constructor marker-char))) | ||
| 1216 | |||
| 1217 | (defun dired-mark-read-regexp (operation) | ||
| 1218 | ;; Prompt user about performing OPERATION. | ||
| 1219 | ;; Read and return list of: regexp newname arg whole-path. | ||
| 1220 | (let* ((whole-path | ||
| 1221 | (equal 0 (prefix-numeric-value current-prefix-arg))) | ||
| 1222 | (arg | ||
| 1223 | (if whole-path nil current-prefix-arg)) | ||
| 1224 | (regexp | ||
| 1225 | (dired-read-regexp | ||
| 1226 | (concat (if whole-path "Path " "") operation " from (regexp): ") | ||
| 1227 | dired-flagging-regexp)) | ||
| 1228 | (newname | ||
| 1229 | (read-string | ||
| 1230 | (concat (if whole-path "Path " "") operation " " regexp " to: ")))) | ||
| 1231 | (list regexp newname arg whole-path))) | ||
| 1232 | |||
| 1233 | ;;;###autoload | ||
| 1234 | (defun dired-do-rename-regexp (regexp newname &optional arg whole-path) | ||
| 1235 | "Rename marked files containing REGEXP to NEWNAME. | ||
| 1236 | As each match is found, the user must type a character saying | ||
| 1237 | what to do with it. For directions, type \\[help-command] at that time. | ||
| 1238 | NEWNAME may contain \\=\\<n> or \\& as in `query-replace-regexp'. | ||
| 1239 | REGEXP defaults to the last regexp used. | ||
| 1240 | With a zero prefix arg, renaming by regexp affects the complete | ||
| 1241 | pathname - usually only the non-directory part of file names is used | ||
| 1242 | and changed." | ||
| 1243 | (interactive (dired-mark-read-regexp "Rename")) | ||
| 1244 | (dired-do-create-files-regexp | ||
| 1245 | (function dired-rename-file) | ||
| 1246 | "Rename" arg regexp newname whole-path dired-keep-marker-rename)) | ||
| 1247 | |||
| 1248 | ;;;###autoload | ||
| 1249 | (defun dired-do-copy-regexp (regexp newname &optional arg whole-path) | ||
| 1250 | "Copy all marked files containing REGEXP to NEWNAME. | ||
| 1251 | See function `dired-rename-regexp' for more info." | ||
| 1252 | (interactive (dired-mark-read-regexp "Copy")) | ||
| 1253 | (dired-do-create-files-regexp | ||
| 1254 | (function dired-copy-file) | ||
| 1255 | (if dired-copy-preserve-time "Copy [-p]" "Copy") | ||
| 1256 | arg regexp newname whole-path dired-keep-marker-copy)) | ||
| 1257 | |||
| 1258 | ;;;###autoload | ||
| 1259 | (defun dired-do-hardlink-regexp (regexp newname &optional arg whole-path) | ||
| 1260 | "Hardlink all marked files containing REGEXP to NEWNAME. | ||
| 1261 | See function `dired-rename-regexp' for more info." | ||
| 1262 | (interactive (dired-mark-read-regexp "HardLink")) | ||
| 1263 | (dired-do-create-files-regexp | ||
| 1264 | (function add-name-to-file) | ||
| 1265 | "HardLink" arg regexp newname whole-path dired-keep-marker-hardlink)) | ||
| 1266 | |||
| 1267 | ;;;###autoload | ||
| 1268 | (defun dired-do-symlink-regexp (regexp newname &optional arg whole-path) | ||
| 1269 | "Symlink all marked files containing REGEXP to NEWNAME. | ||
| 1270 | See function `dired-rename-regexp' for more info." | ||
| 1271 | (interactive (dired-mark-read-regexp "SymLink")) | ||
| 1272 | (dired-do-create-files-regexp | ||
| 1273 | (function make-symbolic-link) | ||
| 1274 | "SymLink" arg regexp newname whole-path dired-keep-marker-symlink)) | ||
| 1275 | |||
| 1276 | (defun dired-create-files-non-directory | ||
| 1277 | (file-creator basename-constructor operation arg) | ||
| 1278 | ;; Perform FILE-CREATOR on the non-directory part of marked files | ||
| 1279 | ;; using function BASENAME-CONSTRUCTOR, with query for each file. | ||
| 1280 | ;; OPERATION like in dired-create-files, ARG as in dired-get-marked-files. | ||
| 1281 | (let (rename-non-directory-query) | ||
| 1282 | (dired-create-files | ||
| 1283 | file-creator | ||
| 1284 | operation | ||
| 1285 | (dired-get-marked-files nil arg) | ||
| 1286 | (function | ||
| 1287 | (lambda (from) | ||
| 1288 | (let ((to (concat (file-name-directory from) | ||
| 1289 | (funcall basename-constructor | ||
| 1290 | (file-name-nondirectory from))))) | ||
| 1291 | (and (let ((help-form (format "\ | ||
| 1292 | Type SPC or `y' to %s one file, DEL or `n' to skip to next, | ||
| 1293 | `!' to %s all remaining matches with no more questions." | ||
| 1294 | (downcase operation) | ||
| 1295 | (downcase operation)))) | ||
| 1296 | (dired-query 'rename-non-directory-query | ||
| 1297 | (concat operation " `%s' to `%s'") | ||
| 1298 | (dired-make-relative from) | ||
| 1299 | (dired-make-relative to))) | ||
| 1300 | to)))) | ||
| 1301 | dired-keep-marker-rename))) | ||
| 1302 | |||
| 1303 | (defun dired-rename-non-directory (basename-constructor operation arg) | ||
| 1304 | (dired-create-files-non-directory | ||
| 1305 | (function dired-rename-file) | ||
| 1306 | basename-constructor operation arg)) | ||
| 1307 | |||
| 1308 | ;;;###autoload | ||
| 1309 | (defun dired-upcase (&optional arg) | ||
| 1310 | "Rename all marked (or next ARG) files to upper case." | ||
| 1311 | (interactive "P") | ||
| 1312 | (dired-rename-non-directory (function upcase) "Rename upcase" arg)) | ||
| 1313 | |||
| 1314 | ;;;###autoload | ||
| 1315 | (defun dired-downcase (&optional arg) | ||
| 1316 | "Rename all marked (or next ARG) files to lower case." | ||
| 1317 | (interactive "P") | ||
| 1318 | (dired-rename-non-directory (function downcase) "Rename downcase" arg)) | ||
| 1319 | |||
| 1320 | ;;;###end dired-re.el | ||
| 1321 | |||
| 1322 | ;;; 13K | ||
| 1323 | ;;;###begin dired-ins.el | ||
| 1324 | |||
| 1325 | ;;;###autoload | ||
| 1326 | (defun dired-maybe-insert-subdir (dirname &optional | ||
| 1327 | switches no-error-if-not-dir-p) | ||
| 1328 | "Insert this subdirectory into the same dired buffer. | ||
| 1329 | If it is already present, just move to it (type \\[dired-do-redisplay] to refresh), | ||
| 1330 | else inserts it at its natural place (as `ls -lR' would have done). | ||
| 1331 | With a prefix arg, you may edit the ls switches used for this listing. | ||
| 1332 | You can add `R' to the switches to expand the whole tree starting at | ||
| 1333 | this subdirectory. | ||
| 1334 | This function takes some pains to conform to `ls -lR' output." | ||
| 1335 | (interactive | ||
| 1336 | (list (dired-get-filename) | ||
| 1337 | (if current-prefix-arg | ||
| 1338 | (read-string "Switches for listing: " dired-actual-switches)))) | ||
| 1339 | (let ((opoint (point))) | ||
| 1340 | ;; We don't need a marker for opoint as the subdir is always | ||
| 1341 | ;; inserted *after* opoint. | ||
| 1342 | (setq dirname (file-name-as-directory dirname)) | ||
| 1343 | (or (and (not switches) | ||
| 1344 | (dired-goto-subdir dirname)) | ||
| 1345 | (dired-insert-subdir dirname switches no-error-if-not-dir-p)) | ||
| 1346 | ;; Push mark so that it's easy to find back. Do this after the | ||
| 1347 | ;; insert message so that the user sees the `Mark set' message. | ||
| 1348 | (push-mark opoint))) | ||
| 1349 | |||
| 1350 | (defun dired-insert-subdir (dirname &optional switches no-error-if-not-dir-p) | ||
| 1351 | "Insert this subdirectory into the same dired buffer. | ||
| 1352 | If it is already present, overwrites previous entry, | ||
| 1353 | else inserts it at its natural place (as `ls -lR' would have done). | ||
| 1354 | With a prefix arg, you may edit the `ls' switches used for this listing. | ||
| 1355 | You can add `R' to the switches to expand the whole tree starting at | ||
| 1356 | this subdirectory. | ||
| 1357 | This function takes some pains to conform to `ls -lR' output." | ||
| 1358 | ;; NO-ERROR-IF-NOT-DIR-P needed for special filesystems like | ||
| 1359 | ;; Prospero where dired-ls does the right thing, but | ||
| 1360 | ;; file-directory-p has not been redefined. | ||
| 1361 | (interactive | ||
| 1362 | (list (dired-get-filename) | ||
| 1363 | (if current-prefix-arg | ||
| 1364 | (read-string "Switches for listing: " dired-actual-switches)))) | ||
| 1365 | (setq dirname (file-name-as-directory (expand-file-name dirname))) | ||
| 1366 | (dired-insert-subdir-validate dirname switches) | ||
| 1367 | (or no-error-if-not-dir-p | ||
| 1368 | (file-directory-p dirname) | ||
| 1369 | (error "Attempt to insert a non-directory: %s" dirname)) | ||
| 1370 | (let ((elt (assoc dirname dired-subdir-alist)) | ||
| 1371 | switches-have-R mark-alist case-fold-search buffer-read-only) | ||
| 1372 | ;; case-fold-search is nil now, so we can test for capital `R': | ||
| 1373 | (if (setq switches-have-R (and switches (string-match "R" switches))) | ||
| 1374 | ;; avoid duplicated subdirs | ||
| 1375 | (setq mark-alist (dired-kill-tree dirname t))) | ||
| 1376 | (if elt | ||
| 1377 | ;; If subdir is already present, remove it and remember its marks | ||
| 1378 | (setq mark-alist (nconc (dired-insert-subdir-del elt) mark-alist)) | ||
| 1379 | (dired-insert-subdir-newpos dirname)) ; else compute new position | ||
| 1380 | (dired-insert-subdir-doupdate | ||
| 1381 | dirname elt (dired-insert-subdir-doinsert dirname switches)) | ||
| 1382 | (if switches-have-R (dired-build-subdir-alist)) | ||
| 1383 | (dired-initial-position dirname) | ||
| 1384 | (save-excursion (dired-mark-remembered mark-alist)))) | ||
| 1385 | |||
| 1386 | ;; This is a separate function for dired-vms. | ||
| 1387 | (defun dired-insert-subdir-validate (dirname &optional switches) | ||
| 1388 | ;; Check that it is valid to insert DIRNAME with SWITCHES. | ||
| 1389 | ;; Signal an error if invalid (e.g. user typed `i' on `..'). | ||
| 1390 | (or (dired-in-this-tree dirname default-directory) | ||
| 1391 | (error "%s: not in this directory tree" dirname)) | ||
| 1392 | (if switches | ||
| 1393 | (let (case-fold-search) | ||
| 1394 | (mapcar | ||
| 1395 | (function | ||
| 1396 | (lambda (x) | ||
| 1397 | (or (eq (null (string-match x switches)) | ||
| 1398 | (null (string-match x dired-actual-switches))) | ||
| 1399 | (error "Can't have dirs with and without -%s switches together" | ||
| 1400 | x)))) | ||
| 1401 | ;; all switches that make a difference to dired-get-filename: | ||
| 1402 | '("F" "b"))))) | ||
| 1403 | |||
| 1404 | (defun dired-alist-add (dir new-marker) | ||
| 1405 | ;; Add new DIR at NEW-MARKER. Sort alist. | ||
| 1406 | (dired-alist-add-1 dir new-marker) | ||
| 1407 | (dired-alist-sort)) | ||
| 1408 | |||
| 1409 | (defun dired-alist-sort () | ||
| 1410 | ;; Keep the alist sorted on buffer position. | ||
| 1411 | (setq dired-subdir-alist | ||
| 1412 | (sort dired-subdir-alist | ||
| 1413 | (function (lambda (elt1 elt2) | ||
| 1414 | (> (dired-get-subdir-min elt1) | ||
| 1415 | (dired-get-subdir-min elt2))))))) | ||
| 1416 | |||
| 1417 | (defun dired-kill-tree (dirname &optional remember-marks) | ||
| 1418 | ;;"Kill all proper subdirs of DIRNAME, excluding DIRNAME itself. | ||
| 1419 | ;; With optional arg REMEMBER-MARKS, return an alist of marked files." | ||
| 1420 | (interactive "DKill tree below directory: ") | ||
| 1421 | (let ((s-alist dired-subdir-alist) dir m-alist) | ||
| 1422 | (while s-alist | ||
| 1423 | (setq dir (car (car s-alist)) | ||
| 1424 | s-alist (cdr s-alist)) | ||
| 1425 | (if (and (not (string-equal dir dirname)) | ||
| 1426 | (dired-in-this-tree dir dirname) | ||
| 1427 | (dired-goto-subdir dir)) | ||
| 1428 | (setq m-alist (nconc (dired-kill-subdir remember-marks) m-alist)))) | ||
| 1429 | m-alist)) | ||
| 1430 | |||
| 1431 | (defun dired-insert-subdir-newpos (new-dir) | ||
| 1432 | ;; Find pos for new subdir, according to tree order. | ||
| 1433 | ;;(goto-char (point-max)) | ||
| 1434 | (let ((alist dired-subdir-alist) elt dir pos new-pos) | ||
| 1435 | (while alist | ||
| 1436 | (setq elt (car alist) | ||
| 1437 | alist (cdr alist) | ||
| 1438 | dir (car elt) | ||
| 1439 | pos (dired-get-subdir-min elt)) | ||
| 1440 | (if (dired-tree-lessp dir new-dir) | ||
| 1441 | ;; Insert NEW-DIR after DIR | ||
| 1442 | (setq new-pos (dired-get-subdir-max elt) | ||
| 1443 | alist nil))) | ||
| 1444 | (goto-char new-pos)) | ||
| 1445 | ;; want a separating newline between subdirs | ||
| 1446 | (or (eobp) | ||
| 1447 | (forward-line -1)) | ||
| 1448 | (insert "\n") | ||
| 1449 | (point)) | ||
| 1450 | |||
| 1451 | (defun dired-insert-subdir-del (element) | ||
| 1452 | ;; Erase an already present subdir (given by ELEMENT) from buffer. | ||
| 1453 | ;; Move to that buffer position. Return a mark-alist. | ||
| 1454 | (let ((begin-marker (dired-get-subdir-min element))) | ||
| 1455 | (goto-char begin-marker) | ||
| 1456 | ;; Are at beginning of subdir (and inside it!). Now determine its end: | ||
| 1457 | (goto-char (dired-subdir-max)) | ||
| 1458 | (or (eobp);; want a separating newline _between_ subdirs: | ||
| 1459 | (forward-char -1)) | ||
| 1460 | (prog1 | ||
| 1461 | (dired-remember-marks begin-marker (point)) | ||
| 1462 | (delete-region begin-marker (point))))) | ||
| 1463 | |||
| 1464 | (defun dired-insert-subdir-doinsert (dirname switches) | ||
| 1465 | ;; Insert ls output after point and put point on the correct | ||
| 1466 | ;; position for the subdir alist. | ||
| 1467 | ;; Return the boundary of the inserted text (as list of BEG and END). | ||
| 1468 | (let ((begin (point)) end) | ||
| 1469 | (message "Reading directory %s..." dirname) | ||
| 1470 | (let ((dired-actual-switches | ||
| 1471 | (or switches | ||
| 1472 | (dired-replace-in-string "R" "" dired-actual-switches)))) | ||
| 1473 | (if (equal dirname (car (car (reverse dired-subdir-alist)))) | ||
| 1474 | ;; top level directory may contain wildcards: | ||
| 1475 | (dired-readin-insert dired-directory) | ||
| 1476 | (dired-ls dirname dired-actual-switches nil t))) | ||
| 1477 | (message "Reading directory %s...done" dirname) | ||
| 1478 | (setq end (point-marker)) | ||
| 1479 | (indent-rigidly begin end 2) | ||
| 1480 | ;; call dired-insert-headerline afterwards, as under VMS dired-ls | ||
| 1481 | ;; does insert the headerline itself and the insert function just | ||
| 1482 | ;; moves point. | ||
| 1483 | ;; Need a marker for END as this inserts text. | ||
| 1484 | (goto-char begin) | ||
| 1485 | (dired-insert-headerline dirname) | ||
| 1486 | ;; point is now like in dired-build-subdir-alist | ||
| 1487 | (prog1 | ||
| 1488 | (list begin (marker-position end)) | ||
| 1489 | (set-marker end nil)))) | ||
| 1490 | |||
| 1491 | (defun dired-insert-subdir-doupdate (dirname elt beg-end) | ||
| 1492 | ;; Point is at the correct subdir alist position for ELT, | ||
| 1493 | ;; BEG-END is the subdir-region (as list of begin and end). | ||
| 1494 | (if elt ; subdir was already present | ||
| 1495 | ;; update its position (should actually be unchanged) | ||
| 1496 | (set-marker (dired-get-subdir-min elt) (point-marker)) | ||
| 1497 | (dired-alist-add dirname (point-marker))) | ||
| 1498 | ;; The hook may depend on the subdir-alist containing the just | ||
| 1499 | ;; inserted subdir, so run it after dired-alist-add: | ||
| 1500 | (if dired-after-readin-hook | ||
| 1501 | (save-excursion | ||
| 1502 | (let ((begin (nth 0 beg-end)) | ||
| 1503 | (end (nth 1 beg-end))) | ||
| 1504 | (goto-char begin) | ||
| 1505 | (save-restriction | ||
| 1506 | (narrow-to-region begin end) | ||
| 1507 | ;; hook may add or delete lines, but the subdir boundary | ||
| 1508 | ;; marker floats | ||
| 1509 | (run-hooks 'dired-after-readin-hook)))))) | ||
| 1510 | |||
| 1511 | (defun dired-tree-lessp (dir1 dir2) | ||
| 1512 | ;; Lexicographic order on pathname components, like `ls -lR': | ||
| 1513 | ;; DIR1 < DIR2 iff DIR1 comes *before* DIR2 in an `ls -lR' listing, | ||
| 1514 | ;; i.e., iff DIR1 is a (grand)parent dir of DIR2, | ||
| 1515 | ;; or DIR1 and DIR2 are in the same parentdir and their last | ||
| 1516 | ;; components are string-lessp. | ||
| 1517 | ;; Thus ("/usr/" "/usr/bin") and ("/usr/a/" "/usr/b/") are tree-lessp. | ||
| 1518 | ;; string-lessp could arguably be replaced by file-newer-than-file-p | ||
| 1519 | ;; if dired-actual-switches contained `t'. | ||
| 1520 | (setq dir1 (file-name-as-directory dir1) | ||
| 1521 | dir2 (file-name-as-directory dir2)) | ||
| 1522 | (let ((components-1 (dired-split "/" dir1)) | ||
| 1523 | (components-2 (dired-split "/" dir2))) | ||
| 1524 | (while (and components-1 | ||
| 1525 | components-2 | ||
| 1526 | (equal (car components-1) (car components-2))) | ||
| 1527 | (setq components-1 (cdr components-1) | ||
| 1528 | components-2 (cdr components-2))) | ||
| 1529 | (let ((c1 (car components-1)) | ||
| 1530 | (c2 (car components-2))) | ||
| 1531 | |||
| 1532 | (cond ((and c1 c2) | ||
| 1533 | (string-lessp c1 c2)) | ||
| 1534 | ((and (null c1) (null c2)) | ||
| 1535 | nil) ; they are equal, not lessp | ||
| 1536 | ((null c1) ; c2 is a subdir of c1: c1<c2 | ||
| 1537 | t) | ||
| 1538 | ((null c2) ; c1 is a subdir of c2: c1>c2 | ||
| 1539 | nil) | ||
| 1540 | (t (error "This can't happen")))))) | ||
| 1541 | |||
| 1542 | ;; There should be a builtin split function - inverse to mapconcat. | ||
| 1543 | (defun dired-split (pat str &optional limit) | ||
| 1544 | "Splitting on regexp PAT, turn string STR into a list of substrings. | ||
| 1545 | Optional third arg LIMIT (>= 1) is a limit to the length of the | ||
| 1546 | resulting list. | ||
| 1547 | Thus, if SEP is a regexp that only matches itself, | ||
| 1548 | |||
| 1549 | (mapconcat 'identity (dired-split SEP STRING) SEP) | ||
| 1550 | |||
| 1551 | is always equal to STRING." | ||
| 1552 | (let* ((start (string-match pat str)) | ||
| 1553 | (result (list (substring str 0 start))) | ||
| 1554 | (count 1) | ||
| 1555 | (end (if start (match-end 0)))) | ||
| 1556 | (if end ; else nothing left | ||
| 1557 | (while (and (or (not (integerp limit)) | ||
| 1558 | (< count limit)) | ||
| 1559 | (string-match pat str end)) | ||
| 1560 | (setq start (match-beginning 0) | ||
| 1561 | count (1+ count) | ||
| 1562 | result (cons (substring str end start) result) | ||
| 1563 | end (match-end 0) | ||
| 1564 | start end) | ||
| 1565 | )) | ||
| 1566 | (if (and (or (not (integerp limit)) | ||
| 1567 | (< count limit)) | ||
| 1568 | end) ; else nothing left | ||
| 1569 | (setq result | ||
| 1570 | (cons (substring str end) result))) | ||
| 1571 | (nreverse result))) | ||
| 1572 | |||
| 1573 | ;;; moving by subdirectories | ||
| 1574 | |||
| 1575 | (defun dired-subdir-index (dir) | ||
| 1576 | ;; Return an index into alist for use with nth | ||
| 1577 | ;; for the sake of subdir moving commands. | ||
| 1578 | (let (found (index 0) (alist dired-subdir-alist)) | ||
| 1579 | (while alist | ||
| 1580 | (if (string= dir (car (car alist))) | ||
| 1581 | (setq alist nil found t) | ||
| 1582 | (setq alist (cdr alist) index (1+ index)))) | ||
| 1583 | (if found index nil))) | ||
| 1584 | |||
| 1585 | ;;;###autoload | ||
| 1586 | (defun dired-next-subdir (arg &optional no-error-if-not-found no-skip) | ||
| 1587 | "Go to next subdirectory, regardless of level." | ||
| 1588 | ;; Use 0 arg to go to this directory's header line. | ||
| 1589 | ;; NO-SKIP prevents moving to end of header line, returning whatever | ||
| 1590 | ;; position was found in dired-subdir-alist. | ||
| 1591 | (interactive "p") | ||
| 1592 | (let ((this-dir (dired-current-directory)) | ||
| 1593 | pos index) | ||
| 1594 | ;; nth with negative arg does not return nil but the first element | ||
| 1595 | (setq index (- (dired-subdir-index this-dir) arg)) | ||
| 1596 | (setq pos (if (>= index 0) | ||
| 1597 | (dired-get-subdir-min (nth index dired-subdir-alist)))) | ||
| 1598 | (if pos | ||
| 1599 | (progn | ||
| 1600 | (goto-char pos) | ||
| 1601 | (or no-skip (skip-chars-forward "^\n\r")) | ||
| 1602 | (point)) | ||
| 1603 | (if no-error-if-not-found | ||
| 1604 | nil ; return nil if not found | ||
| 1605 | (error "%s directory" (if (> arg 0) "Last" "First")))))) | ||
| 1606 | |||
| 1607 | ;;;###autoload | ||
| 1608 | (defun dired-prev-subdir (arg &optional no-error-if-not-found no-skip) | ||
| 1609 | "Go to previous subdirectory, regardless of level. | ||
| 1610 | When called interactively and not on a subdir line, go to this subdir's line." | ||
| 1611 | ;;(interactive "p") | ||
| 1612 | (interactive | ||
| 1613 | (list (if current-prefix-arg | ||
| 1614 | (prefix-numeric-value current-prefix-arg) | ||
| 1615 | ;; if on subdir start already, don't stay there! | ||
| 1616 | (if (dired-get-subdir) 1 0)))) | ||
| 1617 | (dired-next-subdir (- arg) no-error-if-not-found no-skip)) | ||
| 1618 | |||
| 1619 | (defun dired-subdir-min () | ||
| 1620 | (save-excursion | ||
| 1621 | (if (not (dired-prev-subdir 0 t t)) | ||
| 1622 | (error "Not in a subdir!") | ||
| 1623 | (point)))) | ||
| 1624 | |||
| 1625 | ;;;###autoload | ||
| 1626 | (defun dired-goto-subdir (dir) | ||
| 1627 | "Go to end of header line of DIR in this dired buffer. | ||
| 1628 | Return value of point on success, otherwise return nil. | ||
| 1629 | The next char is either \\n, or \\r if DIR is hidden." | ||
| 1630 | (interactive | ||
| 1631 | (prog1 ; let push-mark display its message | ||
| 1632 | (list (expand-file-name | ||
| 1633 | (completing-read "Goto in situ directory: " ; prompt | ||
| 1634 | dired-subdir-alist ; table | ||
| 1635 | nil ; predicate | ||
| 1636 | t ; require-match | ||
| 1637 | (dired-current-directory)))) | ||
| 1638 | (push-mark))) | ||
| 1639 | (setq dir (file-name-as-directory dir)) | ||
| 1640 | (let ((elt (assoc dir dired-subdir-alist))) | ||
| 1641 | (and elt | ||
| 1642 | (goto-char (dired-get-subdir-min elt)) | ||
| 1643 | ;; dired-subdir-hidden-p and dired-add-entry depend on point being | ||
| 1644 | ;; at either \r or \n after this function succeeds. | ||
| 1645 | (progn (skip-chars-forward "^\r\n") | ||
| 1646 | (point))))) | ||
| 1647 | |||
| 1648 | ;;;###autoload | ||
| 1649 | (defun dired-mark-subdir-files () | ||
| 1650 | "Mark all files except `.' and `..'." | ||
| 1651 | (interactive "P") | ||
| 1652 | (let ((p-min (dired-subdir-min))) | ||
| 1653 | (dired-mark-files-in-region p-min (dired-subdir-max)))) | ||
| 1654 | |||
| 1655 | ;;;###autoload | ||
| 1656 | (defun dired-kill-subdir (&optional remember-marks) | ||
| 1657 | "Remove all lines of current subdirectory. | ||
| 1658 | Lower levels are unaffected." | ||
| 1659 | ;; With optional REMEMBER-MARKS, return a mark-alist. | ||
| 1660 | (interactive) | ||
| 1661 | (let ((beg (dired-subdir-min)) | ||
| 1662 | (end (dired-subdir-max)) | ||
| 1663 | buffer-read-only cur-dir) | ||
| 1664 | (setq cur-dir (dired-current-directory)) | ||
| 1665 | (if (equal cur-dir default-directory) | ||
| 1666 | (error "Attempt to kill top level directory")) | ||
| 1667 | (prog1 | ||
| 1668 | (if remember-marks (dired-remember-marks beg end)) | ||
| 1669 | (delete-region beg end) | ||
| 1670 | (if (eobp) ; don't leave final blank line | ||
| 1671 | (delete-char -1)) | ||
| 1672 | (dired-unsubdir cur-dir)))) | ||
| 1673 | |||
| 1674 | (defun dired-unsubdir (dir) | ||
| 1675 | ;; Remove DIR from the alist | ||
| 1676 | (setq dired-subdir-alist | ||
| 1677 | (delq (assoc dir dired-subdir-alist) dired-subdir-alist))) | ||
| 1678 | |||
| 1679 | ;;;###autoload | ||
| 1680 | (defun dired-tree-up (arg) | ||
| 1681 | "Go up ARG levels in the dired tree." | ||
| 1682 | (interactive "p") | ||
| 1683 | (let ((dir (dired-current-directory))) | ||
| 1684 | (while (>= arg 1) | ||
| 1685 | (setq arg (1- arg) | ||
| 1686 | dir (file-name-directory (directory-file-name dir)))) | ||
| 1687 | ;;(setq dir (expand-file-name dir)) | ||
| 1688 | (or (dired-goto-subdir dir) | ||
| 1689 | (error "Cannot go up to %s - not in this tree." dir)))) | ||
| 1690 | |||
| 1691 | ;;;###autoload | ||
| 1692 | (defun dired-tree-down () | ||
| 1693 | "Go down in the dired tree." | ||
| 1694 | (interactive) | ||
| 1695 | (let ((dir (dired-current-directory)) ; has slash | ||
| 1696 | pos case-fold-search) ; filenames are case sensitive | ||
| 1697 | (let ((rest (reverse dired-subdir-alist)) elt) | ||
| 1698 | (while rest | ||
| 1699 | (setq elt (car rest) | ||
| 1700 | rest (cdr rest)) | ||
| 1701 | (if (dired-in-this-tree (directory-file-name (car elt)) dir) | ||
| 1702 | (setq rest nil | ||
| 1703 | pos (dired-goto-subdir (car elt)))))) | ||
| 1704 | (if pos | ||
| 1705 | (goto-char pos) | ||
| 1706 | (error "At the bottom")))) | ||
| 1707 | |||
| 1708 | ;;; hiding | ||
| 1709 | |||
| 1710 | (defun dired-unhide-subdir () | ||
| 1711 | (let (buffer-read-only) | ||
| 1712 | (subst-char-in-region (dired-subdir-min) (dired-subdir-max) ?\r ?\n))) | ||
| 1713 | |||
| 1714 | (defun dired-hide-check () | ||
| 1715 | (or selective-display | ||
| 1716 | (error "selective-display must be t for subdir hiding to work!"))) | ||
| 1717 | |||
| 1718 | (defun dired-subdir-hidden-p (dir) | ||
| 1719 | (and selective-display | ||
| 1720 | (save-excursion | ||
| 1721 | (dired-goto-subdir dir) | ||
| 1722 | (looking-at "\r")))) | ||
| 1723 | |||
| 1724 | ;;;###autoload | ||
| 1725 | (defun dired-hide-subdir (arg) | ||
| 1726 | "Hide or unhide the current subdirectory and move to next directory. | ||
| 1727 | Optional prefix arg is a repeat factor. | ||
| 1728 | Use \\[dired-hide-all] to (un)hide all directories." | ||
| 1729 | (interactive "p") | ||
| 1730 | (dired-hide-check) | ||
| 1731 | (while (>= (setq arg (1- arg)) 0) | ||
| 1732 | (let* ((cur-dir (dired-current-directory)) | ||
| 1733 | (hidden-p (dired-subdir-hidden-p cur-dir)) | ||
| 1734 | (elt (assoc cur-dir dired-subdir-alist)) | ||
| 1735 | (end-pos (1- (dired-get-subdir-max elt))) | ||
| 1736 | buffer-read-only) | ||
| 1737 | ;; keep header line visible, hide rest | ||
| 1738 | (goto-char (dired-get-subdir-min elt)) | ||
| 1739 | (skip-chars-forward "^\n\r") | ||
| 1740 | (if hidden-p | ||
| 1741 | (subst-char-in-region (point) end-pos ?\r ?\n) | ||
| 1742 | (subst-char-in-region (point) end-pos ?\n ?\r))) | ||
| 1743 | (dired-next-subdir 1 t))) | ||
| 1744 | |||
| 1745 | ;;;###autoload | ||
| 1746 | (defun dired-hide-all (arg) | ||
| 1747 | "Hide all subdirectories, leaving only their header lines. | ||
| 1748 | If there is already something hidden, make everything visible again. | ||
| 1749 | Use \\[dired-hide-subdir] to (un)hide a particular subdirectory." | ||
| 1750 | (interactive "P") | ||
| 1751 | (dired-hide-check) | ||
| 1752 | (let (buffer-read-only) | ||
| 1753 | (if (save-excursion | ||
| 1754 | (goto-char (point-min)) | ||
| 1755 | (search-forward "\r" nil t)) | ||
| 1756 | ;; unhide - bombs on \r in filenames | ||
| 1757 | (subst-char-in-region (point-min) (point-max) ?\r ?\n) | ||
| 1758 | ;; hide | ||
| 1759 | (let ((pos (point-max)) ; pos of end of last directory | ||
| 1760 | (alist dired-subdir-alist)) | ||
| 1761 | (while alist ; while there are dirs before pos | ||
| 1762 | (subst-char-in-region (dired-get-subdir-min (car alist)) ; pos of prev dir | ||
| 1763 | (save-excursion | ||
| 1764 | (goto-char pos) ; current dir | ||
| 1765 | ;; we're somewhere on current dir's line | ||
| 1766 | (forward-line -1) | ||
| 1767 | (point)) | ||
| 1768 | ?\n ?\r) | ||
| 1769 | (setq pos (dired-get-subdir-min (car alist))) ; prev dir gets current dir | ||
| 1770 | (setq alist (cdr alist))))))) | ||
| 1771 | |||
| 1772 | ;;;###end dired-ins.el | ||