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