Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
15 KiB

4 years ago
  1. ;;; org-ref-glossary.el --- glossary support in org-ref -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2016 John Kitchin
  3. ;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
  4. ;; Keywords:
  5. ;; This program 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. ;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; Provides Some glossary support for org-mode. Only export to LaTeX is
  17. ;; supported. The functionality is based on the LaTeX glossaries package. See
  18. ;; https://en.wikibooks.org/wiki/LaTeX/Glossary and
  19. ;; http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/glossaries/glossaries-user.pdf
  20. ;; Put something like this in your org-file.
  21. ;; #+latex_header: \usepackage{glossaries}
  22. ;; #+latex_header: \makeglossaries
  23. ;; Put this where you want the glossaries to appear in your org-file.
  24. ;; \printglossaries
  25. ;; Add new glossary entries to your org-file like this. Enclose strings
  26. ;; containing a comma in {}. Multiline entries are supported.
  27. ;; #+latex_header_extra: \newglossaryentry{computer}{name=computer,description={A machine, that computes}}
  28. ;; #+latex_header_extra: \newglossaryentry{tree}{name=tree,description=a big plant}
  29. ;; #+latex_header_extra: \newglossaryentry{naiive}
  30. ;; #+latex_header_extra: {
  31. ;; #+latex_header_extra: name=na\"{\i}ve,
  32. ;; #+latex_header_extra: description={is a French loanword (adjective, form of naïf)
  33. ;; #+latex_header_extra: indicating having or showing a lack of experience,
  34. ;; #+latex_header_extra: understanding or sophistication}
  35. ;; #+latex_header_extra: }
  36. ;; Here is an example acronym definition
  37. ;; #+latex_header_extra: \newacronym{lvm}{LVM}{Logical Volume Manager}
  38. ;; New links defined:
  39. ;; gls:name A reference to the glossary entry NAME.
  40. ;; glspl:name The plural version of the entry
  41. ;; Gls:name Capitalized glossary entry
  42. ;; Glspl: Capitalized, plural glossary entry
  43. ;; [[gslink:name][alternate text]]
  44. ;; glssymbol:name Outputs the symbol value of the glossary entry settings.
  45. ;; glsdesc:name The description of name
  46. ;; The links export to LaTeX. You can click on the link and jump to the
  47. ;; definition. The links have tooltips for the definitions.
  48. ;; Acronym links
  49. ;; acrshort:label
  50. ;; acrfull:label
  51. ;; acrlong:label
  52. ;; ac:label (exports to \gls{label})
  53. ;; Ac:label (exports to \Gls{label})
  54. ;; acp:label (exports to \glspl{label})
  55. ;; Acp:label (exports to \Glspl{label})
  56. (require 'org-element)
  57. (require 'org-ref-utils)
  58. (declare-function helm "helm")
  59. (declare-function helm-build-sync-source "helm-source")
  60. ;;; Code:
  61. (defgroup org-ref-glossary nil
  62. "Customization group for org-ref-glossary."
  63. :tag "Org Ref glossary"
  64. :group 'org)
  65. (defcustom org-ref-glossary-color "Mediumpurple3"
  66. "Color for glossary links."
  67. :type 'string
  68. :group 'org-ref)
  69. (defcustom org-ref-acronym-color "Darkorange2"
  70. "Color for acronym links."
  71. :type 'string
  72. :group 'org-ref)
  73. (defun or-find-closing-curly-bracket (&optional limit)
  74. "Find closing bracket for the bracket at point and move point to it.
  75. Go up to LIMIT or `point-max'. This is a parsing function. I
  76. wrote this because using `forward-list' does not always work if
  77. there is an escaped \" for example. This seems pretty robust."
  78. (unless (looking-at "{") (error "Not at a curley bracket"))
  79. (let ((level 1))
  80. (while (and (not (= 0 level))
  81. (not (eobp))
  82. (< (point) (or limit (point-max))))
  83. (forward-char)
  84. (when (and (looking-at "{")
  85. (not (looking-back "\\\\" (- (point) 2))))
  86. (cl-incf level))
  87. (when (and (looking-at "}")
  88. (not (looking-back "\\\\" (- (point) 2))))
  89. (cl-decf level)))
  90. (point)))
  91. ;;* Glossary
  92. (defun or-parse-glossary-entry (entry)
  93. "Parse glossary ENTRY definition to a p-list of key=value.
  94. Typically:
  95. (:name name :description description)
  96. but there could be other :key value pairs."
  97. (save-excursion
  98. (let (end-of-entry
  99. data
  100. key value p1 p2)
  101. (goto-char (point-min))
  102. ;; We may not find an entry if it is defined as an acronym
  103. (when (re-search-forward
  104. (format "\\newglossaryentry{%s}" entry) nil t)
  105. (re-search-forward "{")
  106. (save-excursion
  107. (backward-char)
  108. (or-find-closing-curly-bracket)
  109. (setq end-of-entry (point)))
  110. (while (re-search-forward "\\(\\w+?\\)=" end-of-entry t)
  111. (setq key (match-string 1))
  112. ;; get value
  113. (goto-char (+ 1 (match-end 1)))
  114. (setq p1 (point))
  115. (if (looking-at "{")
  116. ;; value is wrapped in {}
  117. (progn
  118. (or-find-closing-curly-bracket)
  119. (setq p2 (point)
  120. value (buffer-substring (+ 1 p1) p2)))
  121. ;; value is up to the next comma
  122. (re-search-forward "," end-of-entry 'mv)
  123. (setq value (buffer-substring p1 (- (point) 1))))
  124. ;; remove #+latex_header_extra:
  125. (setq value (replace-regexp-in-string
  126. "#\\+latex_header_extra: " "" value))
  127. (setq value (replace-regexp-in-string
  128. "\n +" " " value))
  129. (setq data (append data
  130. (list (intern (format ":%s" key)))
  131. (list value))))
  132. data))))
  133. ;;;###autoload
  134. (defun org-ref-add-glossary-entry (label name description)
  135. "Insert a new glossary entry.
  136. LABEL is how you refer to it with links.
  137. NAME is the name of the entry to be defined.
  138. DESCRIPTION is the definition of the entry.
  139. Entry gets added after the last #+latex_header line."
  140. (interactive "sLabel: \nsName: \nsDescription: ")
  141. (save-excursion
  142. (re-search-backward "#\\+latex_header" nil t)
  143. (forward-line)
  144. (when (not (looking-at "^$"))
  145. (beginning-of-line)
  146. (insert "\n")
  147. (forward-line -1))
  148. (insert (format "#+latex_header_extra: \\newglossaryentry{%s}{name={%s},description={%s}}\n"
  149. label name description))))
  150. ;;** Glossary links
  151. (defun or-follow-glossary (entry)
  152. "Goto beginning of the glossary ENTRY."
  153. (org-mark-ring-push)
  154. (goto-char (point-min))
  155. (re-search-forward (format "\\newglossaryentry{%s}" entry))
  156. (goto-char (match-beginning 0)))
  157. (defvar org-ref-glossary-gls-commands
  158. '("gls" "glspl" "Gls" "Glspl" "glssymbol" "glsdesc"))
  159. (dolist (command org-ref-glossary-gls-commands)
  160. (org-ref-link-set-parameters command
  161. :follow #'or-follow-glossary
  162. :face 'org-ref-glossary-face
  163. :help-echo 'or-glossary-tooltip
  164. :export (lambda (path _ format)
  165. (cond
  166. ((eq format 'latex)
  167. (format "\\%s{%s}" command path))
  168. (t
  169. (format "%s" path))))))
  170. (org-ref-link-set-parameters "glslink"
  171. :follow #'or-follow-glossary
  172. :face 'org-ref-glossary-face
  173. :help-echo 'or-glossary-tooltip
  174. :export (lambda (path desc format)
  175. (cond
  176. ((eq format 'latex)
  177. (format "\\glslink{%s}{%s}" path desc))
  178. (t
  179. (format "%s" path)))))
  180. ;;** Tooltips on glossary entries
  181. (defface org-ref-glossary-face
  182. `((t (:inherit org-link :foreground ,org-ref-glossary-color)))
  183. "Face for glossary links.")
  184. (defun or-glossary-tooltip (_window _object position)
  185. "Return tooltip for the glossary entry.
  186. The entry is in WINDOW and OBJECT at POSITION.
  187. Used in fontification."
  188. (save-excursion
  189. (goto-char position)
  190. (let* ((label (org-element-property :path (org-element-context)))
  191. (data (or (or-parse-glossary-entry label)
  192. (or-parse-acronym-entry label)))
  193. (name (or (plist-get data :name)
  194. (plist-get data :abbrv)))
  195. (description (or (plist-get data :description)
  196. (plist-get data :full))))
  197. (format
  198. "%s: %s"
  199. name
  200. (with-temp-buffer
  201. (insert (concat description "."))
  202. (fill-paragraph)
  203. (buffer-string))))))
  204. (unless (fboundp 'org-link-set-parameters)
  205. (defun or-next-glossary-link (limit)
  206. "Search to next glossary link up to LIMIT.
  207. Adds a tooltip to the link that is found."
  208. (when (and (re-search-forward
  209. (concat
  210. (regexp-opt '("gls" "glspl"
  211. "Gls" "Glspl"
  212. "glslink"
  213. "glssymbol"
  214. "glsdesc"))
  215. ":[a-zA-Z]\\{2,\\}")
  216. limit t)
  217. (not (org-in-src-block-p))
  218. (not (org-at-comment-p)))
  219. (forward-char -2)
  220. (let ((next-link (org-element-context)))
  221. (if next-link
  222. (progn
  223. (set-match-data (list (org-element-property :begin next-link)
  224. (- (org-element-property :end next-link)
  225. (org-element-property :post-blank next-link))))
  226. (add-text-properties
  227. (org-element-property :begin next-link)
  228. (- (org-element-property :end next-link)
  229. (org-element-property :post-blank next-link))
  230. (list
  231. 'help-echo 'or-glossary-tooltip))
  232. (goto-char (org-element-property :end next-link)))
  233. (goto-char limit)
  234. nil)))))
  235. ;;* Acronyms
  236. ;;;###autoload
  237. (defun org-ref-add-acronym-entry (label abbrv full)
  238. "Add an acronym entry with LABEL.
  239. ABBRV is the abbreviated form.
  240. FULL is the expanded acronym."
  241. (interactive "sLabel: \nsAcronym: \nsFull name: ")
  242. (save-excursion
  243. (re-search-backward "#\\+latex_header" nil t)
  244. (forward-line)
  245. (when (not (looking-at "^$"))
  246. (beginning-of-line)
  247. (insert "\n")
  248. (forward-line -1))
  249. (insert (format "#+latex_header_extra: \\newacronym{%s}{%s}{%s}\n"
  250. label abbrv full))))
  251. (defun or-parse-acronym-entry (label)
  252. "Parse an acronym entry LABEL to a plist.
  253. \(:abbrv abbrv :full full)
  254. \newacronym{<label>}{<abbrv>}{<full>}"
  255. (save-excursion
  256. (let (abbrv full p1)
  257. (goto-char (point-min))
  258. (when
  259. (re-search-forward (format "\\newacronym{%s}" label) nil t)
  260. (setq p1 (+ 1 (point)))
  261. (forward-list)
  262. (setq abbrv (buffer-substring p1 (- (point) 1)))
  263. (setq p1 (+ 1 (point)))
  264. (forward-list)
  265. (setq full (buffer-substring p1 (- (point) 1)))
  266. (list :abbrv abbrv :full full)))))
  267. ;;** Acronym links
  268. (defun or-follow-acronym (label)
  269. "Go to the definition of the acronym LABEL."
  270. (org-mark-ring-push)
  271. (goto-char (point-min))
  272. (re-search-forward (format "\\\\newacronym{%s}" label))
  273. (goto-char (match-beginning 0)))
  274. (defvar org-ref-glossary-acr-commands-mapping
  275. '(("acrshort" . "acrshort")
  276. ("acrlong" . "acrlong")
  277. ("acrfull" . "acrfull")
  278. ("ac" . "gls")
  279. ("Ac" . "Gls")
  280. ("acp" . "glspl")
  281. ("Acp" . "Glspl")))
  282. (dolist (mapping org-ref-glossary-acr-commands-mapping)
  283. (org-ref-link-set-parameters (car mapping)
  284. :follow #'or-follow-acronym
  285. :face 'org-ref-acronym-face
  286. :help-echo 'or-acronym-tooltip
  287. :export (lambda (path _ format)
  288. (cond
  289. ((eq format 'latex)
  290. (format "\\%s{%s}" (cdr mapping) path))
  291. (t
  292. (format "%s" (upcase path)))))))
  293. ;;** Tooltips on acronyms
  294. (defface org-ref-acronym-face
  295. `((t (:inherit org-link :foreground ,org-ref-acronym-color)))
  296. "Face for acronym links.")
  297. (defun or-acronym-tooltip (_window _object position)
  298. "Return tooltip for the acronym entry.
  299. The entry is in WINDOW and OBJECT at POSITION.
  300. Used in fontification.
  301. WINDOW and OBJECT are ignored."
  302. (save-excursion
  303. (goto-char position)
  304. (let* ((label (org-element-property :path (org-element-context)))
  305. (acronym-data (or-parse-acronym-entry label))
  306. (abbrv (plist-get acronym-data :abbrv))
  307. (full (plist-get acronym-data :full)))
  308. (if acronym-data
  309. (format
  310. "%s: %s"
  311. abbrv full)
  312. (format "%s is not defined in this file." label)))))
  313. ;; We use search instead of a regexp to match links with descriptions. These are
  314. ;; hard to do with regexps.
  315. (unless (fboundp 'org-link-set-parameters)
  316. (defun or-next-acronym-link (limit)
  317. "Search to next acronym link up to LIMIT and add a tooltip."
  318. (when (and (re-search-forward
  319. (concat
  320. (regexp-opt '("acrshort" "acrfull" "acrlong" "ac" "Ac" "acp" "Acp"))
  321. ":[a-zA-Z]\\{2,\\}")
  322. limit t)
  323. (not (org-in-src-block-p))
  324. (not (org-at-comment-p)))
  325. (save-excursion
  326. (forward-char -2)
  327. (let ((next-link (org-element-context)))
  328. (if next-link
  329. (progn
  330. (set-match-data
  331. (list (org-element-property :begin next-link)
  332. (- (org-element-property :end next-link)
  333. (org-element-property :post-blank next-link))))
  334. (add-text-properties
  335. (org-element-property :begin next-link)
  336. (- (org-element-property :end next-link)
  337. (org-element-property :post-blank next-link))
  338. (list
  339. 'help-echo 'or-acronym-tooltip))
  340. (goto-char (org-element-property :end next-link)))
  341. (goto-char limit)
  342. nil))))))
  343. ;; * Helm command to insert entries
  344. ;;;###autoload
  345. (defun org-ref-insert-glossary-link ()
  346. "Helm command to insert glossary and acronym entries as links."
  347. (interactive)
  348. ;; gather entries
  349. (let ((glossary-candidates '())
  350. (acronym-candidates '())
  351. key
  352. entry)
  353. (save-excursion
  354. (goto-char (point-min))
  355. (while (re-search-forward
  356. "\\\\newglossaryentry{\\([[:ascii:]]+?\\)}" nil t)
  357. (setq key (match-string 1)
  358. entry (or-parse-glossary-entry key))
  359. (setq glossary-candidates
  360. (append
  361. glossary-candidates
  362. (list
  363. (cons
  364. ;; for helm
  365. (format "%s: %s."
  366. (plist-get entry :name)
  367. (plist-get entry :description))
  368. ;; the returned candidate
  369. (list key
  370. (plist-get entry :name))))))))
  371. ;; acronym candidates
  372. (save-excursion
  373. (goto-char (point-min))
  374. (while (re-search-forward
  375. "\\\\newacronym{\\([[:ascii:]]+?\\)}" nil t)
  376. (setq key (match-string 1)
  377. entry (or-parse-acronym-entry key))
  378. (setq acronym-candidates
  379. (append
  380. acronym-candidates
  381. (list
  382. (cons
  383. ;; for helm
  384. (format "%s (%s)."
  385. (plist-get entry :full)
  386. (plist-get entry :abbrv))
  387. ;; the returned candidate
  388. (list key
  389. (plist-get entry :abbrv))))))))
  390. (helm :sources
  391. `(,(helm-build-sync-source "Insert glossary term"
  392. :candidates glossary-candidates
  393. :action (lambda (candidate)
  394. (insert (format
  395. "[[%s:%s][%s]]"
  396. (completing-read "Type: "
  397. '("gls"
  398. "glspl"
  399. "Gls"
  400. "Glspl"
  401. "glssymbol"
  402. "glsdesc")
  403. nil t
  404. "gls")
  405. (nth 0 candidate)
  406. (nth 1 candidate)))))
  407. ,(helm-build-sync-source "Insert acronym term"
  408. :candidates acronym-candidates
  409. :action (lambda (candidate)
  410. (insert (format
  411. "[[%s:%s][%s]]"
  412. (completing-read "Type: "
  413. '("acrshort"
  414. "acrlong"
  415. "acrfull"
  416. "ac"
  417. "Ac"
  418. "acp"
  419. "Acp")
  420. nil t
  421. "ac")
  422. (nth 0 candidate)
  423. (nth 1 candidate)))))
  424. ,(helm-build-sync-source "Add new term"
  425. :candidates '(("Add glossary term" . org-ref-add-glossary-entry)
  426. ("Add acronym term" . org-ref-add-acronym-entry))
  427. :action (lambda (x)
  428. (call-interactively x)))))))
  429. (provide 'org-ref-glossary)
  430. ;;; org-ref-glossary.el ends here