]> code.delx.au - gnu-emacs/blob - lisp/cedet/semantic/symref.el
lisp/cedet/semantic/symref.el: Autoload subclasses for
[gnu-emacs] / lisp / cedet / semantic / symref.el
1 ;;; semantic/symref.el --- Symbol Reference API
2
3 ;;; Copyright (C) 2008, 2009 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 apropriate 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, sublcass `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 (require 'eieio)
69 ;; (require 'ede)
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/files")
75 (declare-function ede-project-root-directory "ede/files")
76
77 ;;; Code:
78 (defvar semantic-symref-tool 'detect
79 "*The active symbol reference tool name.
80 The tool symbol can be 'detect, or a symbol that is the name of
81 a tool that can be used for symbol referencing.")
82 (make-variable-buffer-local 'semantic-symref-tool)
83
84 ;;; TOOL SETUP
85 ;;
86 (defvar semantic-symref-tool-alist
87 '( ( (lambda (rootdir) (file-exists-p (expand-file-name "GPATH" rootdir))) .
88 global)
89 ( (lambda (rootdir) (file-exists-p (expand-file-name "ID" rootdir))) .
90 idutils)
91 ( (lambda (rootdir) (file-exists-p (expand-file-name "cscope.out" rootdir))) .
92 cscope )
93 )
94 "Alist of tools usable by `semantic-symref'.
95 Each entry is of the form:
96 ( PREDICATE . KEY )
97 Where PREDICATE is a function that takes a directory name for the
98 root of a project, and returns non-nil if the tool represented by KEY
99 is supported.
100
101 If no tools are supported, then 'grep is assumed.")
102
103 (defun semantic-symref-detect-symref-tool ()
104 "Detect the symref tool to use for the current buffer."
105 (if (not (eq semantic-symref-tool 'detect))
106 semantic-symref-tool
107 ;; We are to perform a detection for the right tool to use.
108 (let* ((rootproj (when (and (featurep 'ede) ede-minor-mode)
109 (ede-toplevel)))
110 (rootdir (if rootproj
111 (ede-project-root-directory rootproj)
112 default-directory))
113 (tools semantic-symref-tool-alist))
114 (while (and tools (eq semantic-symref-tool 'detect))
115 (when (funcall (car (car tools)) rootdir)
116 (setq semantic-symref-tool (cdr (car tools))))
117 (setq tools (cdr tools)))
118
119 (when (eq semantic-symref-tool 'detect)
120 (setq semantic-symref-tool 'grep))
121
122 semantic-symref-tool)))
123
124 (defun semantic-symref-instantiate (&rest args)
125 "Instantiate a new symref search object.
126 ARGS are the initialization arguments to pass to the created class."
127 (let* ((srt (symbol-name (semantic-symref-detect-symref-tool)))
128 (class (intern-soft (concat "semantic-symref-tool-" srt)))
129 (inst nil)
130 )
131 (when (not (class-p class))
132 (error "Unknown symref tool %s" semantic-symref-tool))
133 (setq inst (apply 'make-instance class args))
134 inst))
135
136 (defvar semantic-symref-last-result nil
137 "The last calculated symref result.")
138
139 (defun semantic-symref-data-debug-last-result ()
140 "Run the last symref data result in Data Debug."
141 (interactive)
142 (require 'eieio-datadebug)
143 (if semantic-symref-last-result
144 (progn
145 (data-debug-new-buffer "*Symbol Reference ADEBUG*")
146 (data-debug-insert-object-slots semantic-symref-last-result "]"))
147 (message "Empty results.")))
148
149 ;;; EXTERNAL API
150 ;;
151
152 ;;;###autoload
153 (defun semantic-symref-find-references-by-name (name &optional scope tool-return)
154 "Find a list of references to NAME in the current project.
155 Optional SCOPE specifies which file set to search. Defaults to 'project.
156 Refers to `semantic-symref-tool', to determine the reference tool to use
157 for the current buffer.
158 Returns an object of class `semantic-symref-result'.
159 TOOL-RETURN is an optional symbol, which will be assigned the tool used
160 to perform the search. This was added for use by a test harness."
161 (interactive "sName: ")
162 (let* ((inst (semantic-symref-instantiate
163 :searchfor name
164 :searchtype 'symbol
165 :searchscope (or scope 'project)
166 :resulttype 'line))
167 (result (semantic-symref-get-result inst)))
168 (when tool-return
169 (set tool-return inst))
170 (prog1
171 (setq semantic-symref-last-result result)
172 (when (interactive-p)
173 (semantic-symref-data-debug-last-result))))
174 )
175
176 ;;;###autoload
177 (defun semantic-symref-find-tags-by-name (name &optional scope)
178 "Find a list of references to NAME in the current project.
179 Optional SCOPE specifies which file set to search. Defaults to 'project.
180 Refers to `semantic-symref-tool', to determine the reference tool to use
181 for the current buffer.
182 Returns an object of class `semantic-symref-result'."
183 (interactive "sName: ")
184 (let* ((inst (semantic-symref-instantiate
185 :searchfor name
186 :searchtype 'tagname
187 :searchscope (or scope 'project)
188 :resulttype 'line))
189 (result (semantic-symref-get-result inst)))
190 (prog1
191 (setq semantic-symref-last-result result)
192 (when (interactive-p)
193 (semantic-symref-data-debug-last-result))))
194 )
195
196 ;;;###autoload
197 (defun semantic-symref-find-tags-by-regexp (name &optional scope)
198 "Find a list of references to NAME in the current project.
199 Optional SCOPE specifies which file set to search. Defaults to 'project.
200 Refers to `semantic-symref-tool', to determine the reference tool to use
201 for the current buffer.
202 Returns an object of class `semantic-symref-result'."
203 (interactive "sName: ")
204 (let* ((inst (semantic-symref-instantiate
205 :searchfor name
206 :searchtype 'tagregexp
207 :searchscope (or scope 'project)
208 :resulttype 'line))
209 (result (semantic-symref-get-result inst)))
210 (prog1
211 (setq semantic-symref-last-result result)
212 (when (interactive-p)
213 (semantic-symref-data-debug-last-result))))
214 )
215
216 ;;;###autoload
217 (defun semantic-symref-find-tags-by-completion (name &optional scope)
218 "Find a list of references to NAME in the current project.
219 Optional SCOPE specifies which file set to search. Defaults to 'project.
220 Refers to `semantic-symref-tool', to determine the reference tool to use
221 for the current buffer.
222 Returns an object of class `semantic-symref-result'."
223 (interactive "sName: ")
224 (let* ((inst (semantic-symref-instantiate
225 :searchfor name
226 :searchtype 'tagcompletions
227 :searchscope (or scope 'project)
228 :resulttype 'line))
229 (result (semantic-symref-get-result inst)))
230 (prog1
231 (setq semantic-symref-last-result result)
232 (when (interactive-p)
233 (semantic-symref-data-debug-last-result))))
234 )
235
236 ;;;###autoload
237 (defun semantic-symref-find-file-references-by-name (name &optional scope)
238 "Find a list of references to NAME in the current project.
239 Optional SCOPE specifies which file set to search. Defaults to 'project.
240 Refers to `semantic-symref-tool', to determine the reference tool to use
241 for the current buffer.
242 Returns an object of class `semantic-symref-result'."
243 (interactive "sName: ")
244 (let* ((inst (semantic-symref-instantiate
245 :searchfor name
246 :searchtype 'regexp
247 :searchscope (or scope 'project)
248 :resulttype 'file))
249 (result (semantic-symref-get-result inst)))
250 (prog1
251 (setq semantic-symref-last-result result)
252 (when (interactive-p)
253 (semantic-symref-data-debug-last-result))))
254 )
255
256 ;;;###autoload
257 (defun semantic-symref-find-text (text &optional scope)
258 "Find a list of occurances of TEXT in the current project.
259 TEXT is a regexp formatted for use with egrep.
260 Optional SCOPE specifies which file set to search. Defaults to 'project.
261 Refers to `semantic-symref-tool', to determine the reference tool to use
262 for the current buffer.
263 Returns an object of class `semantic-symref-result'."
264 (interactive "sEgrep style Regexp: ")
265 (let* ((inst (semantic-symref-instantiate
266 :searchfor text
267 :searchtype 'regexp
268 :searchscope (or scope 'project)
269 :resulttype 'line))
270 (result (semantic-symref-get-result inst)))
271 (prog1
272 (setq semantic-symref-last-result result)
273 (when (interactive-p)
274 (semantic-symref-data-debug-last-result))))
275 )
276
277 ;;; RESULTS
278 ;;
279 ;; The results class and methods provide features for accessing hits.
280 (defclass semantic-symref-result ()
281 ((created-by :initarg :created-by
282 :type semantic-symref-tool-baseclass
283 :documentation
284 "Back-pointer to the symref tool creating these results.")
285 (hit-files :initarg :hit-files
286 :type list
287 :documentation
288 "The list of files hit.")
289 (hit-text :initarg :hit-text
290 :type list
291 :documentation
292 "If the result doesn't provide full lines, then fill in hit-text.
293 GNU Global does completion search this way.")
294 (hit-lines :initarg :hit-lines
295 :type list
296 :documentation
297 "The list of line hits.
298 Each element is a cons cell of the form (LINE . FILENAME).")
299 (hit-tags :initarg :hit-tags
300 :type list
301 :documentation
302 "The list of tags with hits in them.
303 Use the `semantic-symref-hit-tags' method to get this list.")
304 )
305 "The results from a symbol reference search.")
306
307 (defmethod semantic-symref-result-get-files ((result semantic-symref-result))
308 "Get the list of files from the symref result RESULT."
309 (if (slot-boundp result :hit-files)
310 (oref result hit-files)
311 (let* ((lines (oref result :hit-lines))
312 (files (mapcar (lambda (a) (cdr a)) lines))
313 (ans nil))
314 (setq ans (list (car files))
315 files (cdr files))
316 (dolist (F files)
317 ;; This algorithm for uniqing the file list depends on the
318 ;; tool in question providing all the hits in the same file
319 ;; grouped together.
320 (when (not (string= F (car ans)))
321 (setq ans (cons F ans))))
322 (oset result hit-files (nreverse ans))
323 )
324 ))
325
326 (defmethod semantic-symref-result-get-tags ((result semantic-symref-result)
327 &optional open-buffers)
328 "Get the list of tags from the symref result RESULT.
329 Optional OPEN-BUFFERS indicates that the buffers that the hits are
330 in should remain open after scanning.
331 Note: This can be quite slow if most of the hits are not in buffers
332 already."
333 (if (and (slot-boundp result :hit-tags) (oref result hit-tags))
334 (oref result hit-tags)
335 ;; Calculate the tags.
336 (let ((lines (oref result :hit-lines))
337 (txt (oref (oref result :created-by) :searchfor))
338 (searchtype (oref (oref result :created-by) :searchtype))
339 (ans nil)
340 (out nil)
341 (buffs-to-kill nil))
342 (save-excursion
343 (setq
344 ans
345 (mapcar
346 (lambda (hit)
347 (let* ((line (car hit))
348 (file (cdr hit))
349 (buff (get-file-buffer file))
350 (tag nil)
351 )
352 (cond
353 ;; We have a buffer already. Check it out.
354 (buff
355 (set-buffer buff))
356
357 ;; We have a table, but it needs a refresh.
358 ;; This means we should load in that buffer.
359 (t
360 (let ((kbuff
361 (if open-buffers
362 ;; Even if we keep the buffers open, don't
363 ;; let EDE ask lots of questions.
364 (let ((ede-auto-add-method 'never))
365 (find-file-noselect file t))
366 ;; When not keeping the buffers open, then
367 ;; don't setup all the fancy froo-froo features
368 ;; either.
369 (semantic-find-file-noselect file t))))
370 (set-buffer kbuff)
371 (setq buffs-to-kill (cons kbuff buffs-to-kill))
372 (semantic-fetch-tags)
373 ))
374 )
375
376 ;; Too much baggage in goto-line
377 ;; (goto-line line)
378 (goto-char (point-min))
379 (forward-line (1- line))
380
381 ;; Search forward for the matching text
382 (re-search-forward (regexp-quote txt)
383 (point-at-eol)
384 t)
385
386 (setq tag (semantic-current-tag))
387
388 ;; If we are searching for a tag, but bound the tag we are looking
389 ;; for, see if it resides in some other parent tag.
390 ;;
391 ;; If there is no parent tag, then we still need to hang the originator
392 ;; in our list.
393 (when (and (eq searchtype 'symbol)
394 (string= (semantic-tag-name tag) txt))
395 (setq tag (or (semantic-current-tag-parent) tag)))
396
397 ;; Copy the tag, which adds a :filename property.
398 (when tag
399 (setq tag (semantic-tag-copy tag nil t))
400 ;; Ad this hit to the tag.
401 (semantic--tag-put-property tag :hit (list line)))
402 tag))
403 lines)))
404 ;; Kill off dead buffers, unless we were requested to leave them open.
405 (when (not open-buffers)
406 (mapc 'kill-buffer buffs-to-kill))
407 ;; Strip out duplicates.
408 (dolist (T ans)
409 (if (and T (not (semantic-equivalent-tag-p (car out) T)))
410 (setq out (cons T out))
411 (when T
412 ;; Else, add this line into the existing list of lines.
413 (let ((lines (append (semantic--tag-get-property (car out) :hit)
414 (semantic--tag-get-property T :hit))))
415 (semantic--tag-put-property (car out) :hit lines)))
416 ))
417 ;; Out is reversed... twice
418 (oset result :hit-tags (nreverse out)))))
419
420 ;;; SYMREF TOOLS
421 ;;
422 ;; The base symref tool provides something to hang new tools off of
423 ;; for finding symbol references.
424 (defclass semantic-symref-tool-baseclass ()
425 ((searchfor :initarg :searchfor
426 :type string
427 :documentation "The thing to search for.")
428 (searchtype :initarg :searchtype
429 :type symbol
430 :documentation "The type of search to do.
431 Values could be `symbol, `regexp, 'tagname, or 'completion.")
432 (searchscope :initarg :searchscope
433 :type symbol
434 :documentation
435 "The scope to search for.
436 Can be 'project, 'target, or 'file.")
437 (resulttype :initarg :resulttype
438 :type symbol
439 :documentation
440 "The kind of search results desired.
441 Can be 'line, 'file, or 'tag.
442 The type of result can be converted from 'line to 'file, or 'line to 'tag,
443 but not from 'file to 'line or 'tag.")
444 )
445 "Baseclass for all symbol references tools.
446 A symbol reference tool supplies functionality to identify the locations of
447 where different symbols are used.
448
449 Subclasses should be named `semantic-symref-tool-NAME', where
450 NAME is the name of the tool used in the configuration variable
451 `semantic-symref-tool'"
452 :abstract t)
453
454 (defmethod semantic-symref-get-result ((tool semantic-symref-tool-baseclass))
455 "Calculate the results of a search based on TOOL.
456 The symref TOOL should already contain the search criteria."
457 (let ((answer (semantic-symref-perform-search tool))
458 )
459 (when answer
460 (let ((answersym (if (eq (oref tool :resulttype) 'file)
461 :hit-files
462 (if (stringp (car answer))
463 :hit-text
464 :hit-lines))))
465 (semantic-symref-result (oref tool searchfor)
466 answersym
467 answer
468 :created-by tool))
469 )
470 ))
471
472 (defmethod semantic-symref-perform-search ((tool semantic-symref-tool-baseclass))
473 "Base search for symref tools should throw an error."
474 (error "Symref tool objects must implement `semantic-symref-perform-search'"))
475
476 (defmethod semantic-symref-parse-tool-output ((tool semantic-symref-tool-baseclass)
477 outputbuffer)
478 "Parse the entire OUTPUTBUFFER of a symref tool.
479 Calls the method `semantic-symref-parse-tool-output-one-line' over and
480 over until it returns nil."
481 (save-excursion
482 (set-buffer outputbuffer)
483 (goto-char (point-min))
484 (let ((result nil)
485 (hit nil))
486 (while (setq hit (semantic-symref-parse-tool-output-one-line tool))
487 (setq result (cons hit result)))
488 (nreverse result)))
489 )
490
491 (defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-baseclass))
492 "Base tool output parser is not implemented."
493 (error "Symref tool objects must implement `semantic-symref-parse-tool-output-one-line'"))
494
495
496 ;;; Autoload subclasses for semantic-symref-tool-baseclass:
497
498 (eieio-defclass-autoload
499 'semantic-symref-tool-cscope '(semantic-symref-tool-baseclass)
500 "semantic/symref/cscope"
501 "A symref tool implementation using CScope.
502 The CScope command can be used to generate lists of tags in a way
503 similar to that of `grep'. This tool will parse the output to generate
504 the hit list.
505
506 See the function `cedet-cscope-search' for more details.")
507
508 (eieio-defclass-autoload
509 'semantic-symref-tool-global '(semantic-symref-tool-baseclass)
510 "semantic/symref/global"
511 "A symref tool implementation using GNU Global.
512 The GNU Global command can be used to generate lists of tags in a way
513 similar to that of `grep'. This tool will parse the output to generate
514 the hit list.
515
516 See the function `cedet-gnu-global-search' for more details.")
517
518 (eieio-defclass-autoload
519 'semantic-symref-tool-grep '(semantic-symref-tool-baseclass)
520 "semantic/symref/grep"
521 "A symref tool implementation using grep.
522 This tool uses EDE to find he root of the project, then executes
523 find-grep in the project. The output is parsed for hits
524 and those hits returned.")
525
526 (eieio-defclass-autoload
527 'semantic-symref-tool-idutils '(semantic-symref-tool-baseclass)
528 "semantic/symref/idutils"
529 "A symref tool implementation using ID Utils.
530 The udutils command set can be used to generate lists of tags in a way
531 similar to that of `grep'. This tool will parse the output to generate
532 the hit list.
533
534 See the function `cedet-idutils-search' for more details.")
535
536 (provide 'semantic/symref)
537
538 ;; Local variables:
539 ;; generated-autoload-file: "loaddefs.el"
540 ;; generated-autoload-feature: semantic/loaddefs
541 ;; generated-autoload-load-name: "semantic/symref"
542 ;; End:
543
544 ;;; semantic/symref.el ends here