Subject: bug#76603: 30.1; multi-file-replace-regexp-as-diff won't end prompting with icomplete-show-matches-on-no-input

From: Rahul Martim Juliato <[email-masked]>
Date: Wed, 12 Mar 2025 21:21:46 -0300

Juri Linkov <[email-masked]> writes:

>> If I may add another inquire.
>
> Sorry, I missed this inquiry.
>
>> The command dired-do-find-regexp-and-replace, accepts marking files and
>> directories before executing it. I initially thought I could  do the
>> same with dired-do-replace-regexp-as-diff but it only works marking
>> files not directories.

Thanks for this info.

>
> dired-do-find-regexp-and-replace uses dired-do-find-regexp
> to get marked files.  So I guess dired-do-replace-regexp-as-diff
> could use dired-do-find-regexp as well?
>
>> So my first question is: are there any plans (or there is already some way)
>> to make dired-do-replace-regexp-as-diff also find all files in a marked
>> directory?
>
> There are no plans.  If you could try to use dired-do-find-regexp
> in dired-do-replace-regexp-as-diff this would be nice.
>

Maybe I could send a patch?

>> Secondly: Are there any plans to make multi-file-replace-regexp-as-diff
>> to work with glob patterns?
>
> Currently multi-file-replace-regexp-as-diff uses multi-isearch-read-files.
>
> I vaguely remember there exists such a function that reads a glob pattern,
> but can't find it right away.

Neither did I :(

--

Thanks for taking a look into it Juri!

In the meanwhile I created two `wrapper` functions to expand
`multi-file-replace-regexp-as-diff' and do why I meant to.

Do you think two more functions with extended
functionality are worth a patch?  I do not intend to replace the
original ones, but provide:

1.) `multi-file-replace-regexp-as-diff-with-glob':
    Use it with glob pattern matching in files, including inside
    subfolders.

2.) `dired-do-replace-regexp-as-diff-with-directories':
    Use it with marked files and or directories in dired

The code I'm currently using/testing:

```emacs-lisp
  ;; `M-x multi-file-replace-regexp-as-diff-with-glob RET'
  ;;
  ;; A wrapper for `multi-file-replace-regexp-as-diff' that extends its functionality
  ;; to support glob patterns for file matching. It recursively searches all files
  ;; in the specified directory (including subdirectories) that match the given glob
  ;; pattern (e.g., `*.js`), and displays the replacements as diffs in the
  ;; `*replace-diff*` buffer. This allows for easy review and application of changes
  ;; across multiple files.
  (defun glob-to-regexp (glob)
    "Convert a GLOB pattern (e.g., '*.el') to a regexp that `directory-files-recursively` can use."
    (concat "^" (replace-regexp-in-string
                 (rx (any "*?."))
                 (lambda (match)
                   (pcase match
                     ("*" ".*")
                     ("?" ".")
                     ("." "\\\\.") ; Properly escape the dot
                     (_ match)))
                 glob)
            "$"))
  (defun multi-file-replace-regexp-as-diff-with-glob (dir regexp to-string &optional delimited glob-pattern)
    "Wrapper for `multi-file-replace-regexp-as-diff` that accepts a directory and a glob pattern.
DIR is the directory to search recursively.
REGEXP is the regular expression to replace.
TO-STRING is the replacement string.
DELIMITED is an optional argument passed to `multi-file-replace-regexp-as-diff`.
GLOB-PATTERN is the glob pattern to match files (e.g., \"*.el\")."
    (interactive
     (let ((dir (file-truename (read-directory-name "Directory: ")))
           (common (query-replace-read-args
                    (concat "Replace"
                            (if current-prefix-arg " word" "")
                            " regexp as diff in files")
                    t t))
           (glob-pattern (read-string "Glob pattern (e.g., *.el): " "*")))
       (list dir (nth 0 common) (nth 1 common) (nth 2 common) glob-pattern)))

    (let* ((glob-regexp (glob-to-regexp glob-pattern))
           (files (directory-files-recursively dir glob-regexp)))

      (if files
          (multi-file-replace-regexp-as-diff files regexp to-string delimited)
        (message "No files found for glob-pattern: %s" glob-pattern))))

  ;; `M-x dired RET' mark files and/or directories then
  ;; `M-x dired-do-replace-regexp-as-diff-with-directories RET'
  ;;
  ;; A version of `dired-do-replace-regexp-as-diff' that adds support for selected
  ;; directories in Dired. When directories are marked, it recursively includes all
  ;; files within them (and their subdirectories) in the replacement operation.
  ;; The replacements are displayed as diffs in the `*replace-diff*` buffer, allowing
  ;; for review and application of changes across multiple files and directories.
  (defun expand-directories (items)
    "Expand ITEMS to include all files within directories (recursively).
Directories themselves are excluded from the final list."
    (cl-loop for item in items
             if (file-directory-p item)
             append (let ((files (directory-files-recursively item ".*" t)))
                      (cl-remove-if #'file-directory-p files))
             else if (file-regular-p item) ; Ensure only regular files are included
             collect item))
  (defun dired-do-replace-regexp-as-diff-with-directories (from to &optional delimited)
    "Do `replace-regexp' of FROM with TO as diff, on all marked files and directories.
If a marked item is a directory, all files within it (recursively) are included.
Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
The replacements are displayed in the buffer *replace-diff* that
you can later apply as a patch after reviewing the changes."
    (interactive
     (let ((common
            (query-replace-read-args
             "Replace regexp as diff in marked files and directories" t t t)))
       (list (nth 0 common) (nth 1 common) (nth 2 common))))
    (dired-post-do-command)
    (let* ((marked-items (dired-get-marked-files)) ; Include directories in the list
           (files (expand-directories marked-items)))
      (if files
          (progn
            (multi-file-replace-regexp-as-diff files from to delimited))
        (message "No files found in marked items."))))
```

--

Rahul Martim Juliato