From 52e4d82dd3f28065e5e3ea390c4629783bcf139d Mon Sep 17 00:00:00 2001 From: Visuwesh Date: Mon, 9 Sep 2024 20:08:04 +0530 Subject: [PATCH] Make the *grep* buffer editable * lisp/progmodes/compile.el (compilation--update-markers): Factor out function... (compilation-next-error-function): from here. Adjust to use above. * lisp/progmodes/grep.el (grep-edit--prepare-buffer) (grep-edit-mode-map, grep-edit-mode-hook, grep-edit-mode) (grep-change-to-grep-edit-mode, grep-edit-save-changes): Add new grep-edit-mode to make the grep results editable like in 'occur-edit-mode' by using the 'occur' framework. (grep-mode-map): Bind 'e' to the new command 'grep-change-to-grep-edit-mode'. * doc/emacs/building.texi (Grep Searching): Update Info manual to include the above command. * etc/NEWS: Announce the change. (bug#70820) --- doc/emacs/building.texi | 10 +++++ etc/NEWS | 9 ++++ lisp/progmodes/compile.el | 95 +++++++++++++++++++++------------------ lisp/progmodes/grep.el | 86 +++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 44 deletions(-) diff --git a/doc/emacs/building.texi b/doc/emacs/building.texi index bb03d8cf325..4b2f1ed0649 100644 --- a/doc/emacs/building.texi +++ b/doc/emacs/building.texi @@ -528,6 +528,16 @@ Grep Searching shell commands, customize the option @code{grep-find-abbreviate} to a @code{nil} value. +@findex grep-change-to-grep-edit-mode +@cindex Grep Edit mode +@cindex mode, Grep Edit + Typing @kbd{e} in the @file{*grep*} buffer makes the buffer writiable +and enters the Grep Edit mode. Similar to Occur Edit mode (@pxref{Other +Repeating Search}), you can edit the matching lines reported by +@code{grep} and have those changes reflected in the buffer visiting the +originating file. Type @kbd{C-c C-c} to leave the Grep Edit mode and +return to the Grep mode. + @node Flymake @section Finding Syntax Errors On The Fly @cindex checking syntax diff --git a/etc/NEWS b/etc/NEWS index c6f8b0062e4..24b43108ecb 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -290,6 +290,15 @@ the built-in Web server. Interactively, when invoked with a prefix argument, 'php-ts-mode-run-php-webserver' prompts for the config file as well as for other connection parameters. +** Grep + ++++ +*** Grep results can be edited to reflect changes in the originating file. +Like Occur Edit mode, typing 'e' in the '*grep*' buffer will now make +the 'grep' results editable. The edits will be reflected in the buffer +visiting the originating file. Typing 'C-c C-c' will leave the Grep +Edit mode. + * New Modes and Packages in Emacs 31.1 diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el index d2e74aa44a6..a78ac1b6462 100644 --- a/lisp/progmodes/compile.el +++ b/lisp/progmodes/compile.el @@ -2855,6 +2855,53 @@ compilation-find-buffer (current-buffer) (next-error-find-buffer avoid-current 'compilation-buffer-internal-p))) +(defun compilation--update-markers (loc marker screen-columns first-column) + "Update markers in LOC, and set MARKER to location pointed by LOC. +SCREEN-COLUMNS and FIRST-COLUMN are the value of +`compilation-error-screen-columns' and `compilation-first-column' to use +if they are not set buffer-locally in the target buffer." + (with-current-buffer + (if (bufferp (caar (compilation--loc->file-struct loc))) + (caar (compilation--loc->file-struct loc)) + (apply #'compilation-find-file + marker + (caar (compilation--loc->file-struct loc)) + (cadr (car (compilation--loc->file-struct loc))) + (compilation--file-struct->formats + (compilation--loc->file-struct loc)))) + (let ((screen-columns + ;; Obey the compilation-error-screen-columns of the target + ;; buffer if its major mode set it buffer-locally. + (if (local-variable-p 'compilation-error-screen-columns) + compilation-error-screen-columns screen-columns)) + (compilation-first-column + (if (local-variable-p 'compilation-first-column) + compilation-first-column first-column)) + (last 1)) + (save-restriction + (widen) + (goto-char (point-min)) + ;; Treat file's found lines in forward order, 1 by 1. + (dolist (line (reverse (cddr (compilation--loc->file-struct loc)))) + (when (car line) ; else this is a filename without a line# + (compilation-beginning-of-line (- (car line) last -1)) + (setq last (car line))) + ;; Treat line's found columns and store/update a marker for each. + (dolist (col (cdr line)) + (if (compilation--loc->col col) + (if (eq (compilation--loc->col col) -1) + ;; Special case for range end. + (end-of-line) + (compilation-move-to-column (compilation--loc->col col) + screen-columns)) + (beginning-of-line) + (skip-chars-forward " \t")) + (if (compilation--loc->marker col) + (set-marker (compilation--loc->marker col) (point)) + (setf (compilation--loc->marker col) (point-marker))) + ;; (setf (compilation--loc->timestamp col) timestamp) + )))))) + ;;;###autoload (defun compilation-next-error-function (n &optional reset) "Advance to the next error message and visit the file where the error was. @@ -2864,7 +2911,6 @@ compilation-next-error-function (setq compilation-current-error nil)) (let* ((screen-columns compilation-error-screen-columns) (first-column compilation-first-column) - (last 1) (msg (compilation-next-error (or n 1) nil (or compilation-current-error compilation-messages-start @@ -2876,9 +2922,9 @@ compilation-next-error-function (user-error "No next error")) (setq compilation-current-error (point-marker) overlay-arrow-position - (if (bolp) - compilation-current-error - (copy-marker (line-beginning-position)))) + (if (bolp) + compilation-current-error + (copy-marker (line-beginning-position)))) ;; If loc contains no marker, no error in that file has been visited. ;; If the marker is invalid the buffer has been killed. ;; So, recalculate all markers for that file. @@ -2895,46 +2941,7 @@ compilation-next-error-function ;; (equal (compilation--loc->timestamp loc) ;; (setq timestamp compilation-buffer-modtime))) ) - (with-current-buffer - (if (bufferp (caar (compilation--loc->file-struct loc))) - (caar (compilation--loc->file-struct loc)) - (apply #'compilation-find-file - marker - (caar (compilation--loc->file-struct loc)) - (cadr (car (compilation--loc->file-struct loc))) - (compilation--file-struct->formats - (compilation--loc->file-struct loc)))) - (let ((screen-columns - ;; Obey the compilation-error-screen-columns of the target - ;; buffer if its major mode set it buffer-locally. - (if (local-variable-p 'compilation-error-screen-columns) - compilation-error-screen-columns screen-columns)) - (compilation-first-column - (if (local-variable-p 'compilation-first-column) - compilation-first-column first-column))) - (save-restriction - (widen) - (goto-char (point-min)) - ;; Treat file's found lines in forward order, 1 by 1. - (dolist (line (reverse (cddr (compilation--loc->file-struct loc)))) - (when (car line) ; else this is a filename without a line# - (compilation-beginning-of-line (- (car line) last -1)) - (setq last (car line))) - ;; Treat line's found columns and store/update a marker for each. - (dolist (col (cdr line)) - (if (compilation--loc->col col) - (if (eq (compilation--loc->col col) -1) - ;; Special case for range end. - (end-of-line) - (compilation-move-to-column (compilation--loc->col col) - screen-columns)) - (beginning-of-line) - (skip-chars-forward " \t")) - (if (compilation--loc->marker col) - (set-marker (compilation--loc->marker col) (point)) - (setf (compilation--loc->marker col) (point-marker))) - ;; (setf (compilation--loc->timestamp col) timestamp) - )))))) + (compilation--update-markers loc marker screen-columns first-column)) (compilation-goto-locus marker (compilation--loc->marker loc) (compilation--loc->marker end-loc)) (setf (compilation--loc->visited loc) t))) diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index d2d0baa235c..54006560224 100644 --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el @@ -310,6 +310,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-change-to-grep-edit-mode) map) "Keymap for grep buffers. `compilation-minor-mode-map' is a cdr of this.") @@ -1052,6 +1054,90 @@ grep command-args) #'grep-mode)) +(defun grep-edit--prepare-buffer () + "Mark relevant regions read-only, and add relevant occur text-properties." + (save-excursion + (goto-char (point-min)) + (let ((inhibit-read-only t) + (dummy (make-marker)) + match) + (while (setq match (text-property-search-forward 'compilation-annotation)) + (add-text-properties (prop-match-beginning match) (prop-match-end match) + '(read-only t))) + (goto-char (point-min)) + (while (setq match (text-property-search-forward 'compilation-message)) + (add-text-properties (prop-match-beginning match) (prop-match-end match) + '(read-only t occur-prefix t)) + (let ((loc (compilation--message->loc (prop-match-value match))) + m) + ;; Update the markers if necessary. + (unless (and (compilation--loc->marker loc) + (marker-buffer (compilation--loc->marker loc))) + (compilation--update-markers loc dummy compilation-error-screen-columns compilation-first-column)) + (setq m (compilation--loc->marker loc)) + (add-text-properties (prop-match-beginning match) + (or (next-single-property-change + (prop-match-end match) + 'compilation-message) + (1+ (pos-eol))) + `(occur-target ((,m . ,m))))))))) + +(defvar grep-edit-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-mode'.") + +(defvar grep-edit-mode-hook nil + "Hooks run when changing to Grep-Edit mode.") + +(defun grep-edit-mode () + "Major mode for editing *grep* buffers. +In this mode, changes to the *grep* buffer are applied to the +originating files. +\\ +Type \\[grep-edit-save-changes] to exit Grep-Edit mode, return to Grep +mode. + +The only editable texts in a Grep-Edit buffer are the match results." + (interactive) + (error "This mode can be enabled only by `grep-change-to-grep-edit-mode'")) +(put 'grep-edit-mode 'mode-class 'special) + +(defun grep-change-to-grep-edit-mode () + "Switch to `grep-edit-mode' to edit *grep* buffer." + (interactive) + (unless (derived-mode-p 'grep-mode) + (error "Not a Grep buffer")) + (when (get-buffer-process (current-buffer)) + (error "Cannot switch when grep is running")) + (use-local-map grep-edit-mode-map) + (grep-edit--prepare-buffer) + (setq buffer-read-only nil) + (setq major-mode 'grep-edit-mode) + (setq mode-name "Grep-Edit") + (buffer-enable-undo) + (set-buffer-modified-p nil) + (setq buffer-undo-list nil) + (add-hook 'after-change-functions #'occur-after-change-function nil t) + (run-mode-hooks 'grep-edit-mode-hook) + (message "Editing: \\[grep-edit-save-changes] to return to Grep mode")) + +(defun grep-edit-save-changes () + "Switch back to Grep mode." + (interactive) + (unless (derived-mode-p 'grep-edit-mode) + (error "Not a Grep-Edit buffer")) + (remove-hook 'after-change-functions #'occur-after-change-function t) + (use-local-map grep-mode-map) + (setq buffer-read-only t) + (setq major-mode 'grep-mode) + (setq mode-name "Grep") + (force-mode-line-update) + (buffer-disable-undo) + (setq buffer-undo-list t) + (message "Switching to Grep mode")) ;;;###autoload (defun grep-find (command-args) -- 2.45.2