]> code.delx.au - gnu-emacs/blobdiff - lisp/dired-aux.el
Merged in changes from CVS trunk.
[gnu-emacs] / lisp / dired-aux.el
index ea6cdef8fc690f89c03d281e97efc350ce851629..b31d20782f38f25e6a0ec75ffefb73747bcce678 100644 (file)
@@ -1,6 +1,6 @@
 ;;; dired-aux.el --- less commonly used parts of dired  -*-byte-compile-dynamic: t;-*-
 
-;; Copyright (C) 1985, 1986, 1992, 1994, 1998, 2000, 2001
+;; Copyright (C) 1985, 1986, 1992, 1994, 1998, 2000, 2001, 2004
 ;;   Free Software Foundation, Inc.
 
 ;; Author: Sebastian Kremer <sk@thp.uni-koeln.de>.
@@ -64,7 +64,10 @@ With prefix arg, prompt for second argument SWITCHES,
                                   (if default
                                       (concat "(default " default ") ")
                                     ""))
-                          (dired-current-directory) default t)
+                          (if default
+                              (dired-current-directory)
+                            (dired-dwim-target-directory))
+                          default t)
           (if current-prefix-arg
               (read-string "Options for diff: "
                            (if (stringp diff-switches)
@@ -88,8 +91,117 @@ With prefix arg, prompt for argument SWITCHES which is options for `diff'."
       nil))
   (diff-backup (dired-get-filename) switches))
 
+(defun dired-compare-directories (dir2 predicate)
+  "Mark files with different file attributes in two dired buffers.
+Compare file attributes of files in the current directory
+with file attributes in directory DIR2 using PREDICATE on pairs of files
+with the same name.  Mark files for which PREDICATE returns non-nil.
+Mark files with different names if PREDICATE is nil (or interactively
+when the user enters empty input at the predicate prompt).
+
+PREDICATE is a Lisp expression that can refer to the following variables:
+
+    size1, size2   - file size in bytes
+    mtime1, mtime2 - last modification time in seconds, as a float
+    fa1, fa2       - list of file attributes
+                     returned by function `file-attributes'
+
+    where 1 refers to attribute of file in the current dired buffer
+    and 2 to attribute of file in second dired buffer.
+
+Examples of PREDICATE:
+
+    (> mtime1 mtime2) - mark newer files
+    (not (= size1 size2)) - mark files with different sizes
+    (not (string= (nth 8 fa1) (nth 8 fa2))) - mark files with different modes
+    (not (and (= (nth 2 fa1) (nth 2 fa2))   - mark files with different UID
+              (= (nth 3 fa1) (nth 3 fa2))))   and GID."
+  (interactive
+   (list (read-file-name (format "Compare %s with: "
+                                (dired-current-directory))
+                        (dired-dwim-target-directory))
+         (read-from-minibuffer "Mark if (lisp expr or RET): " nil nil t nil "nil")))
+  (let* ((dir1 (dired-current-directory))
+         (file-alist1 (dired-files-attributes dir1))
+         (file-alist2 (dired-files-attributes dir2))
+        (file-list1 (mapcar
+                     'cadr
+                      (dired-file-set-difference
+                       file-alist1 file-alist2
+                      predicate)))
+        (file-list2 (mapcar
+                     'cadr
+                      (dired-file-set-difference
+                       file-alist2 file-alist1
+                      predicate))))
+    (dired-fun-in-all-buffers
+     dir1 nil
+     (lambda ()
+       (dired-mark-if
+        (member (dired-get-filename nil t) file-list1) nil)))
+    (dired-fun-in-all-buffers
+     dir2 nil
+     (lambda ()
+       (dired-mark-if
+        (member (dired-get-filename nil t) file-list2) nil)))
+    (message "Marked in dir1: %s files, in dir2: %s files"
+             (length file-list1)
+             (length file-list2))))
+
+(defun dired-file-set-difference (list1 list2 predicate)
+  "Combine LIST1 and LIST2 using a set-difference operation.
+The result list contains all file items that appear in LIST1 but not LIST2.
+This is a non-destructive function; it makes a copy of the data if necessary
+to avoid corrupting the original LIST1 and LIST2.
+PREDICATE (see `dired-compare-directories') is an additional match
+condition.  Two file items are considered to match if they are equal
+*and* PREDICATE evaluates to t."
+  (if (or (null list1) (null list2))
+      list1
+    (let (res)
+      (dolist (file1 list1)
+       (unless (let ((list list2))
+                 (while (and list
+                             (not (let* ((file2 (car list))
+                                         (fa1 (caddr file1))
+                                         (fa2 (caddr file2))
+                                         (size1 (nth 7 fa1))
+                                         (size2 (nth 7 fa2))
+                                         (mtime1 (float-time (nth 5 fa1)))
+                                         (mtime2 (float-time (nth 5 fa2))))
+                                    (and
+                                     (equal (car file1) (car file2))
+                                     (not (eval predicate))))))
+                   (setq list (cdr list)))
+                 list)
+         (setq res (cons file1 res))))
+      (nreverse res))))
+
+(defun dired-files-attributes (dir)
+  "Return a list of all file names and attributes from DIR.
+List has a form of (file-name full-file-name (attribute-list))"
+  (mapcar
+   (lambda (file-name)
+     (let ((full-file-name (expand-file-name file-name dir)))
+       (list file-name
+             full-file-name
+             (file-attributes full-file-name))))
+   (directory-files dir)))
+\f
+
+(defun dired-touch-initial (files)
+  "Create initial input value for `touch' command."
+  (let (initial)
+    (while files
+      (let ((current (nth 5 (file-attributes (car files)))))
+        (if (and initial (not (equal initial current)))
+            (setq initial (current-time) files nil)
+          (setq initial current))
+        (setq files (cdr files))))
+    (format-time-string "%Y%m%d%H%M.%S" initial)))
+
 (defun dired-do-chxxx (attribute-name program op-symbol arg)
-  ;; Change file attributes (mode, group, owner) of marked files and
+  ;; Change file attributes (mode, group, owner, timestamp) of marked files and
   ;; refresh their file lines.
   ;; ATTRIBUTE-NAME is a string describing the attribute to the user.
   ;; PROGRAM is the program used to change the attribute.
@@ -99,14 +211,18 @@ With prefix arg, prompt for argument SWITCHES which is options for `diff'."
         (new-attribute
          (dired-mark-read-string
           (concat "Change " attribute-name " of %s to: ")
-          nil op-symbol arg files))
+          (if (eq op-symbol 'touch) (dired-touch-initial files))
+          op-symbol arg files))
         (operation (concat program " " new-attribute))
         failures)
     (setq failures
          (dired-bunch-files 10000
                             (function dired-check-process)
                             (append
-                             (list operation program new-attribute)
+                             (list operation program)
+                             (if (eq op-symbol 'touch)
+                                 '("-t") nil)
+                             (list new-attribute)
                              (if (string-match "gnu" system-configuration)
                                  '("--") nil))
                             files))
@@ -139,6 +255,12 @@ This calls chmod, thus symbolic modes like `g+w' are allowed."
       (error "chown not supported on this system"))
   (dired-do-chxxx "Owner" dired-chown-program 'chown arg))
 
+(defun dired-do-touch (&optional arg)
+  "Change the timestamp of the marked (or next ARG) files.
+This calls touch."
+  (interactive "P")
+  (dired-do-chxxx "Timestamp" dired-touch-program 'touch arg))
+
 ;; Process all the files in FILES in batches of a convenient size,
 ;; by means of (FUNCALL FUNCTION ARGS... SOME-FILES...).
 ;; Batches are chosen to need less than MAX chars for the file names,
@@ -466,8 +588,8 @@ the list of file names explicitly with the FILE-LIST argument."
       (set-buffer err-buffer)
       (erase-buffer)
       (setq default-directory dir      ; caller's default-directory
-           err (/= 0
-                (apply (function dired-call-process) program nil arguments)))
+           err (not (eq 0
+                (apply (function dired-call-process) program nil arguments))))
       (if err
          (progn
            (dired-log (concat program " " (prin1-to-string arguments) "\n"))
@@ -564,6 +686,8 @@ and use this command with a prefix argument (the value does not matter)."
     ;; For .z, try gunzip.  It might be an old gzip file,
     ;; or it might be from compact? pack? (which?) but gunzip handles both.
     ("\\.z\\'" "" "gunzip")
+    ("\\.dz\\'" "" "dictunzip")
+    ("\\.tbz\\'" ".tar" "bunzip2")
     ("\\.bz2\\'" "" "bunzip2")
     ;; This item controls naming for compression.
     ("\\.tar\\'" ".tgz" nil))
@@ -677,7 +801,7 @@ Otherwise, the rule is a compression rule, and compression is done with gzip.")
   '((?\y . y) (?\040 . y)              ; `y' or SPC means accept once
     (?n . n) (?\177 . n)               ; `n' or DEL skips once
     (?! . yes)                         ; `!' accepts rest
-    (?q. no) (?\e . no)                        ; `q' or ESC skips rest
+    (?q . no) (?\e . no)               ; `q' or ESC skips rest
     ;; None of these keys quit - use C-g for that.
     ))
 
@@ -859,6 +983,7 @@ a prefix arg lets you edit the `ls' switches used for the new listing."
                (dired-insert-directory directory
                                        (concat dired-actual-switches "d")
                                        (list filename)))
+              (goto-char opoint)
              ;; Put in desired marker char.
              (when marker-char
                (let ((dired-marker-char
@@ -1257,7 +1382,7 @@ Optional arg HOW-TO is used to set the value of the into-dir variable
                          ;; will return t because the filesystem is
                          ;; case-insensitive, and Emacs will try to move
                          ;; foo -> foo/foo, which fails.
-                         (if (and (memq system-type '(ms-dos windows-nt))
+                         (if (and (memq system-type '(ms-dos windows-nt cygwin))
                                   (eq op-symbol 'move)
                                   dired-one-file
                                   (string= (downcase
@@ -1416,13 +1541,13 @@ of `dired-dwim-target', which see."
 ;;; 5K
 ;;;###begin dired-re.el
 (defun dired-do-create-files-regexp
-  (file-creator operation arg regexp newname &optional whole-path marker-char)
+  (file-creator operation arg regexp newname &optional whole-name marker-char)
   ;; Create a new file for each marked file using regexps.
   ;; FILE-CREATOR and OPERATION as in dired-create-files.
   ;; ARG as in dired-get-marked-files.
   ;; Matches each marked file against REGEXP and constructs the new
   ;;   filename from NEWNAME (like in function replace-match).
-  ;; Optional arg WHOLE-PATH means match/replace the whole file name
+  ;; Optional arg WHOLE-NAME means match/replace the whole file name
   ;;   instead of only the non-directory part of the file.
   ;; Optional arg MARKER-CHAR as in dired-create-files.
   (let* ((fn-list (dired-get-marked-files nil arg))
@@ -1435,7 +1560,7 @@ Type SPC or `y' to %s one match, DEL or `n' to skip to next,
                                          (downcase operation)))
         (regexp-name-constructor
          ;; Function to construct new filename using REGEXP and NEWNAME:
-         (if whole-path                ; easy (but rare) case
+         (if whole-name                ; easy (but rare) case
              (function
               (lambda (from)
                 (let ((to (dired-string-replace-match regexp from newname))
@@ -1450,7 +1575,7 @@ Type SPC or `y' to %s one match, DEL or `n' to skip to next,
                            to)
                     (dired-log "%s: %s did not match regexp %s\n"
                                operation from regexp)))))
-           ;; not whole-path, replace non-directory part only
+           ;; not whole-name, replace non-directory part only
            (function
             (lambda (from)
               (let* ((new (dired-string-replace-match
@@ -1473,21 +1598,21 @@ Type SPC or `y' to %s one match, DEL or `n' to skip to next,
 
 (defun dired-mark-read-regexp (operation)
   ;; Prompt user about performing OPERATION.
-  ;; Read and return list of: regexp newname arg whole-path.
-  (let* ((whole-path
+  ;; Read and return list of: regexp newname arg whole-name.
+  (let* ((whole-name
          (equal 0 (prefix-numeric-value current-prefix-arg)))
         (arg
-         (if whole-path nil current-prefix-arg))
+         (if whole-name nil current-prefix-arg))
         (regexp
          (dired-read-regexp
-          (concat (if whole-path "Path " "") operation " from (regexp): ")))
+          (concat (if whole-name "Abs. " "") operation " from (regexp): ")))
         (newname
          (read-string
-          (concat (if whole-path "Path " "") operation " " regexp " to: "))))
-    (list regexp newname arg whole-path)))
+          (concat (if whole-name "Abs. " "") operation " " regexp " to: "))))
+    (list regexp newname arg whole-name)))
 
 ;;;###autoload
-(defun dired-do-rename-regexp (regexp newname &optional arg whole-path)
+(defun dired-do-rename-regexp (regexp newname &optional arg whole-name)
   "Rename selected files whose names match REGEXP to NEWNAME.
 
 With non-zero prefix argument ARG, the command operates on the next ARG
@@ -1504,10 +1629,10 @@ Normally, only the non-directory part of the file name is used and changed."
   (interactive (dired-mark-read-regexp "Rename"))
   (dired-do-create-files-regexp
    (function dired-rename-file)
-   "Rename" arg regexp newname whole-path dired-keep-marker-rename))
+   "Rename" arg regexp newname whole-name dired-keep-marker-rename))
 
 ;;;###autoload
-(defun dired-do-copy-regexp (regexp newname &optional arg whole-path)
+(defun dired-do-copy-regexp (regexp newname &optional arg whole-name)
   "Copy selected files whose names match REGEXP to NEWNAME.
 See function `dired-do-rename-regexp' for more info."
   (interactive (dired-mark-read-regexp "Copy"))
@@ -1515,25 +1640,25 @@ See function `dired-do-rename-regexp' for more info."
     (dired-do-create-files-regexp
      (function dired-copy-file)
      (if dired-copy-preserve-time "Copy [-p]" "Copy")
-     arg regexp newname whole-path dired-keep-marker-copy)))
+     arg regexp newname whole-name dired-keep-marker-copy)))
 
 ;;;###autoload
-(defun dired-do-hardlink-regexp (regexp newname &optional arg whole-path)
+(defun dired-do-hardlink-regexp (regexp newname &optional arg whole-name)
   "Hardlink selected files whose names match REGEXP to NEWNAME.
 See function `dired-do-rename-regexp' for more info."
   (interactive (dired-mark-read-regexp "HardLink"))
   (dired-do-create-files-regexp
    (function add-name-to-file)
-   "HardLink" arg regexp newname whole-path dired-keep-marker-hardlink))
+   "HardLink" arg regexp newname whole-name dired-keep-marker-hardlink))
 
 ;;;###autoload
-(defun dired-do-symlink-regexp (regexp newname &optional arg whole-path)
+(defun dired-do-symlink-regexp (regexp newname &optional arg whole-name)
   "Symlink selected files whose names match REGEXP to NEWNAME.
 See function `dired-do-rename-regexp' for more info."
   (interactive (dired-mark-read-regexp "SymLink"))
   (dired-do-create-files-regexp
    (function make-symbolic-link)
-   "SymLink" arg regexp newname whole-path dired-keep-marker-symlink))
+   "SymLink" arg regexp newname whole-name dired-keep-marker-symlink))
 
 (defun dired-create-files-non-directory
   (file-creator basename-constructor operation arg)
@@ -1728,20 +1853,19 @@ With optional arg REMEMBER-MARKS, return an alist of marked files."
 (defun dired-insert-subdir-doinsert (dirname switches)
   ;; Insert ls output after point.
   ;; Return the boundary of the inserted text (as list of BEG and END).
-  (let ((begin (point)))
-    (message "Reading directory %s..." dirname)
-    (let ((dired-actual-switches
-          (or switches
-              (dired-replace-in-string "R" "" dired-actual-switches))))
-      (if (equal dirname (car (car (last dired-subdir-alist))))
-         ;; If doing the top level directory of the buffer,
-         ;; redo it as specified in dired-directory.
-         (dired-readin-insert)
-       (let ((pt (point)))
-         (dired-insert-directory dirname dired-actual-switches nil nil t)
-         (goto-char pt))))
-    (message "Reading directory %s...done" dirname)
-    (list begin (point))))
+  (save-excursion
+    (let ((begin (point)))
+      (message "Reading directory %s..." dirname)
+      (let ((dired-actual-switches
+            (or switches
+                (dired-replace-in-string "R" "" dired-actual-switches))))
+       (if (equal dirname (car (car (last dired-subdir-alist))))
+           ;; If doing the top level directory of the buffer,
+           ;; redo it as specified in dired-directory.
+           (dired-readin-insert)
+         (dired-insert-directory dirname dired-actual-switches nil nil t)))
+      (message "Reading directory %s...done" dirname)
+      (list begin (point)))))
 
 (defun dired-insert-subdir-doupdate (dirname elt beg-end)
   ;; Point is at the correct subdir alist position for ELT,
@@ -2015,6 +2139,11 @@ If you exit (\\[keyboard-quit], RET or q), you can resume the query replace
 with the command \\[tags-loop-continue]."
   (interactive
    "sQuery replace in marked files (regexp): \nsQuery replace %s by: \nP")
+  (dolist (file (dired-get-marked-files nil nil 'dired-nondirectory-p))
+    (let ((buffer (get-file-buffer file)))
+      (if (and buffer (with-current-buffer buffer
+                       buffer-read-only))
+         (error "File `%s' is visited read-only" file))))
   (tags-query-replace from to delimited
                      '(dired-get-marked-files nil nil 'dired-nondirectory-p)))
 
@@ -2033,8 +2162,9 @@ true then the type of the file linked to by FILE is printed instead."
       (call-process "file" nil t t "--" file))
     (when (bolp)
       (backward-delete-char 1))
-    (message (buffer-string))))
+    (message "%s" (buffer-string))))
 
 (provide 'dired-aux)
 
+;;; arch-tag: 4b508de9-a153-423d-8d3f-a1bbd86f4f60
 ;;; dired-aux.el ends here