symref.el 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. ;;; semantic/symref.el --- Symbol Reference API
  2. ;; Copyright (C) 2008-2012 Free Software Foundation, Inc.
  3. ;; Author: Eric M. Ludlam <eric@siege-engine.com>
  4. ;; This file is part of GNU Emacs.
  5. ;; GNU Emacs is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; GNU Emacs is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;; Semantic Symbol Reference API.
  18. ;;
  19. ;; Semantic's native parsing tools do not handle symbol references.
  20. ;; Tracking such information is a task that requires a huge amount of
  21. ;; space and processing not appropriate for an Emacs Lisp program.
  22. ;;
  23. ;; Many desired tools used in refactoring, however, need to have
  24. ;; such references available to them. This API aims to provide a
  25. ;; range of functions that can be used to identify references. The
  26. ;; API is backed by an OO system that is used to allow multiple
  27. ;; external tools to provide the information.
  28. ;;
  29. ;; The default implementation uses a find/grep combination to do a
  30. ;; search. This works ok in small projects. For larger projects, it
  31. ;; is important to find an alternate tool to use as a back-end to
  32. ;; symref.
  33. ;;
  34. ;; See the command: `semantic-symref' for an example app using this api.
  35. ;;
  36. ;; TO USE THIS TOOL
  37. ;;
  38. ;; The following functions can be used to find different kinds of
  39. ;; references.
  40. ;;
  41. ;; `semantic-symref-find-references-by-name'
  42. ;; `semantic-symref-find-file-references-by-name'
  43. ;; `semantic-symref-find-text'
  44. ;;
  45. ;; All the search routines return a class of type
  46. ;; `semantic-symref-result'. You can reference the various slots, but
  47. ;; you will need the following methods to get extended information.
  48. ;;
  49. ;; `semantic-symref-result-get-files'
  50. ;; `semantic-symref-result-get-tags'
  51. ;;
  52. ;; ADD A NEW EXTERNAL TOOL
  53. ;;
  54. ;; To support a new external tool, subclass `semantic-symref-tool-baseclass'
  55. ;; and implement the methods. The baseclass provides support for
  56. ;; managing external processes that produce parsable output.
  57. ;;
  58. ;; Your tool should then create an instance of `semantic-symref-result'.
  59. (require 'semantic)
  60. (defvar ede-minor-mode)
  61. (declare-function data-debug-new-buffer "data-debug")
  62. (declare-function data-debug-insert-object-slots "eieio-datadebug")
  63. (declare-function ede-toplevel "ede/base")
  64. (declare-function ede-project-root-directory "ede/files")
  65. (declare-function ede-up-directory "ede/files")
  66. ;;; Code:
  67. (defvar semantic-symref-tool 'detect
  68. "*The active symbol reference tool name.
  69. The tool symbol can be 'detect, or a symbol that is the name of
  70. a tool that can be used for symbol referencing.")
  71. (make-variable-buffer-local 'semantic-symref-tool)
  72. ;;; TOOL SETUP
  73. ;;
  74. (defvar semantic-symref-tool-alist
  75. '( ( (lambda (rootdir) (file-exists-p (expand-file-name "GPATH" rootdir))) .
  76. global)
  77. ( (lambda (rootdir) (file-exists-p (expand-file-name "ID" rootdir))) .
  78. idutils)
  79. ( (lambda (rootdir) (file-exists-p (expand-file-name "cscope.out" rootdir))) .
  80. cscope )
  81. )
  82. "Alist of tools usable by `semantic-symref'.
  83. Each entry is of the form:
  84. ( PREDICATE . KEY )
  85. Where PREDICATE is a function that takes a directory name for the
  86. root of a project, and returns non-nil if the tool represented by KEY
  87. is supported.
  88. If no tools are supported, then 'grep is assumed.")
  89. (defun semantic-symref-calculate-rootdir ()
  90. "Calculate the root directory for a symref search.
  91. Start with and EDE project, or use the default directory."
  92. (let* ((rootproj (when (and (featurep 'ede) ede-minor-mode)
  93. (ede-toplevel)))
  94. (rootdirbase (if rootproj
  95. (ede-project-root-directory rootproj)
  96. default-directory)))
  97. (if (and rootproj (condition-case nil
  98. ;; Hack for subprojects.
  99. (oref rootproj :metasubproject)
  100. (error nil)))
  101. (ede-up-directory rootdirbase)
  102. rootdirbase)))
  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* ((rootdir (semantic-symref-calculate-rootdir))
  109. (tools semantic-symref-tool-alist))
  110. (while (and tools (eq semantic-symref-tool 'detect))
  111. (when (funcall (car (car tools)) rootdir)
  112. (setq semantic-symref-tool (cdr (car tools))))
  113. (setq tools (cdr tools)))
  114. (when (eq semantic-symref-tool 'detect)
  115. (setq semantic-symref-tool 'grep))
  116. semantic-symref-tool)))
  117. (defun semantic-symref-instantiate (&rest args)
  118. "Instantiate a new symref search object.
  119. ARGS are the initialization arguments to pass to the created class."
  120. (let* ((srt (symbol-name (semantic-symref-detect-symref-tool)))
  121. (class (intern-soft (concat "semantic-symref-tool-" srt)))
  122. (inst nil)
  123. )
  124. (when (not (class-p class))
  125. (error "Unknown symref tool %s" semantic-symref-tool))
  126. (setq inst (apply 'make-instance class args))
  127. inst))
  128. (defvar semantic-symref-last-result nil
  129. "The last calculated symref result.")
  130. (defun semantic-symref-data-debug-last-result ()
  131. "Run the last symref data result in Data Debug."
  132. (interactive)
  133. (require 'eieio-datadebug)
  134. (if semantic-symref-last-result
  135. (progn
  136. (data-debug-new-buffer "*Symbol Reference ADEBUG*")
  137. (data-debug-insert-object-slots semantic-symref-last-result "]"))
  138. (message "Empty results.")))
  139. ;;; EXTERNAL API
  140. ;;
  141. ;;;###autoload
  142. (defun semantic-symref-find-references-by-name (name &optional scope tool-return)
  143. "Find a list of references to NAME in the current project.
  144. Optional SCOPE specifies which file set to search. Defaults to 'project.
  145. Refers to `semantic-symref-tool', to determine the reference tool to use
  146. for the current buffer.
  147. Returns an object of class `semantic-symref-result'.
  148. TOOL-RETURN is an optional symbol, which will be assigned the tool used
  149. to perform the search. This was added for use by a test harness."
  150. (interactive "sName: ")
  151. (let* ((inst (semantic-symref-instantiate
  152. :searchfor name
  153. :searchtype 'symbol
  154. :searchscope (or scope 'project)
  155. :resulttype 'line))
  156. (result (semantic-symref-get-result inst)))
  157. (when tool-return
  158. (set tool-return inst))
  159. (prog1
  160. (setq semantic-symref-last-result result)
  161. (when (called-interactively-p 'interactive)
  162. (semantic-symref-data-debug-last-result))))
  163. )
  164. ;;;###autoload
  165. (defun semantic-symref-find-tags-by-name (name &optional scope)
  166. "Find a list of references to NAME in the current project.
  167. Optional SCOPE specifies which file set to search. Defaults to 'project.
  168. Refers to `semantic-symref-tool', to determine the reference tool to use
  169. for the current buffer.
  170. Returns an object of class `semantic-symref-result'."
  171. (interactive "sName: ")
  172. (let* ((inst (semantic-symref-instantiate
  173. :searchfor name
  174. :searchtype 'tagname
  175. :searchscope (or scope 'project)
  176. :resulttype 'line))
  177. (result (semantic-symref-get-result inst)))
  178. (prog1
  179. (setq semantic-symref-last-result result)
  180. (when (called-interactively-p 'interactive)
  181. (semantic-symref-data-debug-last-result))))
  182. )
  183. ;;;###autoload
  184. (defun semantic-symref-find-tags-by-regexp (name &optional scope)
  185. "Find a list of references to NAME in the current project.
  186. Optional SCOPE specifies which file set to search. Defaults to 'project.
  187. Refers to `semantic-symref-tool', to determine the reference tool to use
  188. for the current buffer.
  189. Returns an object of class `semantic-symref-result'."
  190. (interactive "sName: ")
  191. (let* ((inst (semantic-symref-instantiate
  192. :searchfor name
  193. :searchtype 'tagregexp
  194. :searchscope (or scope 'project)
  195. :resulttype 'line))
  196. (result (semantic-symref-get-result inst)))
  197. (prog1
  198. (setq semantic-symref-last-result result)
  199. (when (called-interactively-p 'interactive)
  200. (semantic-symref-data-debug-last-result))))
  201. )
  202. ;;;###autoload
  203. (defun semantic-symref-find-tags-by-completion (name &optional scope)
  204. "Find a list of references to NAME in the current project.
  205. Optional SCOPE specifies which file set to search. Defaults to 'project.
  206. Refers to `semantic-symref-tool', to determine the reference tool to use
  207. for the current buffer.
  208. Returns an object of class `semantic-symref-result'."
  209. (interactive "sName: ")
  210. (let* ((inst (semantic-symref-instantiate
  211. :searchfor name
  212. :searchtype 'tagcompletions
  213. :searchscope (or scope 'project)
  214. :resulttype 'line))
  215. (result (semantic-symref-get-result inst)))
  216. (prog1
  217. (setq semantic-symref-last-result result)
  218. (when (called-interactively-p 'interactive)
  219. (semantic-symref-data-debug-last-result))))
  220. )
  221. ;;;###autoload
  222. (defun semantic-symref-find-file-references-by-name (name &optional scope)
  223. "Find a list of references to NAME in the current project.
  224. Optional SCOPE specifies which file set to search. Defaults to 'project.
  225. Refers to `semantic-symref-tool', to determine the reference tool to use
  226. for the current buffer.
  227. Returns an object of class `semantic-symref-result'."
  228. (interactive "sName: ")
  229. (let* ((inst (semantic-symref-instantiate
  230. :searchfor name
  231. :searchtype 'regexp
  232. :searchscope (or scope 'project)
  233. :resulttype 'file))
  234. (result (semantic-symref-get-result inst)))
  235. (prog1
  236. (setq semantic-symref-last-result result)
  237. (when (called-interactively-p 'interactive)
  238. (semantic-symref-data-debug-last-result))))
  239. )
  240. ;;;###autoload
  241. (defun semantic-symref-find-text (text &optional scope)
  242. "Find a list of occurrences of TEXT in the current project.
  243. TEXT is a regexp formatted for use with egrep.
  244. Optional SCOPE specifies which file set to search. Defaults to 'project.
  245. Refers to `semantic-symref-tool', to determine the reference tool to use
  246. for the current buffer.
  247. Returns an object of class `semantic-symref-result'."
  248. (interactive "sEgrep style Regexp: ")
  249. (let* ((inst (semantic-symref-instantiate
  250. :searchfor text
  251. :searchtype 'regexp
  252. :searchscope (or scope 'project)
  253. :resulttype 'line))
  254. (result (semantic-symref-get-result inst)))
  255. (prog1
  256. (setq semantic-symref-last-result result)
  257. (when (called-interactively-p 'interactive)
  258. (semantic-symref-data-debug-last-result))))
  259. )
  260. ;;; RESULTS
  261. ;;
  262. ;; The results class and methods provide features for accessing hits.
  263. (defclass semantic-symref-result ()
  264. ((created-by :initarg :created-by
  265. :type semantic-symref-tool-baseclass
  266. :documentation
  267. "Back-pointer to the symref tool creating these results.")
  268. (hit-files :initarg :hit-files
  269. :type list
  270. :documentation
  271. "The list of files hit.")
  272. (hit-text :initarg :hit-text
  273. :type list
  274. :documentation
  275. "If the result doesn't provide full lines, then fill in hit-text.
  276. GNU Global does completion search this way.")
  277. (hit-lines :initarg :hit-lines
  278. :type list
  279. :documentation
  280. "The list of line hits.
  281. Each element is a cons cell of the form (LINE . FILENAME).")
  282. (hit-tags :initarg :hit-tags
  283. :type list
  284. :documentation
  285. "The list of tags with hits in them.
  286. Use the `semantic-symref-hit-tags' method to get this list.")
  287. )
  288. "The results from a symbol reference search.")
  289. (defmethod semantic-symref-result-get-files ((result semantic-symref-result))
  290. "Get the list of files from the symref result RESULT."
  291. (if (slot-boundp result :hit-files)
  292. (oref result hit-files)
  293. (let* ((lines (oref result :hit-lines))
  294. (files (mapcar (lambda (a) (cdr a)) lines))
  295. (ans nil))
  296. (setq ans (list (car files))
  297. files (cdr files))
  298. (dolist (F files)
  299. ;; This algorithm for uniquifying the file list depends on the
  300. ;; tool in question providing all the hits in the same file
  301. ;; grouped together.
  302. (when (not (string= F (car ans)))
  303. (setq ans (cons F ans))))
  304. (oset result hit-files (nreverse ans))
  305. )
  306. ))
  307. (defmethod semantic-symref-result-get-tags ((result semantic-symref-result)
  308. &optional open-buffers)
  309. "Get the list of tags from the symref result RESULT.
  310. Optional OPEN-BUFFERS indicates that the buffers that the hits are
  311. in should remain open after scanning.
  312. Note: This can be quite slow if most of the hits are not in buffers
  313. already."
  314. (if (and (slot-boundp result :hit-tags) (oref result hit-tags))
  315. (oref result hit-tags)
  316. ;; Calculate the tags.
  317. (let ((lines (oref result :hit-lines))
  318. (txt (oref (oref result :created-by) :searchfor))
  319. (searchtype (oref (oref result :created-by) :searchtype))
  320. (ans nil)
  321. (out nil)
  322. (buffs-to-kill nil))
  323. (save-excursion
  324. (setq
  325. ans
  326. (mapcar
  327. (lambda (hit)
  328. (let* ((line (car hit))
  329. (file (cdr hit))
  330. (buff (get-file-buffer file))
  331. (tag nil)
  332. )
  333. (cond
  334. ;; We have a buffer already. Check it out.
  335. (buff
  336. (set-buffer buff))
  337. ;; We have a table, but it needs a refresh.
  338. ;; This means we should load in that buffer.
  339. (t
  340. (let ((kbuff
  341. (if open-buffers
  342. ;; Even if we keep the buffers open, don't
  343. ;; let EDE ask lots of questions.
  344. (let ((ede-auto-add-method 'never))
  345. (find-file-noselect file t))
  346. ;; When not keeping the buffers open, then
  347. ;; don't setup all the fancy froo-froo features
  348. ;; either.
  349. (semantic-find-file-noselect file t))))
  350. (set-buffer kbuff)
  351. (setq buffs-to-kill (cons kbuff buffs-to-kill))
  352. (semantic-fetch-tags)
  353. ))
  354. )
  355. ;; Too much baggage in goto-line
  356. ;; (goto-line line)
  357. (goto-char (point-min))
  358. (forward-line (1- line))
  359. ;; Search forward for the matching text
  360. (re-search-forward (regexp-quote txt)
  361. (point-at-eol)
  362. t)
  363. (setq tag (semantic-current-tag))
  364. ;; If we are searching for a tag, but bound the tag we are looking
  365. ;; for, see if it resides in some other parent tag.
  366. ;;
  367. ;; If there is no parent tag, then we still need to hang the originator
  368. ;; in our list.
  369. (when (and (eq searchtype 'symbol)
  370. (string= (semantic-tag-name tag) txt))
  371. (setq tag (or (semantic-current-tag-parent) tag)))
  372. ;; Copy the tag, which adds a :filename property.
  373. (when tag
  374. (setq tag (semantic-tag-copy tag nil t))
  375. ;; Ad this hit to the tag.
  376. (semantic--tag-put-property tag :hit (list line)))
  377. tag))
  378. lines)))
  379. ;; Kill off dead buffers, unless we were requested to leave them open.
  380. (when (not open-buffers)
  381. (mapc 'kill-buffer buffs-to-kill))
  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)))
  391. ))
  392. ;; Out is reversed... twice
  393. (oset result :hit-tags (nreverse out)))))
  394. ;;; SYMREF TOOLS
  395. ;;
  396. ;; The base symref tool provides something to hang new tools off of
  397. ;; for finding symbol references.
  398. (defclass semantic-symref-tool-baseclass ()
  399. ((searchfor :initarg :searchfor
  400. :type string
  401. :documentation "The thing to search for.")
  402. (searchtype :initarg :searchtype
  403. :type symbol
  404. :documentation "The type of search to do.
  405. Values could be `symbol, `regexp, 'tagname, or 'completion.")
  406. (searchscope :initarg :searchscope
  407. :type symbol
  408. :documentation
  409. "The scope to search for.
  410. Can be 'project, 'target, or 'file.")
  411. (resulttype :initarg :resulttype
  412. :type symbol
  413. :documentation
  414. "The kind of search results desired.
  415. Can be 'line, 'file, or 'tag.
  416. The type of result can be converted from 'line to 'file, or 'line to 'tag,
  417. but not from 'file to 'line or 'tag.")
  418. )
  419. "Baseclass for all symbol references tools.
  420. A symbol reference tool supplies functionality to identify the locations of
  421. where different symbols are used.
  422. Subclasses should be named `semantic-symref-tool-NAME', where
  423. NAME is the name of the tool used in the configuration variable
  424. `semantic-symref-tool'"
  425. :abstract t)
  426. (defmethod semantic-symref-get-result ((tool semantic-symref-tool-baseclass))
  427. "Calculate the results of a search based on TOOL.
  428. The symref TOOL should already contain the search criteria."
  429. (let ((answer (semantic-symref-perform-search tool))
  430. )
  431. (when answer
  432. (let ((answersym (if (eq (oref tool :resulttype) 'file)
  433. :hit-files
  434. (if (stringp (car answer))
  435. :hit-text
  436. :hit-lines))))
  437. (semantic-symref-result (oref tool searchfor)
  438. answersym
  439. answer
  440. :created-by tool))
  441. )
  442. ))
  443. (defmethod semantic-symref-perform-search ((tool semantic-symref-tool-baseclass))
  444. "Base search for symref tools should throw an error."
  445. (error "Symref tool objects must implement `semantic-symref-perform-search'"))
  446. (defmethod semantic-symref-parse-tool-output ((tool semantic-symref-tool-baseclass)
  447. outputbuffer)
  448. "Parse the entire OUTPUTBUFFER of a symref tool.
  449. Calls the method `semantic-symref-parse-tool-output-one-line' over and
  450. over until it returns nil."
  451. (with-current-buffer outputbuffer
  452. (goto-char (point-min))
  453. (let ((result nil)
  454. (hit nil))
  455. (while (setq hit (semantic-symref-parse-tool-output-one-line tool))
  456. (setq result (cons hit result)))
  457. (nreverse result)))
  458. )
  459. (defmethod semantic-symref-parse-tool-output-one-line ((tool semantic-symref-tool-baseclass))
  460. "Base tool output parser is not implemented."
  461. (error "Symref tool objects must implement `semantic-symref-parse-tool-output-one-line'"))
  462. (provide 'semantic/symref)
  463. ;; Local variables:
  464. ;; generated-autoload-file: "loaddefs.el"
  465. ;; generated-autoload-load-name: "semantic/symref"
  466. ;; End:
  467. ;;; semantic/symref.el ends here