diff options
| author | Dmitry Gutov | 2014-12-25 22:08:19 +0200 |
|---|---|---|
| committer | Dmitry Gutov | 2014-12-25 22:19:28 +0200 |
| commit | 394ce9514f0f0b473e4e8974b8529d0389fb627e (patch) | |
| tree | f1fe158638ee0a0f581fcd743c042c780d2453de | |
| parent | ac549019742bac11c249814d7744670a56671f97 (diff) | |
| download | emacs-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/NEWS | 19 | ||||
| -rw-r--r-- | lisp/ChangeLog | 30 | ||||
| -rw-r--r-- | lisp/progmodes/elisp-mode.el | 88 | ||||
| -rw-r--r-- | lisp/progmodes/etags.el | 97 | ||||
| -rw-r--r-- | lisp/progmodes/xref.el | 499 |
5 files changed, 682 insertions, 51 deletions
| @@ -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 |
| 435 | helper functions) obsolete. | 435 | helper functions) obsolete. |
| 436 | 436 | ||
| 437 | ** xref | ||
| 438 | The new package provides generic framework and new commands to find | ||
| 439 | and 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 | ||
| 443 | to pick one destination among several. Hence, `tags-toop-continue' is | ||
| 444 | unbound. `xref-pop-marker-stack' replaces `pop-tag-mark', but uses an | ||
| 445 | easier 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 | ||
| 453 | alias 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 @@ | |||
| 1 | 2014-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 | |||
| 1 | 2014-12-25 Martin Rudalics <rudalics@gmx.at> | 31 | 2014-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. |
| 190 | This function receives no arguments and should return the default | 191 | This function receives no arguments and should return the default |
| 191 | tags table file to use for the current buffer.") | 192 | tags 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]. |
| 195 | Pop back to the last location with \\[negative-argument] \\[find-tag].") | 196 | Pop 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 | ||
| 1055 | This is distinct from invoking \\[find-tag] with a negative argument | ||
| 1056 | since that pops a stack of markers at which tags were found, not from | ||
| 1057 | where 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. | ||
| 71 | This 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. | ||
| 82 | Line 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 | ||
| 127 | indicate errors, e.g. when we know that a function exists but the | ||
| 128 | actual 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 | ||
| 148 | variables or functions.") | ||
| 149 | |||
| 150 | (defun xref-make (description location) | ||
| 151 | "Create and return a new xref. | ||
| 152 | DESCRIPTION is a short string to describe the xref. | ||
| 153 | LOCATION 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. | ||
| 165 | It can be called in several ways: | ||
| 166 | |||
| 167 | (definitions IDENTIFIER): Find definitions of IDENTIFIER. The | ||
| 168 | result must be a list of xref objects. If no definitions can be | ||
| 169 | found, return nil. | ||
| 170 | |||
| 171 | (references IDENTIFIER): Find references of IDENTIFIER. The | ||
| 172 | result must be a list of xref objects. If no references can be | ||
| 173 | found, return nil. | ||
| 174 | |||
| 175 | (apropos PATTERN): Find all symbols that match PATTERN. PATTERN | ||
| 176 | is a regexp. | ||
| 177 | |||
| 178 | IDENTIFIER can be any string returned by | ||
| 179 | `xref-identifier-at-point-function', or from the table returned | ||
| 180 | by `xref-identifier-completion-table-function'. | ||
| 181 | |||
| 182 | To 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 | |||
| 187 | The return value must be a string or nil. nil means no | ||
| 188 | identifier at point found. | ||
| 189 | |||
| 190 | If it's hard to determinte the identifier precisely (e.g. because | ||
| 191 | it's a method call on unknown type), the implementation can | ||
| 192 | return a simple string (such as symbol at point) marked with a | ||
| 193 | special text property which `xref-find-function' would recognize | ||
| 194 | and 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. | ||
| 207 | KEY extracts the key from an element and TEST is used to compare | ||
| 208 | keys." | ||
| 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. | ||
| 228 | Return the value of PROPERTY. If BACKWARD is non-nil, search | ||
| 229 | backward." | ||
| 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. | ||
| 291 | WINDOW 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. | ||
| 369 | XREF-ALIST is of the form ((GROUP . (XREF ...)) ...). Where | ||
| 370 | GROUP 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. | ||
| 386 | Return 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. | ||
| 445 | With 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. | ||
| 464 | With 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. | ||
| 473 | The 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 | ||