finder.el 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. ;;; finder.el --- topic & keyword-based code finder
  2. ;; Copyright (C) 1992, 1997-1999, 2001-2017 Free Software Foundation,
  3. ;; Inc.
  4. ;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
  5. ;; Created: 16 Jun 1992
  6. ;; Version: 1.0
  7. ;; Keywords: help
  8. ;; 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.
  13. ;; GNU Emacs is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  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. ;;; Commentary:
  20. ;; This mode uses the Keywords library header to provide code-finding
  21. ;; services by keyword.
  22. ;;; Code:
  23. (require 'package)
  24. (require 'lisp-mnt)
  25. (require 'find-func) ;for find-library(-suffixes)
  26. (require 'finder-inf nil t)
  27. ;; These are supposed to correspond to top-level customization groups,
  28. ;; says rms.
  29. (defvar finder-known-keywords
  30. '((abbrev . "abbreviation handling, typing shortcuts, and macros")
  31. (bib . "bibliography processors")
  32. (c . "C and related programming languages")
  33. (calendar . "calendar and time management tools")
  34. (comm . "communications, networking, and remote file access")
  35. (convenience . "convenience features for faster editing")
  36. (data . "editing data (non-text) files")
  37. (docs . "Emacs documentation facilities")
  38. (emulations . "emulations of other editors")
  39. (extensions . "Emacs Lisp language extensions")
  40. (faces . "fonts and colors for text")
  41. (files . "file editing and manipulation")
  42. (frames . "Emacs frames and window systems")
  43. (games . "games, jokes and amusements")
  44. (hardware . "interfacing with system hardware")
  45. (help . "Emacs help systems")
  46. (hypermedia . "links between text or other media types")
  47. (i18n . "internationalization and character-set support")
  48. (internal . "code for Emacs internals, build process, defaults")
  49. (languages . "specialized modes for editing programming languages")
  50. (lisp . "Lisp support, including Emacs Lisp")
  51. (local . "code local to your site")
  52. (maint . "Emacs development tools and aids")
  53. (mail . "email reading and posting")
  54. (matching . "searching, matching, and sorting")
  55. (mouse . "mouse support")
  56. (multimedia . "images and sound")
  57. (news . "USENET news reading and posting")
  58. (outlines . "hierarchical outlining and note taking")
  59. (processes . "processes, subshells, and compilation")
  60. (terminals . "text terminals (ttys)")
  61. (tex . "the TeX document formatter")
  62. (tools . "programming tools")
  63. (unix . "UNIX feature interfaces and emulators")
  64. (vc . "version control")
  65. (wp . "word processing"))
  66. "Association list of the standard \"Keywords:\" headers.
  67. Each element has the form (KEYWORD . DESCRIPTION).")
  68. (defvar finder-mode-map
  69. (let ((map (make-sparse-keymap))
  70. (menu-map (make-sparse-keymap "Finder")))
  71. (define-key map " " 'finder-select)
  72. (define-key map "f" 'finder-select)
  73. (define-key map [follow-link] 'mouse-face)
  74. (define-key map [mouse-2] 'finder-mouse-select)
  75. (define-key map "\C-m" 'finder-select)
  76. (define-key map "?" 'finder-summary)
  77. (define-key map "n" 'next-line)
  78. (define-key map "p" 'previous-line)
  79. (define-key map "q" 'finder-exit)
  80. (define-key map "d" 'finder-list-keywords)
  81. (define-key map [menu-bar finder-mode]
  82. (cons "Finder" menu-map))
  83. (define-key menu-map [finder-exit]
  84. '(menu-item "Quit" finder-exit
  85. :help "Exit Finder mode"))
  86. (define-key menu-map [finder-summary]
  87. '(menu-item "Summary" finder-summary
  88. :help "Summary item on current line in a finder buffer"))
  89. (define-key menu-map [finder-list-keywords]
  90. '(menu-item "List keywords" finder-list-keywords
  91. :help "Display descriptions of the keywords in the Finder buffer"))
  92. (define-key menu-map [finder-select]
  93. '(menu-item "Select" finder-select
  94. :help "Select item on current line in a finder buffer"))
  95. map)
  96. "Keymap used in `finder-mode'.")
  97. (defvar finder-mode-syntax-table
  98. (let ((st (make-syntax-table emacs-lisp-mode-syntax-table)))
  99. (modify-syntax-entry ?\; ". " st)
  100. st)
  101. "Syntax table used while in `finder-mode'.")
  102. (defvar finder-headmark nil
  103. "Internal Finder mode variable, local in Finder buffer.")
  104. ;;; Code for regenerating the keyword list.
  105. (defvar finder-keywords-hash nil
  106. "Hash table mapping keywords to lists of package names.
  107. Keywords and package names both should be symbols.")
  108. (defvar generated-finder-keywords-file "finder-inf.el"
  109. "The function `finder-compile-keywords' writes keywords into this file.")
  110. ;; Skip autogenerated files, because they will never contain anything
  111. ;; useful, and because in parallel builds of Emacs they may get
  112. ;; modified while we are trying to read them.
  113. ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-01/msg00469.html
  114. ;; ldefs-boot is not auto-generated, but has nothing useful.
  115. (defvar finder-no-scan-regexp "\\(^\\.#\\|\\(loaddefs\\|ldefs-boot\\|\
  116. cus-load\\|finder-inf\\|esh-groups\\|subdirs\\|leim-list\\)\\.el$\\)"
  117. "Regexp matching file names not to scan for keywords.")
  118. (autoload 'autoload-rubric "autoload")
  119. (defconst finder--builtins-descriptions
  120. ;; I have no idea whether these are supposed to be capitalized
  121. ;; and/or end in a full-stop. Existing file headers are inconsistent,
  122. ;; but mainly seem to not do so.
  123. '((emacs . "the extensible text editor")
  124. (nxml . "a new XML mode"))
  125. "Alist of built-in package descriptions.
  126. Entries have the form (PACKAGE-SYMBOL . DESCRIPTION).
  127. When generating `package--builtins', this overrides what the description
  128. would otherwise be.")
  129. (defvar finder--builtins-alist
  130. '(("calc" . calc)
  131. ("ede" . ede)
  132. ("erc" . erc)
  133. ("eshell" . eshell)
  134. ("gnus" . gnus)
  135. ("international" . emacs)
  136. ("language" . emacs)
  137. ("mh-e" . mh-e)
  138. ("semantic" . semantic)
  139. ("analyze" . semantic)
  140. ("bovine" . semantic)
  141. ("decorate" . semantic)
  142. ("symref" . semantic)
  143. ("wisent" . semantic)
  144. ;; This should really be ("nxml" . nxml-mode), because nxml-mode.el
  145. ;; is the main file for the package. Then we would not need an
  146. ;; entry in finder--builtins-descriptions. But I do not know if
  147. ;; it is safe to change this, in case it is already in use.
  148. ("nxml" . nxml)
  149. ("org" . org)
  150. ("srecode" . srecode)
  151. ("term" . emacs)
  152. ("url" . url))
  153. "Alist of built-in package directories.
  154. Each element should have the form (DIR . PACKAGE), where DIR is a
  155. directory name and PACKAGE is the name of a package (a symbol).
  156. When generating `package--builtins', Emacs assumes any file in
  157. DIR is part of the package PACKAGE.")
  158. (defun finder-compile-keywords (&rest dirs)
  159. "Regenerate list of built-in Emacs packages.
  160. This recomputes `package--builtins' and `finder-keywords-hash',
  161. and prints them into the file `generated-finder-keywords-file'.
  162. Optional DIRS is a list of Emacs Lisp directories to compile
  163. from; the default is `load-path'."
  164. ;; Allow compressed files also.
  165. (setq package--builtins nil)
  166. (setq finder-keywords-hash (make-hash-table :test 'eq))
  167. (let ((el-file-regexp "^\\([^=].*\\)\\.el\\(\\.\\(gz\\|Z\\)\\)?$")
  168. package-override files base-name ; processed
  169. summary keywords package version entry desc)
  170. (dolist (d (or dirs load-path))
  171. (when (file-exists-p (directory-file-name d))
  172. (message "Scanning %s for finder" d)
  173. (setq package-override
  174. (intern-soft
  175. (cdr-safe
  176. (assoc (file-name-nondirectory (directory-file-name d))
  177. finder--builtins-alist))))
  178. (setq files (directory-files d nil el-file-regexp))
  179. (dolist (f files)
  180. (unless (or (string-match finder-no-scan-regexp f)
  181. (null (setq base-name
  182. (and (string-match el-file-regexp f)
  183. (intern (match-string 1 f))))))
  184. ;; (memq base-name processed))
  185. ;; There are multiple files in the tree with the same basename.
  186. ;; So skipping files based on basename means you randomly (depending
  187. ;; on which order the files are traversed in) miss some packages.
  188. ;; http://debbugs.gnu.org/14010
  189. ;; You might think this could lead to two files providing the same package,
  190. ;; but it does not, because the duplicates are (at time of writing)
  191. ;; all due to files in cedet, which end up with package-override set.
  192. ;; FIXME this is obviously fragile.
  193. ;; Make the (eq base-name package) case below issue a warning if
  194. ;; package-override is nil?
  195. ;; (push base-name processed)
  196. (with-temp-buffer
  197. (insert-file-contents (expand-file-name f d))
  198. (setq keywords (mapcar 'intern (lm-keywords-list))
  199. package (or package-override
  200. (let ((str (lm-header "package")))
  201. (if str (intern str)))
  202. base-name)
  203. summary (or (cdr
  204. (assq package finder--builtins-descriptions))
  205. (lm-synopsis))
  206. version (lm-header "version")))
  207. (when summary
  208. (setq version (ignore-errors (version-to-list version)))
  209. (setq entry (assq package package--builtins))
  210. (cond ((null entry)
  211. (push (cons package
  212. (package-make-builtin version summary))
  213. package--builtins))
  214. ;; The idea here is that eg calc.el gets to define
  215. ;; the description of the calc package.
  216. ;; This does not work for eg nxml-mode.el.
  217. ((or (eq base-name package) version)
  218. (setq desc (cdr entry))
  219. (aset desc 0 version)
  220. (aset desc 2 summary)))
  221. (dolist (kw keywords)
  222. (puthash kw
  223. (cons package
  224. (delq package
  225. (gethash kw finder-keywords-hash)))
  226. finder-keywords-hash))))))))
  227. (setq package--builtins
  228. (sort package--builtins
  229. (lambda (a b) (string< (symbol-name (car a))
  230. (symbol-name (car b))))))
  231. (with-current-buffer
  232. (find-file-noselect generated-finder-keywords-file)
  233. (setq buffer-undo-list t)
  234. (erase-buffer)
  235. (insert (autoload-rubric generated-finder-keywords-file
  236. "keyword-to-package mapping" t))
  237. (search-backward " ")
  238. ;; FIXME: Now that we have package--builtin-versions, package--builtins is
  239. ;; only needed to get the list of unversioned packages and to get the
  240. ;; summary description of each package.
  241. (insert "(setq package--builtins '(\n")
  242. (dolist (package package--builtins)
  243. (insert " ")
  244. (prin1 package (current-buffer))
  245. (insert "\n"))
  246. (insert "))\n\n")
  247. ;; Insert hash table.
  248. (insert "(setq finder-keywords-hash\n ")
  249. (prin1 finder-keywords-hash (current-buffer))
  250. (insert ")\n")
  251. (basic-save-buffer)))
  252. (defun finder-compile-keywords-make-dist ()
  253. "Regenerate `finder-inf.el' for the Emacs distribution."
  254. (apply 'finder-compile-keywords command-line-args-left)
  255. (kill-emacs))
  256. ;;; Now the retrieval code
  257. (defun finder-insert-at-column (column &rest strings)
  258. "Insert, at column COLUMN, other args STRINGS."
  259. (if (>= (current-column) column) (insert "\n"))
  260. (move-to-column column t)
  261. (apply 'insert strings))
  262. (defvar finder-help-echo nil)
  263. (defun finder-mouse-face-on-line ()
  264. "Put `mouse-face' and `help-echo' properties on the previous line."
  265. (save-excursion
  266. (forward-line -1)
  267. ;; If finder-insert-at-column moved us to a new line, go back one more.
  268. (if (looking-at "[ \t]") (forward-line -1))
  269. (unless finder-help-echo
  270. (setq finder-help-echo
  271. (let* ((keys1 (where-is-internal 'finder-select
  272. finder-mode-map))
  273. (keys (nconc (where-is-internal
  274. 'finder-mouse-select finder-mode-map)
  275. keys1)))
  276. (concat (mapconcat 'key-description keys ", ")
  277. ": select item"))))
  278. (add-text-properties
  279. (line-beginning-position) (line-end-position)
  280. '(mouse-face highlight
  281. help-echo finder-help-echo))))
  282. (defun finder-unknown-keywords ()
  283. "Return an alist of unknown keywords and number of their occurrences.
  284. Unknown keywords are those present in `finder-keywords-hash' but
  285. not `finder-known-keywords'."
  286. (let (alist)
  287. (maphash (lambda (kw packages)
  288. (unless (assq kw finder-known-keywords)
  289. (push (cons kw (length packages)) alist)))
  290. finder-keywords-hash)
  291. (sort alist (lambda (a b) (string< (car a) (car b))))))
  292. ;;;###autoload
  293. (defun finder-list-keywords ()
  294. "Display descriptions of the keywords in the Finder buffer."
  295. (interactive)
  296. (if (get-buffer "*Finder*")
  297. (pop-to-buffer "*Finder*")
  298. (pop-to-buffer (get-buffer-create "*Finder*"))
  299. (finder-mode)
  300. (let ((inhibit-read-only t))
  301. (erase-buffer)
  302. (dolist (assoc finder-known-keywords)
  303. (let ((keyword (car assoc)))
  304. (insert (propertize (symbol-name keyword)
  305. 'font-lock-face 'font-lock-constant-face))
  306. (finder-insert-at-column 14 (concat (cdr assoc) "\n"))
  307. (finder-mouse-face-on-line)))
  308. (goto-char (point-min))
  309. (setq finder-headmark (point)
  310. buffer-read-only t)
  311. (set-buffer-modified-p nil)
  312. (balance-windows)
  313. (finder-summary))))
  314. (defun finder-list-matches (key)
  315. (let* ((id (intern key))
  316. (packages (gethash id finder-keywords-hash)))
  317. (unless packages
  318. (error "No packages matching key `%s'" key))
  319. (let ((package-list-unversioned t))
  320. (package-show-package-list packages))))
  321. (define-button-type 'finder-xref 'action #'finder-goto-xref)
  322. (defun finder-goto-xref (button)
  323. "Jump to a lisp file for the BUTTON at point."
  324. (let* ((file (button-get button 'xref))
  325. (lib (locate-library file)))
  326. (if lib (finder-commentary lib)
  327. (message "Unable to locate `%s'" file))))
  328. ;;;###autoload
  329. (defun finder-commentary (file)
  330. "Display FILE's commentary section.
  331. FILE should be in a form suitable for passing to `locate-library'."
  332. (interactive
  333. (list
  334. (completing-read "Library name: "
  335. (apply-partially 'locate-file-completion-table
  336. (or find-function-source-path load-path)
  337. (find-library-suffixes)))))
  338. (let ((str (lm-commentary (find-library-name file))))
  339. (or str (error "Can't find any Commentary section"))
  340. ;; This used to use *Finder* but that would clobber the
  341. ;; directory of categories.
  342. (pop-to-buffer "*Finder-package*")
  343. (setq buffer-read-only nil
  344. buffer-undo-list t)
  345. (erase-buffer)
  346. (insert str)
  347. (goto-char (point-min))
  348. (delete-blank-lines)
  349. (goto-char (point-max))
  350. (delete-blank-lines)
  351. (goto-char (point-min))
  352. (while (re-search-forward "^;+ ?" nil t)
  353. (replace-match "" nil nil))
  354. (goto-char (point-min))
  355. (while (re-search-forward "\\<\\([-[:alnum:]]+\\.el\\)\\>" nil t)
  356. (if (locate-library (match-string 1))
  357. (make-text-button (match-beginning 1) (match-end 1)
  358. 'xref (match-string-no-properties 1)
  359. 'help-echo "Read this file's commentary"
  360. :type 'finder-xref)))
  361. (goto-char (point-min))
  362. (setq buffer-read-only t)
  363. (set-buffer-modified-p nil)
  364. (shrink-window-if-larger-than-buffer)
  365. (finder-mode)
  366. (finder-summary)))
  367. (defun finder-current-item ()
  368. (let ((key (save-excursion
  369. (beginning-of-line)
  370. (current-word))))
  371. (if (or (and finder-headmark (< (point) finder-headmark))
  372. (zerop (length key)))
  373. (error "No keyword or filename on this line")
  374. key)))
  375. (defun finder-select ()
  376. "Select item on current line in a Finder buffer."
  377. (interactive)
  378. (let ((key (finder-current-item)))
  379. (if (string-match "\\.el$" key)
  380. (finder-commentary key)
  381. (finder-list-matches key))))
  382. (defun finder-mouse-select (event)
  383. "Select item in a Finder buffer with the mouse."
  384. (interactive "e")
  385. (with-current-buffer (window-buffer (posn-window (event-start event)))
  386. (goto-char (posn-point (event-start event)))
  387. (finder-select)))
  388. ;;;###autoload
  389. (defun finder-by-keyword ()
  390. "Find packages matching a given keyword."
  391. (interactive)
  392. (finder-list-keywords))
  393. (define-derived-mode finder-mode nil "Finder"
  394. "Major mode for browsing package documentation.
  395. \\<finder-mode-map>
  396. \\[finder-select] more help for the item on the current line
  397. \\[finder-exit] exit Finder mode and kill the Finder buffer."
  398. :syntax-table finder-mode-syntax-table
  399. (setq buffer-read-only t
  400. buffer-undo-list t)
  401. (set (make-local-variable 'finder-headmark) nil))
  402. (defun finder-summary ()
  403. "Summarize basic Finder commands."
  404. (interactive)
  405. (message "%s"
  406. (substitute-command-keys
  407. "\\<finder-mode-map>\\[finder-select] = select, \
  408. \\[finder-mouse-select] = select, \\[finder-list-keywords] = to \
  409. finder directory, \\[finder-exit] = quit, \\[finder-summary] = help")))
  410. (defun finder-exit ()
  411. "Exit Finder mode.
  412. Delete the window and kill all Finder-related buffers."
  413. (interactive)
  414. (ignore-errors (delete-window))
  415. (let ((buf "*Finder*"))
  416. (and (get-buffer buf) (kill-buffer buf))))
  417. (defun finder-unload-function ()
  418. "Unload the Finder library."
  419. (with-demoted-errors (unload-feature 'finder-inf t))
  420. ;; continue standard unloading
  421. nil)
  422. (provide 'finder)
  423. ;;; finder.el ends here