diff options
| author | Dmitry Gutov | 2026-03-31 19:30:33 +0300 |
|---|---|---|
| committer | Dmitry Gutov | 2026-04-01 04:04:39 +0300 |
| commit | cda65ffa58e1280fabfdf3eb7340f429b2aedd4d (patch) | |
| tree | 1994b6c5b1169ef28aafcc4aea5c465dc000de44 | |
| parent | f898d94c7b117b77f66d9472f9103961b5f6b6af (diff) | |
| download | emacs-cda65ffa58e1280fabfdf3eb7340f429b2aedd4d.tar.gz emacs-cda65ffa58e1280fabfdf3eb7340f429b2aedd4d.zip | |
Add xref-edit-mode (bug#80616)
Based on the existing grep-edit-mode code.
* lisp/progmodes/xref.el (xref-edit--prepare-buffer): New function.
(xref-edit-mode-map, xref-edit-mode-hook): New variables.
(xref-edit-mode, xref-change-to-xref-edit-mode)
(xref-edit-save-changes): New functions.
(xref--xref-buffer-mode-map): New binding ("e").
* doc/emacs/maintaining.texi (Identifier Search):
Mention xref-change-to-xref-edit-mode.
* etc/NEWS: Describe the addition.
| -rw-r--r-- | doc/emacs/maintaining.texi | 10 | ||||
| -rw-r--r-- | etc/NEWS | 5 | ||||
| -rw-r--r-- | lisp/progmodes/xref.el | 87 |
3 files changed, 102 insertions, 0 deletions
diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 4aee5e1045d..72f25c0bdf8 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi | |||
| @@ -2926,6 +2926,16 @@ matches of that regexp in the names of the identifiers with | |||
| 2926 | @code{xref-query-replace-in-results}, but is more convenient when you | 2926 | @code{xref-query-replace-in-results}, but is more convenient when you |
| 2927 | want to rename a single identifier specified by its name @var{from}. | 2927 | want to rename a single identifier specified by its name @var{from}. |
| 2928 | 2928 | ||
| 2929 | @findex xref-change-to-xref-edit-mode | ||
| 2930 | @cindex Xref Edit mode | ||
| 2931 | @cindex mode, Xref Edit | ||
| 2932 | Typing @kbd{e} in the @file{*xref*} buffer makes the buffer writable | ||
| 2933 | and enters the Xref Edit mode. Similar to Occur Edit mode (@pxref{Other | ||
| 2934 | Repeating Search}), you can edit the matching lines reported by | ||
| 2935 | Xref backend and have those changes reflected in the buffer visiting the | ||
| 2936 | originating file. Type @kbd{C-c C-c} to leave the Xref Edit mode and | ||
| 2937 | return to the Xref mode. | ||
| 2938 | |||
| 2929 | @findex tags-search | 2939 | @findex tags-search |
| 2930 | @kbd{M-x tags-search} reads a regexp using the minibuffer, then | 2940 | @kbd{M-x tags-search} reads a regexp using the minibuffer, then |
| 2931 | searches for matches in all the files in the selected tags table, one | 2941 | searches for matches in all the files in the selected tags table, one |
| @@ -3202,6 +3202,11 @@ This minor mode binds 'xref-find-definitions-at-mouse' to | |||
| 3202 | definition, following the convention from other editors. The global | 3202 | definition, following the convention from other editors. The global |
| 3203 | minor mode 'global-xref-mouse-mode' will enable this in all buffers. | 3203 | minor mode 'global-xref-mouse-mode' will enable this in all buffers. |
| 3204 | 3204 | ||
| 3205 | +++ | ||
| 3206 | *** New command 'xref-change-to-xref-edit-mode'. | ||
| 3207 | It's bound to "e" and it switches an Xref buffer into an "editable" mode | ||
| 3208 | like similar features in Occur and Grep buffers. | ||
| 3209 | |||
| 3205 | ** Revert | 3210 | ** Revert |
| 3206 | 3211 | ||
| 3207 | +++ | 3212 | +++ |
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index ebf8df9f795..b69a4c7fdde 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el | |||
| @@ -73,6 +73,7 @@ | |||
| 73 | (require 'cl-lib) | 73 | (require 'cl-lib) |
| 74 | (require 'ring) | 74 | (require 'ring) |
| 75 | (require 'project) | 75 | (require 'project) |
| 76 | (require 'text-property-search) | ||
| 76 | 77 | ||
| 77 | (eval-and-compile | 78 | (eval-and-compile |
| 78 | (when (version< emacs-version "28.0.60") | 79 | (when (version< emacs-version "28.0.60") |
| @@ -1004,6 +1005,7 @@ point." | |||
| 1004 | (define-key map (kbd ".") #'xref-next-line) | 1005 | (define-key map (kbd ".") #'xref-next-line) |
| 1005 | (define-key map (kbd ",") #'xref-prev-line) | 1006 | (define-key map (kbd ",") #'xref-prev-line) |
| 1006 | (define-key map (kbd "M-,") #'xref-quit-and-pop-marker-stack) | 1007 | (define-key map (kbd "M-,") #'xref-quit-and-pop-marker-stack) |
| 1008 | (define-key map (kbd "e") #'xref-change-to-xref-edit-mode) | ||
| 1007 | map)) | 1009 | map)) |
| 1008 | 1010 | ||
| 1009 | (declare-function outline-search-text-property "outline" | 1011 | (declare-function outline-search-text-property "outline" |
| @@ -1471,6 +1473,91 @@ between them by typing in the minibuffer with completion." | |||
| 1471 | 'xref--show-defs-minibuffer #'xref-show-definitions-completing-read "28.1") | 1473 | 'xref--show-defs-minibuffer #'xref-show-definitions-completing-read "28.1") |
| 1472 | 1474 | ||
| 1473 | 1475 | ||
| 1476 | (defun xref-edit--prepare-buffer () | ||
| 1477 | "Mark relevant regions read-only, and add relevant occur text-properties." | ||
| 1478 | (save-excursion | ||
| 1479 | (goto-char (point-min)) | ||
| 1480 | (let ((inhibit-read-only t) | ||
| 1481 | match) | ||
| 1482 | (while (setq match (text-property-search-forward 'xref-group)) | ||
| 1483 | (add-text-properties (prop-match-beginning match) (prop-match-end match) | ||
| 1484 | '(read-only t))) | ||
| 1485 | (goto-char (point-min)) | ||
| 1486 | (while (setq match (text-property-search-forward 'xref-item)) | ||
| 1487 | (let ((line-number-end (save-excursion | ||
| 1488 | (forward-line 0) | ||
| 1489 | (and (looking-at " *[0-9]+:") | ||
| 1490 | (match-end 0)))) | ||
| 1491 | (m (xref-location-marker (xref-item-location (prop-match-value match ))))) | ||
| 1492 | (when line-number-end | ||
| 1493 | (add-text-properties (prop-match-beginning match) line-number-end | ||
| 1494 | '(read-only t occur-prefix t))) | ||
| 1495 | (add-text-properties (prop-match-beginning match) | ||
| 1496 | (1+ (pos-eol)) | ||
| 1497 | `(occur-target ((,m . ,m))))))))) | ||
| 1498 | |||
| 1499 | (defvar xref-edit-mode-map | ||
| 1500 | (let ((map (make-sparse-keymap))) | ||
| 1501 | (define-key map (kbd "C-c C-c") #'xref-edit-save-changes) | ||
| 1502 | (define-key map (kbd "RET") #'xref-goto-xref) | ||
| 1503 | (define-key map (kbd "M-,") #'xref-quit-and-pop-marker-stack) | ||
| 1504 | (define-key map (kbd "C-o") #'xref-show-location-at-point) | ||
| 1505 | map) | ||
| 1506 | "Keymap for `xref-edit-mode'.") | ||
| 1507 | |||
| 1508 | (defvar xref-edit-mode-hook nil | ||
| 1509 | "Hooks run when changing to Xref-Edit mode.") | ||
| 1510 | |||
| 1511 | (defun xref-edit-mode () | ||
| 1512 | "Major mode for editing *xref* buffers. | ||
| 1513 | In this mode, changes to the *xref* buffer are applied to the | ||
| 1514 | originating files. | ||
| 1515 | \\<xref-edit-mode-map> | ||
| 1516 | Type \\[xref-edit-save-changes] to exit Xref-Edit mode, return to Xref | ||
| 1517 | mode. | ||
| 1518 | |||
| 1519 | The only editable texts in an Xref-Edit buffer are the match results." | ||
| 1520 | (interactive) | ||
| 1521 | (error "This mode can be enabled only by `xref-change-to-xref-edit-mode'")) | ||
| 1522 | (put 'xref-edit-mode 'mode-class 'special) | ||
| 1523 | |||
| 1524 | (defun xref-change-to-xref-edit-mode () | ||
| 1525 | "Switch to `xref-edit-mode' to edit *xref* buffer." | ||
| 1526 | (interactive) | ||
| 1527 | (unless (derived-mode-p 'xref--xref-buffer-mode) | ||
| 1528 | (error "Not an Xref buffer")) | ||
| 1529 | (use-local-map xref-edit-mode-map) | ||
| 1530 | (xref-edit--prepare-buffer) | ||
| 1531 | (setq buffer-read-only nil) | ||
| 1532 | (setq major-mode 'xref-edit-mode) | ||
| 1533 | (setq mode-name "Xref-Edit") | ||
| 1534 | (buffer-enable-undo) | ||
| 1535 | (set-buffer-modified-p nil) | ||
| 1536 | (setq buffer-undo-list nil) | ||
| 1537 | (add-hook 'after-change-functions #'occur-after-change-function nil t) | ||
| 1538 | (run-mode-hooks 'xref-edit-mode-hook) | ||
| 1539 | (message (substitute-command-keys | ||
| 1540 | "Editing: Type \\[xref-edit-save-changes] to return to Xref mode"))) | ||
| 1541 | |||
| 1542 | (defun xref-edit-save-changes () | ||
| 1543 | "Switch back to Xref mode." | ||
| 1544 | (interactive) | ||
| 1545 | (unless (derived-mode-p 'xref-edit-mode) | ||
| 1546 | (error "Not a Xref-Edit buffer")) | ||
| 1547 | (remove-hook 'after-change-functions #'occur-after-change-function t) | ||
| 1548 | (use-local-map xref--xref-buffer-mode-map) | ||
| 1549 | (setq buffer-read-only t) | ||
| 1550 | (setq major-mode 'xref--xref-buffer-mode) | ||
| 1551 | (setq mode-name "XREF") | ||
| 1552 | (force-mode-line-update) | ||
| 1553 | (buffer-disable-undo) | ||
| 1554 | (setq buffer-undo-list t) | ||
| 1555 | (let ((inhibit-read-only t)) | ||
| 1556 | (remove-text-properties (point-min) (point-max) | ||
| 1557 | '(occur-target nil occur-prefix nil))) | ||
| 1558 | (message "Switching to Xref mode")) | ||
| 1559 | |||
| 1560 | |||
| 1474 | (defcustom xref-show-xrefs-function 'xref--show-xref-buffer | 1561 | (defcustom xref-show-xrefs-function 'xref--show-xref-buffer |
| 1475 | "Function to display a list of search results. | 1562 | "Function to display a list of search results. |
| 1476 | 1563 | ||