aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Gutov2014-12-25 22:08:19 +0200
committerDmitry Gutov2014-12-25 22:19:28 +0200
commit394ce9514f0f0b473e4e8974b8529d0389fb627e (patch)
treef1fe158638ee0a0f581fcd743c042c780d2453de
parentac549019742bac11c249814d7744670a56671f97 (diff)
downloademacs-394ce9514f0f0b473e4e8974b8529d0389fb627e.tar.gz
emacs-394ce9514f0f0b473e4e8974b8529d0389fb627e.zip
Consolidate cross-referencing commands
Move autoloaded bindings for `M-.', `M-,', `C-x 4 .' and `C-x 5 .' from etags.el to xref.el. * progmodes/xref.el: New file. * progmodes/elisp-mode.el (elisp--identifier-types): New variable. (elisp--identifier-location): New function, extracted from `elisp--company-location'. (elisp--company-location): Use it. (elisp--identifier-completion-table): New variable. (elisp-completion-at-point): Use it. (emacs-lisp-mode): Set the local values of `xref-find-function' and `xref-identifier-completion-table-function'. (elisp-xref-find, elisp--xref-find-definitions) (elisp--xref-identifier-completion-table): New functions. * progmodes/etags.el (find-tag-marker-ring): Mark obsolete in favor of `xref--marker-ring'. (tags-lazy-completion-table): Autoload. (tags-reset-tags-tables): Use `xref-clear-marker-stack'. (find-tag-noselect): Use `xref-push-marker-stack'. (pop-tag-mark): Make an alias for `xref-pop-marker-stack'. (etags--xref-limit): New constant. (etags-xref-find, etags--xref-find-definitions): New functions.
-rw-r--r--etc/NEWS19
-rw-r--r--lisp/ChangeLog30
-rw-r--r--lisp/progmodes/elisp-mode.el88
-rw-r--r--lisp/progmodes/etags.el97
-rw-r--r--lisp/progmodes/xref.el499
5 files changed, 682 insertions, 51 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 16aa297aed0..37806a7fe56 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -434,6 +434,25 @@ By default, 32 spaces and four TABs are considered to be too much but
434`tildify-ignored-environments-alist' variables (as well as a few 434`tildify-ignored-environments-alist' variables (as well as a few
435helper functions) obsolete. 435helper functions) obsolete.
436 436
437** xref
438The new package provides generic framework and new commands to find
439and move to definitions, as well as pop back to the original location.
440
441*** New key bindings
442`xref-find-definitions' replaces `find-tag' and provides an interface
443to pick one destination among several. Hence, `tags-toop-continue' is
444unbound. `xref-pop-marker-stack' replaces `pop-tag-mark', but uses an
445easier binding, which is now unoccupied (`M-,').
446`xref-find-definitions-other-window' replaces `find-tag-other-window'.
447`xref-find-definitions-other-frame' replaces `find-tag-other-frame'.
448`xref-find-apropos' replaces `find-tag-regexp'.
449
450*** New variables
451`find-tag-marker-ring-length' is now an obsolete alias for
452`xref-marker-ring-length'. `find-tag-marker-ring' is now an obsolete
453alias for a private variable. `xref-push-marker-stack' and
454`xref-pop-marker-stack' should be used to mutate it instead.
455
437** Obsolete packages 456** Obsolete packages
438 457
439--- 458---
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 6b0f2961d65..a2bee149b7f 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,33 @@
12014-12-25 Helmut Eller <eller.helmut@gmail.com>
2 Dmitry Gutov <dgutov@yandex.ru>
3
4 Consolidate cross-referencing commands.
5
6 Move autoloaded bindings for `M-.', `M-,', `C-x 4 .' and
7 `C-x 5 .' from etags.el to xref.el.
8
9 * progmodes/xref.el: New file.
10
11 * progmodes/elisp-mode.el (elisp--identifier-types): New variable.
12 (elisp--identifier-location): New function, extracted from
13 `elisp--company-location'.
14 (elisp--company-location): Use it.
15 (elisp--identifier-completion-table): New variable.
16 (elisp-completion-at-point): Use it.
17 (emacs-lisp-mode): Set the local values of `xref-find-function'
18 and `xref-identifier-completion-table-function'.
19 (elisp-xref-find, elisp--xref-find-definitions)
20 (elisp--xref-identifier-completion-table): New functions.
21
22 * progmodes/etags.el (find-tag-marker-ring): Mark obsolete in
23 favor of `xref--marker-ring'.
24 (tags-lazy-completion-table): Autoload.
25 (tags-reset-tags-tables): Use `xref-clear-marker-stack'.
26 (find-tag-noselect): Use `xref-push-marker-stack'.
27 (pop-tag-mark): Make an alias for `xref-pop-marker-stack'.
28 (etags--xref-limit): New constant.
29 (etags-xref-find, etags--xref-find-definitions): New functions.
30
12014-12-25 Martin Rudalics <rudalics@gmx.at> 312014-12-25 Martin Rudalics <rudalics@gmx.at>
2 32
3 * cus-start.el (resize-mini-windows): Make it customizable. 33 * cus-start.el (resize-mini-windows): Make it customizable.
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index ba70f903b4b..e73c20df263 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -227,10 +227,15 @@ Blank lines separate paragraphs. Semicolons start comments.
227 227
228\\{emacs-lisp-mode-map}" 228\\{emacs-lisp-mode-map}"
229 :group 'lisp 229 :group 'lisp
230 (defvar xref-find-function)
231 (defvar xref-identifier-completion-table-function)
230 (lisp-mode-variables nil nil 'elisp) 232 (lisp-mode-variables nil nil 'elisp)
231 (setq imenu-case-fold-search nil) 233 (setq imenu-case-fold-search nil)
232 (setq-local eldoc-documentation-function 234 (setq-local eldoc-documentation-function
233 #'elisp-eldoc-documentation-function) 235 #'elisp-eldoc-documentation-function)
236 (setq-local xref-find-function #'elisp-xref-find)
237 (setq-local xref-identifier-completion-table-function
238 #'elisp--xref-identifier-completion-table)
234 (add-hook 'completion-at-point-functions 239 (add-hook 'completion-at-point-functions
235 #'elisp-completion-at-point nil 'local)) 240 #'elisp-completion-at-point nil 'local))
236 241
@@ -414,17 +419,39 @@ It can be quoted, or be inside a quoted form."
414 419
415(declare-function find-library-name "find-func" (library)) 420(declare-function find-library-name "find-func" (library))
416 421
422(defvar elisp--identifier-types '(defun defvar feature defface))
423
424(defun elisp--identifier-location (type sym)
425 (pcase (cons type sym)
426 (`(defun . ,(pred fboundp))
427 (find-definition-noselect sym nil))
428 (`(defvar . ,(pred boundp))
429 (find-definition-noselect sym 'defvar))
430 (`(defface . ,(pred facep))
431 (find-definition-noselect sym 'defface))
432 (`(feature . ,(pred featurep))
433 (require 'find-func)
434 (cons (find-file-noselect (find-library-name
435 (symbol-name sym)))
436 1))))
437
417(defun elisp--company-location (str) 438(defun elisp--company-location (str)
418 (let ((sym (intern-soft str))) 439 (catch 'res
419 (cond 440 (let ((sym (intern-soft str)))
420 ((fboundp sym) (find-definition-noselect sym nil)) 441 (when sym
421 ((boundp sym) (find-definition-noselect sym 'defvar)) 442 (dolist (type elisp--identifier-types)
422 ((featurep sym) 443 (let ((loc (elisp--identifier-location type sym)))
423 (require 'find-func) 444 (and loc (throw 'res loc))))))))
424 (cons (find-file-noselect (find-library-name 445
425 (symbol-name sym))) 446(defvar elisp--identifier-completion-table
426 0)) 447 (apply-partially #'completion-table-with-predicate
427 ((facep sym) (find-definition-noselect sym 'defface))))) 448 obarray
449 (lambda (sym)
450 (or (boundp sym)
451 (fboundp sym)
452 (featurep sym)
453 (symbol-plist sym)))
454 'strict))
428 455
429(defun elisp-completion-at-point () 456(defun elisp-completion-at-point ()
430 "Function used for `completion-at-point-functions' in `emacs-lisp-mode'." 457 "Function used for `completion-at-point-functions' in `emacs-lisp-mode'."
@@ -466,13 +493,8 @@ It can be quoted, or be inside a quoted form."
466 :company-docsig #'elisp--company-doc-string 493 :company-docsig #'elisp--company-doc-string
467 :company-location #'elisp--company-location)) 494 :company-location #'elisp--company-location))
468 ((elisp--form-quoted-p beg) 495 ((elisp--form-quoted-p beg)
469 (list nil obarray 496 ;; Don't include all symbols (bug#16646).
470 ;; Don't include all symbols 497 (list nil elisp--identifier-completion-table
471 ;; (bug#16646).
472 :predicate (lambda (sym)
473 (or (boundp sym)
474 (fboundp sym)
475 (symbol-plist sym)))
476 :annotation-function 498 :annotation-function
477 (lambda (str) (if (fboundp (intern-soft str)) " <f>")) 499 (lambda (str) (if (fboundp (intern-soft str)) " <f>"))
478 :company-doc-buffer #'elisp--company-doc-buffer 500 :company-doc-buffer #'elisp--company-doc-buffer
@@ -548,6 +570,38 @@ It can be quoted, or be inside a quoted form."
548(define-obsolete-function-alias 570(define-obsolete-function-alias
549 'lisp-completion-at-point 'elisp-completion-at-point "25.1") 571 'lisp-completion-at-point 'elisp-completion-at-point "25.1")
550 572
573;;; Xref backend
574
575(declare-function xref-make-buffer-location "xref" (buffer position))
576(declare-function xref-make-bogus-location "xref" (message))
577(declare-function xref-make "xref" (description location))
578
579(defun elisp-xref-find (action id)
580 (when (eq action 'definitions)
581 (let ((sym (intern-soft id)))
582 (when sym
583 (remove nil (elisp--xref-find-definitions sym))))))
584
585(defun elisp--xref-find-definitions (symbol)
586 (save-excursion
587 (mapcar
588 (lambda (type)
589 (let ((loc
590 (condition-case err
591 (let ((buf-pos (elisp--identifier-location type symbol)))
592 (when buf-pos
593 (xref-make-buffer-location (car buf-pos)
594 (or (cdr buf-pos) 1))))
595 (error
596 (xref-make-bogus-location (error-message-string err))))))
597 (when loc
598 (xref-make (format "(%s %s)" type symbol)
599 loc))))
600 elisp--identifier-types)))
601
602(defun elisp--xref-identifier-completion-table ()
603 elisp--identifier-completion-table)
604
551;;; Elisp Interaction mode 605;;; Elisp Interaction mode
552 606
553(defvar lisp-interaction-mode-map 607(defvar lisp-interaction-mode-map
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index b89b4cf0fe5..c6a421a3173 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -28,6 +28,7 @@
28 28
29(require 'ring) 29(require 'ring)
30(require 'button) 30(require 'button)
31(require 'xref)
31 32
32;;;###autoload 33;;;###autoload
33(defvar tags-file-name nil 34(defvar tags-file-name nil
@@ -141,11 +142,8 @@ Otherwise, `find-tag-default' is used."
141 :group 'etags 142 :group 'etags
142 :type '(choice (const nil) function)) 143 :type '(choice (const nil) function))
143 144
144(defcustom find-tag-marker-ring-length 16 145(define-obsolete-variable-alias 'find-tag-marker-ring-length
145 "Length of marker rings `find-tag-marker-ring' and `tags-location-ring'." 146 'xref-marker-ring-length "25.1")
146 :group 'etags
147 :type 'integer
148 :version "20.3")
149 147
150(defcustom tags-tag-face 'default 148(defcustom tags-tag-face 'default
151 "Face for tags in the output of `tags-apropos'." 149 "Face for tags in the output of `tags-apropos'."
@@ -182,15 +180,18 @@ Example value:
182 (sexp :tag "Tags to search"))) 180 (sexp :tag "Tags to search")))
183 :version "21.1") 181 :version "21.1")
184 182
185(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length) 183(defvaralias 'find-tag-marker-ring 'xref--marker-ring)
186 "Ring of markers which are locations from which \\[find-tag] was invoked.") 184(make-obsolete-variable
185 'find-tag-marker-ring
186 "use `xref-push-marker-stack' or `xref-pop-marker-stack' instead."
187 "25.1")
187 188
188(defvar default-tags-table-function nil 189(defvar default-tags-table-function nil
189 "If non-nil, a function to choose a default tags file for a buffer. 190 "If non-nil, a function to choose a default tags file for a buffer.
190This function receives no arguments and should return the default 191This function receives no arguments and should return the default
191tags table file to use for the current buffer.") 192tags table file to use for the current buffer.")
192 193
193(defvar tags-location-ring (make-ring find-tag-marker-ring-length) 194(defvar tags-location-ring (make-ring xref-marker-ring-length)
194 "Ring of markers which are locations visited by \\[find-tag]. 195 "Ring of markers which are locations visited by \\[find-tag].
195Pop back to the last location with \\[negative-argument] \\[find-tag].") 196Pop back to the last location with \\[negative-argument] \\[find-tag].")
196 197
@@ -713,15 +714,13 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
713 (interactive) 714 (interactive)
714 ;; Clear out the markers we are throwing away. 715 ;; Clear out the markers we are throwing away.
715 (let ((i 0)) 716 (let ((i 0))
716 (while (< i find-tag-marker-ring-length) 717 (while (< i xref-marker-ring-length)
717 (if (aref (cddr tags-location-ring) i) 718 (if (aref (cddr tags-location-ring) i)
718 (set-marker (aref (cddr tags-location-ring) i) nil)) 719 (set-marker (aref (cddr tags-location-ring) i) nil))
719 (if (aref (cddr find-tag-marker-ring) i)
720 (set-marker (aref (cddr find-tag-marker-ring) i) nil))
721 (setq i (1+ i)))) 720 (setq i (1+ i))))
721 (xref-clear-marker-stack)
722 (setq tags-file-name nil 722 (setq tags-file-name nil
723 tags-location-ring (make-ring find-tag-marker-ring-length) 723 tags-location-ring (make-ring xref-marker-ring-length)
724 find-tag-marker-ring (make-ring find-tag-marker-ring-length)
725 tags-table-list nil 724 tags-table-list nil
726 tags-table-computed-list nil 725 tags-table-computed-list nil
727 tags-table-computed-list-for nil 726 tags-table-computed-list-for nil
@@ -780,6 +779,7 @@ tags table and its (recursively) included tags tables."
780 (quit (message "Tags completion table construction aborted.") 779 (quit (message "Tags completion table construction aborted.")
781 (setq tags-completion-table nil))))) 780 (setq tags-completion-table nil)))))
782 781
782;;;###autoload
783(defun tags-lazy-completion-table () 783(defun tags-lazy-completion-table ()
784 (let ((buf (current-buffer))) 784 (let ((buf (current-buffer)))
785 (lambda (string pred action) 785 (lambda (string pred action)
@@ -898,7 +898,7 @@ See documentation of variable `tags-file-name'."
898 ;; Run the user's hook. Do we really want to do this for pop? 898 ;; Run the user's hook. Do we really want to do this for pop?
899 (run-hooks 'local-find-tag-hook)))) 899 (run-hooks 'local-find-tag-hook))))
900 ;; Record whence we came. 900 ;; Record whence we came.
901 (ring-insert find-tag-marker-ring (point-marker)) 901 (xref-push-marker-stack)
902 (if (and next-p last-tag) 902 (if (and next-p last-tag)
903 ;; Find the same table we last used. 903 ;; Find the same table we last used.
904 (visit-tags-table-buffer 'same) 904 (visit-tags-table-buffer 'same)
@@ -954,7 +954,6 @@ See documentation of variable `tags-file-name'."
954 (switch-to-buffer buf) 954 (switch-to-buffer buf)
955 (error (pop-to-buffer buf))) 955 (error (pop-to-buffer buf)))
956 (goto-char pos))) 956 (goto-char pos)))
957;;;###autoload (define-key esc-map "." 'find-tag)
958 957
959;;;###autoload 958;;;###autoload
960(defun find-tag-other-window (tagname &optional next-p regexp-p) 959(defun find-tag-other-window (tagname &optional next-p regexp-p)
@@ -995,7 +994,6 @@ See documentation of variable `tags-file-name'."
995 ;; the window's point from the buffer. 994 ;; the window's point from the buffer.
996 (set-window-point (selected-window) tagpoint)) 995 (set-window-point (selected-window) tagpoint))
997 window-point))) 996 window-point)))
998;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
999 997
1000;;;###autoload 998;;;###autoload
1001(defun find-tag-other-frame (tagname &optional next-p) 999(defun find-tag-other-frame (tagname &optional next-p)
@@ -1020,7 +1018,6 @@ See documentation of variable `tags-file-name'."
1020 (interactive (find-tag-interactive "Find tag other frame: ")) 1018 (interactive (find-tag-interactive "Find tag other frame: "))
1021 (let ((pop-up-frames t)) 1019 (let ((pop-up-frames t))
1022 (find-tag-other-window tagname next-p))) 1020 (find-tag-other-window tagname next-p)))
1023;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
1024 1021
1025;;;###autoload 1022;;;###autoload
1026(defun find-tag-regexp (regexp &optional next-p other-window) 1023(defun find-tag-regexp (regexp &optional next-p other-window)
@@ -1044,25 +1041,10 @@ See documentation of variable `tags-file-name'."
1044 ;; We go through find-tag-other-window to do all the display hair there. 1041 ;; We go through find-tag-other-window to do all the display hair there.
1045 (funcall (if other-window 'find-tag-other-window 'find-tag) 1042 (funcall (if other-window 'find-tag-other-window 'find-tag)
1046 regexp next-p t)) 1043 regexp next-p t))
1047;;;###autoload (define-key esc-map [?\C-.] 'find-tag-regexp)
1048
1049;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
1050 1044
1051;;;###autoload 1045;;;###autoload
1052(defun pop-tag-mark () 1046(defalias 'pop-tag-mark 'xref-pop-marker-stack)
1053 "Pop back to where \\[find-tag] was last invoked.
1054 1047
1055This is distinct from invoking \\[find-tag] with a negative argument
1056since that pops a stack of markers at which tags were found, not from
1057where they were found."
1058 (interactive)
1059 (if (ring-empty-p find-tag-marker-ring)
1060 (error "No previous locations for find-tag invocation"))
1061 (let ((marker (ring-remove find-tag-marker-ring 0)))
1062 (switch-to-buffer (or (marker-buffer marker)
1063 (error "The marked buffer has been deleted")))
1064 (goto-char (marker-position marker))
1065 (set-marker marker nil nil)))
1066 1048
1067(defvar tag-lines-already-matched nil 1049(defvar tag-lines-already-matched nil
1068 "Matches remembered between calls.") ; Doc string: calls to what? 1050 "Matches remembered between calls.") ; Doc string: calls to what?
@@ -1859,7 +1841,6 @@ nil, we exit; otherwise we scan the next file."
1859 (and messaged 1841 (and messaged
1860 (null tags-loop-operate) 1842 (null tags-loop-operate)
1861 (message "Scanning file %s...found" buffer-file-name)))) 1843 (message "Scanning file %s...found" buffer-file-name))))
1862;;;###autoload (define-key esc-map "," 'tags-loop-continue)
1863 1844
1864;;;###autoload 1845;;;###autoload
1865(defun tags-search (regexp &optional file-list-form) 1846(defun tags-search (regexp &optional file-list-form)
@@ -2077,6 +2058,54 @@ for \\[find-tag] (which see)."
2077 (completion-in-region (car comp-data) (cadr comp-data) 2058 (completion-in-region (car comp-data) (cadr comp-data)
2078 (nth 2 comp-data) 2059 (nth 2 comp-data)
2079 (plist-get (nthcdr 3 comp-data) :predicate))))) 2060 (plist-get (nthcdr 3 comp-data) :predicate)))))
2061
2062
2063;;; Xref backend
2064
2065;; Stop searching if we find more than xref-limit matches, as the xref
2066;; infrastracture is not designed to handle very long lists.
2067;; Switching to some kind of lazy list might be better, but hopefully
2068;; we hit the limit rarely.
2069(defconst etags--xref-limit 1000)
2070
2071;;;###autoload
2072(defun etags-xref-find (action id)
2073 (pcase action
2074 (`definitions (etags--xref-find-definitions id))
2075 (`apropos (etags--xref-find-definitions id t))))
2076
2077(defun etags--xref-find-definitions (pattern &optional regexp?)
2078 ;; This emulates the behaviour of `find-tag-in-order' but instead of
2079 ;; returning one match at a time all matches are returned as list.
2080 ;; NOTE: find-tag-tag-order is typically a buffer-local variable.
2081 (let* ((xrefs '())
2082 (first-time t)
2083 (search-fun (if regexp? #'re-search-forward #'search-forward))
2084 (marks (make-hash-table :test 'equal))
2085 (case-fold-search (if (memq tags-case-fold-search '(nil t))
2086 tags-case-fold-search
2087 case-fold-search)))
2088 (save-excursion
2089 (while (visit-tags-table-buffer (not first-time))
2090 (setq first-time nil)
2091 (dolist (order-fun (cond (regexp? find-tag-regexp-tag-order)
2092 (t find-tag-tag-order)))
2093 (goto-char (point-min))
2094 (while (and (funcall search-fun pattern nil t)
2095 (< (hash-table-count marks) etags--xref-limit))
2096 (when (funcall order-fun pattern)
2097 (beginning-of-line)
2098 (cl-destructuring-bind (hint line &rest pos) (etags-snarf-tag)
2099 (unless (eq hint t) ; hint==t if we are in a filename line
2100 (let* ((file (file-of-tag))
2101 (mark-key (cons file line)))
2102 (unless (gethash mark-key marks)
2103 (let ((loc (xref-make-file-location
2104 (expand-file-name file) line 0)))
2105 (push (xref-make hint loc) xrefs)
2106 (puthash mark-key t marks)))))))))))
2107 (nreverse xrefs)))
2108
2080 2109
2081(provide 'etags) 2110(provide 'etags)
2082 2111
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
new file mode 100644
index 00000000000..30d28ffe4c9
--- /dev/null
+++ b/lisp/progmodes/xref.el
@@ -0,0 +1,499 @@
1;; xref.el --- Cross-referencing commands -*-lexical-binding:t-*-
2
3;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; This file provides a somewhat generic infrastructure for cross
23;; referencing commands, in particular "find-definition".
24;;
25;; Some part of the functionality must be implemented in a language
26;; dependent way and that's done by defining `xref-find-function',
27;; `xref-identifier-at-point-function' and
28;; `xref-identifier-completion-table-function', which see.
29;;
30;; A major mode should make these variables buffer-local first.
31;;
32;; `xref-find-function' can be called in several ways, see its
33;; description. It has to operate with "xref" and "location" values.
34;;
35;; One would usually call `make-xref' and `xref-make-file-location',
36;; `xref-make-buffer-location' or `xref-make-bogus-location' to create
37;; them.
38;;
39;; Each identifier must be represented as a string. Implementers can
40;; use string properties to store additional information about the
41;; identifier, but they should keep in mind that values returned from
42;; `xref-identifier-completion-table-function' should still be
43;; distinct, because the user can't see the properties when making the
44;; choice.
45;;
46;; See the functions `etags-xref-find' and `elisp-xref-find' for full
47;; examples.
48
49;;; Code:
50
51(require 'cl-lib)
52(require 'eieio)
53(require 'ring)
54
55(defgroup xref nil "Cross-referencing commands"
56 :group 'tools)
57
58
59;;; Locations
60
61(defclass xref-location () ()
62 :documentation "A location represents a position in a file or buffer.")
63
64;; If a backend decides to subclass xref-location it can provide
65;; methods for some of the following functions:
66(defgeneric xref-location-marker (location)
67 "Return the marker for LOCATION.")
68
69(defgeneric xref-location-group (location)
70 "Return a string used to group a set of locations.
71This is typically the filename.")
72
73;;;; Commonly needed location classes are defined here:
74
75;; FIXME: might be useful to have an optional "hint" i.e. a string to
76;; search for in case the line number is sightly out of date.
77(defclass xref-file-location (xref-location)
78 ((file :type string :initarg :file)
79 (line :type fixnum :initarg :line)
80 (column :type fixnum :initarg :column))
81 :documentation "A file location is a file/line/column triple.
82Line numbers start from 1 and columns from 0.")
83
84(defun xref-make-file-location (file line column)
85 "Create and return a new xref-file-location."
86 (make-instance 'xref-file-location :file file :line line :column column))
87
88(defmethod xref-location-marker ((l xref-file-location))
89 (with-slots (file line column) l
90 (with-current-buffer
91 (or (get-file-buffer file)
92 (let ((find-file-suppress-same-file-warnings t))
93 (find-file-noselect file)))
94 (save-restriction
95 (widen)
96 (save-excursion
97 (goto-char (point-min))
98 (beginning-of-line line)
99 (move-to-column column)
100 (point-marker))))))
101
102(defmethod xref-location-group ((l xref-file-location))
103 (oref l :file))
104
105(defclass xref-buffer-location (xref-location)
106 ((buffer :type buffer :initarg :buffer)
107 (position :type fixnum :initarg :position)))
108
109(defun xref-make-buffer-location (buffer position)
110 "Create and return a new xref-buffer-location."
111 (make-instance 'xref-buffer-location :buffer buffer :position position))
112
113(defmethod xref-location-marker ((l xref-buffer-location))
114 (with-slots (buffer position) l
115 (let ((m (make-marker)))
116 (move-marker m position buffer))))
117
118(defmethod xref-location-group ((l xref-buffer-location))
119 (with-slots (buffer) l
120 (or (buffer-file-name buffer)
121 (format "(buffer %s)" (buffer-name buffer)))))
122
123(defclass xref-bogus-location (xref-location)
124 ((message :type string :initarg :message
125 :reader xref-bogus-location-message))
126 :documentation "Bogus locations are sometimes useful to
127indicate errors, e.g. when we know that a function exists but the
128actual location is not known.")
129
130(defun xref-make-bogus-location (message)
131 "Create and return a new xref-bogus-location."
132 (make-instance 'xref-bogus-location :message message))
133
134(defmethod xref-location-marker ((l xref-bogus-location))
135 (user-error "%s" (oref l :message)))
136
137(defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
138
139
140;;; Cross-reference
141
142(defclass xref--xref ()
143 ((description :type string :initarg :description
144 :reader xref--xref-description)
145 (location :type xref-location :initarg :location
146 :reader xref--xref-location))
147 :comment "An xref is used to display and locate constructs like
148variables or functions.")
149
150(defun xref-make (description location)
151 "Create and return a new xref.
152DESCRIPTION is a short string to describe the xref.
153LOCATION is an `xref-location'."
154 (make-instance 'xref--xref :description description :location location))
155
156
157;;; API
158
159(declare-function etags-xref-find "etags" (action id))
160(declare-function tags-lazy-completion-table "etags" ())
161
162;; For now, make the etags backend the default.
163(defvar xref-find-function #'etags-xref-find
164 "Function to look for cross-references.
165It can be called in several ways:
166
167 (definitions IDENTIFIER): Find definitions of IDENTIFIER. The
168result must be a list of xref objects. If no definitions can be
169found, return nil.
170
171 (references IDENTIFIER): Find references of IDENTIFIER. The
172result must be a list of xref objects. If no references can be
173found, return nil.
174
175 (apropos PATTERN): Find all symbols that match PATTERN. PATTERN
176is a regexp.
177
178IDENTIFIER can be any string returned by
179`xref-identifier-at-point-function', or from the table returned
180by `xref-identifier-completion-table-function'.
181
182To create an xref object, call `xref-make'.")
183
184(defvar xref-identifier-at-point-function #'xref-default-identifier-at-point
185 "Function to get the relevant identifier at point.
186
187The return value must be a string or nil. nil means no
188identifier at point found.
189
190If it's hard to determinte the identifier precisely (e.g. because
191it's a method call on unknown type), the implementation can
192return a simple string (such as symbol at point) marked with a
193special text property which `xref-find-function' would recognize
194and then delegate the work to an external process.")
195
196(defvar xref-identifier-completion-table-function #'tags-lazy-completion-table
197 "Function that returns the completion table for identifiers.")
198
199(defun xref-default-identifier-at-point ()
200 (let ((thing (thing-at-point 'symbol)))
201 (and thing (substring-no-properties thing))))
202
203
204;;; misc utilities
205(defun xref--alistify (list key test)
206 "Partition the elements of LIST into an alist.
207KEY extracts the key from an element and TEST is used to compare
208keys."
209 (let ((alist '()))
210 (dolist (e list)
211 (let* ((k (funcall key e))
212 (probe (cl-assoc k alist :test test)))
213 (if probe
214 (setcdr probe (cons e (cdr probe)))
215 (push (cons k (list e)) alist))))
216 ;; Put them back in order.
217 (cl-loop for (key . value) in (reverse alist)
218 collect (cons key (reverse value)))))
219
220(defun xref--insert-propertized (props &rest strings)
221 "Insert STRINGS with text properties PROPS."
222 (let ((start (point)))
223 (apply #'insert strings)
224 (add-text-properties start (point) props)))
225
226(defun xref--search-property (property &optional backward)
227 "Search the next text range where text property PROPERTY is non-nil.
228Return the value of PROPERTY. If BACKWARD is non-nil, search
229backward."
230 (let ((next (if backward
231 #'previous-single-char-property-change
232 #'next-single-char-property-change))
233 (start (point))
234 (value nil))
235 (while (progn
236 (goto-char (funcall next (point) property))
237 (not (or (setq value (get-text-property (point) property))
238 (eobp)
239 (bobp)))))
240 (cond (value)
241 (t (goto-char start) nil))))
242
243
244;;; Marker stack (M-. pushes, M-, pops)
245
246(defcustom xref-marker-ring-length 16
247 "Length of the xref marker ring."
248 :type 'integer
249 :version "25.1")
250
251(defvar xref--marker-ring (make-ring xref-marker-ring-length)
252 "Ring of markers to implement the marker stack.")
253
254(defun xref-push-marker-stack ()
255 "Add point to the marker stack."
256 (ring-insert xref--marker-ring (point-marker)))
257
258;;;###autoload
259(defun xref-pop-marker-stack ()
260 "Pop back to where \\[xref-find-definitions] was last invoked."
261 (interactive)
262 (let ((ring xref--marker-ring))
263 (when (ring-empty-p ring)
264 (error "Marker stack is empty"))
265 (let ((marker (ring-remove ring 0)))
266 (switch-to-buffer (or (marker-buffer marker)
267 (error "The marked buffer has been deleted")))
268 (goto-char (marker-position marker))
269 (set-marker marker nil nil))))
270
271;; etags.el needs this
272(defun xref-clear-marker-stack ()
273 "Discard all markers from the marker stack."
274 (let ((ring xref--marker-ring))
275 (while (not (ring-empty-p ring))
276 (let ((marker (ring-remove ring)))
277 (set-marker marker nil nil)))))
278
279
280(defun xref--goto-location (location)
281 "Set buffer and point according to xref-location LOCATION."
282 (let ((marker (xref-location-marker location)))
283 (set-buffer (marker-buffer marker))
284 (cond ((and (<= (point-min) marker) (<= marker (point-max))))
285 (widen-automatically (widen))
286 (t (error "Location is outside accessible part of buffer")))
287 (goto-char marker)))
288
289(defun xref--pop-to-location (location &optional window)
290 "Goto xref-location LOCATION and display the buffer.
291WINDOW controls how the buffer is displayed:
292 nil -- switch-to-buffer
293 'window -- pop-to-buffer (other window)
294 'frame -- pop-to-buffer (other frame)"
295 (xref--goto-location location)
296 (cl-ecase window
297 ((nil) (switch-to-buffer (current-buffer)))
298 (window (pop-to-buffer (current-buffer) t))
299 (frame (let ((pop-up-frames t)) (pop-to-buffer (current-buffer) t)))))
300
301
302;;; XREF buffer (part of the UI)
303
304;; The xref buffer is used to display a set of xrefs.
305
306(defun xref--display-position (pos other-window recenter-arg)
307 ;; show the location, but don't hijack focus.
308 (with-selected-window (display-buffer (current-buffer) other-window)
309 (goto-char pos)
310 (recenter recenter-arg)))
311
312(defun xref--show-location (location)
313 (condition-case err
314 (progn
315 (xref--goto-location location)
316 (xref--display-position (point) t 1))
317 (user-error (message (error-message-string err)))))
318
319(defun xref--next-line (backward)
320 (let ((loc (xref--search-property 'xref-location backward)))
321 (when loc
322 (save-window-excursion
323 (xref--show-location loc)
324 (sit-for most-positive-fixnum)))))
325
326(defun xref-next-line ()
327 "Move to the next xref and display its source in the other window."
328 (interactive)
329 (xref--next-line nil))
330
331(defun xref-prev-line ()
332 "Move to the previous xref and display its source in the other window."
333 (interactive)
334 (xref--next-line t))
335
336(defun xref--location-at-point ()
337 (or (get-text-property (point) 'xref-location)
338 (error "No reference at point")))
339
340(defvar-local xref--window nil)
341
342(defun xref-goto-xref ()
343 "Jump to the xref at point and bury the xref buffer."
344 (interactive)
345 (let ((loc (xref--location-at-point))
346 (window xref--window))
347 (quit-window)
348 (xref--pop-to-location loc window)))
349
350(define-derived-mode xref--xref-buffer-mode fundamental-mode "XREF"
351 "Mode for displaying cross-refenences."
352 (setq buffer-read-only t))
353
354(let ((map xref--xref-buffer-mode-map))
355 (define-key map (kbd "q") #'quit-window)
356 (define-key map [remap next-line] #'xref-next-line)
357 (define-key map [remap previous-line] #'xref-prev-line)
358 (define-key map (kbd "RET") #'xref-goto-xref)
359
360 ;; suggested by Johan Claesson "to further reduce finger movement":
361 (define-key map (kbd ".") #'xref-next-line)
362 (define-key map (kbd ",") #'xref-prev-line))
363
364(defconst xref-buffer-name "*xref*"
365 "The name of the buffer to show xrefs.")
366
367(defun xref--insert-xrefs (xref-alist)
368 "Insert XREF-ALIST in the current-buffer.
369XREF-ALIST is of the form ((GROUP . (XREF ...)) ...). Where
370GROUP is a string for decoration purposes and XREF is an
371`xref--xref' object."
372 (cl-loop for ((group . xrefs) . more1) on xref-alist do
373 (xref--insert-propertized '(face bold) group "\n")
374 (cl-loop for (xref . more2) on xrefs do
375 (insert " ")
376 (with-slots (description location) xref
377 (xref--insert-propertized
378 (list 'xref-location location
379 'face 'font-lock-keyword-face)
380 description))
381 (when (or more1 more2)
382 (insert "\n")))))
383
384(defun xref--analyze (xrefs)
385 "Find common filenames in XREFS.
386Return an alist of the form ((FILENAME . (XREF ...)) ...)."
387 (xref--alistify xrefs
388 (lambda (x)
389 (xref-location-group (xref--xref-location x)))
390 #'equal))
391
392(defun xref--show-xref-buffer (xrefs window)
393 (let ((xref-alist (xref--analyze xrefs)))
394 (with-current-buffer (get-buffer-create xref-buffer-name)
395 (let ((inhibit-read-only t))
396 (erase-buffer)
397 (xref--insert-xrefs xref-alist)
398 (xref--xref-buffer-mode)
399 (pop-to-buffer (current-buffer))
400 (goto-char (point-min))
401 (setq xref--window window)
402 (current-buffer)))))
403
404
405;; This part of the UI seems fairly uncontroversial: it reads the
406;; identifier and deals with the single definition case.
407;;
408;; The controversial multiple definitions case is handed off to
409;; xref-show-xrefs-function.
410
411(defvar xref-show-xrefs-function 'xref--show-xref-buffer
412 "Function to display a list of xrefs.")
413
414(defun xref--show-xrefs (id kind xrefs window)
415 (cond
416 ((null xrefs)
417 (error "No known %s for: %s" kind id))
418 ((not (cdr xrefs))
419 (xref-push-marker-stack)
420 (xref--pop-to-location (xref--xref-location (car xrefs)) window))
421 (t
422 (xref-push-marker-stack)
423 (funcall xref-show-xrefs-function xrefs window))))
424
425(defun xref--read-identifier (prompt)
426 "Return the identifier at point or read it from the minibuffer."
427 (let ((id (funcall xref-identifier-at-point-function)))
428 (cond ((or current-prefix-arg (not id))
429 (completing-read prompt
430 (funcall xref-identifier-completion-table-function)
431 nil t id))
432 (t id))))
433
434
435;;; Commands
436
437(defun xref--find-definitions (id window)
438 (xref--show-xrefs id "definitions"
439 (funcall xref-find-function 'definitions id)
440 window))
441
442;;;###autoload
443(defun xref-find-definitions (identifier)
444 "Find the definition of the identifier at point.
445With prefix argument, prompt for the identifier."
446 (interactive (list (xref--read-identifier "Find definitions of: ")))
447 (xref--find-definitions identifier nil))
448
449;;;###autoload
450(defun xref-find-definitions-other-window (identifier)
451 "Like `xref-find-definitions' but switch to the other window."
452 (interactive (list (xref--read-identifier "Find definitions of: ")))
453 (xref--find-definitions identifier 'window))
454
455;;;###autoload
456(defun xref-find-definitions-other-frame (identifier)
457 "Like `xref-find-definitions' but switch to the other frame."
458 (interactive (list (xref--read-identifier "Find definitions of: ")))
459 (xref--find-definitions identifier 'frame))
460
461;;;###autoload
462(defun xref-find-references (identifier)
463 "Find references to the identifier at point.
464With prefix argument, prompt for the identifier."
465 (interactive (list (xref--read-identifier "Find references of: ")))
466 (xref--show-xrefs identifier "references"
467 (funcall xref-find-function 'references identifier)
468 nil))
469
470;;;###autoload
471(defun xref-find-apropos (pattern)
472 "Find all meaningful symbols that match PATTERN.
473The argument has the same meaning as in `apropos'."
474 (interactive (list (read-from-minibuffer
475 "Search for pattern (word list or regexp): ")))
476 (require 'apropos)
477 (xref--show-xrefs pattern "apropos"
478 (funcall xref-find-function 'apropos
479 (apropos-parse-pattern
480 (if (string-equal (regexp-quote pattern) pattern)
481 ;; Split into words
482 (or (split-string pattern "[ \t]+" t)
483 (user-error "No word list given"))
484 pattern)))
485 nil))
486
487
488;;; Key bindings
489
490;;;###autoload (define-key esc-map "." #'xref-find-definitions)
491;;;###autoload (define-key esc-map "," #'xref-pop-marker-stack)
492;;;###autoload (define-key esc-map [?\C-.] #'xref-find-apropos)
493;;;###autoload (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
494;;;###autoload (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame)
495
496
497(provide 'xref)
498
499;;; xref.el ends here