Clickable text for learning environments

| categories: emacs | tags:

One use for clickable text is in educational texts, or technical documents where you want easy access to glossaries for jargon or new words, or other context specific information. Here we consider some approaches to highlight words in an Emacs buffer that are defined in a glossary, to give them tooltips and make them clickable.

You may want to see the video of this in action, the blog post does not do it justice:

We assume we have a 1 in the current document that has the words we want to highlight as headlines. Here is a somewhat hacky way to get the list of keywords (hacky because we use cdr to get rid of the Glossary in the list). Our glossary only has two terms: INCAR and KPOINTS.

    (org-open-link-from-string "[[*Glossary]]")
    (cdr  (org-map-entries (lambda ()
                             (nth 4 (org-heading-components)))
                           nil 'tree)))

We can use that list to make the regexp for button lock with regexp-opt like we did before. We illustrate two ideas here for the highlighted text. One is a dynamic tooltip, which we calculate on the fly and use to display the contents of the glossary heading when you mouse over the word or call local help from the keyboard (C-h .). Second, when you click on the word, you jump to the section in the glossary, and you can readily jump back with C-c & (Thanks org-mode!).

(defun highlight-glossary-words ()
   (regexp-opt (save-excursion
                 (org-open-link-from-string "[[*Glossary]]")
                 (cdr  (org-map-entries
                        (lambda ()
                          (nth 4 (org-heading-components)))
                        nil 'tree))))
   (lambda ()
     "Jump to definition."
     (let ((keyword (get-surrounding-text-with-property 'glossary)))
       (org-open-link-from-string (format "[[*%s]]" keyword))))
   :additional-property 'glossary
   :face '((:background "gray80") (:underline t))
   :help-echo (lambda (window object position)
                  (goto-char position)
                     (format "[[*%s]]" (get-surrounding-text-with-property 'glossary)))

\(?:INCAR\ KPOINTS\) (0 (quote (face ((:background gray80) (:underline t)) keymap (keymap (mouse-1 lambda nil Jump to definition. (interactive) (let ((keyword (get-surrounding-text-with-property (quote glossary)))) (org-open-link-from-string (format *%s keyword))))) button-lock t glossary t mouse-face button-lock-mouse-face help-echo (lambda (window object position) (save-excursion (goto-char position) (save-restriction (org-open-link-from-string (format *%s (get-surrounding-text-with-property (quote glossary)))) (org-narrow-to-subtree) (buffer-string)))) rear-nonsticky t)) append)

That is pretty cool. You might want something a little smarter for the tooltip, e.g. just the first line of the headline, but this works fine for this little example. I noticed that flyspell seems to get the tooltip in KPOINTS, sometimes, when it thinks it is misspelled.

It might take some local variables to make this work only in this just a file, rather than in every file. Alternatively, you could define a function that opens the file and then applies this.

1 Glossary


The file containing all the input parameters for VASP.


The file containing the definitions of the kpoint grid.


Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter