Make the *grep* buffer editable

* lisp/progmodes/compile.el (compilation--update-markers):
Factor out function...
(compilation-next-error-function): ...from here.  Adjust
to use the 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)
This commit is contained in:
Visuwesh 2024-09-09 20:08:04 +05:30 committed by Eli Zaretskii
parent 7c767ec781
commit db1eb8a282
4 changed files with 156 additions and 44 deletions

View file

@ -528,6 +528,16 @@ grep-find-toggle-abbreviation}. To disable this abbreviation of the
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

View file

@ -328,6 +328,15 @@ fontifying them, which can be slow for remote directories. Setting
'dired-check-symlinks' to nil disables these checks. Defaults to t, can
be set as a connection-local variable.
** 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

View file

@ -2855,6 +2855,53 @@ as a last resort."
(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 @@ This is the value of `next-error-function' in Compilation buffers."
(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 @@ This is the value of `next-error-function' in Compilation buffers."
(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 @@ This is the value of `next-error-function' in Compilation buffers."
;; (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)))

View file

@ -310,6 +310,8 @@ See `compilation-error-screen-columns'."
(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 @@ list is empty)."
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.
\\<grep-edit-mode-map>
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)