Subject: bug#70820: closed (Re: bug#70820: [PATCH] Editable grep buffers)

From: [email-masked] (GNU bug Tracking System)
Date: Sat, 14 Sep 2024 09:45:02 +0000

Your bug report

#70820: [PATCH] Editable grep buffers

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
70820: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=70820
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#70820: [PATCH] Editable grep buffers
From: Eli Zaretskii <[email-masked]>
To: Visuwesh <[email-masked]>
Cc: [email-masked], [email-masked]
Date: Sat, 14 Sep 2024 12:43:52 +0300
List-Id: <debbugs-submit.debbugs.gnu.org>

> From: Visuwesh <[email-masked]>
> Cc: Eli Zaretskii <[email-masked]>,  [email-masked]
> Date: Mon, 09 Sep 2024 20:09:22 +0530
> 
> [புதன் ஆகஸ்ட் 14, 2024] Visuwesh wrote:
> 
> > [...]
> > If everyone is okay with the current patch, I can get to writing the
> > NEWS entry and updating the manual.  Thanks.
> 
> Sorry for the long delay.  I've now attached a patch with such
> documentation changes.

Thanks, installed on the master branch, and closing the bug.

Subject: [PATCH] Editable grep buffers
From: Visuwesh <[email-masked]>
To: [email-masked]
Date: Tue, 07 May 2024 21:55:09 +0530
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

Attached patch is a proof of concept for an occur-edit-mode alike for
*grep* buffers.  It uses the new track-changes library to track the
edits made in the *grep* buffer to then finally save these edits to the
corresponding files.  I've tested this with:

    1. pure deletions: If you have a match that says "(cur-beg start"
       and you change it to "(cur- start".  This is special because
       track-changes-fetch passes equal BEG and END to
       grep-edit--track-changes-finalise.
    2. edits: something like "(cur-beg start" -> "(cur-beg start balh"
       is handled like you would expect.
    3. edits with newline: if the edit includes a newline, then that is
       reproduced.

and what is not handled currently: deleting a match line to imply
deletion of that line from the matched file.  I don't know how to handle
this currently, I need to learn the library more.

There's also definitely a million more cases that I didn't anticipate
when I wrote the logic for grep-edit--track-changes-finalise.

I'm sending the patch now to see if there's enough interest before I go
about fixing the edge cases (there's bug#52815).  If there is enough
interest, I will take a shot at implementing something like this for
xref result buffers too.

In GNU Emacs 30.0.50 (build 8, x86_64-pc-linux-gnu, X toolkit, cairo
 version 1.18.0, Xaw scroll bars) of 2024-05-07 built on astatine
Repository revision: 4b31074f5f49898ba0499a2b617cad425750eafa
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12101011
System Description: Debian GNU/Linux trixie/sid

Configured using:
 'configure --with-sound=alsa --with-x-toolkit=lucid --with-json
 --without-xaw3d --without-gconf --without-libsystemd --with-cairo
 --with-xft'
diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el
index 657349cbdff..3fa6fce0e8d 100644
--- a/lisp/progmodes/grep.el
+++ b/lisp/progmodes/grep.el
@@ -31,6 +31,7 @@
 
 (eval-when-compile (require 'cl-lib))
 (require 'compile)
+(require 'track-changes)
 
 (defgroup grep nil
   "Run `grep' and display the results."
@@ -295,6 +296,8 @@ grep-mode-map
     (define-key map "}" #'compilation-next-file)
     (define-key map "\t" #'compilation-next-error)
     (define-key map [backtab] #'compilation-previous-error)
+
+    (define-key map "e" #'grep-edit-minor-mode)
     map)
   "Keymap for grep buffers.
 `compilation-minor-mode-map' is a cdr of this.")
@@ -1029,6 +1032,71 @@ grep
 		       command-args)
 		     #'grep-mode))
 
+(defvar-local grep-edit--tracker nil
+  "ID of the `track-changes' tracker.")
+
+(defun grep-edit--track-changes-signal (_id &optional _distance)
+  ;; Empty because we want to simply accumulate the changes at end
+  ;; when the user calls the finish function.
+  nil)
+
+(defun grep-edit--track-changes-finalise (beg end _before)
+  (let ((grep-buf (current-buffer))
+        (cur-res beg))                  ; Point at current grep result.
+    (save-excursion
+      (while (<= cur-res end)
+        (goto-char cur-res)
+        (let* ((change-beg (next-single-char-property-change (pos-bol) 'compilation-message))
+               ;; `1-' is required to ignore the newline.
+               (change-end (1- (next-single-char-property-change (pos-eol) 'compilation-message)))
+               (loc (compilation--message->loc
+                     (get-text-property (pos-bol) 'compilation-message)))
+               (line (compilation--loc->line loc))
+               (file (caar (compilation--loc->file-struct loc))))
+          (with-current-buffer (find-file-noselect file)
+            (save-excursion
+              (goto-char (point-min))
+              (forward-line (1- line))
+              (delete-region (pos-bol) (pos-eol))
+              (insert-buffer-substring-no-properties grep-buf change-beg change-end))))
+        (setq cur-res  (next-single-property-change cur-res 'compilation-message))))))
+
+(define-minor-mode grep-edit-minor-mode
+  "Minor mode for editing *grep* buffers.
+In this mode, changes to the *grep* buffer are applied to the
+originating files upon saving using \\[grep-save-changes]."
+  :lighter " Grep-Edit"
+  (if (null grep-edit-minor-mode)
+      (progn
+        (setq buffer-read-only t)
+        (buffer-disable-undo)
+        (use-local-map grep-mode-map))
+    (unless grep-edit--tracker
+      (use-local-map grep-edit-minor-mode-map)
+      (setq buffer-read-only nil)
+      (buffer-enable-undo)
+      (setq grep-edit--tracker
+            (track-changes-register #'grep-edit--track-changes-signal
+                                    :disjoint t)))
+    (message (substitute-command-keys
+              "Editing: \\[grep-edit-save-changes] to return to Grep mode."))))
+
+(defun grep-edit-save-changes ()
+  "Save the changes made to the *grep* buffer."
+  (interactive)
+  (when (and grep-edit-minor-mode grep-edit--tracker)
+    (track-changes-fetch grep-edit--tracker #'grep-edit--track-changes-finalise)
+    (message "Applied edits, switching to Grep mode.")
+    (track-changes-unregister grep-edit--tracker)
+    (setq grep-edit--tracker nil)
+    (grep-edit-minor-mode -1)))
+
+(defvar grep-edit-minor-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map text-mode-map)
+    (define-key map (kbd "C-c C-c") #'grep-edit-save-changes)
+    map)
+  "Keymap for `grep-edit-minor-mode'.")
 
 ;;;###autoload
 (defun grep-find (command-args)