all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#65854: Multi-file replacement diff
@ 2023-09-10 17:18 Juri Linkov
  2023-09-10 17:58 ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2023-09-24  1:43 ` Dmitry Gutov
  0 siblings, 2 replies; 27+ messages in thread
From: Juri Linkov @ 2023-09-10 17:18 UTC (permalink / raw)
  To: 65854

[-- Attachment #1: Type: text/plain, Size: 415 bytes --]

Tags: patch

As discussed on emacs-devel, here is the patch that implements
a standalone command that reads a list of files and replacement strings,
then shows a diff to review before applying replacements.
Also provided the Dired integration to show the replacement diff
on marked files.  Later the same function could be used
to show replacement diffs from the xref buffer and maybe
from other packages as well.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: multi-file-replace-as-diff.patch --]
[-- Type: text/x-diff, Size: 6672 bytes --]

diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el
index 28513a2c61a..f27abc645bc 100644
--- a/lisp/dired-aux.el
+++ b/lisp/dired-aux.el
@@ -3655,6 +3655,20 @@ dired-do-query-replace-regexp
    delimited)
   (fileloop-continue))
 
+;;;###autoload
+(defun dired-do-replace-regexp-as-diff (from to &optional delimited)
+  "Do `replace-regexp' of FROM with TO as diff, on all marked files.
+Third arg DELIMITED (prefix arg) means replace only word-delimited matches."
+  (interactive
+   (let ((common
+	  (query-replace-read-args
+	   "Replace regexp as diff in marked files" t t)))
+     (list (nth 0 common) (nth 1 common) (nth 2 common))))
+  (dired-post-do-command)
+  (multi-file-replace-regexp-as-diff
+   (dired-get-marked-files nil nil #'dired-nondirectory-p)
+   from to delimited))
+
 (declare-function xref-query-replace-in-results "xref")
 (declare-function project--files-in-directory "project")
 
diff --git a/lisp/misearch.el b/lisp/misearch.el
index 9ac28c26c48..da70d708a9e 100644
--- a/lisp/misearch.el
+++ b/lisp/misearch.el
@@ -387,6 +387,120 @@ multi-isearch-files-regexp
     (goto-char (if isearch-forward (point-min) (point-max)))
     (isearch-forward-regexp nil t)))
 
