]> code.delx.au - gnu-emacs/blob - lisp/cedet/semantic/symref.el
Leading "*" in the doc of defvars is long obsolete.
[gnu-emacs] / lisp / cedet / semantic / symref.el
1 ;;; semantic/symref.el --- Symbol Reference API
2
3 ;; Copyright (C) 2008-2016 Free Software Foundation, Inc.
4
5 ;; Author: Eric M. Ludlam <eric@siege-engine.com>
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21
22 ;;; Commentary:
23 ;;
24 ;; Semantic Symbol Reference API.
25 ;;
26 ;; Semantic's native parsing tools do not handle symbol references.
27 ;; Tracking such information is a task that requires a huge amount of
28 ;; space and processing not appropriate for an Emacs Lisp program.
29 ;;
30 ;; Many desired tools used in refactoring, however, need to have
31 ;; such references available to them. This API aims to provide a
32 ;; range of functions that can be used to identify references. The
33 ;; API is backed by an OO system that is used to allow multiple
34 ;; external tools to provide the information.
35 ;;
36 ;; The default implementation uses a find/grep combination to do a
37 ;; search. This works ok in small projects. For larger projects, it
38 ;; is important to find an alternate tool to use as a back-end to
39 ;; symref.
40 ;;
41 ;; See the command: `semantic-symref' for an example app using this api.
42 ;;
43 ;; TO USE THIS TOOL
44 ;;
45 ;; The following functions can be used to find different kinds of
46 ;; references.
47 ;;
48 ;; `semantic-symref-find-references-by-name'
49 ;; `semantic-symref-find-file-references-by-name'
50 ;; `semantic-symref-find-text'
51 ;;
52 ;; All the search routines return a class of type
53 ;; `semantic-symref-result'. You can reference the various slots, but
54 ;; you will need the following methods to get extended information.
55 ;;
56 ;; `semantic-symref-result-get-files'
57 ;; `semantic-symref-result-get-tags'
58 ;;
59 ;; ADD A NEW EXTERNAL TOOL
60 ;;
61 ;; To support a new external tool, subclass `semantic-symref-tool-baseclass'
62 ;; and implement the methods. The baseclass provides support for
63 ;; managing external processes that produce parsable output.
64 ;;
65 ;; Your tool should then create an instance of `semantic-symref-result'.
66
67 (require 'semantic)
68 (eval-when-compile (require 'semantic/find)) ;For semantic-find-tags-*
69 (eval-when-compile (require 'ede/proj)) ;For `metasubproject' warning.
70
71 (defvar ede-minor-mode)
72 (declare-function data-debug-new-buffer "data-debug")
73 (declare-function data-debug-insert-object-slots "eieio-datadebug")
74 (declare-function ede-toplevel "ede/base")
75 (declare-function ede-project-root-directory "ede/files")
76 (declare-function ede-up-directory "ede/files")
77
78 ;;; Code:
79 (defcustom semantic-symref-tool 'detect
80 "The active symbol reference tool name.
81 The tool symbol can be 'detect, or a symbol that is the name of
82 a tool that can be used for symbol referencing."
83 :type 'symbol
84 :group 'semantic)
85 (make-variable-buffer-local 'semantic-symref-tool)
86
87 ;;; TOOL SETUP
88 ;;
89 (defvar semantic-symref-tool-alist
90 '( ( (lambda (rootdir) (file-exists-p (expand-file-name "GPATH" rootdir))) .
91 global)
92 ( (lambda (rootdir) (file-exists-p (expand-file-name "ID" rootdir))) .
93 idutils)
94 ( (lambda (rootdir) (file-exists-p (expand-file-name "cscope.out" rootdir))) .
95 cscope )
96 )
97 "Alist of tools usable by `semantic-symref'.
98 Each entry is of the form:
99 ( PREDICATE . KEY )
100 Where PREDICATE is a function that takes a directory name for the
101 root of a project, and returns non-nil if the tool represented by KEY
102 is supported.
103
104 If no tools are supported, then 'grep is assumed.")
105
106 (defun semantic-symref-calculate-rootdir ()
107 "Calculate the root directory for a symref search.
108 Start with an EDE project, or use the default directory."
109 (let* ((rootproj (when (and (featurep 'ede) ede-minor-mode)
110 (ede-toplevel)))
111 (rootdirbase (if rootproj
112 (ede-project-root-directory rootproj)
113 default-directory)))
114 (if (and rootproj (condition-case nil
115 ;; Hack for subprojects.
116 (oref rootproj metasubproject)
117 (error nil)))
118 (ede-up-directory rootdirbase)
119 rootdirbase)))
120
121 (defun semantic-symref-detect-symref-tool ()
122 "Detect the symref tool to use for the current buffer."
123 (if (not (eq semantic-symref-tool 'detect))
124 semantic-symref-tool
125 ;; We are to perform a detection for the right tool to use.
126 (let* ((rootdir (semantic-symref-calculate-rootdir))
127 (tools semantic-symref-tool-alist))
128 (while (and tools (eq semantic-symref-tool 'detect))
129 (when (funcall (car (car tools)) rootdir)
130 (setq semantic-symref-tool (cdr (car tools))))
131 (setq tools (cdr tools)))
132
133 (when (eq semantic-symref-tool 'detect)
134 (setq semantic-symref-tool 'grep))
135
136 semantic-symref-tool)))
137
138 (defun semantic-symref-instantiate (&rest args)
139 "Instantiate a new symref search object.
140 ARGS are the initialization arguments to pass to the created class."
141 (let* ((srt (symbol-name (semantic-symref-detect-symref-tool)))
142 (class (intern-soft (concat "semantic-symref-tool-" srt)))
143 (inst nil)
144 )
145 (when (not (class-p class))
146 (error "Unknown symref tool %s" semantic-symref-tool))
147 (setq inst (apply 'make-instance class args))
148 inst))
149
150 (defvar semantic-symref-last-result nil
151 "The last calculated symref result.")
152
153 (defun semantic-symref-data-debug-last-result ()
154 "Run the last symref data result in Data Debug."
155 (interactive)
156 (require 'eieio-datadebug)
157 (if semantic-symref-last-result
158 (progn
159 (data-debug-new-buffer "*Symbol Reference ADEBUG*")
160 (data-debug-insert-object-slots semantic-symref-last-result "]"))
161 (message "Empty results.")))
162
163 ;;; EXTERNAL API
164 ;;
165
166 ;;;###autoload
167 (defun semantic-symref-find-references-by-name (name &optional scope tool-return)
168 "Find a list of references to NAME in the current project.
169 Optional SCOPE specifies which file set to search. Defaults to `project'.
170 Refers to `semantic-symref-tool', to determine the reference tool to use
171 for the current buffer.
172 Returns an object of class `semantic-symref-result'.
173 TOOL-RETURN is an optional symbol, which will be assigned the tool used
174 to perform the search. This was added for use by a test harness."
175 (interactive "sName: ")
176 (let* ((inst (semantic-symref-instantiate
177 :searchfor name
178 :searchtype 'symbol
179 :searchscope (or scope 'project)
180 :resulttype 'line))
181 (result (semantic-symref-get-result inst)))
182 (when tool-return
183 (set tool-return inst))
184 (prog1
185 (setq semantic-symref-last-result result)
186 (when (called-interactively-p 'interactive)
187 (semantic-symref-data-debug-last-result))))
188 )
189
190 ;;;###autoload
191 (defun semantic-symref-find-tags-by-name (name &optional scope)
192 "Find a list of tags by NAME in the current project.
193 Optional SCOPE specifies which file set to search. Defaults to `project'.
194 Refers to `semantic-symref-tool', to determine the reference tool to use
195 for the current buffer.
196 Returns an object of class `semantic-symref-result'."
197 (interactive "sName: ")
198 (let* ((inst (semantic-symref-instantiate
199 :searchfor name
200 :searchtype 'tagname
201 :searchscope (or scope 'project)
202 :resulttype 'line))
203 (result (semantic-symref-get-result inst)))
204 (prog1
205 (setq semantic-symref-last-result result)
206 (when (called-interactively-p 'interactive)
207 (semantic-symref-data-debug-last-result))))
208 )
209
210 ;;;###autoload
211 (defun semantic-symref-find-tags-by-regexp (name &optional scope)
212 "Find a list of references to NAME in the current project.
213 Optional SCOPE specifies which file set to search. Defaults to `project'.
214 Refers to `semantic-symref-tool', to determine the reference tool to use
215 for the current buffer.
216 Returns an object of class `semantic-symref-result'."
217 (interactive "sName: ")
218 (let* ((inst (semantic-symref-instantiate
219 :searchfor name
220 :searchtype 'tagregexp
221 :searchscope (or scope 'project)
222 :resulttype 'line))
223 (result (semantic-symref-get-result inst)))
224 (prog1
225 (setq semantic-symref-last-result result)
226 (when (called-interactively-p 'interactive)
227 (semantic-symref-data-debug-last-result))))
228 )
229
230 ;;;###autoload
231 (defun semantic-symref-find-tags-by-completion (name &optional scope)
232 "Find a list of references to NAME in the current project.
233 Optional SCOPE specifies which file set to search. Defaults to `project'.
234 Refers to `semantic-symref-tool', to determine the reference tool to use
235 for the current buffer.
236 Returns an object of class `semantic-symref-result'."
237 (interactive "sName: ")
238 (let* ((inst (semantic-symref-instantiate
239 :searchfor name
240 :searchtype 'tagcompletions
241 :searchscope (or scope 'project)
242 :resulttype 'line))
243 (result (semantic-symref-get-result inst)))
244 (prog1
245 (setq semantic-symref-last-result result)
246 (when (called-interactively-p 'interactive)
247 (semantic-symref-data-debug-last-result))))
248 )
249
250 ;;;###autoload
251 (defun semantic-symref-find-file-references-by-name (name &optional scope)
252 "Find a list of references to NAME in the current project.
253 Optional SCOPE specifies which file set to search. Defaults to `project'.
254 Refers to `semantic-symref-tool', to determine the reference tool to use
255 for the current buffer.
256 Returns an object of class `semantic-symref-result'."
257 (interactive "sName: ")
258 (let* ((inst (semantic-symref-instantiate
259 :searchfor name
260 :searchtype 'regexp
261 :searchscope (or scope 'project)
262 :resulttype 'file))
263 (result (semantic-symref-get-result inst)))
264 (prog1
265 (setq semantic-symref-last-result result)
266 (when (called-interactively-p 'interactive)
267 (semantic-symref-data-debug-last-result))))
268 )
269
270 ;;;###autoload
271 (defun semantic-symref-find-text (text &optional scope)
272 "Find a list of occurrences of TEXT in the current project.
273 TEXT is a regexp formatted for use with grep -E.
274 Optional SCOPE specifies which file set to search. Defaults to `project'.
275 Refers to `semantic-symref-tool', to determine the reference tool to use
276 for the current buffer.
277 Returns an object of class `semantic-symref-result'."
278 (interactive "sGrep -E style Regexp: ")
279 (let* ((inst (semantic-symref-instantiate
280 :searchfor text
281 :searchtype 'regexp
282 :searchscope (or scope 'project)
283 :resulttype 'line))
284 (result (semantic-symref-get-result inst)))
285 (prog1
286 (setq semantic-symref-last-result result)
287 (when (called-interactively-p 'interactive)
288 (semantic-symref-data-debug-last-result))))
289 )
290
291 ;;; SYMREF TOOLS
292 ;;
293 ;; The base symref tool provides something to hang new tools off of
294 ;; for finding symbol references.
295 (defclass semantic-symref-tool-baseclass ()
296 ((searchfor :initarg :searchfor
297 :type string
298 :documentation "The thing to search for.")
299 (searchtype :initarg :searchtype
300 :type symbol
301 :documentation "The type of search to do.
302 Values could be 'symbol, 'regexp, 'tagname, or 'completion.")
303 (searchscope :initarg :searchscope
304 :type symbol
305 :documentation
306 "The scope to search for.
307 Can be 'project, 'target, or 'file.")
308 (resulttype :initarg :resulttype
309 :type symbol
310 :documentation
311 "The kind of search results desired.
312 Can be 'line, 'file, or 'tag.
313 The type of result can be converted from 'line to 'file, or 'line to 'tag,
314 but not from 'file to 'line or 'tag.")
315 )
316 "Baseclass for all symbol references tools.
317 A symbol reference tool supplies functionality to identify the locations of
318 where different symbols are used.
319
320 Subclasses should be named `semantic-symref-tool-NAME', where
321 NAME is the name of the tool used in the configuration variable
322 `semantic-symref-tool'"
323 :abstract t)
324
325 (cl-defmethod semantic-symref-get-result ((tool semantic-symref-tool-baseclass))
326 "Calculate the results of a search based on TOOL.
327 The symref TOOL should already contain the search criteria."
328 (let ((answer (semantic-symref-perform-search tool))
329 )
330 (when answer
331 (let ((answersym (if (eq (oref tool resulttype) 'file)
332 :hit-files
333 (if (stringp (car answer))
334 :hit-text
335 :hit-lines))))
336 (semantic-symref-result (oref tool searchfor)
337 answersym
338 answer
339 :created-by tool))
340 )
341 ))
342
343 (cl-defmethod semantic-symref-perform-search ((_tool semantic-symref-tool-baseclass))
344 "Base search for symref tools should throw an error."
345 (error "Symref tool objects must implement `semantic-symref-perform-search'"))
346
347 (cl-defmethod semantic-symref-parse-tool-output ((tool semantic-symref-tool-baseclass)
348 outputbuffer)
349 "Parse the entire OUTPUTBUFFER of a symref tool.
350 Calls the method `semantic-symref-parse-tool-output-one-line' over and
351 over until it returns nil."
352 (with-current-buffer outputbuffer
353 (goto-char (point-min))
354 (let ((result nil)
355 (hit nil))
356 (while (setq hit (semantic-symref-parse-tool-output-one-line tool))
357 (setq result (cons hit result)))
358 (nreverse result)))
359 )
360
361 (cl-defmethod semantic-symref-parse-tool-output-one-line ((_tool semantic-symref-tool-baseclass))
362 "Base tool output parser is not implemented."
363 (error "Symref tool objects must implement `semantic-symref-parse-tool-output-one-line'"))
364
365 ;;; RESULTS
366 ;;
367 ;; The results class and methods provide features for accessing hits.
368 (defclass semantic-symref-result ()
369 ((created-by :initarg :created-by
370 :type semantic-symref-tool-baseclass
371 :documentation
372 "Back-pointer to the symref tool creating these results.")
373 (hit-files :initarg :hit-files
374 :type list
375 :documentation
376 "The list of files hit.")
377 (hit-text :initarg :hit-text
378 :type list
379 :documentation
380 "If the result doesn't provide full lines, then fill in hit-text.
381 GNU Global does completion search this way.")
382 (hit-lines :initarg :hit-lines
383 :type list
384 :documentation
385 "The list of line hits.
386 Each element is a cons cell of the form (LINE . FILENAME).")
387 (hit-tags :initarg :hit-tags
388 :type list
389 :documentation
390 "The list of tags with hits in them.
391 Use the `semantic-symref-hit-tags' method to get this list.")
392 )
393 "The results from a symbol reference search.")
394
395 (cl-defmethod semantic-symref-result-get-files ((result semantic-symref-result))
396 "Get the list of files from the symref result RESULT."
397 (if (slot-boundp result 'hit-files)
398 (oref result hit-files)
399 (let* ((lines (oref result hit-lines))
400 (files (mapcar (lambda (a) (cdr a)) lines))
401 (ans nil))
402 (setq ans (list (car files))
403 files (cdr files))
404 (dolist (F files)
405 ;; This algorithm for uniquifying the file list depends on the
406 ;; tool in question providing all the hits in the same file
407 ;; grouped together.
408 (when (not (string= F (car ans)))
409 (setq ans (cons F ans))))
410 (oset result hit-files (nreverse ans))
411 )
412 ))
413
414 (defvar semantic-symref-recently-opened-buffers nil
415 "List of buffers opened by `semantic-symref-result-get-tags'.")
416
417 (defun semantic-symref-cleanup-recent-buffers-fcn ()
418 "Hook function to be used in `post-command-hook' to cleanup buffers.
419 Buffers collected during symref can result in some files being
420 opened multiple times for one operation. This will keep buffers open
421 until the next command is executed."
422 ;;(message "To Clean Up: %S" semantic-symref-recently-opened-buffers)
423 (mapc (lambda (buff)
424 ;; Don't delete any buffers which are being used
425 ;; upon completion of some command.
426 (when (not (get-buffer-window buff))
427 (kill-buffer buff)))
428 semantic-symref-recently-opened-buffers)
429 (setq semantic-symref-recently-opened-buffers nil)
430 (remove-hook 'post-command-hook 'semantic-symref-cleanup-recent-buffers-fcn)
431 )
432
433 (cl-defmethod semantic-symref-result-get-tags ((result semantic-symref-result)
434 &optional open-buffers)
435 "Get the list of tags from the symref result RESULT.
436 Optional OPEN-BUFFERS indicates that the buffers that the hits are
437 in should remain open after scanning.
438 Note: This can be quite slow if most of the hits are not in buffers
439 already."
440 (if (and (slot-boundp result 'hit-tags) (oref result hit-tags))
441 (oref result hit-tags)
442 ;; Calculate the tags.
443 (let ((lines (oref result hit-lines))
444 (txt (oref (oref result created-by) searchfor))
445 (searchtype (oref (oref result created-by) searchtype))
446 (ans nil)
447 (out nil))
448 (save-excursion
449 (setq ans (mapcar
450 (lambda (hit)
451 (semantic-symref-hit-to-tag-via-buffer
452 hit txt searchtype open-buffers))
453 lines)))
454 ;; Kill off dead buffers, unless we were requested to leave them open.
455 (if (not open-buffers)
456 (add-hook 'post-command-hook 'semantic-symref-cleanup-recent-buffers-fcn)
457 ;; Else, just clear the saved buffers so they aren't deleted later.
458 (setq semantic-symref-recently-opened-buffers nil)
459 )
460 ;; Strip out duplicates.
461 (dolist (T ans)
462 (if (and T (not (semantic-equivalent-tag-p (car out) T)))
463 (setq out (cons T out))
464 (when T
465 ;; Else, add this line into the existing list of lines.
466 (let ((lines (append (semantic--tag-get-property (car out) :hit)
467 (semantic--tag-get-property T :hit))))
468 (semantic--tag-put-property (car out) :hit lines)))
469 ))
470 ;; Out is reversed... twice
471 (oset result hit-tags (nreverse out)))))
472
473 (defun semantic-symref-hit-to-tag-via-db (hit searchtxt searchtype)
474 "Convert the symref HIT into a TAG by looking up the tag via a database.
475 Return the Semantic tag associated with HIT.
476 SEARCHTXT is the text that is being searched for.
477 Used to narrow the in-buffer search.
478 SEARCHTYPE is the type of search (such as 'symbol or 'tagname).
479 If there is no database, of if the searchtype is wrong, return nil."
480 ;; Allowed search types for this mechanism:
481 ;; tagname, tagregexp, tagcompletions
482 (if (not (memq searchtype '(tagname tagregexp tagcompletions)))
483 nil
484 (let* ((file (cdr hit))
485 ;; FAIL here vv - don't load is not obeyed if no table found.
486 (db (semanticdb-file-table-object file t))
487 (found
488 (cond ((eq searchtype 'tagname)
489 (semantic-find-tags-by-name searchtxt db))
490 ((eq searchtype 'tagregexp)
491 (semantic-find-tags-by-name-regexp searchtxt db))
492 ((eq searchtype 'tagcompletions)
493 (semantic-find-tags-for-completion searchtxt db))))
494 (hit nil)
495 )
496 ;; Loop over FOUND to see if we can line up a match with a line number.
497 (when (= (length found) 1)
498 (setq hit (car found)))
499
500 ;; FAIL here ^^ - symref finds line numbers, but our DB uses character locations.
501 ;; as such, this is a cheat and we will need to give up.
502 hit)))
503
504 (defun semantic-symref-hit-to-tag-via-buffer (hit searchtxt searchtype &optional open-buffers)
505 "Convert the symref HIT into a TAG by looking up the tag via a buffer.
506 Return the Semantic tag associated with HIT.
507 SEARCHTXT is the text that is being searched for.
508 Used to narrow the in-buffer search.
509 SEARCHTYPE is the type of search (such as 'symbol or 'tagname).
510 Optional OPEN-BUFFERS, when nil will use a faster version of
511 `find-file' when a file needs to be opened. If non-nil, then
512 normal buffer initialization will be used.
513 This function will leave buffers loaded from a file open, but
514 will add buffers that must be opened to `semantic-symref-recently-opened-buffers'.
515 Any caller MUST deal with that variable, either clearing it, or deleting the
516 buffers that were opened."
517 (let* ((line (car hit))
518 (file (cdr hit))
519 (buff (find-buffer-visiting file))
520 (tag nil)
521 )
522 (cond
523 ;; We have a buffer already. Check it out.
524 (buff
525 (set-buffer buff))
526
527 ;; We have a table, but it needs a refresh.
528 ;; This means we should load in that buffer.
529 (t
530 (let ((kbuff
531 (if open-buffers
532 ;; Even if we keep the buffers open, don't
533 ;; let EDE ask lots of questions.
534 (let ((ede-auto-add-method 'never))
535 (find-file-noselect file t))
536 ;; When not keeping the buffers open, then
537 ;; don't setup all the fancy froo-froo features
538 ;; either.
539 (semantic-find-file-noselect file t))))
540 (set-buffer kbuff)
541 (push kbuff semantic-symref-recently-opened-buffers)
542 (semantic-fetch-tags)
543 ))
544 )
545
546 ;; Too much baggage in goto-line
547 ;; (goto-line line)
548 (goto-char (point-min))
549 (forward-line (1- line))
550
551 ;; Search forward for the matching text.
552 ;; FIXME: This still fails if the regexp uses something specific
553 ;; to the extended syntax, like grouping.
554 (when (re-search-forward (if (memq searchtype '(regexp tagregexp))
555 searchtxt
556 (regexp-quote searchtxt))
557 (point-at-eol)
558 t)
559 (goto-char (match-beginning 0))
560 )
561
562 (setq tag (semantic-current-tag))
563
564 ;; If we are searching for a tag, but bound the tag we are looking
565 ;; for, see if it resides in some other parent tag.
566 ;;
567 ;; If there is no parent tag, then we still need to hang the originator
568 ;; in our list.
569 (when (and (eq searchtype 'symbol)
570 (string= (semantic-tag-name tag) searchtxt))
571 (setq tag (or (semantic-current-tag-parent) tag)))
572
573 ;; Copy the tag, which adds a :filename property.
574 (when tag
575 (setq tag (semantic-tag-copy tag nil t))
576 ;; Ad this hit to the tag.
577 (semantic--tag-put-property tag :hit (list line)))
578 tag))
579
580 (provide 'semantic/symref)
581
582 ;; Local variables:
583 ;; generated-autoload-file: "loaddefs.el"
584 ;; generated-autoload-load-name: "semantic/symref"
585 ;; End:
586
587 ;;; semantic/symref.el ends here