]> code.delx.au - gnu-emacs/blobdiff - lisp/dired-aux.el
* cl-generic.el (cl-defmethod): Make docstring dynamic
[gnu-emacs] / lisp / dired-aux.el
index a678fca3ea3c5dc5ae337a718fc4b0ee9d4e6eb0..4732d9ce85cb34b76d8d0d49916a0a34125aa1cd 100644 (file)
@@ -35,6 +35,7 @@
 
 ;;; Code:
 
+(require 'cl-lib)
 ;; We need macros in dired.el to compile properly,
 ;; and we call subroutines in it too.
 (require 'dired)
@@ -728,26 +729,52 @@ can be produced by `dired-get-marked-files', for example."
         (command (if sequentially
                      (substring command 0 (match-beginning 0))
                    command))
+         (parallel-in-background
+          (and in-background (not sequentially) (not (eq system-type 'ms-dos))))
+         (w32-shell (and (fboundp 'w32-shell-dos-semantics)
+                         (w32-shell-dos-semantics)))
+         ;; The way to run a command in background in Windows shells
+         ;; is to use the START command.  The /B switch means not to
+         ;; create a new window for the command.
+         (cmd-prefix (if w32-shell "start /b " ""))
+         ;; Windows shells don't support chaining with ";", they use
+         ;; "&" instead.
+         (cmd-sep (if (and (not w32-shell) (not parallel-in-background))
+                      ";"
+                    "&"))
         (stuff-it
          (if (or (string-match-p dired-star-subst-regexp command)
                  (string-match-p dired-quark-subst-regexp command))
              (lambda (x)
-               (let ((retval command))
+               (let ((retval (concat cmd-prefix command)))
                  (while (string-match
                          "\\(^\\|[ \t]\\)\\([*?]\\)\\([ \t]\\|$\\)" retval)
                    (setq retval (replace-match x t t retval 2)))
                  retval))
-           (lambda (x) (concat command dired-mark-separator x)))))
+           (lambda (x) (concat cmd-prefix command dired-mark-separator x)))))
     (concat
-     (if on-each
-        (mapconcat stuff-it (mapcar 'shell-quote-argument file-list)
-                   (if (and in-background (not sequentially)) "&" ";"))
-       (let ((files (mapconcat 'shell-quote-argument
-                              file-list dired-mark-separator)))
-        (if (> (length file-list) 1)
-            (setq files (concat dired-mark-prefix files dired-mark-postfix)))
-        (funcall stuff-it files)))
-     (if in-background "&" ""))))
+     (cond (on-each
+            (format "%s%s"
+                    (mapconcat stuff-it (mapcar 'shell-quote-argument file-list)
+                               cmd-sep)
+                    ;; POSIX shells running a list of commands in the background
+                    ;; (LIST = cmd_1 & [cmd_2 & ... cmd_i & ... cmd_N &])
+                    ;; return once cmd_N ends, i.e., the shell does not
+                    ;; wait for cmd_i to finish before executing cmd_i+1.
+                    ;; That means, running (shell-command LIST) may not show
+                    ;; the output of all the commands (Bug#23206).
+                    ;; Add 'wait' to force those POSIX shells to wait until
+                    ;; all commands finish.
+                    (or (and parallel-in-background (not w32-shell)
+                             "&wait")
+                        "")))
+           (t
+            (let ((files (mapconcat 'shell-quote-argument
+                                    file-list dired-mark-separator)))
+              (when (cdr file-list)
+                (setq files (concat dired-mark-prefix files dired-mark-postfix)))
+              (funcall stuff-it files))))
+     (or (and in-background "&") ""))))
 
 ;; This is an extra function so that it can be redefined by ange-ftp.
 ;;;###autoload
@@ -900,6 +927,7 @@ command with a prefix argument (the value does not matter)."
     ("\\.bz2\\'" "" "bunzip2")
     ("\\.xz\\'" "" "unxz")
     ("\\.zip\\'" "" "unzip -o -d %o %i")
+    ("\\.7z\\'" "" "7z x -aoa -o%o %i")
     ;; This item controls naming for compression.
     ("\\.tar\\'" ".tgz" nil)
     ;; This item controls the compression of directories
@@ -2485,8 +2513,8 @@ Lower levels are unaffected."
         (cur-dir (dired-current-directory))
         (cons (assoc-string cur-dir dired-switches-alist))
         buffer-read-only)
-    (if (equal cur-dir default-directory)
-       (error "Attempt to kill top level directory"))
+    (when (equal cur-dir (expand-file-name default-directory))
+      (error "Attempt to kill top level directory"))
     (prog1
        (if remember-marks (dired-remember-marks beg end))
       (delete-region beg end)
@@ -2713,6 +2741,54 @@ with the command \\[tags-loop-continue]."
   (tags-query-replace from to delimited
                      '(dired-get-marked-files nil nil 'dired-nondirectory-p)))
 
+(declare-function xref--show-xrefs "xref")
+(declare-function xref-query-replace-in-results "xref")
+
+;;;###autoload
+(defun dired-do-find-regexp (regexp)
+  "Find all matches for REGEXP in all marked files.
+For any marked directory, all of its files are searched recursively.
+However, files matching `grep-find-ignored-files' and subdirectories
+matching `grep-find-ignored-directories' are skipped in the marked
+directories.
+
+REGEXP should use constructs supported by your local `grep' command."
+  (interactive "sSearch marked files (regexp): ")
+  (require 'grep)
+  (defvar grep-find-ignored-files)
+  (defvar grep-find-ignored-directories)
+  (let* ((files (dired-get-marked-files))
+         (ignores (nconc (mapcar
+                          (lambda (s) (concat s "/"))
+                          grep-find-ignored-directories)
+                         grep-find-ignored-files))
+         (xrefs (mapcan
+                 (lambda (file)
+                   (xref-collect-matches regexp "*" file
+                                         (and (file-directory-p file)
+                                              ignores)))
+                 files)))
+    (unless xrefs
+      (user-error "No matches for: %s" regexp))
+    (xref--show-xrefs xrefs nil t)))
+
+;;;###autoload
+(defun dired-do-find-regexp-and-replace (from to)
+  "Replace matches of FROM with TO, in all marked files.
+For any marked directory, matches in all of its files are replaced,
+recursively.  However, files matching `grep-find-ignored-files'
+and subdirectories matching `grep-find-ignored-directories' are skipped
+in the marked directories.
+
+REGEXP should use constructs supported by your local `grep' command."
+  (interactive
+   (let ((common
+          (query-replace-read-args
+           "Query replace regexp in marked files" t t)))
+     (list (nth 0 common) (nth 1 common))))
+  (with-current-buffer (dired-do-find-regexp from)
+    (xref-query-replace-in-results from to)))
+
 (defun dired-nondirectory-p (file)
   (not (file-directory-p file)))
 \f