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: Wed, 07 Feb 2024 18:04:24 +0100 Message-ID: References: <86le7wzcjj.fsf@gnu.org> 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="33042"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: dmitry@gutov.dev, 68958@debbugs.gnu.org To: Eli Zaretskii Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Wed Feb 07 18:05:15 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 1rXlMH-0008Gy-Sm for geb-bug-gnu-emacs@m.gmane-mx.org; Wed, 07 Feb 2024 18:05:14 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rXlLw-0003rJ-9M; Wed, 07 Feb 2024 12:04: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 1rXlLt-0003rA-8h for bug-gnu-emacs@gnu.org; Wed, 07 Feb 2024 12:04: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 1rXlLs-0002iu-Va for bug-gnu-emacs@gnu.org; Wed, 07 Feb 2024 12:04:49 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1rXlM6-0003Qa-Fe for bug-gnu-emacs@gnu.org; Wed, 07 Feb 2024 12:05:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Eshel Yaron Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Wed, 07 Feb 2024 17:05:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 68958 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 68958-submit@debbugs.gnu.org id=B68958.170732548413149 (code B ref 68958); Wed, 07 Feb 2024 17:05:02 +0000 Original-Received: (at 68958) by debbugs.gnu.org; 7 Feb 2024 17:04:44 +0000 Original-Received: from localhost ([127.0.0.1]:57798 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rXlLn-0003Q0-MH for submit@debbugs.gnu.org; Wed, 07 Feb 2024 12:04:44 -0500 Original-Received: from mail.eshelyaron.com ([107.175.124.16]:56732 helo=eshelyaron.com) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rXlLl-0003Ps-E2 for 68958@debbugs.gnu.org; Wed, 07 Feb 2024 12:04:42 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=eshelyaron.com; s=mail; t=1707325466; bh=bDpt816oz8NxhmNk++moG9syt1gxz02sRZooU5DBSa8=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=MaQXJos/2VzZxPD4bfwB4BdmP8LNPqAbac7dGqou7gtvbkXUsnCfve0h+SSK8Q010 Puz9tIj88vFIF8atGeaqvkx6wiL2EBvackNVBHEacuh6wYXqP46CIHNojE+LoBVJ3a +1N6XYHoDfx12bF55TerBlbaNdMcBr/OPxu0lvnGkv7yMxFCQ5cy8V5w6oCEXm6+PE XrL1npAnJU+kHSU/Bny9zyHf6Wv2bNoTeaXrNO/M+VdvvBHsreyGEopLm7QU2smsq6 CnY/fXkio0PlOjnrnrfP+lVt41CXo4bKOTk6YE9QS5arpTpGcHlsgk7KfK93MDNkyg vx2Wnq0ATBCcQ== In-Reply-To: <86le7wzcjj.fsf@gnu.org> (Eli Zaretskii's message of "Wed, 07 Feb 2024 14:25:36 +0200") 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:279562 Archived-At: --=-=-= Content-Type: text/plain Eli Zaretskii writes: >> Cc: Dmitry Gutov >> Date: Tue, 06 Feb 2024 21:17:45 +0100 >> From: Eshel Yaron via "Bug reports for GNU Emacs, >> the Swiss army knife of text editors" >> >> 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. > > Thanks. Frankly, I'm surprised we need such a complex changeset for > supporting such a simple extension, but I'll let Dmitry judge that. Thanks for taking a look. It's a simple extension, but unless I'm missing something in the code, the Xref infrastructure needs these extra tweaks to accommodate for it. >> --- 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}. > > Since "C-x r m" and "C-x r b" are bookmark commands, they should not > be described here; instead, its description in "Bookmarks" should > mention any special features related to the Xref buffers (not that I > see what is there to mention, but maybe I'm missing something). If > you think this capability is worth mentioning in the "Xref Commands" > node, you should do it in passage, like > > You can bookmark and restore your place in @file{*xref*} buffers, > see @ref{Bookmarks}. That makes sense. I updated the text accordingly in the attached patch. >> +** 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. > > I'm not sure this is for NEWS. Either expand the documentation, place > it in the ELisp manual, and just mention the function's name in NEWS, > or simply don't mention it in NEWS. All right, I removed this bit. Here's the updated patch (v2): --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v2-0001-Support-bookmarking-Xref-results-buffers.patch >From 51b748c4dd97cb60e7328b6510e8dd55da4a75ce Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Tue, 6 Feb 2024 20:33:53 +0100 Subject: [PATCH v2] 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 it. (Bug#68958) --- doc/emacs/maintaining.texi | 3 + etc/NEWS | 5 ++ lisp/progmodes/xref.el | 166 +++++++++++++++++++++++++++++++++---- 3 files changed, 157 insertions(+), 17 deletions(-) diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index d3e06fa697b..4cd02851594 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -2466,6 +2466,9 @@ Xref Commands @kbd{C-n}, and @kbd{C-p} are available for moving around the buffer without displaying the references. +You can bookmark and restore your place in @file{*xref*} buffers, see +@ref{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..500433ff955 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 --- 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 --=-=-=--