]> code.delx.au - gnu-emacs/blob - lisp/progmodes/xref.el
Merge branch 'master' into emacs-25
[gnu-emacs] / lisp / progmodes / xref.el
1 ;; xref.el --- Cross-referencing commands -*-lexical-binding:t-*-
2
3 ;; Copyright (C) 2014-2015 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 an xref backend.
27 ;;
28 ;; That consists of a constructor function, which should return a
29 ;; backend value, and a set of implementations for the generic
30 ;; functions:
31 ;;
32 ;; `xref-backend-identifier-at-point',
33 ;; `xref-backend-identifier-completion-table',
34 ;; `xref-backend-definitions', `xref-backend-references',
35 ;; `xref-backend-apropos', which see.
36 ;;
37 ;; A major mode would normally use `add-hook' to add the backend
38 ;; constructor to `xref-backend-functions'.
39 ;;
40 ;; The last three methods operate with "xref" and "location" values.
41 ;;
42 ;; One would usually call `make-xref' and `xref-make-file-location',
43 ;; `xref-make-buffer-location' or `xref-make-bogus-location' to create
44 ;; them. More generally, a location must be an instance of an EIEIO
45 ;; class inheriting from `xref-location' and implementing
46 ;; `xref-location-group' and `xref-location-marker'.
47 ;;
48 ;; There's a special kind of xrefs we call "match xrefs", which
49 ;; correspond to search results. For these values,
50 ;; `xref-match-length' must be defined, and `xref-location-marker'
51 ;; must return the beginning of the match.
52 ;;
53 ;; Each identifier must be represented as a string. Implementers can
54 ;; use string properties to store additional information about the
55 ;; identifier, but they should keep in mind that values returned from
56 ;; `xref-backend-identifier-completion-table' should still be
57 ;; distinct, because the user can't see the properties when making the
58 ;; choice.
59 ;;
60 ;; See the etags and elisp-mode implementations for full examples.
61
62 ;;; Code:
63
64 (require 'cl-lib)
65 (require 'eieio)
66 (require 'ring)
67 (require 'pcase)
68 (require 'project)
69
70 (eval-when-compile
71 (require 'semantic/symref)) ;; for hit-lines slot
72
73 (defgroup xref nil "Cross-referencing commands"
74 :group 'tools)
75
76 \f
77 ;;; Locations
78
79 (defclass xref-location () ()
80 :documentation "A location represents a position in a file or buffer.")
81
82 (cl-defgeneric xref-location-marker (location)
83 "Return the marker for LOCATION.")
84
85 (cl-defgeneric xref-location-group (location)
86 "Return a string used to group a set of locations.
87 This is typically the filename.")
88
89 (cl-defgeneric xref-location-line (_location)
90 "Return the line number corresponding to the location."
91 nil)
92
93 (cl-defgeneric xref-match-length (_item)
94 "Return the length of the match."
95 nil)
96
97 ;;;; Commonly needed location classes are defined here:
98
99 ;; FIXME: might be useful to have an optional "hint" i.e. a string to
100 ;; search for in case the line number is sightly out of date.
101 (defclass xref-file-location (xref-location)
102 ((file :type string :initarg :file)
103 (line :type fixnum :initarg :line :reader xref-location-line)
104 (column :type fixnum :initarg :column :reader xref-file-location-column))
105 :documentation "A file location is a file/line/column triple.
106 Line numbers start from 1 and columns from 0.")
107
108 (defun xref-make-file-location (file line column)
109 "Create and return a new `xref-file-location'."
110 (make-instance 'xref-file-location :file file :line line :column column))
111
112 (cl-defmethod xref-location-marker ((l xref-file-location))
113 (with-slots (file line column) l
114 (with-current-buffer
115 (or (get-file-buffer file)
116 (let ((find-file-suppress-same-file-warnings t))
117 (find-file-noselect file)))
118 (save-restriction
119 (widen)
120 (save-excursion
121 (goto-char (point-min))
122 (beginning-of-line line)
123 (forward-char column)
124 (point-marker))))))
125
126 (cl-defmethod xref-location-group ((l xref-file-location))
127 (oref l file))
128
129 (defclass xref-buffer-location (xref-location)
130 ((buffer :type buffer :initarg :buffer)
131 (position :type fixnum :initarg :position)))
132
133 (defun xref-make-buffer-location (buffer position)
134 "Create and return a new `xref-buffer-location'."
135 (make-instance 'xref-buffer-location :buffer buffer :position position))
136
137 (cl-defmethod xref-location-marker ((l xref-buffer-location))
138 (with-slots (buffer position) l
139 (let ((m (make-marker)))
140 (move-marker m position buffer))))
141
142 (cl-defmethod xref-location-group ((l xref-buffer-location))
143 (with-slots (buffer) l
144 (or (buffer-file-name buffer)
145 (format "(buffer %s)" (buffer-name buffer)))))
146
147 (defclass xref-bogus-location (xref-location)
148 ((message :type string :initarg :message
149 :reader xref-bogus-location-message))
150 :documentation "Bogus locations are sometimes useful to
151 indicate errors, e.g. when we know that a function exists but the
152 actual location is not known.")
153
154 (defun xref-make-bogus-location (message)
155 "Create and return a new `xref-bogus-location'."
156 (make-instance 'xref-bogus-location :message message))
157
158 (cl-defmethod xref-location-marker ((l xref-bogus-location))
159 (user-error "%s" (oref l message)))
160
161 (cl-defmethod xref-location-group ((_ xref-bogus-location)) "(No location)")
162
163 \f
164 ;;; Cross-reference
165
166 (defclass xref-item ()
167 ((summary :type string :initarg :summary
168 :reader xref-item-summary
169 :documentation "One line which will be displayed for
170 this item in the output buffer.")
171 (location :initarg :location
172 :reader xref-item-location
173 :documentation "An object describing how to navigate
174 to the reference's target."))
175 :comment "An xref item describes a reference to a location
176 somewhere.")
177
178 (defun xref-make (summary location)
179 "Create and return a new `xref-item'.
180 SUMMARY is a short string to describe the xref.
181 LOCATION is an `xref-location'."
182 (make-instance 'xref-item :summary summary :location location))
183
184 (defclass xref-match-item ()
185 ((summary :type string :initarg :summary
186 :reader xref-item-summary)
187 (location :initarg :location
188 :type xref-file-location
189 :reader xref-item-location)
190 (length :initarg :length :reader xref-match-length))
191 :comment "A match xref item describes a search result.")
192
193 (defun xref-make-match (summary location length)
194 "Create and return a new `xref-match-item'.
195 SUMMARY is a short string to describe the xref.
196 LOCATION is an `xref-location'.
197 LENGTH is the match length, in characters."
198 (make-instance 'xref-match-item :summary summary
199 :location location :length length))
200
201 \f
202 ;;; API
203
204 ;; We make the etags backend the default for now, until something
205 ;; better comes along.
206 (defvar xref-backend-functions (list #'xref--etags-backend)
207 "Special hook to find the xref backend for the current context.
208 Each functions on this hook is called in turn with no arguments
209 and should return either nil to mean that it is not applicable,
210 or an xref backend, which is a value to be used to dispatch the
211 generic functions.")
212
213 (defun xref-find-backend ()
214 (run-hook-with-args-until-success 'xref-backend-functions))
215
216 (defun xref--etags-backend () 'etags)
217
218 (cl-defgeneric xref-backend-definitions (backend identifier)
219 "Find definitions of IDENTIFIER.
220
221 The result must be a list of xref objects. If IDENTIFIER
222 contains sufficient information to determine a unique definition,
223 return only that definition. If there are multiple possible
224 definitions, return all of them. If no definitions can be found,
225 return nil.
226
227 IDENTIFIER can be any string returned by
228 `xref-backend-identifier-at-point', or from the table returned by
229 `xref-backend-identifier-completion-table'.
230
231 To create an xref object, call `xref-make'.")
232
233 (cl-defgeneric xref-backend-references (backend identifier)
234 "Find references of IDENTIFIER.
235 The result must be a list of xref objects. If no references can
236 be found, return nil.")
237
238 (cl-defgeneric xref-backend-apropos (backend pattern)
239 "Find all symbols that match PATTERN.
240 PATTERN is a regexp")
241
242 (cl-defgeneric xref-backend-identifier-at-point (_backend)
243 "Return the relevant identifier at point.
244
245 The return value must be a string or nil. nil means no
246 identifier at point found.
247
248 If it's hard to determine the identifier precisely (e.g., because
249 it's a method call on unknown type), the implementation can
250 return a simple string (such as symbol at point) marked with a
251 special text property which e.g. `xref-backend-definitions' would
252 recognize and then delegate the work to an external process."
253 (let ((thing (thing-at-point 'symbol)))
254 (and thing (substring-no-properties thing))))
255
256 (cl-defgeneric xref-backend-identifier-completion-table (backend)
257 "Returns the completion table for identifiers.")
258
259 \f
260 ;;; misc utilities
261 (defun xref--alistify (list key test)
262 "Partition the elements of LIST into an alist.
263 KEY extracts the key from an element and TEST is used to compare
264 keys."
265 (let ((alist '()))
266 (dolist (e list)
267 (let* ((k (funcall key e))
268 (probe (cl-assoc k alist :test test)))
269 (if probe
270 (setcdr probe (cons e (cdr probe)))
271 (push (cons k (list e)) alist))))
272 ;; Put them back in order.
273 (cl-loop for (key . value) in (reverse alist)
274 collect (cons key (reverse value)))))
275
276 (defun xref--insert-propertized (props &rest strings)
277 "Insert STRINGS with text properties PROPS."
278 (let ((start (point)))
279 (apply #'insert strings)
280 (add-text-properties start (point) props)))
281
282 (defun xref--search-property (property &optional backward)
283 "Search the next text range where text property PROPERTY is non-nil.
284 Return the value of PROPERTY. If BACKWARD is non-nil, search
285 backward."
286 (let ((next (if backward
287 #'previous-single-char-property-change
288 #'next-single-char-property-change))
289 (start (point))
290 (value nil))
291 (while (progn
292 (goto-char (funcall next (point) property))
293 (not (or (setq value (get-text-property (point) property))
294 (eobp)
295 (bobp)))))
296 (cond (value)
297 (t (goto-char start) nil))))
298
299 \f
300 ;;; Marker stack (M-. pushes, M-, pops)
301
302 (defcustom xref-marker-ring-length 16
303 "Length of the xref marker ring."
304 :type 'integer)
305
306 (defcustom xref-prompt-for-identifier '(not xref-find-definitions
307 xref-find-definitions-other-window
308 xref-find-definitions-other-frame)
309 "When t, always prompt for the identifier name.
310
311 When nil, prompt only when there's no value at point we can use,
312 or when the command has been called with the prefix argument.
313
314 Otherwise, it's a list of xref commands which will prompt
315 anyway (the value at point, if any, will be used as the default).
316
317 If the list starts with `not', the meaning of the rest of the
318 elements is negated."
319 :type '(choice (const :tag "always" t)
320 (const :tag "auto" nil)
321 (set :menu-tag "command specific" :tag "commands"
322 :value (not)
323 (const :tag "Except" not)
324 (repeat :inline t (symbol :tag "command")))))
325
326 (defcustom xref-after-jump-hook '(recenter
327 xref-pulse-momentarily)
328 "Functions called after jumping to an xref."
329 :type 'hook)
330
331 (defcustom xref-after-return-hook '(xref-pulse-momentarily)
332 "Functions called after returning to a pre-jump location."
333 :type 'hook)
334
335 (defvar xref--marker-ring (make-ring xref-marker-ring-length)
336 "Ring of markers to implement the marker stack.")
337
338 (defun xref-push-marker-stack (&optional m)
339 "Add point M (defaults to `point-marker') to the marker stack."
340 (ring-insert xref--marker-ring (or m (point-marker))))
341
342 ;;;###autoload
343 (defun xref-pop-marker-stack ()
344 "Pop back to where \\[xref-find-definitions] was last invoked."
345 (interactive)
346 (let ((ring xref--marker-ring))
347 (when (ring-empty-p ring)
348 (error "Marker stack is empty"))
349 (let ((marker (ring-remove ring 0)))
350 (switch-to-buffer (or (marker-buffer marker)
351 (error "The marked buffer has been deleted")))
352 (goto-char (marker-position marker))
353 (set-marker marker nil nil)
354 (run-hooks 'xref-after-return-hook))))
355
356 (defvar xref--current-item nil)
357
358 (defun xref-pulse-momentarily ()
359 (pcase-let ((`(,beg . ,end)
360 (save-excursion
361 (or
362 (let ((length (xref-match-length xref--current-item)))
363 (and length (cons (point) (+ (point) length))))
364 (back-to-indentation)
365 (if (eolp)
366 (cons (line-beginning-position) (1+ (point)))
367 (cons (point) (line-end-position)))))))
368 (pulse-momentary-highlight-region beg end 'next-error)))
369
370 ;; etags.el needs this
371 (defun xref-clear-marker-stack ()
372 "Discard all markers from the marker stack."
373 (let ((ring xref--marker-ring))
374 (while (not (ring-empty-p ring))
375 (let ((marker (ring-remove ring)))
376 (set-marker marker nil nil)))))
377
378 ;;;###autoload
379 (defun xref-marker-stack-empty-p ()
380 "Return t if the marker stack is empty; nil otherwise."
381 (ring-empty-p xref--marker-ring))
382
383 \f
384
385 (defun xref--goto-char (pos)
386 (cond
387 ((and (<= (point-min) pos) (<= pos (point-max))))
388 (widen-automatically (widen))
389 (t (user-error "Position is outside accessible part of buffer")))
390 (goto-char pos))
391
392 (defun xref--goto-location (location)
393 "Set buffer and point according to xref-location LOCATION."
394 (let ((marker (xref-location-marker location)))
395 (set-buffer (marker-buffer marker))
396 (xref--goto-char marker)))
397
398 (defun xref--pop-to-location (item &optional window)
399 "Go to the location of ITEM and display the buffer.
400 WINDOW controls how the buffer is displayed:
401 nil -- switch-to-buffer
402 `window' -- pop-to-buffer (other window)
403 `frame' -- pop-to-buffer (other frame)"
404 (let* ((marker (save-excursion
405 (xref-location-marker (xref-item-location item))))
406 (buf (marker-buffer marker)))
407 (cl-ecase window
408 ((nil) (switch-to-buffer buf))
409 (window (pop-to-buffer buf t))
410 (frame (let ((pop-up-frames t)) (pop-to-buffer buf t))))
411 (xref--goto-char marker))
412 (let ((xref--current-item item))
413 (run-hooks 'xref-after-jump-hook)))
414
415 \f
416 ;;; XREF buffer (part of the UI)
417
418 ;; The xref buffer is used to display a set of xrefs.
419
420 (defvar-local xref--display-history nil
421 "List of pairs (BUFFER . WINDOW), for temporarily displayed buffers.")
422
423 (defun xref--save-to-history (buf win)
424 (let ((restore (window-parameter win 'quit-restore)))
425 ;; Save the new entry if the window displayed another buffer
426 ;; previously.
427 (when (and restore (not (eq (car restore) 'same)))
428 (push (cons buf win) xref--display-history))))
429
430 (defun xref--display-position (pos other-window buf)
431 ;; Show the location, but don't hijack focus.
432 (let ((xref-buf (current-buffer)))
433 (with-selected-window (display-buffer buf other-window)
434 (xref--goto-char pos)
435 (run-hooks 'xref-after-jump-hook)
436 (let ((buf (current-buffer))
437 (win (selected-window)))
438 (with-current-buffer xref-buf
439 (setq-local other-window-scroll-buffer buf)
440 (xref--save-to-history buf win))))))
441
442 (defun xref--show-location (location)
443 (condition-case err
444 (let* ((marker (xref-location-marker location))
445 (buf (marker-buffer marker)))
446 (xref--display-position marker t buf))
447 (user-error (message (error-message-string err)))))
448
449 (defun xref-show-location-at-point ()
450 "Display the source of xref at point in the other window, if any."
451 (interactive)
452 (let* ((xref (xref--item-at-point))
453 (xref--current-item xref))
454 (when xref
455 (xref--show-location (xref-item-location xref)))))
456
457 (defun xref-next-line ()
458 "Move to the next xref and display its source in the other window."
459 (interactive)
460 (xref--search-property 'xref-item)
461 (xref-show-location-at-point))
462
463 (defun xref-prev-line ()
464 "Move to the previous xref and display its source in the other window."
465 (interactive)
466 (xref--search-property 'xref-item t)
467 (xref-show-location-at-point))
468
469 (defun xref--item-at-point ()
470 (save-excursion
471 (back-to-indentation)
472 (get-text-property (point) 'xref-item)))
473
474 (defvar-local xref--window nil
475 "ACTION argument to call `display-buffer' with.")
476
477 (defun xref-goto-xref ()
478 "Jump to the xref on the current line and bury the xref buffer."
479 (interactive)
480 (let ((xref (or (xref--item-at-point)
481 (user-error "No reference at point")))
482 (window xref--window))
483 (xref-quit)
484 (xref--pop-to-location xref window)))
485
486 (defun xref-query-replace (from to)
487 "Perform interactive replacement in all current matches."
488 (interactive
489 (list (read-regexp "Query replace regexp in matches" ".*")
490 (read-regexp "Replace with: ")))
491 (let (pairs item)
492 (unwind-protect
493 (progn
494 (save-excursion
495 (goto-char (point-min))
496 (while (setq item (xref--search-property 'xref-item))
497 (when (xref-match-length item)
498 (save-excursion
499 (let* ((loc (xref-item-location item))
500 (beg (xref-location-marker loc))
501 (len (xref-match-length item)))
502 ;; Perform sanity check first.
503 (xref--goto-location loc)
504 ;; FIXME: The check should probably be a generic
505 ;; function, instead of the assumption that all
506 ;; matches contain the full line as summary.
507 ;; TODO: Offer to re-scan otherwise.
508 (unless (equal (buffer-substring-no-properties
509 (line-beginning-position)
510 (line-end-position))
511 (xref-item-summary item))
512 (user-error "Search results out of date"))
513 (push (cons beg len) pairs)))))
514 (setq pairs (nreverse pairs)))
515 (unless pairs (user-error "No suitable matches here"))
516 (xref--query-replace-1 from to pairs))
517 (dolist (pair pairs)
518 (move-marker (car pair) nil)))))
519
520 ;; FIXME: Write a nicer UI.
521 (defun xref--query-replace-1 (from to pairs)
522 (let* ((query-replace-lazy-highlight nil)
523 current-beg current-len current-buf
524 ;; Counteract the "do the next match now" hack in
525 ;; `perform-replace'. And still, it'll report that those
526 ;; matches were "filtered out" at the end.
527 (isearch-filter-predicate
528 (lambda (beg end)
529 (and current-beg
530 (eq (current-buffer) current-buf)
531 (>= beg current-beg)
532 (<= end (+ current-beg current-len)))))
533 (replace-re-search-function
534 (lambda (from &optional _bound noerror)
535 (let (found pair)
536 (while (and (not found) pairs)
537 (setq pair (pop pairs)
538 current-beg (car pair)
539 current-len (cdr pair)
540 current-buf (marker-buffer current-beg))
541 (pop-to-buffer current-buf)
542 (goto-char current-beg)
543 (when (re-search-forward from (+ current-beg current-len) noerror)
544 (setq found t)))
545 found))))
546 ;; FIXME: Despite this being a multi-buffer replacement, `N'
547 ;; doesn't work, because we're not using
548 ;; `multi-query-replace-map', and it would expect the below
549 ;; function to be called once per buffer.
550 (perform-replace from to t t nil)))
551
552 (defvar xref--xref-buffer-mode-map
553 (let ((map (make-sparse-keymap)))
554 (define-key map [remap quit-window] #'xref-quit)
555 (define-key map (kbd "n") #'xref-next-line)
556 (define-key map (kbd "p") #'xref-prev-line)
557 (define-key map (kbd "r") #'xref-query-replace)
558 (define-key map (kbd "RET") #'xref-goto-xref)
559 (define-key map (kbd "C-o") #'xref-show-location-at-point)
560 ;; suggested by Johan Claesson "to further reduce finger movement":
561 (define-key map (kbd ".") #'xref-next-line)
562 (define-key map (kbd ",") #'xref-prev-line)
563 map))
564
565 (define-derived-mode xref--xref-buffer-mode special-mode "XREF"
566 "Mode for displaying cross-references."
567 (setq buffer-read-only t)
568 (setq next-error-function #'xref--next-error-function)
569 (setq next-error-last-buffer (current-buffer)))
570
571 (defun xref--next-error-function (n reset?)
572 (when reset?
573 (goto-char (point-min)))
574 (let ((backward (< n 0))
575 (n (abs n))
576 (xref nil))
577 (dotimes (_ n)
578 (setq xref (xref--search-property 'xref-item backward)))
579 (cond (xref
580 (xref--pop-to-location xref))
581 (t
582 (error "No %s xref" (if backward "previous" "next"))))))
583
584 (defun xref-quit (&optional kill)
585 "Bury temporarily displayed buffers, then quit the current window.
586
587 If KILL is non-nil, also kill the current buffer.
588
589 The buffers that the user has otherwise interacted with in the
590 meantime are preserved."
591 (interactive "P")
592 (let ((window (selected-window))
593 (history xref--display-history))
594 (setq xref--display-history nil)
595 (pcase-dolist (`(,buf . ,win) history)
596 (when (and (window-live-p win)
597 (eq buf (window-buffer win)))
598 (quit-window nil win)))
599 (quit-window kill window)))
600
601 (defconst xref-buffer-name "*xref*"
602 "The name of the buffer to show xrefs.")
603
604 (defvar xref--button-map
605 (let ((map (make-sparse-keymap)))
606 (define-key map [(control ?m)] #'xref-goto-xref)
607 (define-key map [mouse-1] #'xref-goto-xref)
608 (define-key map [mouse-2] #'xref--mouse-2)
609 map))
610
611 (defun xref--mouse-2 (event)
612 "Move point to the button and show the xref definition."
613 (interactive "e")
614 (mouse-set-point event)
615 (forward-line 0)
616 (xref--search-property 'xref-item)
617 (xref-show-location-at-point))
618
619 (defun xref--insert-xrefs (xref-alist)
620 "Insert XREF-ALIST in the current-buffer.
621 XREF-ALIST is of the form ((GROUP . (XREF ...)) ...), where
622 GROUP is a string for decoration purposes and XREF is an
623 `xref-item' object."
624 (require 'compile) ; For the compilation faces.
625 (cl-loop for ((group . xrefs) . more1) on xref-alist
626 for max-line-width =
627 (cl-loop for xref in xrefs
628 maximize (let ((line (xref-location-line
629 (oref xref location))))
630 (length (and line (format "%d" line)))))
631 for line-format = (and max-line-width
632 (format "%%%dd: " max-line-width))
633 do
634 (xref--insert-propertized '(face compilation-info) group "\n")
635 (cl-loop for (xref . more2) on xrefs do
636 (with-slots (summary location) xref
637 (let* ((line (xref-location-line location))
638 (prefix
639 (if line
640 (propertize (format line-format line)
641 'face 'compilation-line-number)
642 " ")))
643 (xref--insert-propertized
644 (list 'xref-item xref
645 ;; 'face 'font-lock-keyword-face
646 'mouse-face 'highlight
647 'keymap xref--button-map
648 'help-echo
649 (concat "mouse-2: display in another window, "
650 "RET or mouse-1: follow reference"))
651 prefix summary)))
652 (insert "\n"))))
653
654 (defun xref--analyze (xrefs)
655 "Find common filenames in XREFS.
656 Return an alist of the form ((FILENAME . (XREF ...)) ...)."
657 (xref--alistify xrefs
658 (lambda (x)
659 (xref-location-group (xref-item-location x)))
660 #'equal))
661
662 (defun xref--show-xref-buffer (xrefs alist)
663 (let ((xref-alist (xref--analyze xrefs)))
664 (with-current-buffer (get-buffer-create xref-buffer-name)
665 (let ((inhibit-read-only t))
666 (erase-buffer)
667 (xref--insert-xrefs xref-alist)
668 (xref--xref-buffer-mode)
669 (pop-to-buffer (current-buffer))
670 (goto-char (point-min))
671 (setq xref--window (assoc-default 'window alist))
672 (current-buffer)))))
673
674 \f
675 ;; This part of the UI seems fairly uncontroversial: it reads the
676 ;; identifier and deals with the single definition case.
677 ;; (FIXME: do we really want this case to be handled like that in
678 ;; "find references" and "find regexp searches"?)
679 ;;
680 ;; The controversial multiple definitions case is handed off to
681 ;; xref-show-xrefs-function.
682
683 (defvar xref-show-xrefs-function 'xref--show-xref-buffer
684 "Function to display a list of xrefs.")
685
686 (defvar xref--read-identifier-history nil)
687
688 (defvar xref--read-pattern-history nil)
689
690 (defun xref--show-xrefs (xrefs window)
691 (cond
692 ((not (cdr xrefs))
693 (xref-push-marker-stack)
694 (xref--pop-to-location (car xrefs) window))
695 (t
696 (xref-push-marker-stack)
697 (funcall xref-show-xrefs-function xrefs
698 `((window . ,window))))))
699
700 (defun xref--prompt-p (command)
701 (or (eq xref-prompt-for-identifier t)
702 (if (eq (car xref-prompt-for-identifier) 'not)
703 (not (memq command (cdr xref-prompt-for-identifier)))
704 (memq command xref-prompt-for-identifier))))
705
706 (defun xref--read-identifier (prompt)
707 "Return the identifier at point or read it from the minibuffer."
708 (let* ((backend (xref-find-backend))
709 (id (xref-backend-identifier-at-point backend)))
710 (cond ((or current-prefix-arg
711 (not id)
712 (xref--prompt-p this-command))
713 (completing-read (if id
714 (format "%s (default %s): "
715 (substring prompt 0 (string-match
716 "[ :]+\\'" prompt))
717 id)
718 prompt)
719 (xref-backend-identifier-completion-table backend)
720 nil nil nil
721 'xref--read-identifier-history id))
722 (t id))))
723
724 \f
725 ;;; Commands
726
727 (defun xref--find-xrefs (input kind arg window)
728 (let ((xrefs (funcall (intern (format "xref-backend-%s" kind))
729 (xref-find-backend)
730 arg)))
731 (unless xrefs
732 (user-error "No %s found for: %s" (symbol-name kind) input))
733 (xref--show-xrefs xrefs window)))
734
735 (defun xref--find-definitions (id window)
736 (xref--find-xrefs id 'definitions id window))
737
738 ;;;###autoload
739 (defun xref-find-definitions (identifier)
740 "Find the definition of the identifier at point.
741 With prefix argument or when there's no identifier at point,
742 prompt for it.
743
744 If the backend has sufficient information to determine a unique
745 definition for IDENTIFIER, it returns only that definition. If
746 there are multiple possible definitions, it returns all of them.
747
748 If the backend returns one definition, jump to it; otherwise,
749 display the list in a buffer."
750 (interactive (list (xref--read-identifier "Find definitions of: ")))
751 (xref--find-definitions identifier nil))
752
753 ;;;###autoload
754 (defun xref-find-definitions-other-window (identifier)
755 "Like `xref-find-definitions' but switch to the other window."
756 (interactive (list (xref--read-identifier "Find definitions of: ")))
757 (xref--find-definitions identifier 'window))
758
759 ;;;###autoload
760 (defun xref-find-definitions-other-frame (identifier)
761 "Like `xref-find-definitions' but switch to the other frame."
762 (interactive (list (xref--read-identifier "Find definitions of: ")))
763 (xref--find-definitions identifier 'frame))
764
765 ;;;###autoload
766 (defun xref-find-references (identifier)
767 "Find references to the identifier at point.
768 With prefix argument, prompt for the identifier."
769 (interactive (list (xref--read-identifier "Find references of: ")))
770 (xref--find-xrefs identifier 'references identifier nil))
771
772 (declare-function apropos-parse-pattern "apropos" (pattern))
773
774 ;;;###autoload
775 (defun xref-find-apropos (pattern)
776 "Find all meaningful symbols that match PATTERN.
777 The argument has the same meaning as in `apropos'."
778 (interactive (list (read-string
779 "Search for pattern (word list or regexp): "
780 nil 'xref--read-pattern-history)))
781 (require 'apropos)
782 (xref--find-xrefs pattern 'apropos
783 (apropos-parse-pattern
784 (if (string-equal (regexp-quote pattern) pattern)
785 ;; Split into words
786 (or (split-string pattern "[ \t]+" t)
787 (user-error "No word list given"))
788 pattern))
789 nil))
790
791 \f
792 ;;; Key bindings
793
794 ;;;###autoload (define-key esc-map "." #'xref-find-definitions)
795 ;;;###autoload (define-key esc-map "," #'xref-pop-marker-stack)
796 ;;;###autoload (define-key esc-map "?" #'xref-find-references)
797 ;;;###autoload (define-key esc-map [?\C-.] #'xref-find-apropos)
798 ;;;###autoload (define-key ctl-x-4-map "." #'xref-find-definitions-other-window)
799 ;;;###autoload (define-key ctl-x-5-map "." #'xref-find-definitions-other-frame)
800
801 \f
802 ;;; Helper functions
803
804 (defvar xref-etags-mode--saved nil)
805
806 (define-minor-mode xref-etags-mode
807 "Minor mode to make xref use etags again.
808
809 Certain major modes install their own mechanisms for listing
810 identifiers and navigation. Turn this on to undo those settings
811 and just use etags."
812 :lighter ""
813 (if xref-etags-mode
814 (progn
815 (setq xref-etags-mode--saved xref-backend-functions)
816 (kill-local-variable 'xref-backend-functions))
817 (setq-local xref-backend-functions xref-etags-mode--saved)))
818
819 (declare-function semantic-symref-find-references-by-name "semantic/symref")
820 (declare-function semantic-find-file-noselect "semantic/fw")
821 (declare-function grep-expand-template "grep")
822
823 (defun xref-collect-references (symbol dir)
824 "Collect references to SYMBOL inside DIR.
825 This function uses the Semantic Symbol Reference API, see
826 `semantic-symref-find-references-by-name' for details on which
827 tools are used, and when."
828 (cl-assert (directory-name-p dir))
829 (require 'semantic/symref)
830 (defvar semantic-symref-tool)
831 (let* ((default-directory dir)
832 (semantic-symref-tool 'detect)
833 (res (semantic-symref-find-references-by-name symbol 'subdirs))
834 (hits (and res (oref res hit-lines)))
835 (orig-buffers (buffer-list)))
836 (unwind-protect
837 (cl-mapcan (lambda (hit) (xref--collect-matches
838 hit (format "\\_<%s\\_>" (regexp-quote symbol))))
839 hits)
840 ;; TODO: Implement "lightweight" buffer visiting, so that we
841 ;; don't have to kill them.
842 (mapc #'kill-buffer
843 (cl-set-difference (buffer-list) orig-buffers)))))
844
845 (defun xref-collect-matches (regexp files dir ignores)
846 "Collect matches for REGEXP inside FILES in DIR.
847 FILES is a string with glob patterns separated by spaces.
848 IGNORES is a list of glob patterns."
849 (cl-assert (directory-name-p dir))
850 (require 'semantic/fw)
851 (grep-compute-defaults)
852 (defvar grep-find-template)
853 (defvar grep-highlight-matches)
854 (let* ((grep-find-template (replace-regexp-in-string "-e " "-E "
855 grep-find-template t t))
856 (grep-highlight-matches nil)
857 (command (xref--rgrep-command (xref--regexp-to-extended regexp)
858 files dir ignores))
859 (orig-buffers (buffer-list))
860 (buf (get-buffer-create " *xref-grep*"))
861 (grep-re (caar grep-regexp-alist))
862 hits)
863 (with-current-buffer buf
864 (erase-buffer)
865 (call-process-shell-command command nil t)
866 (goto-char (point-min))
867 (while (re-search-forward grep-re nil t)
868 (push (cons (string-to-number (match-string 2))
869 (match-string 1))
870 hits)))
871 (unwind-protect
872 (cl-mapcan (lambda (hit) (xref--collect-matches hit regexp))
873 (nreverse hits))
874 ;; TODO: Same as above.
875 (mapc #'kill-buffer
876 (cl-set-difference (buffer-list) orig-buffers)))))
877
878 (defun xref--rgrep-command (regexp files dir ignores)
879 (require 'find-dired) ; for `find-name-arg'
880 (defvar grep-find-template)
881 (defvar find-name-arg)
882 (grep-expand-template
883 grep-find-template
884 regexp
885 (concat (shell-quote-argument "(")
886 " " find-name-arg " "
887 (mapconcat
888 #'shell-quote-argument
889 (split-string files)
890 (concat " -o " find-name-arg " "))
891 " "
892 (shell-quote-argument ")"))
893 dir
894 (concat
895 (shell-quote-argument "(")
896 " -path "
897 (mapconcat
898 (lambda (ignore)
899 (when (string-match-p "/\\'" ignore)
900 (setq ignore (concat ignore "*")))
901 (if (string-match "\\`\\./" ignore)
902 (setq ignore (replace-match dir t t ignore))
903 (unless (string-prefix-p "*" ignore)
904 (setq ignore (concat "*/" ignore))))
905 (shell-quote-argument ignore))
906 ignores
907 " -o -path ")
908 " "
909 (shell-quote-argument ")")
910 " -prune -o ")))
911
912 (defun xref--regexp-to-extended (str)
913 (replace-regexp-in-string
914 ;; FIXME: Add tests. Move to subr.el, make a public function.
915 ;; Maybe error on Emacs-only constructs.
916 "\\(?:\\\\\\\\\\)*\\(?:\\\\[][]\\)?\\(?:\\[.+?\\]\\|\\(\\\\?[(){}|]\\)\\)"
917 (lambda (str)
918 (cond
919 ((not (match-beginning 1))
920 str)
921 ((eq (length (match-string 1 str)) 2)
922 (concat (substring str 0 (match-beginning 1))
923 (substring (match-string 1 str) 1 2)))
924 (t
925 (concat (substring str 0 (match-beginning 1))
926 "\\"
927 (match-string 1 str)))))
928 str t t))
929
930 (defun xref--collect-matches (hit regexp)
931 (pcase-let* ((`(,line . ,file) hit)
932 (buf (or (find-buffer-visiting file)
933 (semantic-find-file-noselect file))))
934 (with-current-buffer buf
935 (save-excursion
936 (goto-char (point-min))
937 (forward-line (1- line))
938 (let ((line-end (line-end-position))
939 (line-beg (line-beginning-position))
940 matches)
941 (syntax-propertize line-end)
942 ;; FIXME: This results in several lines with the same
943 ;; summary. Solve with composite pattern?
944 (while (re-search-forward regexp line-end t)
945 (let* ((beg-column (- (match-beginning 0) line-beg))
946 (end-column (- (match-end 0) line-beg))
947 (loc (xref-make-file-location file line beg-column))
948 (summary (buffer-substring line-beg line-end)))
949 (add-face-text-property beg-column end-column 'highlight
950 t summary)
951 (push (xref-make-match summary loc (- end-column beg-column))
952 matches)))
953 (nreverse matches))))))
954
955 (provide 'xref)
956
957 ;;; xref.el ends here