;;; org-ref-glossary.el --- glossary support in org-ref -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2016 John Kitchin
|
|
|
|
;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
|
|
;; Keywords:
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Provides Some glossary support for org-mode. Only export to LaTeX is
|
|
;; supported. The functionality is based on the LaTeX glossaries package. See
|
|
;; https://en.wikibooks.org/wiki/LaTeX/Glossary and
|
|
;; http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/glossaries/glossaries-user.pdf
|
|
|
|
;; Put something like this in your org-file.
|
|
;; #+latex_header: \usepackage{glossaries}
|
|
;; #+latex_header: \makeglossaries
|
|
|
|
;; Put this where you want the glossaries to appear in your org-file.
|
|
;; \printglossaries
|
|
|
|
;; Add new glossary entries to your org-file like this. Enclose strings
|
|
;; containing a comma in {}. Multiline entries are supported.
|
|
|
|
;; #+latex_header_extra: \newglossaryentry{computer}{name=computer,description={A machine, that computes}}
|
|
;; #+latex_header_extra: \newglossaryentry{tree}{name=tree,description=a big plant}
|
|
|
|
;; #+latex_header_extra: \newglossaryentry{naiive}
|
|
;; #+latex_header_extra: {
|
|
;; #+latex_header_extra: name=na\"{\i}ve,
|
|
;; #+latex_header_extra: description={is a French loanword (adjective, form of naïf)
|
|
;; #+latex_header_extra: indicating having or showing a lack of experience,
|
|
;; #+latex_header_extra: understanding or sophistication}
|
|
;; #+latex_header_extra: }
|
|
|
|
;; Here is an example acronym definition
|
|
;; #+latex_header_extra: \newacronym{lvm}{LVM}{Logical Volume Manager}
|
|
|
|
;; New links defined:
|
|
;; gls:name A reference to the glossary entry NAME.
|
|
;; glspl:name The plural version of the entry
|
|
;; Gls:name Capitalized glossary entry
|
|
;; Glspl: Capitalized, plural glossary entry
|
|
;; [[gslink:name][alternate text]]
|
|
;; glssymbol:name Outputs the symbol value of the glossary entry settings.
|
|
;; glsdesc:name The description of name
|
|
|
|
;; The links export to LaTeX. You can click on the link and jump to the
|
|
;; definition. The links have tooltips for the definitions.
|
|
|
|
;; Acronym links
|
|
;; acrshort:label
|
|
;; acrfull:label
|
|
;; acrlong:label
|
|
;; ac:label (exports to \gls{label})
|
|
;; Ac:label (exports to \Gls{label})
|
|
;; acp:label (exports to \glspl{label})
|
|
;; Acp:label (exports to \Glspl{label})
|
|
|
|
(require 'org-element)
|
|
(require 'org-ref-utils)
|
|
|
|
(declare-function helm "helm")
|
|
(declare-function helm-build-sync-source "helm-source")
|
|
|
|
;;; Code:
|
|
(defgroup org-ref-glossary nil
|
|
"Customization group for org-ref-glossary."
|
|
:tag "Org Ref glossary"
|
|
:group 'org)
|
|
|
|
|
|
(defcustom org-ref-glossary-color "Mediumpurple3"
|
|
"Color for glossary links."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defcustom org-ref-acronym-color "Darkorange2"
|
|
"Color for acronym links."
|
|
:type 'string
|
|
:group 'org-ref)
|
|
|
|
|
|
(defun or-find-closing-curly-bracket (&optional limit)
|
|
"Find closing bracket for the bracket at point and move point to it.
|
|
Go up to LIMIT or `point-max'. This is a parsing function. I
|
|
wrote this because using `forward-list' does not always work if
|
|
there is an escaped \" for example. This seems pretty robust."
|
|
(unless (looking-at "{") (error "Not at a curley bracket"))
|
|
|
|
(let ((level 1))
|
|
(while (and (not (= 0 level))
|
|
(not (eobp))
|
|
(< (point) (or limit (point-max))))
|
|
(forward-char)
|
|
(when (and (looking-at "{")
|
|
(not (looking-back "\\\\" (- (point) 2))))
|
|
(cl-incf level))
|
|
(when (and (looking-at "}")
|
|
(not (looking-back "\\\\" (- (point) 2))))
|
|
(cl-decf level)))
|
|
(point)))
|
|
|
|
|
|
;;* Glossary
|
|
(defun or-parse-glossary-entry (entry)
|
|
"Parse glossary ENTRY definition to a p-list of key=value.
|
|
Typically:
|
|
(:name name :description description)
|
|
but there could be other :key value pairs."
|
|
(save-excursion
|
|
(let (end-of-entry
|
|
data
|
|
key value p1 p2)
|
|
(goto-char (point-min))
|
|
;; We may not find an entry if it is defined as an acronym
|
|
(when (re-search-forward
|
|
(format "\\newglossaryentry{%s}" entry) nil t)
|
|
(re-search-forward "{")
|
|
(save-excursion
|
|
(backward-char)
|
|
(or-find-closing-curly-bracket)
|
|
(setq end-of-entry (point)))
|
|
|
|
(while (re-search-forward "\\(\\w+?\\)=" end-of-entry t)
|
|
(setq key (match-string 1))
|
|
;; get value
|
|
(goto-char (+ 1 (match-end 1)))
|
|
(setq p1 (point))
|
|
(if (looking-at "{")
|
|
;; value is wrapped in {}
|
|
(progn
|
|
(or-find-closing-curly-bracket)
|
|
(setq p2 (point)
|
|
value (buffer-substring (+ 1 p1) p2)))
|
|
;; value is up to the next comma
|
|
(re-search-forward "," end-of-entry 'mv)
|
|
(setq value (buffer-substring p1 (- (point) 1))))
|
|
;; remove #+latex_header_extra:
|
|
(setq value (replace-regexp-in-string
|
|
"#\\+latex_header_extra: " "" value))
|
|
(setq value (replace-regexp-in-string
|
|
"\n +" " " value))
|
|
(setq data (append data
|
|
(list (intern (format ":%s" key)))
|
|
(list value))))
|
|
data))))
|
|
|
|
|
|
;;;###autoload
|
|
(defun org-ref-add-glossary-entry (label name description)
|
|
"Insert a new glossary entry.
|
|
LABEL is how you refer to it with links.
|
|
NAME is the name of the entry to be defined.
|
|
DESCRIPTION is the definition of the entry.
|
|
Entry gets added after the last #+latex_header line."
|
|
(interactive "sLabel: \nsName: \nsDescription: ")
|
|
(save-excursion
|
|
(re-search-backward "#\\+latex_header" nil t)
|
|
(forward-line)
|
|
(when (not (looking-at "^$"))
|
|
(beginning-of-line)
|
|
(insert "\n")
|
|
(forward-line -1))
|
|
(insert (format "#+latex_header_extra: \\newglossaryentry{%s}{name={%s},description={%s}}\n"
|
|
label name description))))
|
|
|
|
;;** Glossary links
|
|
(defun or-follow-glossary (entry)
|
|
"Goto beginning of the glossary ENTRY."
|
|
(org-mark-ring-push)
|
|
(goto-char (point-min))
|
|
(re-search-forward (format "\\newglossaryentry{%s}" entry))
|
|
(goto-char (match-beginning 0)))
|
|
|
|
|
|
(defvar org-ref-glossary-gls-commands
|
|
'("gls" "glspl" "Gls" "Glspl" "glssymbol" "glsdesc"))
|
|
|
|
|
|
(dolist (command org-ref-glossary-gls-commands)
|
|
(org-ref-link-set-parameters command
|
|
:follow #'or-follow-glossary
|
|
:face 'org-ref-glossary-face
|
|
:help-echo 'or-glossary-tooltip
|
|
:export (lambda (path _ format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\%s{%s}" command path))
|
|
(t
|
|
(format "%s" path))))))
|
|
|
|
|
|
(org-ref-link-set-parameters "glslink"
|
|
:follow #'or-follow-glossary
|
|
:face 'org-ref-glossary-face
|
|
:help-echo 'or-glossary-tooltip
|
|
:export (lambda (path desc format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\glslink{%s}{%s}" path desc))
|
|
(t
|
|
(format "%s" path)))))
|
|
|
|
|
|
;;** Tooltips on glossary entries
|
|
(defface org-ref-glossary-face
|
|
`((t (:inherit org-link :foreground ,org-ref-glossary-color)))
|
|
"Face for glossary links.")
|
|
|
|
|
|
(defun or-glossary-tooltip (_window _object position)
|
|
"Return tooltip for the glossary entry.
|
|
The entry is in WINDOW and OBJECT at POSITION.
|
|
Used in fontification."
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let* ((label (org-element-property :path (org-element-context)))
|
|
(data (or (or-parse-glossary-entry label)
|
|
(or-parse-acronym-entry label)))
|
|
(name (or (plist-get data :name)
|
|
(plist-get data :abbrv)))
|
|
(description (or (plist-get data :description)
|
|
(plist-get data :full))))
|
|
(format
|
|
"%s: %s"
|
|
name
|
|
(with-temp-buffer
|
|
(insert (concat description "."))
|
|
(fill-paragraph)
|
|
(buffer-string))))))
|
|
|
|
|
|
(unless (fboundp 'org-link-set-parameters)
|
|
(defun or-next-glossary-link (limit)
|
|
"Search to next glossary link up to LIMIT.
|
|
Adds a tooltip to the link that is found."
|
|
(when (and (re-search-forward
|
|
(concat
|
|
(regexp-opt '("gls" "glspl"
|
|
"Gls" "Glspl"
|
|
"glslink"
|
|
"glssymbol"
|
|
"glsdesc"))
|
|
":[a-zA-Z]\\{2,\\}")
|
|
limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
(forward-char -2)
|
|
(let ((next-link (org-element-context)))
|
|
(if next-link
|
|
(progn
|
|
(set-match-data (list (org-element-property :begin next-link)
|
|
(- (org-element-property :end next-link)
|
|
(org-element-property :post-blank next-link))))
|
|
(add-text-properties
|
|
(org-element-property :begin next-link)
|
|
(- (org-element-property :end next-link)
|
|
(org-element-property :post-blank next-link))
|
|
(list
|
|
'help-echo 'or-glossary-tooltip))
|
|
(goto-char (org-element-property :end next-link)))
|
|
(goto-char limit)
|
|
nil)))))
|
|
|
|
|
|
;;* Acronyms
|
|
;;;###autoload
|
|
(defun org-ref-add-acronym-entry (label abbrv full)
|
|
"Add an acronym entry with LABEL.
|
|
ABBRV is the abbreviated form.
|
|
FULL is the expanded acronym."
|
|
(interactive "sLabel: \nsAcronym: \nsFull name: ")
|
|
(save-excursion
|
|
(re-search-backward "#\\+latex_header" nil t)
|
|
(forward-line)
|
|
(when (not (looking-at "^$"))
|
|
(beginning-of-line)
|
|
(insert "\n")
|
|
(forward-line -1))
|
|
|
|
(insert (format "#+latex_header_extra: \\newacronym{%s}{%s}{%s}\n"
|
|
label abbrv full))))
|
|
|
|
|
|
(defun or-parse-acronym-entry (label)
|
|
"Parse an acronym entry LABEL to a plist.
|
|
\(:abbrv abbrv :full full)
|
|
\newacronym{<label>}{<abbrv>}{<full>}"
|
|
(save-excursion
|
|
(let (abbrv full p1)
|
|
(goto-char (point-min))
|
|
(when
|
|
(re-search-forward (format "\\newacronym{%s}" label) nil t)
|
|
(setq p1 (+ 1 (point)))
|
|
(forward-list)
|
|
(setq abbrv (buffer-substring p1 (- (point) 1)))
|
|
(setq p1 (+ 1 (point)))
|
|
(forward-list)
|
|
(setq full (buffer-substring p1 (- (point) 1)))
|
|
(list :abbrv abbrv :full full)))))
|
|
|
|
;;** Acronym links
|
|
(defun or-follow-acronym (label)
|
|
"Go to the definition of the acronym LABEL."
|
|
(org-mark-ring-push)
|
|
(goto-char (point-min))
|
|
(re-search-forward (format "\\\\newacronym{%s}" label))
|
|
(goto-char (match-beginning 0)))
|
|
|
|
|
|
(defvar org-ref-glossary-acr-commands-mapping
|
|
'(("acrshort" . "acrshort")
|
|
("acrlong" . "acrlong")
|
|
("acrfull" . "acrfull")
|
|
("ac" . "gls")
|
|
("Ac" . "Gls")
|
|
("acp" . "glspl")
|
|
("Acp" . "Glspl")))
|
|
|
|
|
|
(dolist (mapping org-ref-glossary-acr-commands-mapping)
|
|
(org-ref-link-set-parameters (car mapping)
|
|
:follow #'or-follow-acronym
|
|
:face 'org-ref-acronym-face
|
|
:help-echo 'or-acronym-tooltip
|
|
:export (lambda (path _ format)
|
|
(cond
|
|
((eq format 'latex)
|
|
(format "\\%s{%s}" (cdr mapping) path))
|
|
(t
|
|
(format "%s" (upcase path)))))))
|
|
|
|
|
|
;;** Tooltips on acronyms
|
|
(defface org-ref-acronym-face
|
|
`((t (:inherit org-link :foreground ,org-ref-acronym-color)))
|
|
"Face for acronym links.")
|
|
|
|
|
|
(defun or-acronym-tooltip (_window _object position)
|
|
"Return tooltip for the acronym entry.
|
|
The entry is in WINDOW and OBJECT at POSITION.
|
|
Used in fontification.
|
|
WINDOW and OBJECT are ignored."
|
|
(save-excursion
|
|
(goto-char position)
|
|
(let* ((label (org-element-property :path (org-element-context)))
|
|
(acronym-data (or-parse-acronym-entry label))
|
|
(abbrv (plist-get acronym-data :abbrv))
|
|
(full (plist-get acronym-data :full)))
|
|
(if acronym-data
|
|
(format
|
|
"%s: %s"
|
|
abbrv full)
|
|
(format "%s is not defined in this file." label)))))
|
|
|
|
|
|
;; We use search instead of a regexp to match links with descriptions. These are
|
|
;; hard to do with regexps.
|
|
(unless (fboundp 'org-link-set-parameters)
|
|
(defun or-next-acronym-link (limit)
|
|
"Search to next acronym link up to LIMIT and add a tooltip."
|
|
(when (and (re-search-forward
|
|
(concat
|
|
(regexp-opt '("acrshort" "acrfull" "acrlong" "ac" "Ac" "acp" "Acp"))
|
|
":[a-zA-Z]\\{2,\\}")
|
|
limit t)
|
|
(not (org-in-src-block-p))
|
|
(not (org-at-comment-p)))
|
|
(save-excursion
|
|
(forward-char -2)
|
|
(let ((next-link (org-element-context)))
|
|
(if next-link
|
|
(progn
|
|
(set-match-data
|
|
(list (org-element-property :begin next-link)
|
|
(- (org-element-property :end next-link)
|
|
(org-element-property :post-blank next-link))))
|
|
(add-text-properties
|
|
(org-element-property :begin next-link)
|
|
(- (org-element-property :end next-link)
|
|
(org-element-property :post-blank next-link))
|
|
(list
|
|
'help-echo 'or-acronym-tooltip))
|
|
(goto-char (org-element-property :end next-link)))
|
|
(goto-char limit)
|
|
nil))))))
|
|
|
|
|
|
;; * Helm command to insert entries
|
|
;;;###autoload
|
|
(defun org-ref-insert-glossary-link ()
|
|
"Helm command to insert glossary and acronym entries as links."
|
|
(interactive)
|
|
;; gather entries
|
|
(let ((glossary-candidates '())
|
|
(acronym-candidates '())
|
|
key
|
|
entry)
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(while (re-search-forward
|
|
"\\\\newglossaryentry{\\([[:ascii:]]+?\\)}" nil t)
|
|
(setq key (match-string 1)
|
|
entry (or-parse-glossary-entry key))
|
|
(setq glossary-candidates
|
|
(append
|
|
glossary-candidates
|
|
(list
|
|
(cons
|
|
;; for helm
|
|
(format "%s: %s."
|
|
(plist-get entry :name)
|
|
(plist-get entry :description))
|
|
;; the returned candidate
|
|
(list key
|
|
(plist-get entry :name))))))))
|
|
|
|
;; acronym candidates
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(while (re-search-forward
|
|
"\\\\newacronym{\\([[:ascii:]]+?\\)}" nil t)
|
|
(setq key (match-string 1)
|
|
entry (or-parse-acronym-entry key))
|
|
(setq acronym-candidates
|
|
(append
|
|
acronym-candidates
|
|
(list
|
|
(cons
|
|
;; for helm
|
|
(format "%s (%s)."
|
|
(plist-get entry :full)
|
|
(plist-get entry :abbrv))
|
|
;; the returned candidate
|
|
(list key
|
|
(plist-get entry :abbrv))))))))
|
|
|
|
(helm :sources
|
|
`(,(helm-build-sync-source "Insert glossary term"
|
|
:candidates glossary-candidates
|
|
:action (lambda (candidate)
|
|
(insert (format
|
|
"[[%s:%s][%s]]"
|
|
(completing-read "Type: "
|
|
'("gls"
|
|
"glspl"
|
|
"Gls"
|
|
"Glspl"
|
|
"glssymbol"
|
|
"glsdesc")
|
|
nil t
|
|
"gls")
|
|
(nth 0 candidate)
|
|
(nth 1 candidate)))))
|
|
,(helm-build-sync-source "Insert acronym term"
|
|
:candidates acronym-candidates
|
|
:action (lambda (candidate)
|
|
(insert (format
|
|
"[[%s:%s][%s]]"
|
|
(completing-read "Type: "
|
|
'("acrshort"
|
|
"acrlong"
|
|
"acrfull"
|
|
"ac"
|
|
"Ac"
|
|
"acp"
|
|
"Acp")
|
|
nil t
|
|
"ac")
|
|
(nth 0 candidate)
|
|
(nth 1 candidate)))))
|
|
,(helm-build-sync-source "Add new term"
|
|
:candidates '(("Add glossary term" . org-ref-add-glossary-entry)
|
|
("Add acronym term" . org-ref-add-acronym-entry))
|
|
:action (lambda (x)
|
|
(call-interactively x)))))))
|
|
|
|
|
|
(provide 'org-ref-glossary)
|
|
;;; org-ref-glossary.el ends here
|