+\f
+;;; Global multi-file and multi-buffer replacements as diff
+
+(defun multi-file-diff-no-select (old new &optional switches buf label-old label-new)
+  ;; Based on `diff-no-select' tailored to multi-file diffs.
+  "Compare the OLD and NEW file/buffer.
+If the optional SWITCHES is nil, the switches specified in the
+variable `diff-switches' are passed to the diff command,
+otherwise SWITCHES is used.  SWITCHES can be a string or a list
+of strings.  BUF should be non-nil.  LABEL-OLD and LABEL-NEW
+specify labels to use for file names."
+  (unless (bufferp new) (setq new (expand-file-name new)))
+  (unless (bufferp old) (setq old (expand-file-name old)))
+  (or switches (setq switches diff-switches)) ; If not specified, use default.
+  (setq switches (ensure-list switches))
+  (diff-check-labels)
+  (let* ((old-alt (diff-file-local-copy old))
+         (new-alt (diff-file-local-copy new))
+         (command
+          (mapconcat #'identity
+                     `(,diff-command
+                       ;; Use explicitly specified switches
+                       ,@switches
+                       ,@(mapcar #'shell-quote-argument
+                                 (nconc
+                                  (and (or old-alt new-alt)
+                                       (eq diff-use-labels t)
+                                       (list "--label"
+                                             (cond ((stringp label-old) label-old)
+                                                   ((stringp old) old)
+                                                   ((prin1-to-string old)))
+                                             "--label"
+                                             (cond ((stringp label-new) label-new)
+                                                   ((stringp new) new)
+                                                   ((prin1-to-string new)))))
+                                  (list (or old-alt old)
+                                        (or new-alt new)))))
+                     " ")))
+    (with-current-buffer buf
+      (insert command "\n")
+      (call-process shell-file-name nil buf nil
+                    shell-command-switch command)
+      (if old-alt (delete-file old-alt))
+      (if new-alt (delete-file new-alt)))))
+
+(defun multi-file-replace-as-diff (files-or-buffers from-string replacements regexp-flag delimited-flag)
+  (require 'diff)
+  (let ((inhibit-message t)
+        (diff-buffer (get-buffer-create "*replace-diff*")))
+    (with-current-buffer diff-buffer
+      (buffer-disable-undo (current-buffer))
+      (let ((inhibit-read-only t))
+        (erase-buffer))
+      (diff-mode))
+    (dolist (file-or-buffer files-or-buffers)
+      (let ((file-name (if (bufferp file-or-buffer) buffer-file-name file-or-buffer)))
+        (when file-name
+          (with-temp-buffer
+            (if (bufferp file-or-buffer)
+                (insert-buffer-substring file-or-buffer)
+              (insert-file-contents file-or-buffer))
+            (goto-char (point-min))
+            (perform-replace from-string replacements nil regexp-flag delimited-flag)
+            (multi-file-diff-no-select file-or-buffer (current-buffer) nil diff-buffer
+                                       (concat file-name "~") file-name)))))
+    (with-current-buffer diff-buffer
+      (setq-local buffer-read-only t)
+      (setq-local revert-buffer-function
+                  (lambda (_ignore-auto _noconfirm)
+                    (multi-file-replace-as-diff
+                     files-or-buffers from-string replacements regexp-flag delimited-flag)))
+      (diff-setup-whitespace)
+      (font-lock-ensure)
+      (buffer-enable-undo (current-buffer))
+      (goto-char (point-min)))
+    (pop-to-buffer diff-buffer)))
+
+;;;###autoload
+(defun multi-file-replace-regexp-as-diff (files regexp to-string &optional delimited)
+  "Show replacements in FILES matching REGEXP with TO-STRING as diff.
+With a prefix argument, ask for a wildcard, and replace in files
+whose file names match the specified wildcard."
+  (interactive
+   (let ((files (if current-prefix-arg
+                    (multi-isearch-read-matching-files)
+                  (multi-isearch-read-files)))
+         (common
+          (query-replace-read-args
+           (concat "Replace"
+                   (if current-prefix-arg " word" "")
+                   " regexp as diff in files")
+           t t)))
+     (list files (nth 0 common) (nth 1 common) (nth 2 common))))
+  (multi-file-replace-as-diff files regexp to-string t delimited))
+
+;;;###autoload
+(defun multi-buffer-replace-regexp-as-diff (buffers regexp to-string &optional delimited)
+  "Show replacements in file BUFFERS matching REGEXP with TO-STRING as diff.
+With a prefix argument, ask for a regexp, and replace in file buffers
+whose names match the specified regexp."
+  (interactive
+   (let ((buffers (if current-prefix-arg
+                      (multi-isearch-read-matching-buffers)
+                    (multi-isearch-read-buffers)))
+         (common
+          (query-replace-read-args
+           (concat "Replace"
+                   (if current-prefix-arg " word" "")
+                   " regexp as diff in buffers")
+           t t)))
+     (list buffers (nth 0 common) (nth 1 common) (nth 2 common))))
+  (multi-file-replace-as-diff buffers regexp to-string t delimited))
+
+\f
 (defvar unload-function-defs-list)
 
 (defun multi-isearch-unload-function ()

^ permalink raw reply related	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2023-09-30 17:42 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-09-10 17:18 bug#65854: Multi-file replacement diff Juri Linkov
2023-09-10 17:58 ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-09-11  6:33   ` Juri Linkov
2023-09-11  7:23     ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-09-11  7:38       ` Juri Linkov
2023-09-12  6:49         ` Juri Linkov
2023-09-11 12:35       ` Eli Zaretskii
2023-09-12  6:52         ` Juri Linkov
2023-09-15  6:40           ` Juri Linkov
2023-09-15  7:02             ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-09-15  7:38             ` Eli Zaretskii
2023-09-22  6:55               ` Juri Linkov
2023-09-22  7:25                 ` Eli Zaretskii
2023-09-22 16:02                   ` Juri Linkov
2023-09-22 16:06                     ` Eli Zaretskii
2023-09-23 17:36                       ` Juri Linkov
2023-09-23 18:56                         ` Eli Zaretskii
2023-09-24  7:37                           ` Juri Linkov
2023-09-24  8:12                             ` Eli Zaretskii
2023-09-25 18:18                               ` Juri Linkov
2023-09-24  1:43 ` Dmitry Gutov
2023-09-24  7:36   ` Juri Linkov
2023-09-24 11:09     ` Dmitry Gutov
2023-09-25 17:58       ` Juri Linkov
2023-09-26 21:39         ` Dmitry Gutov
2023-09-27 17:21           ` Juri Linkov
2023-09-30 17:42           ` Juri Linkov

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.