From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Eshel Yaron via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#68958: [PATCH] Support bookmarking Xref results buffers Date: Tue, 06 Feb 2024 21:17:45 +0100 Message-ID: Reply-To: Eshel Yaron Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="25034"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Dmitry Gutov To: 68958@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Tue Feb 06 21:19:04 2024 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1rXRuJ-0006FY-41 for geb-bug-gnu-emacs@m.gmane-mx.org; Tue, 06 Feb 2024 21:19:03 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rXRu8-0005z2-P5; Tue, 06 Feb 2024 15:18:52 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rXRu5-0005xl-L7 for bug-gnu-emacs@gnu.org; Tue, 06 Feb 2024 15:18:49 -0500 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rXRu5-0004rL-EN for bug-gnu-emacs@gnu.org; Tue, 06 Feb 2024 15:18:49 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1rXRuI-0002gO-Mi; Tue, 06 Feb 2024 15:19:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Eshel Yaron Original-Sender: "Debbugs-submit" Resent-CC: dmitry@gutov.dev, bug-gnu-emacs@gnu.org Resent-Date: Tue, 06 Feb 2024 20:19:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 68958 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org X-Debbugs-Original-Xcc: Dmitry Gutov Original-Received: via spool by submit@debbugs.gnu.org id=B.170725070210259 (code B ref -1); Tue, 06 Feb 2024 20:19:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 6 Feb 2024 20:18:22 +0000 Original-Received: from localhost ([127.0.0.1]:55179 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rXRtd-0002fO-Cb for submit@debbugs.gnu.org; Tue, 06 Feb 2024 15:18:22 -0500 Original-Received: from lists.gnu.org ([2001:470:142::17]:41682) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rXRtb-0002f3-5X for submit@debbugs.gnu.org; Tue, 06 Feb 2024 15:18:20 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rXRtB-0005tH-BM for bug-gnu-emacs@gnu.org; Tue, 06 Feb 2024 15:17:53 -0500 Original-Received: from mail.eshelyaron.com ([107.175.124.16] helo=eshelyaron.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rXRt7-0004nY-GU for bug-gnu-emacs@gnu.org; Tue, 06 Feb 2024 15:17:52 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=eshelyaron.com; s=mail; t=1707250668; bh=MrrAU2IEG25ixpxx7URyEeirK1Q00aDCvX9Hl6ZKT/U=; h=From:To:Subject:Date:From; b=rP3L1k0qLVC0/CwPSqPvBj5rK9I0c9G2DzBJw963AEa8kckpuZ8naWQEnQRhwP6TV oA5qxX5QSdKNdwSU/Z6HyRu5v8ehP+n366lTkXRLXsoJ0UAZUmrZxeGqZuKvVXg7kC I1GbovbD8fpIkiqAuzef4zm/fqpmiPbur3lCP4xYc9rUojCRISCIKnWPJHLCggg03I JqD9bI3NiKMNZz6PH08Or5FPzPpCfoLXaSYSN8fLS0E3o/P1ulDO/i85HZsjbpzvqf 0fvcyH3Ayr6gWzOEmsgiBhgyNXnqD6my+t/6rFYDht2ncQjoKQNClBoVxwzSDA+Npa TGA8N9K+TPu3g== X-Hashcash: 1:20:240206:bug-gnu-emacs@gnu.org::bjYdasTK/itmQBUE:34q1 Received-SPF: pass client-ip=107.175.124.16; envelope-from=me@eshelyaron.com; helo=eshelyaron.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:279518 Archived-At: --=-=-= Content-Type: text/plain Tags: patch Hello Dmitry, All, This patch adds support for bookmarking "*xref*" buffers and restoring them later, even across Emacs sessions. To make this happen, we need to propagate some more information to the "*xref*" buffer (and any other Xref fronted). We do this, without breaking compatibility, by setting a new variable from inside the xrefs fetcher function. The frontend can examine this variable to learn all about the source of the fetched xrefs after invoking the fetcher. Namely, the "*xref*" buffer uses this information to create bookmarks. WDYT? --=-=-= Content-Type: text/patch Content-Disposition: attachment; filename=0001-Support-bookmarking-Xref-results-buffers.patch >From 62f76297ec240df8101ab47fa4a89fa584b7f05c Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Tue, 6 Feb 2024 20:33:53 +0100 Subject: [PATCH] Support bookmarking Xref results buffers * lisp/progmodes/xref.el (bookmark-make-record-default) (bookmark-make-record, bookmark-prop-get) (bookmark-handle-bookmark, bookmark-get-rear-context-string) (bookmark-get-front-context-string): Declare functions. (xref-backend-context, xref-backend-restore): New generic functions. (xref--backend, xref--identifier, xref--kind) (xref--original-buffer, xref--original-point): New local variables. (xref--show-common-initialize): Set them in Xref results buffer. (xref-default-bookmark-name-format): New user option. (xref-bookmark-make-record, xref-bookmark-jump): New functions. (xref--xref-buffer-mode): Set 'bookmark-make-record-function'. (xref-fetcher-alist): New variable. (xref--show-xref-buffer, xref-show-definitions-buffer) (xref-show-definitions-buffer-at-bottom): Use it. (xref--read-identifier): Improve error message. (xref-make-fetcher): Extract from... (xref--create-fetcher): ...here. * doc/emacs/maintaining.texi (Xref Commands): Document new feature. * etc/NEWS: Announce new feature and Xref generic functions. --- doc/emacs/maintaining.texi | 4 + etc/NEWS | 12 +++ lisp/progmodes/xref.el | 166 +++++++++++++++++++++++++++++++++---- 3 files changed, 165 insertions(+), 17 deletions(-) diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index d3e06fa697b..dd3fb3c2dd2 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -2466,6 +2466,10 @@ Xref Commands @kbd{C-n}, and @kbd{C-p} are available for moving around the buffer without displaying the references. +You can also bookmark the @file{*xref*} buffer with @kbd{C-x r m} and +restore it from the same state later by jumping to that bookmark with +@kbd{C-x r b}. @xref{Bookmarks}. + @node Identifier Search @subsubsection Searching and Replacing with Identifiers @cindex search and replace in multiple source files diff --git a/etc/NEWS b/etc/NEWS index f980d612a57..a33a0e9855b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -437,6 +437,11 @@ This mode now emits 'wheel-up/down/right/left' events instead of It uses the 'mouse-wheel-up/down/left/right-event' variables to decide which button maps to which wheel event (if any). +** Xref + ++++ +*** You can now bookmark (and later restore) "*xref*" buffers. + ** Info --- @@ -1664,6 +1669,13 @@ styles to skip eager fontification of completion candidates, which improves performance. Such a Lisp program can then use the 'completion-lazy-hilit' function to fontify candidates just in time. +** New Xref generic functions for recording and restoring context. +Xref backends can now implement the generic function +'xref-backend-context' to change how Xref records the context used for +fetching cross-references when bookmarking Xref results for later use. +In addition, the new generic function 'xref-backend-restore' lets +backends change how Xref then restores this context. + ** Functions and variables to transpose sexps +++ diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 717b837a2e5..249e018eb56 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -314,6 +314,21 @@ xref-backend-identifier-completion-ignore-case "Return t if case is not significant in identifier completion." completion-ignore-case) +(declare-function bookmark-make-record "bookmark") +(declare-function bookmark-make-record-default "bookmark") +(declare-function bookmark-prop-get "bookmark") +(declare-function bookmark-handle-bookmark "bookmark") +(declare-function bookmark-get-rear-context-string "bookmark") +(declare-function bookmark-get-front-context-string "bookmark") + +(cl-defgeneric xref-backend-context (_backend _identifier _kind) + "Return BACKEND-specific context for finding references to IDENTIFIER." + (bookmark-make-record)) + +(cl-defgeneric xref-backend-restore (_backend context) + "Restore BACKEND-specific CONTEXT." + (bookmark-handle-bookmark context)) + ;;; misc utilities (defun xref--alistify (list key) @@ -671,6 +686,23 @@ xref--original-window (defvar-local xref--fetcher nil "The original function to call to fetch the list of xrefs.") +(defvar-local xref--backend nil + "The backend that produced the xrefs that the current buffer is showing.") + +(defvar-local xref--identifier nil + "The identifier for which the current buffer is showing xrefs.") + +(defvar-local xref--kind nil + "The kind of xrefs the current buffer is showing.") + +(defvar-local xref--original-buffer nil + "Buffer in which the Xref command that created this buffer was called.") + +(defvar-local xref--original-point nil + "Position in which the Xref command that created this buffer was called. + +See also `xref--original-buffer'.") + (defun xref--show-pos-in-buf (pos buf) "Goto and display position POS of buffer BUF in a window. Honor `xref--original-window-intent', run `xref-after-jump-hook' @@ -997,6 +1029,63 @@ xref--xref-buffer-mode-map (define-key map (kbd "M-,") #'xref-quit-and-pop-marker-stack) map)) +(defcustom xref-default-bookmark-name-format "%i %k" + "Format of the default bookmark name for Xref buffer bookmarks. + +The default bookmark name is the value of this option (a string), with +\"%i\" sequences substituted for the identifier that the Xref buffer is +showing information about, \"%k\" substituted with the kind of +information shown (\"references\", \"definitions\", etc.), and \"%b\" +substituted for the name of the backend that produced the information." + :type 'string + :version "30.1") + +(defun xref-bookmark-make-record () + "Return a bookmark record for bookmarking the current Xref buffer. + +This function is used as the value of `bookmark-make-record-function' in +Xref buffers." + (unless xref--backend + (user-error "Cannot bookmark due to unknown Xref backend")) + `(,(format-spec xref-default-bookmark-name-format + `((?i . ,xref--identifier) + (?k . ,xref--kind) + (?b . ,xref--backend))) + ,@(bookmark-make-record-default t) + (backend . ,xref--backend) + (context . ,(when (buffer-live-p xref--original-buffer) + (with-current-buffer xref--original-buffer + (save-excursion + (ignore-errors (goto-char xref--original-point)) + (xref-backend-context xref--backend + xref--identifier + xref--kind))))) + (identifier . ,xref--identifier) + (kind . ,xref--kind) + (handler . xref-bookmark-jump))) + +(defun xref-bookmark-jump (bookmark) + "Jump to Xref buffer bookmark BOOKMARK." + (let* ((backend (bookmark-prop-get bookmark 'backend)) + (context (bookmark-prop-get bookmark 'context)) + (identifier (bookmark-prop-get bookmark 'identifier)) + (kind (bookmark-prop-get bookmark 'kind)) + (fetcher (save-current-buffer + (xref-backend-restore backend context) + (xref-make-fetcher backend identifier kind identifier + (current-buffer) (point)))) + (xref-auto-jump-to-first-xref nil)) + (set-buffer (xref--show-xref-buffer fetcher nil)) + (let ((forward-str (bookmark-get-front-context-string bookmark)) + (behind-str (bookmark-get-rear-context-string bookmark))) + (when (and forward-str (search-forward forward-str (point-max) t)) + (goto-char (match-beginning 0))) + (when (and behind-str (search-backward behind-str (point-min) t)) + (goto-char (match-end 0))) + nil))) + +(put 'xref-bookmark-jump 'bookmark-handler-type "Xref") + (declare-function outline-search-text-property "outline" (property &optional value bound move backward looking-at)) @@ -1017,7 +1106,8 @@ xref--xref-buffer-mode (lambda (&optional bound move backward looking-at) (outline-search-text-property 'xref-group nil bound move backward looking-at))) - (setq-local outline-level (lambda () 1))) + (setq-local outline-level (lambda () 1)) + (setq-local bookmark-make-record-function #'xref-bookmark-make-record)) (defvar xref--transient-buffer-mode-map (let ((map (make-sparse-keymap))) @@ -1235,11 +1325,29 @@ xref--ensure-default-directory 0 nil (lambda () (with-current-buffer buffer (setq default-directory dd))))) +(defvar xref-fetcher-alist nil + "Alist with information about the last used Xref fetcher function. + +Fetcher functions which Xref passes to `xref-show-xrefs-function' set +this variable to an alist with the following key-value pairs: + +- (backend . BACKEND) where BACKEND is the Xref backend that the + fetcher invokes. +- (identifier . ID) where ID is the identifier for which the fetcher + fetches references. +- (kind . KIND) where KIND is the kind of references that the fetcher + fetches. +- (original-buffer . BUF) where BUF is the buffer in which the Xref + command that created the fetcher was invoked. +- (original-point . POS) where POS is the buffer position in which the + Xref command that created the fetcher was invoked.") + (defun xref--show-xref-buffer (fetcher alist) (cl-assert (functionp fetcher)) (let* ((xrefs (or (assoc-default 'fetched-xrefs alist) + (setq xref-fetcher-alist nil) (funcall fetcher))) (xref-alist (xref--analyze xrefs)) (dd default-directory) @@ -1247,7 +1355,7 @@ xref--show-xref-buffer (with-current-buffer (get-buffer-create xref-buffer-name) (xref--ensure-default-directory dd (current-buffer)) (xref--xref-buffer-mode) - (xref--show-common-initialize xref-alist fetcher alist) + (xref--show-common-initialize xref-alist fetcher (append xref-fetcher-alist alist)) (setq xref-num-matches-found (length xrefs)) (setq mode-line-process (list xref-mode-line-matches)) (pop-to-buffer (current-buffer)) @@ -1272,7 +1380,12 @@ xref--show-common-initialize (add-hook 'post-command-hook 'xref--apply-truncation nil t) (goto-char (point-min)) (setq xref--original-window (assoc-default 'window alist) - xref--original-window-intent (assoc-default 'display-action alist)) + xref--original-window-intent (assoc-default 'display-action alist) + xref--original-buffer (assoc-default 'original-buffer alist) + xref--original-point (assoc-default 'original-point alist) + xref--backend (assoc-default 'backend alist) + xref--identifier (assoc-default 'identifier alist) + xref--kind (assoc-default 'kind alist)) (setq xref--fetcher fetcher))) (defun xref-revert-buffer () @@ -1310,6 +1423,7 @@ xref-show-definitions-buffer "Show the definitions list in a regular window. When only one definition found, jump to it right away instead." + (setq xref-fetcher-alist nil) (let ((xrefs (funcall fetcher)) buf) (cond @@ -1333,6 +1447,7 @@ xref-show-definitions-buffer-at-bottom When there is more than one definition, split the selected window and show the list in a small window at the bottom. And use a local keymap that binds `RET' to `xref-quit-and-goto-xref'." + (setq xref-fetcher-alist nil) (let* ((xrefs (funcall fetcher)) (dd default-directory) ;; XXX: Make percentage customizable maybe? @@ -1353,7 +1468,7 @@ xref-show-definitions-buffer-at-bottom (with-current-buffer (get-buffer-create xref-buffer-name) (xref--ensure-default-directory dd (current-buffer)) (xref--transient-buffer-mode) - (xref--show-common-initialize xref-alist fetcher alist) + (xref--show-common-initialize xref-alist fetcher (append xref-fetcher-alist alist)) (pop-to-buffer (current-buffer) `(display-buffer-in-direction . ((direction . below) (window-height . ,size-fun)))) @@ -1552,7 +1667,7 @@ xref--read-identifier nil nil nil 'xref--read-identifier-history def t))) (if (equal id "") - (or def (user-error "There is no default identifier")) + (or def (user-error "No default identifier")) id))) (t def)))) @@ -1569,16 +1684,23 @@ xref--find-definitions (xref--create-fetcher id 'definitions id) display-action)) -(defun xref--create-fetcher (input kind arg) - "Return an xref list fetcher function. +(defun xref-make-fetcher (backend input kind identifier buffer point) + "Return fetcher function for xrefs of kind KIND for IDENTIFIER using BACKEND. -It revisits the saved position and delegates the finding logic to -the xref backend method indicated by KIND and passes ARG to it." - (let* ((orig-buffer (current-buffer)) - (orig-position (point)) - (backend (xref-find-backend)) - (method (intern (format "xref-backend-%s" kind)))) +INPUT is the user input for the Xref operation, usually it is the same +as IDENTIFIER, but the two may differ when KIND is `apropos'. BUFFER +and POINT are the buffer and specific position in which the xref +operation was invoked. + +The fetcher function returns a list of xrefs, and sets +`xref-fetcher-alist', which see." + (let ((method (intern (format "xref-backend-%s" kind)))) (lambda () + (setq xref-fetcher-alist (list (cons 'original-buffer buffer) + (cons 'original-point point) + (cons 'backend backend) + (cons 'identifier identifier) + (cons 'kind kind))) (save-excursion ;; Xref methods are generally allowed to depend on the text ;; around point, not just on their explicit arguments. @@ -1586,14 +1708,24 @@ xref--create-fetcher ;; There is only so much we can do, however, to recreate that ;; context, given that the user is free to change the buffer ;; contents freely in the meantime. - (when (buffer-live-p orig-buffer) - (set-buffer orig-buffer) - (ignore-errors (goto-char orig-position))) - (let ((xrefs (funcall method backend arg))) + (when (buffer-live-p buffer) + (set-buffer buffer) + (ignore-errors (goto-char point))) + (let ((xrefs (funcall method backend identifier))) (unless xrefs (xref--not-found-error kind input)) xrefs))))) +(defun xref--create-fetcher (input kind arg) + "Return an xref list fetcher function. + +It revisits the saved position and delegates the finding logic to +the xref backend method indicated by KIND and passes ARG to it." + (xref-make-fetcher (xref-find-backend) + input kind arg + (current-buffer) + (copy-marker (point)))) + (defun xref--not-found-error (kind input) (user-error "No %s found for: %s" (symbol-name kind) input)) -- 2.42.0 --=-=-=--