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)