latex-help.el 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. ;;; latex-help.el --- Lookup LaTeX symbols -*- lexical-binding: t -*-
  2. ;;; Commentary:
  3. ;; This is inspired by an old package (originally from the 90s!!) called
  4. ;; ltx-help.el. That package used to be called latex-help.el too, but it seems
  5. ;; to have had its name changed sometime around 2010. This package aims for
  6. ;; similar functionality, but using more up to date and convention-conforming
  7. ;; Elisp. For example, the original package still assumes that you may not have
  8. ;; `add-hook' or `buffer-substring-no-properties'. Only very old versions of
  9. ;; Emacs are missing these, so almost everyone has them nowadays.
  10. ;;
  11. ;; This file is mostly internal functions. People looking to use this are
  12. ;; probably only interested in the following commands:
  13. ;; - `latex-help-command'
  14. ;; - `latex-help-environment'
  15. ;; - `latex-help-package'
  16. ;; - `latex-help-class'
  17. ;; - `latex-help-texdoc'
  18. ;; - `latex-help-at-point'
  19. ;; - `latex-help'
  20. ;; The configuration options controlling these can be found by running
  21. ;; M-x customize-group RET latex-help RET
  22. ;;; Code:
  23. (require 'info)
  24. (require 'cl-lib)
  25. (require 'shr)
  26. (defcustom latex-help-info-manual "latex2e"
  27. "The name of the info manual to use when looking up latex commands."
  28. :group 'latex-help
  29. :type '(choice
  30. (string :tag "English" "latex2e")
  31. (string :tag "French" "latex2e-fr")
  32. (string :tag "Spanish" "latex2e-es")))
  33. (defcustom latex-help-buffer-name "*latex-help*"
  34. "The name of the info buffer to use when showing LaTeX documentation."
  35. :group 'latex-help
  36. :type 'string)
  37. (defcustom latex-help-texdoc-buffer-name "*latex-help-texdoc*"
  38. "The name of the buffer to use when showing texdoc files."
  39. :group 'latex-help
  40. :type 'string)
  41. (defcustom latex-help-texdoc-program "texdoc"
  42. "The program to use when looking things up with texdoc."
  43. :group 'latex-help
  44. :type '(string :tag "Executable name"))
  45. (defcustom latex-help-max-texdoc-entries 10
  46. "Maximum number of texdoc entries to show when prompting."
  47. :group 'latex-help
  48. :type 'interger)
  49. (defcustom latex-help-pdf-view-program '(emacs "evince")
  50. "The program to use to view PDF documentation files."
  51. :group 'latex-help
  52. :type '(choice
  53. (string :tag "External program")
  54. (const :tag "Texdoc default" texdoc)
  55. (function :tag "Custom function")
  56. (list :tag "Emacs Doc-View mode"
  57. (const :tag "Emacs will be used as the default" emacs)
  58. (choice :tag "Backup"
  59. (string :tag "Use external program as a backup")
  60. (const :tag "Use texdoc default as a backup" texdoc)
  61. (function :tag "Use a custom function as a backup")))))
  62. (defcustom latex-help-html-view-program 'emacs
  63. "The program to use to view HTML documentation files."
  64. :group 'latex-help
  65. :type '(choice
  66. (string :tag "External program")
  67. (const :tag "Texdoc default" texdoc)
  68. (const :tag "Emacs internal HTML engine" emacs)
  69. (function :tag "Custom function")))
  70. (defcustom latex-help-documentation-roots '("/usr/share/texmf-dist/doc/")
  71. "The directories to search to discover texdoc entries."
  72. :group 'latex-help
  73. :type '(repeat directory))
  74. (defvar latex-help--class-cache nil
  75. "Cache of discovered LaTeX document classes.")
  76. (defvar latex-help--environment-cache nil
  77. "Cache of discovered LaTeX environments.")
  78. (defvar latex-help--package-cache nil
  79. "Cache of discovered LaTeX packages.")
  80. (defvar latex-help--commands-cache nil
  81. "Cache of discovered of LaTeX commands.
  82. These do NOT have a leading '\\'.")
  83. (defvar latex-help--texdoc-cache nil
  84. "Cache of texdoc entries.")
  85. (defvar latex-help--caches-initialized-p nil
  86. "Non-nil if the latex-help caches have been initialized.")
  87. (defun latex-help--maybe-init-caches ()
  88. "Init the latex-help caches if they ware empty."
  89. (unless latex-help--caches-initialized-p
  90. (setq latex-help--commands-cache (latex-help--discover-commands)
  91. latex-help--package-cache (latex-help--discover-packages)
  92. latex-help--environment-cache (latex-help--discover-environments)
  93. latex-help--class-cache (latex-help--discover-classes)
  94. latex-help--texdoc-cache (latex-help--discover-texdoc-entries)
  95. latex-help--caches-initialized-p t)))
  96. (defun latex-help--open-file-with (cmd file)
  97. "Open FILE with shell command CMD."
  98. (call-process-shell-command (format "%s %s" cmd
  99. (shell-quote-argument file))
  100. nil 0))
  101. (defun latex-help--open-file-with-texdoc (file)
  102. "Open FILE with texdoc."
  103. (call-process latex-help-texdoc-program nil 0 nil "--just-view" file))
  104. (defun latex-help--texdoc-open-pdf-file (file)
  105. "Open the PDF file FILE."
  106. (cond
  107. ((and (listp latex-help-pdf-view-program)
  108. (eq (car latex-help-pdf-view-program) 'emacs))
  109. (let ((backup (cadr latex-help-pdf-view-program)))
  110. (cond
  111. ((display-graphic-p)
  112. (find-file-other-window file))
  113. ((eq backup 'texdoc)
  114. (latex-help--open-file-with-texdoc file))
  115. ((functionp backup)
  116. (funcall backup file))
  117. ((stringp backup)
  118. (latex-help--open-file-with backup file)))))
  119. ((eq latex-help-pdf-view-program 'texdoc)
  120. (latex-help--open-file-with-texdoc file))
  121. ((functionp latex-help-pdf-view-program)
  122. (funcall latex-help-pdf-view-program file))
  123. ((stringp latex-help-pdf-view-program)
  124. (latex-help--open-file-with latex-help-pdf-view-program file))))
  125. (defun latex-help--pop-to-texdoc-buffer ()
  126. "Pop to (and possibly create) the texdoc buffer.
  127. The buffer's name is from `latex-help-texdoc-buffer-name'."
  128. (pop-to-buffer (get-buffer-create latex-help-texdoc-buffer-name))
  129. (setq buffer-read-only t)
  130. (special-mode))
  131. (defun latex-help--texdoc-open-html-file (file)
  132. "Open the HTML file FILE."
  133. (cond
  134. ((eq latex-help-html-view-program 'emacs)
  135. (latex-help--pop-to-texdoc-buffer)
  136. (let ((buffer-read-only nil))
  137. (erase-buffer)
  138. (insert-file-contents file nil)
  139. (shr-render-region (point-min) (point-max))
  140. (goto-char (point-min))))
  141. ((eq latex-help-html-view-program 'texdoc)
  142. (latex-help--open-file-with-texdoc file))
  143. ((functionp latex-help-html-view-program)
  144. (funcall latex-help-html-view-program file))
  145. ((stringp latex-help-html-view-program)
  146. (latex-help--open-file-with latex-help-html-view-program file))))
  147. (defun latex-help--texdoc-maybe-text-file (file)
  148. "Try to open FILE as a text file.
  149. Read FILE into a buffer. If it is a text file, show the user that buffer, and
  150. return t. Otherwise, kill the buffer and return nil."
  151. (with-current-buffer (generate-new-buffer "*latex-help-texdoc-temp*")
  152. (setq buffer-read-only t)
  153. (special-mode)
  154. (let ((buffer-read-only nil))
  155. (erase-buffer)
  156. (insert-file-contents file nil)
  157. (if (eq buffer-file-coding-system 'no-conversion)
  158. ;; the file was a binary file
  159. (progn
  160. (let ((kill-buffer-query-functions nil))
  161. (set-buffer-modified-p nil)
  162. (kill-buffer (current-buffer))
  163. (user-error "File \"%s\" is binary" file)))
  164. ;; we are good to go
  165. (when-let (old-buffer (get-buffer latex-help-texdoc-buffer-name))
  166. (kill-buffer old-buffer))
  167. (rename-buffer latex-help-texdoc-buffer-name)
  168. (pop-to-buffer (current-buffer))))))
  169. (defun latex-help--texdoc-open-file (file)
  170. "Open the texdoc file FILE.
  171. This will attempt to detect the file's type and open it with the correct
  172. program."
  173. (let ((ext (or (file-name-extension file) "")))
  174. (cond
  175. ((string-equal-ignore-case ext "pdf")
  176. (latex-help--texdoc-open-pdf-file file))
  177. ((string-equal-ignore-case ext "html")
  178. (latex-help--texdoc-open-html-file file))
  179. (t (latex-help--texdoc-maybe-text-file file)))))
  180. (defun latex-help--get-thing-at-point ()
  181. "Return a cons of the LaTeX thing at point and its type (as a symbol).
  182. If nothing is found, return nil.
  183. The following types are known:
  184. - command
  185. - package
  186. - environment
  187. - class
  188. The following are some examples:
  189. - \\textbf{Hello World} -> \\='(\"textbf\" . command)
  190. - \\begin{math} (on \"math\") -> \\='(\"math\" . environment)
  191. - \\begin{math} (on \"begin\") -> \\='(\"begin\" . command)
  192. - \\usepackage{amsmath} (on \"amsmath\") -> \\='(\"amsmath\" . package)
  193. - \\usepackage{amsmath} (on \"usepackage\") -> \\='(\"usepackage\" . command)"
  194. (save-excursion
  195. (let ((orig-point (point)))
  196. (when (eq (char-after) ?\\)
  197. (forward-char))
  198. (when (and (search-backward "\\" nil t)
  199. (looking-at (rx "\\"
  200. (group (+ (not (any " " "\n" "("
  201. "{" "[" "|"
  202. "}" "]" ")" "%")))))))
  203. (let ((cmd (match-string-no-properties 1)))
  204. (if (> (match-end 1) orig-point)
  205. (cons cmd 'command)
  206. (goto-char orig-point)
  207. (condition-case _
  208. (progn
  209. (backward-up-list nil t t)
  210. (when (looking-at (rx "{" (group (+ (not (any "}" "\n"))))))
  211. (let ((thing (match-string-no-properties 1)))
  212. (cond
  213. ((equal cmd "usepackage")
  214. (cons thing 'package))
  215. ((or (equal cmd "begin")
  216. (equal cmd "end"))
  217. (cons thing 'environment))
  218. ((equal cmd "documentclass")
  219. (cons thing 'class))))))
  220. ;; just return nil
  221. ((or user-error scan-error)))))))))
  222. (defun latex-help--is-marker-file (file root)
  223. "Return non-nil if FILE is a texdoc marker file under ROOT.
  224. A marker file is a file that signifies that its parent is a texdoc entry."
  225. (let ((name (file-name-nondirectory file))
  226. (dirname (file-name-nondirectory
  227. (directory-file-name (file-name-parent-directory file))))
  228. (case-fold-search t))
  229. (and
  230. (not (length= (file-name-split (file-relative-name file root)) 2))
  231. (or (string-match (rx bos "readme" (* "." (+ (any (?a . ?z))))) name)
  232. (string-match (rx bos "doc" eos) name)
  233. (string-match (rx bos "base" eos) name)
  234. ;; check if file is just its parent directories name with an .tex or
  235. ;; .pdf
  236. (string-match (format "^%s[-0-9]*\\.\\(?:tex\\|pdf\\)$"
  237. (regexp-quote dirname))
  238. name)))))
  239. (defun latex-help--search-texdoc-root (root found)
  240. "Search the texdoc root directory ROOT and discover package names.
  241. FOUND is the hash table in which to put the entries."
  242. (cl-loop with to-search = nil
  243. for dir = root then (pop to-search)
  244. while dir
  245. when (file-directory-p dir) do
  246. (let ((files (directory-files dir t)))
  247. (if (cl-member-if (lambda (file)
  248. (latex-help--is-marker-file file root))
  249. files)
  250. ;; dir is an entry
  251. (puthash (file-name-nondirectory dir) nil found)
  252. ;; search all subdirs
  253. (setq to-search
  254. (nconc to-search
  255. (seq-filter
  256. (lambda (file)
  257. (let ((name (file-name-nondirectory file)))
  258. (and (not (equal name "."))
  259. (not (equal name "..")))))
  260. files)))))))
  261. (defun latex-help--texdoc-config-files ()
  262. "Return a list of texdoc config files."
  263. (with-temp-buffer
  264. (call-process latex-help-texdoc-program nil t nil "--files")
  265. ;; goto line 3
  266. (goto-char (point-min))
  267. (forward-line 2)
  268. (cl-loop while (re-search-forward (rx bol (+ " ") "active" "\t"
  269. (group (+ any)) eol) nil t)
  270. collect (match-string 1))))
  271. (defun latex-help--texdoc-config-file-entries (file found)
  272. "Parse the texdoc config file FILE to find entries.
  273. This attempts to find entries that might have been missed during the initial
  274. scan. The entries will be `puthash'ed into FOUND as keys."
  275. (with-temp-buffer
  276. (insert-file-contents file)
  277. (goto-char (point-min))
  278. (while (re-search-forward (rx bol "adjscore("
  279. (group (+ (not ")"))) ")")
  280. nil t)
  281. (puthash (match-string 1) nil found))
  282. (goto-char (point-min))
  283. (while (re-search-forward
  284. (rx bol "alias" (? "(" (+ (any (?0 . ?9) ".")) ")")
  285. " " (group (+ (not " ")))
  286. " = " (group (* (not (any "#" "\n" " ")))))
  287. nil t)
  288. (puthash (match-string 1) nil found)
  289. (let ((m2 (match-string 2)))
  290. (unless (or (zerop (length m2))
  291. (seq-contains-p m2 ?/))
  292. (puthash m2 nil found))))))
  293. (defun latex-help--discover-texdoc-entries ()
  294. "Discover texdoc entries in each of `latex-help-documentation-roots'."
  295. (let ((found (make-hash-table :test 'equal)))
  296. (dolist (root latex-help-documentation-roots)
  297. (latex-help--search-texdoc-root root found))
  298. (dolist (file (latex-help--texdoc-config-files))
  299. (latex-help--texdoc-config-file-entries file found))
  300. (cl-loop for entry being the hash-keys of found
  301. collect entry)))
  302. (defun latex-help--texdoc-files-for-entry (entry)
  303. "List the texdoc files for ENTRY.
  304. This returns a list of conses of the display name of the entry and the file it
  305. belongs to. The first item the the returned list is the default value when
  306. prompting with `completing-read'."
  307. (with-temp-buffer
  308. (when-let ((exit-code (call-process latex-help-texdoc-program nil t
  309. nil "-Ml" entry))
  310. ((not (zerop exit-code))))
  311. ;; try to get the programs output without the normal Emacs process
  312. ;; sentinel message
  313. (goto-char (point-max))
  314. (forward-line -2)
  315. (end-of-line)
  316. (let ((msg (buffer-substring-no-properties (point-min)
  317. (point))))
  318. (user-error "Texdoc exited with a non-zero code: %d%s"
  319. exit-code (if (not (zerop (length msg)))
  320. (concat "\n\n" msg)
  321. ""))))
  322. ;; the process succeeded, try to extract the files it found
  323. (goto-char (point-min))
  324. (cl-loop repeat latex-help-max-texdoc-entries
  325. while (re-search-forward (rx (and bol (= 2 (+ (not "\t")) "\t")
  326. (group (+ (not "\t")))
  327. "\t"
  328. (? (+ (not "\t")))
  329. "\t"
  330. (group (* any))))
  331. nil t)
  332. for file = (match-string 1)
  333. for desc = (match-string 2)
  334. unless (zerop (length desc))
  335. collect (cons (format "%s (%s)" desc file) file)
  336. else
  337. collect (cons (format "%s (%s)" (file-name-nondirectory file) file)
  338. file))))
  339. (defun latex-help--texdoc-prompt-for-entry-file (entry)
  340. "Prompt the user to open a texdoc file from ENTRY.
  341. This will return nil if the user does not want to open the file."
  342. (let ((entries (latex-help--texdoc-files-for-entry entry)))
  343. (if (length= entries 1)
  344. (and (y-or-n-p (format "Open texdoc \"%s\"?" (caar entries)))
  345. (cdar entries))
  346. (let ((ans (completing-read "Texdoc File: " (mapcar 'car entries) nil t
  347. nil nil (caar entries))))
  348. (unless (zerop (length ans))
  349. (cdr (assoc ans entries)))))))
  350. (defvar latex-help--texdoc-history nil
  351. "History for `latex-heklp--list-texdoc-files'.")
  352. (defun latex-help--prompt-texdoc-entry ()
  353. "Ask the user for a texdoc entry."
  354. (latex-help--maybe-init-caches)
  355. (let* ((tap (latex-help--get-thing-at-point))
  356. (has-default-p (and (member (cdr tap) '(package class))
  357. (member (car tap) latex-help--texdoc-cache)))
  358. (ans (completing-read (format "Texdoc Entry%s: "
  359. (if has-default-p
  360. (format " (default %s)" (car tap))
  361. ""))
  362. latex-help--texdoc-cache
  363. nil nil nil 'latex-help--texdoc-history
  364. (and has-default-p (car tap)))))
  365. (unless (zerop (length ans))
  366. ans)))
  367. (defun latex-help--run-index-search (regexp)
  368. "Search the LaTeX info pages index for REGEXP.
  369. This returns a list of cache entries suitable for use in
  370. `latex-help--commands-cache'."
  371. (with-temp-buffer
  372. (Info-mode)
  373. (Info-find-node latex-help-info-manual "Index" nil t)
  374. (let ((found))
  375. (while (re-search-forward regexp nil t)
  376. (let ((match (match-string-no-properties 1))
  377. (node (match-string-no-properties 2)))
  378. (if (equal (caar found) match)
  379. (push (cons node (pos-bol)) (cdar found))
  380. (push (list match (cons node (pos-bol))) found))))
  381. found)))
  382. (defun latex-help--discover-commands ()
  383. "Discover LaTeX commands.
  384. This is done by parsing the index for `latex-help-info-manual'."
  385. (let ((found (latex-help--run-index-search
  386. (rx (and bol "* \\"
  387. (group (or
  388. ","
  389. (+ (not (any " " "{" ",")))))
  390. (*? any) ":" (+ " ")
  391. (group (+? any)) ".")))))
  392. (push (list "(SPACE)" "\\(SPACE)") found)
  393. (when-let (entry (assoc "(...\\)" found))
  394. (setq found (assoc-delete-all "(...\\)" found))
  395. (push (cons "(" (cdr entry)) found)
  396. (push (cons ")" (cdr entry)) found))
  397. (when-let (entry (assoc "[...\\]" found))
  398. (setq found (assoc-delete-all "[...\\]" found))
  399. (push (cons "[" (cdr entry)) found)
  400. (push (cons "]" (cdr entry)) found))
  401. found))
  402. (defun latex-help--discover-packages ()
  403. "Discover LaTeX packages.
  404. This is done by parsing the index for `latex-help-info-manual'."
  405. (latex-help--run-index-search (rx (and bol "* package, "
  406. (group (+? any))
  407. (any " " ":")
  408. (+? any) (+ " ")
  409. (group (+? any))
  410. "."))))
  411. (defun latex-help--discover-environments ()
  412. "Discover LaTeX environments.
  413. This is done by parsing the index for `latex-help-info-manual'."
  414. (latex-help--run-index-search (rx (and bol "* environment, "
  415. (group (+? any))
  416. (any " " ":" "-")
  417. (+? any) (+ " ")
  418. (group (+? any))
  419. "."))))
  420. (defun latex-help--discover-classes ()
  421. "Discover LaTeX document classes.
  422. This is done by parsing the index for `latex-help-info-manual'."
  423. (latex-help--run-index-search (rx (and bol "* "
  424. (group (+ (not (any "," " "))))
  425. " class:" (+ " ")
  426. (group (+ (not ".")))))))
  427. (defun latex-help--info-goto-entry (entry)
  428. "Open the info page for ENTRY, a cache entry."
  429. (let ((buffer (get-buffer-create latex-help-buffer-name)))
  430. (with-current-buffer buffer
  431. (unless (derived-mode-p 'Info-mode)
  432. (Info-mode))
  433. (Info-find-node latex-help-info-manual "Index" nil t)
  434. (goto-char (cdr entry))
  435. (Info-follow-nearest-node))
  436. (pop-to-buffer buffer)))
  437. (defun latex-help--get-cache-for-type (type)
  438. "Lookup the cache for TYPE.
  439. If the caches are not yet initialized, do that first."
  440. (latex-help--maybe-init-caches)
  441. (cl-case type
  442. (command latex-help--commands-cache)
  443. (package latex-help--package-cache)
  444. (environment latex-help--environment-cache)
  445. (class latex-help--class-cache)))
  446. (defvar latex-help--info-history nil
  447. "History list for `latex-help--prompt-for'.")
  448. (defun latex-help--maybe-prompt-entry (name type &optional default)
  449. "Lookup and prompt the user for the node of NAME.
  450. The lookup is performed in the correct cache for TYPE. If there is only one
  451. node associated with NAME, return its entry. Otherwise, ask the user which node
  452. they want to use.
  453. If DEFAULT is non-nil, use that instead of prompting. If it does not exist,
  454. return nil."
  455. (when-let (entries (cdr (assoc name (latex-help--get-cache-for-type type))))
  456. (cond
  457. (default
  458. (assoc default entries))
  459. ((length= entries 1)
  460. (car entries))
  461. (t
  462. (let ((resp (completing-read "Select Node: " (mapcar 'car entries)
  463. nil t nil)))
  464. (assoc resp entries))))))
  465. (defun latex-help--prompt-for (type)
  466. "Prompt for a command, environment, etc. from TYPE.
  467. This returns the name of the thing that was prompted."
  468. (let* ((cache (latex-help--get-cache-for-type type))
  469. (tap (latex-help--get-thing-at-point))
  470. (default (and (eq (cdr tap) type) (car tap))))
  471. (unless (assoc default cache)
  472. (setq default nil))
  473. (completing-read (format "LaTeX %s%s: "
  474. (capitalize (symbol-name type))
  475. (if default
  476. (format " (default %s)" default)
  477. ""))
  478. (latex-help--get-cache-for-type type)
  479. nil t nil 'latex-help--info-history
  480. default)))
  481. ;;;###autoload
  482. (defun latex-help-command (name &optional node)
  483. "Lookup the LaTeX command NAME.
  484. Unless NODE is non-nil, if NAME is in more than one node, prompt the user for
  485. which to use. If NODE is non-nil, use that instead."
  486. (interactive (list (latex-help--prompt-for 'command)))
  487. (when-let (entry (latex-help--maybe-prompt-entry name 'command node))
  488. (latex-help--info-goto-entry entry)))
  489. ;;;###autoload
  490. (defun latex-help-environment (name &optional node)
  491. "Lookup the LaTeX environment NAME.
  492. Unless NODE is non-nil, if NAME is in more than one node, prompt the user for
  493. which to use. If NODE is non-nil, use that instead."
  494. (interactive (list (latex-help--prompt-for 'environment)))
  495. (when-let (entry (latex-help--maybe-prompt-entry name 'environment node))
  496. (latex-help--info-goto-entry entry)))
  497. ;;;###autoload
  498. (defun latex-help-package (name &optional node)
  499. "Lookup the LaTeX package NAME.
  500. Unless NODE is non-nil, if NAME is in more than one node, prompt the user for
  501. which to use. If NODE is non-nil, use that instead."
  502. (interactive (list (latex-help--prompt-for 'package)))
  503. (when-let (entry (latex-help--maybe-prompt-entry name 'package node))
  504. (latex-help--info-goto-entry entry)))
  505. ;;;###autoload
  506. (defun latex-help-class (name &optional node)
  507. "Lookup the LaTeX document class NAME.
  508. Unless NODE is non-nil, if NAME is in more than one node, prompt the user for
  509. which to use. If NODE is non-nil, use that instead."
  510. (interactive (list (latex-help--prompt-for 'class)))
  511. (when-let (entry (latex-help--maybe-prompt-entry name 'class node))
  512. (latex-help--info-goto-entry entry)))
  513. ;;;###autoload
  514. (defun latex-help-texdoc (name)
  515. "Lookup NAME in the texdoc cache.
  516. When used interactively, prompt for NAME."
  517. (interactive (list (latex-help--prompt-texdoc-entry)))
  518. (latex-help--maybe-init-caches)
  519. (when-let ((file (latex-help--texdoc-prompt-for-entry-file name)))
  520. (latex-help--texdoc-open-file file)))
  521. (defun latex-help--prompt-info-and-texdoc (info-entry texdoc-entry)
  522. "Prompt the user for both info and texdoc entries.
  523. INFO-ENTRY is an entry from one of the info caches. TEXDOC-ENTRY is an entry
  524. from the texdoc cache."
  525. (let* ((texdoc-files (and texdoc-entry
  526. (latex-help--texdoc-files-for-entry
  527. texdoc-entry)))
  528. (prompts (nconc (mapcar (lambda (file)
  529. (concat "(Texdoc) " (car file)))
  530. texdoc-files)
  531. (mapcar (lambda (node)
  532. (concat "(Info) " (car node)))
  533. (cdr info-entry)))))
  534. (when prompts
  535. (let ((selected (completing-read "LaTeX Help: " prompts nil t nil
  536. nil (when texdoc-files
  537. (car prompts)))))
  538. (when (string-match (rx bos "(" (group (+ (any (?a . ?z))
  539. (any (?A . ?Z))))
  540. ") " (group (* any)))
  541. selected)
  542. (if (equal (match-string 1 selected) "Info")
  543. (cons (assoc (match-string 2 selected) (cdr info-entry)) 'info)
  544. (cons (cdr (assoc (match-string 2 selected) texdoc-files))
  545. 'texdoc)))))))
  546. ;;;###autoload
  547. (defun latex-help-at-point ()
  548. "Try to lookup the LaTeX thing at point, whatever it may be.
  549. This will try to look up the command, package, document class, or environment at
  550. point. If that thing at point is valid, it will open an info buffer to the
  551. documentation for that thing."
  552. (interactive)
  553. (latex-help--maybe-init-caches)
  554. (if-let (thing (latex-help--get-thing-at-point))
  555. (let ((info-entry (assoc (car thing) (latex-help--get-cache-for-type
  556. (cdr thing))))
  557. (texdoc-entry (and (member (cdr thing) '(class package environment))
  558. (cl-find (car thing) latex-help--texdoc-cache
  559. :test 'equal))))
  560. (unless (or info-entry texdoc-entry)
  561. (user-error "Unknown %s: \"%s\""
  562. (symbol-name (cdr thing))
  563. (if (eq (cdr thing) 'command)
  564. (concat "\\" (car thing))
  565. (car thing))))
  566. (cl-destructuring-bind (thing . type)
  567. (latex-help--prompt-info-and-texdoc info-entry texdoc-entry)
  568. (cl-case type
  569. (texdoc
  570. (latex-help--texdoc-open-file thing))
  571. (info
  572. (latex-help--info-goto-entry thing)))))
  573. (user-error "Nothing at point to look up")))
  574. (defvar latex-help--general-history nil
  575. "History for `latex-help'.")
  576. ;;;###autoload
  577. (defun latex-help ()
  578. "Get help with LaTeX.
  579. Prompt the user for an info topic or texdoc file, then open that thing."
  580. (interactive)
  581. (let ((prompts)
  582. (tap (latex-help--get-thing-at-point))
  583. (def-entry nil)
  584. (def-name nil))
  585. (latex-help--maybe-init-caches)
  586. (cl-flet ((add-cache-for-type (type)
  587. (dolist (entry (latex-help--get-cache-for-type type))
  588. (push (format "(Info) %s - %s"
  589. (capitalize (symbol-name type))
  590. (car entry))
  591. prompts)
  592. (when (and (eq type (cdr tap))
  593. (equal (car entry) (car tap)))
  594. (setq def-entry (car prompts)
  595. def-name (car entry))))))
  596. (add-cache-for-type 'command)
  597. (add-cache-for-type 'package)
  598. (add-cache-for-type 'class)
  599. (add-cache-for-type 'environment)
  600. (dolist (entry latex-help--texdoc-cache)
  601. (push (format "(Texdoc) %s" entry) prompts)
  602. (when (and (member (cdr tap) '(class package environment))
  603. (equal entry (car tap)))
  604. (setq def-entry (car prompts)
  605. def-name entry)))
  606. (when-let ((ans (completing-read (format "LaTeX Help%s: "
  607. (if def-name
  608. (format " (default %s)"
  609. def-name)
  610. ""))
  611. prompts
  612. nil t nil 'latex-help--general-history
  613. def-entry))
  614. ((not (zerop (length ans)))))
  615. (if (string-prefix-p "(Texdoc) " ans)
  616. (latex-help-texdoc (seq-subseq ans (length "(Texdoc) ")))
  617. (string-match (rx "(Info) " (group (+ (not " ")))
  618. " - " (group (+ any)))
  619. ans)
  620. (when-let ((thing (match-string 2 ans))
  621. (type (intern (downcase (match-string 1 ans))))
  622. (entry (latex-help--maybe-prompt-entry thing type)))
  623. (latex-help--info-goto-entry entry)))))))
  624. (provide 'latex-help)
  625. ;;; latex-help.el ends here