org-special-block-extras
A unified interface for special blocks and links: defblock
Abstract
The aim is to write something once using Org-mode markup then generate the markup for multiple backends. That is, write once, generate many!
In particular, we are concerned with ‘custom’, or ‘special’, blocks which delimit how a particular region of text is supposed to be formatted according to the possible export backends. In some sense, special blocks are meta-blocks. Rather than writing text in, say, LaTeX environments using LaTeX commands or in HTML
div
's using HTML tags, we promote using Org-mode markup in special blocks —Org markup cannot be used explicitly within HTML or LaTeX environments.Special blocks, like
centre
andquote
, allow us to use Org-mode as the primary interface regardless of whether the final result is an HTML or PDF article; sometime we need to make our own special blocks to avoid a duplication of effort. However, this can be difficult and may require familiarity with relatively advanced ELisp concepts, such as macros and hooks; as such, users may not be willing to put in the time and instead use ad-hoc solutions.We present a new macro, defblock, which is similar in-spirit to Lisp's standard defun except that where the latter defines functions, ours defines new special blocks for Emacs' Org-mode —as well as, simultaneously, defining new Org link types. Besides the macro, the primary contribution of this effort is an interface for special blocks that admits arguments and is familar to Org users —namely, we ‘try to reuse’ the familiar
src
-block interface, including header-args, but for special blocks.It is hoped that the ease of creating custom special blocks will be a gateway for many Emacs users to start using Lisp.
A 5-page PDF covering ELisp fundamentals can be found here.
This article is featured in EmacsConf2020, with slides here: No pictures, instead we use this system to make the slides have a variety of styling information; i.e., we write Org and the result looks nice. “Look ma, no HTML required!”
Figure 1: Write in Emacs using Org-mode, export beautifully to HTML or LaTeX
Super Simple Intro to Emacs’ Org-mode
Emacs’ Org-mode is an outliner, a rich markup language, spreadsheet tool, literate programming system, and so much more. It is an impressive reason to use Emacs (•̀ᴗ•́)و
Org-mode syntax is very natural; e.g., the following is Org-mode! ( Org Mode Is One of the Most Reasonable Markup Languages to Use for Text )
+ Numbered and bulleted lists are as expected. - Do the things: 1. This first 2. This second 44. [@44] This forty-fourth - [@𝓃] at the beginning of an iterm forces list numbering to start at 𝓃 - [ ] or [X] at the beginning for checkbox lists - Use Alt ↑, ↓ to move items up and down lists; renumbering happens automatically. + Definitions lists: - term :: def + Use a comment, such as # separator, between two lists to communicate that these are two lists that happen to be one after the other. Or use any non-indented text to split a list into two. * My top heading, section words ** Child heading, subsection more words *** Grandchild heading, subsubsection even more!
Export In Emacs, press C-c C-e h o to obtain an HTML webpage ---like this one!— of the Org-mode markup; use C-c C-e l o to obtain a PDF rendition.
You can try Org-mode notation and see how it renders live at: http://mooz.github.io/org-js/
You make a heading by writing * heading
at the start of a line, then you can
TAB to fold/unfold its contents. A table of contents, figures, tables can be
requested as follows:
# figures not implemented in the HTML backend # The 𝓃 is optional and denotes headline depth #+toc: headlines 𝓃 #+toc: figures #+toc: tables
Markup elements can be nested.
Syntax Result /Emphasise/
, italicsEmphasise *Strong*
, boldStrong */very strongly/*
, bold italicsvery strongly =verbatim=
, monospaced typewriterverbatim
+deleted+
deleted_inserted_
inserted super^{script}ed
superscripted sub_{scripted}ed
subscripteded - Markup can span across multiple lines, by default no more than 2.
- In general, markup cannot be ‘in the middle’ of a word.
- New lines demarcate paragraphs
- Use
\\
to force line breaks without starting a new paragraph - Use at least 5 dashes,
-----
, to form a horizontal rule
provides support for numerous other kinds of markup elements, such as red:hello
which becomes “ hello ”.
Working with tables
#+ATTR_HTML: :width 100% #+name: my-tbl #+caption: Example table | Who? | What? | |------+-------| | me | Emacs | | you | Org |
Note the horizontal rule makes a header row and is formed by typing doit then pressing TAB. You can TAB between cells.
- You can make an empty table with
C-c |
, which is just org-table-create-or-convert-from-region, then give it row×column dimensions. - Any lines with comma-separated-values (CSV) can be turned into an Org table by
selecting the region and pressing
C-u C-c |
. (Any CSV file can thus be visualised nicely as an Org table). - Use
C-u C-u C-u C-c |
to make a table from values that are speared by a certain regular expression.
Working with links
Link syntax is [[source url][description]]
; e.g., we can refer to the above
table with [[my-tbl][woah]]
.
Likewise for images: file:path-to-image.
Mathematics
Source
\[ \sin^2 x + \cos^2 x = \int_\pi^{\pi + 1} 1 dx = {3 \over 3} \]
Result
\[ \sin^2 x + \cos^2 x = \int_\pi^{\pi + 1} 1 dx = {3 \over 3} \]
- Instead of
\[...\]
, which displays a formula on its own line, centred, use$...$
to show a formula inline. - Captioned equations are numbered and can be referenced via links, as shown below.
Source
#+name: euler \begin{equation} e ^ {i \pi} + 1 = 0 \end{equation} See equation [[euler]].
Result
\begin{equation} \label{orgf1f3f1a} e ^ {i \pi} + 1 = 0 \end{equation}See equation \eqref{orgf1f3f1a}.
Source code
Source
#+begin_src C -n int tot = 1; (ref:start) for (int i = 0; i != 10; i++) (ref:loop) tot *= i; (ref:next) printf("The factorial of 10 is %d", tot); #+end_src
Result
1: int tot = 1; (start) 2: for (int i = 0; i != 10; i++) (loop) 3: tot *= i; (next) 4: printf("The factorial of 10 is %d", tot);
The labels (ref:name)
refer to the lines in the source code and can be
referenced with link syntax: [[(name)]]
. Hovering over the link, in the HTML
export, will dynamically highlight the corresponding line of code. To strip-out
the labels from the displayed block, use -r -n
in the header so it becomes
#+begin_src C -r -n
, now the references become line numbers.
Another reason to use Org:
If you use :results raw
, you obtain dynamic templates that may use Org-markup:
Source
#+begin_src C printf("*bold* +%d+ (strikethrough) /slanted/", 12345); #+end_src ♯+RESULTS: *bold* +12345+ (strikethrough) /slanted/
Result
printf("*bold* +%d+ (strikethrough) /slanted/", 12345);
♯+RESULTS:
bold 12345 (strikethrough) slanted
The #+RESULTS:
is obtained by pressing C-c C-c on the src
block, to execute
it and obtain its result.
Also: Notice that a C program can be run without a main
;-)
That is, we can write code in between prose that is intended to be read like an essay:
Single source of truth: This mini-tutorial can be included into other Org files by declaring
#+include: ~/.emacs.d/init.org::#Mini-tutorial-on-Org-mode |
For more, see https://orgmode.org/features.html.
Table of Contents
- 1. A unified interface for special blocks and links:
defblock
- 2. Folded Details —As well as boxed text and subtle colours
- 3. Parallel
- 4. :fire: HTML Export Styles as Links
- 5. Fortune Links: Smiles inside and outside of Emacs
- 6. Editor Comments
- 7. Colours
- 8. Nice Keystroke Renditions: kbd:C-h_h
- 9. “Link Here!” & OctoIcons
- 10. Badge Links
- 11. Tooltips for Glossaries, Dictionaries, and Documentation
- 12. Marginal, “one-off”, remarks
- 13. Equational Proofs
The full article may be read as a PDF or as HTML —or visit the repo. Installation instructions are .
1. A unified interface for special blocks and links: defblock
An Org-mode block of type 𝒳 is a bunch of text enclosed in #+begin_𝒳
and
#+end_𝒳
, upon export it modifies the text —e.g., to center it, or to display
it as code. We show how to make new blocks using a simple interface
---defblock— that lets users treat 𝒳 as a string-valued function in the style
of defun.
The notable features of the system are as follows.
- Familiar
defun
syntax for making block ---defblock
- Familiar
src
syntax for passing arguments —e.g.,:key value
- Modular: New blocks can be made out of existing blocks really quickly using
blockcall
—similar to Lisp'sfuncall
.
EmacsConf 2020 Abstract
Users will generally only make use of a few predefined special blocks, such as
example, centre, quote
, and will not bother with the effort required to make new
ones. When new encapsulating notions are required, users will either fallback on
HTML or LaTeX specific solutions, usually littered with #+ATTR
clauses to pass
around configurations or parameters.
Efforts have been exerted to mitigate the trouble of producing new special blocks. However, the issue of passing parameters is still handled in a clumsy fashion; e.g., by having parameters be expressed in a special block's content using specific keywords.
We present a novel approach to making special blocks in a familiar fashion and
their use also in a familiar fashion. We achieve the former by presenting
defblock
, an anaphoric macro exceedingly similar to defun
, and for the latter we
mimic the usual src
-block syntax for argument passing to support special blocks.
For instance, here is a sample declaration.
(org-defblock stutter (nil nil reps 2) "Output the CONTENTS of the block REPS many times" (org-parse (s-repeat reps contents)))
Here is an invocation that passes an optional argument; which defaults to 2 when not given.
#+begin_stutter 5 Emacs for the win :-) #+end_stutter 5
Upon export, to HTML or LaTeX for instance, the contents of this block are
repeated (stuttered) 5 times. The use of src
-like invocation may lead to a
decrease in #+ATTR
clauses.
In the presentation, we aim to show a few practical special blocks that users may want: A block that …
- translates some selected text —useful for multilingual blogs
- hides some selected text —useful for learning, quizzes
- folds/boxes text —useful in blogs for folding away details
In particular, all of these examples will be around ~5 lines long!
We also have a larger collection of more useful block types, already implemented.
The notable features of the system are as follows.
- Familiar
defun
syntax for making block ---defblock
- Familiar
src
syntax for passing arguments —e.g.,:key value
- Fine-grained control over export translation phases —c.f.,
org-parse
above - Modular: New blocks can be made out of existing blocks really quickly using
blockcall
—similar to Lisp'sfuncall
. We will show how to fuse two blocks to make a new one, also within ~5 lines.
It is hoped that the ease of creating custom special blocks will be a gateway for many Emacs users to start using Lisp.
1.1. What is a special block?
An Org mode block is a region of text surrounded by #+BEGIN_𝒳 … #+END_𝒳
; they
serve various purposes as summarised in the table below. However, we shall
use such blocks to execute arbitrary code on their contents.
𝒳 | Description |
---|---|
example |
Format text verbatim, leaving markup as is |
src |
Format source code |
center |
Centre text |
quote |
Format text as a quotation —ignore line breaks |
verse |
Every line is appended with a line break |
tiny |
Render text in a small font; likewise footnotesize |
comment |
Completely omit the text from export |
- They can be folded and unfolded in Emacs by pressing TAB in the
#+BEGIN
line. - The contents of blocks can be highlighted as if they were of language ℒ such
as
org, html, latex, haskell, lisp, python, …
by writing#+BEGIN_𝒳 ℒ
on the starting line, where𝒳
is the name of the block type. - Verbatim environments
src
andexample
may be followed by switch-n
to display line numbers for their contents.
I use snippets in my init to quickly insert special blocks (•̀ᴗ•́)و
You can ‘zoom in temporarily’, narrowing your focus to only on a particular
block, with org-narrow-to-element, C-x n e
, to make your window only show
the block. Then use C-x n w
to widen your vision of the buffer's contents.
Warning! Special blocks of the same kind do not nest!
By their very nature, special blocks of the same name cannot be nested —e.g.,
try to put one quote
block within another and see what (does not) happen.
Moreover, special blocks cannot contain unicode in their names and no
underscore, ‘_’, in their names; e.g., a special block named quote₀
will
actually refer to quote
.
Our goal is to turn Org blocks into LaTeX environments and HTML divs.
Why not use LaTeX or HTML environments directly?
- Can no longer use Org markup in such settings.
- Committed to one specific export type.
[Aside:
The export syntax @@backend: 𝒳@@
inserts text 𝒳 literally as-is precisely when
the current backend being exported to is backend
. This is useful for inserting
html
snippets or latex
commands. We can use @@comment: 𝒳@@
to mimic inline
comments ;-) —Since there is [hopefully] no backend named comment
.
In general, a “special block” such as
#+begin_𝒳 I /love/ Emacs! #+end_𝒳
Exports to LaTeX as:
\begin{𝒳} I \emph{love} Emacs! \end{𝒳}
Exports to HTML as:
<div class="𝒳"> I <em>love</em> Emacs! </div>
Notice that the standard org markup is also translated according to the export type.
If the 𝒳
environment exists in a backend —e.g., by some \usepackage{⋯}
or
manually with
\newenvironment{𝒳}{⋯}{⋯}
in LaTeX— then the file will compile
without error. Otherwise, you need to ensure it exists —e.g., by defining the
backend formatting manually yourself.
[Aside:
LaTeX packages that a user needs consistently are declared in the
list org-latex-packages-alist
. See its documentation, with C-h o
,
to learn more. To export to your own LaTeX classes, C-h o org-latex-classes
.
What is an HTML ‘div’?
A div
tag defines a division or a section in an HTML document that is styled in
a particular fashion or has JavaScript code applied to it. For example
—placing the following in an #+begin_export html ⋯ #+end_export
— results in
a section of text that is editable by the user —i.e., one can just alter text
in-place— and its foreground colour is red, while its background colour is
light blue, and it has an uninformative tooltip.
Source
<div contenteditable="true" title="woah, a tool tip!" style="color:red; background-color:lightblue"> This is some editable text! Click me & type! </div>
Result
What is a CSS ‘class’?
To use a collection of style settings repeatedly, we may declare them in a class
—which is just an alias for the ;-separated list of attribute:value
pairs. Then our div
's refer to that particular class
name.
For example, in an HTML export block, we may declare the following style class
named red
.
#+begin_export html <style> .red { color:red; } </style> #+end_export
Now, the above syntax with 𝒳
replaced by red
works as desired in HTML export:
HTML now knows of a class named red
.
For instance, now this
#+begin_red
I /love/ Emacs!
#+end_red
Results in:
I love Emacs!
This approach, however, will not work if we want to produce LaTeX and so requires a duplication of efforts. We would need to declare such formatting once for each backend.
1.2. How do I make a new link type?
Sometimes using a block is too verbose and it'd be better to ‘inline’ it; for this, we use Org's link mechanism.
What would I use links for?
- Buttons
[[elisp:(find-file user-init-file)][Go to my init!]]
is a nice clickable-actionable button within Emacs. Theelisp
link is part of Emacs.- Textual expansions
show:user-full-name
expands, upon export, to Musa Al-hassy on my machine. Or maybe we want a splash of colour in our lives:*<pink: super neato stuff>*
becomes super neato stuff.Similarly,
link-here:usefulness-of-links
becomes: . When you click on it, the URL of the webpage changes: It is a local anchor to a possibly interesting location in your article. Similarly,octoicon:home
becomes ; org-link/octoicon.More aesthetically,
<badge: Emacs | is awesome | blue | https://www.google.com/search?q=hello%20world | gnu-emacs>
exports to .These link types are all defined below.
- Definitions / Personal Glossary
doc:thread-first, doc:family, doc:Hussain
export as the link labels but with tooltips for the Lisp function thread-first, for the English definition of family, and my personal documentation for the phrase Hussain.Similarly,
[[kbd:M-s h .]]
becomes M-s h ., which has a tooltip for the Lisp function associated with the keybinding.
Example Mini-Article Using The Above Link Types
(
) C-x ESC ESC invokes repeat-complex-command, you will see a Lisp form of the previous interactive action you performed. You can press RET to repeat that command, or, more likely, to convert your actions to Lisp code for a personal function.This is a super neato way to get Lisp from your actions. Conversely, use M-: to evaluate any Lisp you have in-hand 😉
.
Just as there is defun for “def”ining “fun”ctions and defmacro for “def”ining “mac”ros, there is also org-deflink for “def”ining “link”s for “O”rg, and it behaves similar to them.
However, there are usually multiple ways to define things —e.g., macros are code-valued functions defined with cl-defun. Indeed, Org links are popularly defined using org-link-set-parameters —which, unlike org-deflink, is not consistent with defun— and so let's take a look at it first.
1.2.1. A comprehensive example of org-link-set-parameters and org-link-parameters
Use (org-link-set-parameters params)
to add a new
link type —an older obsolete method is org-add-link-type. The list of all
supported link types is org-link-parameters; its documentation identifies
the possibilities for params
.
Let's produce an example link type, then discuss its code.
Intended usage: Raw use example:salam and descriptive, ^_^
1: (org-link-set-parameters 2: ;; The name of the new link type, usage: “example:label” 3: "example" 4: 5: ;; When you click on such links, “let me google that for you” happens 6: :follow (lambda (label) (browse-url (concat "https://lmgtfy.com/?q=" label))) 7: 8: ;; Upon export, make it a “let me google that for you” link 9: :export (lambda (label description backend) 10: (format (pcase backend 11: ('html "<a href=\"%s\">%s</a>") 12: ('latex "\\href{%s}{%s}") 13: (_ "I don’t know how to export that!")) 14: (concat "https://lmgtfy.com/?q=" label) 15: (or description label))) 16: 17: ;; These links should *never* be folded in descriptive display; 18: ;; i.e., “[[example:lable][description]]” will always appear verbatim 19: ;; and not hide the first pair […]. 20: ;; :display 'full 21: 22: ;; The tooltip alongside a link 23: :help-echo (lambda (window object position) 24: (save-excursion 25: (goto-char position) 26: (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) 27: (cadr (org-element-context))) 28: ;; (org-element-property :path (org-element-context)) 29: (description 30: (when (equal format 'bracket) 31: (copy-region-as-kill contents-begin contents-end) 32: (substring-no-properties (car kill-ring))))) 33: (format "“%s” :: Let me google “%s” for you -__- %s and %s" 34: raw-link (or description raw-link))))) 35: 36: ;; How should these links be displayed 37: :face '(:foreground "red" :weight bold 38: :underline "orange" :overline "orange") 39: 40: ;; Any special keybindings when cursour is on this link type? 41: ;; On ‘example:’ links, C-n/p to go to the next/previous such links. 42: :keymap (let ((map (copy-keymap org-mouse-map))) 43: ;; Populate the keymap 44: (define-key map (kbd "C-p") 45: (lambda () (interactive) (re-search-backward "example:" nil t))) 46: (define-key map (kbd "C-n") 47: (lambda () (interactive) (re-search-forward "example:" nil t))) 48: ;; Return the keymap 49: map))
- Line 3
"example"
Add a new
example
link type.- If the type already exists, update it with the given arguments.
The syntax for a raw link is
example:path
and for the bracketed descriptive form[[example:path][description]]
.- Some of my intended uses for links including colouring text and doing nothing else, as such the terminology ‘path’ is not sufficiently generic and so I use the designation ‘label’ instead.
- Line 6
:follow
What should happen when a user clicks on such links?
This is a function taking the link path as the single argument and does whatever is necessary to “follow the link”, for example find a file or display a message. In our case, we open the user's browser and go to a particular URL.
- Line 9
:export
How should this link type be exported to HTML, LaTeX, etc?
This is a three-argument function that formats the link according to the given backend, the resulting string value os placed literally into the exported file. Its arguments are:
label
⇒ the path of the link, the text after the link type prefixdescription
⇒ the description of the link, if anybackend
⇒ the export format, a symbol likehtml
orlatex
orascii
.
In our example above, we return different values depending on the
backend
value.- If
:export
is not provided, default Org-link exportation happens.
- Line 20
:display
- Should links be prettily folded away when a description is provided?
- Line 23
:help-echo
What should happen when the user's mouse is over the link?
This is either a string or a string-valued function that takes the current window, the current buffer object, and its position in the current window.
In our example link, we go to the position of the object, destructure the Org link's properties using
-let
, find the description of the link, if any, then provide a string based on the link's path and description.The general textual property ‘help-echo’
We may use
help-echo
to attach tooltips to arbitrary text in a file, as follows. I have found this to be useful in metaprogramming to have elaborated, generated, code shown as a tooltip attached to its named specification.;; Nearly instantaneous display of tooltips. (setq tooltip-delay 0) ;; Give user 30 seconds before tooltip automatically disappears. (setq tooltip-hide-delay 300) (defun tooltipify (phrase notification &optional underline) "Add a tooltip to every instance of PHRASE to show NOTIFICATION. We only add tooltips to PHRASE as a standalone word, not as a subword. If UNDERLINE is provided, we underline the given PHRASE so as to provide a visual clue that it has a tooltip attched to it. The PHRASE is taken literally; no regexp operators are recognised." (assert (stringp phrase)) (assert (stringp notification)) (save-excursion ;; Return cursour to current-point afterwards. (goto-char 1) ;; The \b are for empty-string at the start or end of a word. (while (search-forward-regexp (format "\\b%s\\b" (regexp-quote phrase)) (point-max) t) ;; (add-text-properties x y ps) ;; ⇒ Override properties ps for all text between x and y. (add-text-properties (match-beginning 0) (match-end 0) (list 'help-echo (s-trim notification))))) ;; Example use (tooltipify "Line" "A sequential formatation of entities or the trace of a particle in linear motion")
We will use the tooltip documentation later on ^_^
Useful info on tooltips:
- Changing text properties —GNU
- Tooltips on text in Emacs —Kitchin
- Getting graphical feedback as tooltips in Emacs —Kitchin
- Defining new tooltips in Emacs —Stackoverflow
- Line 37
:face
What textual properties do these links possess?
This is either a face or a face-valued function that takes the current link's path label as the only argument. That is, we could change the face according to the link's label —which is what we will do for the
color
link type as in[[color:brown][hello]]
will be rendered in brown text.- If
:face
is not provided, the default underlined blue face for Org links is used. - Learn more about faces!
- If
- More
- See
org-link-parameters
for documentation on more parameters.
1.2.2. Define links as you define functions: org-deflink
For export purposes, an Org link can be thought of as a string-valued function
whose inputs are the link's label, description, and the export backend. Since
the input arguments are fixed, the org-deflink
interface is similar to cl-defun
in
that it takes a name, (no input arguments), an optional docstring, and a body
which may make use of (the unchanging input) names o-label, o-description,
o-backend
. Moreover, after the documentation string, it may take an optional
vector of display settings: Org links can be declared to look aesthetically
pleasing within Emacs and to behave (via clicks/hovers/key-bindings) in
meaningful ways.
Here is a minimal working example. After evaluating this, in an Org buffer,
phrases such as shout:hello
will (1) be shown in bold red, (2) have a hover
tooltip that shows the documentation of the link and an HTML export preview, and
(3) the HTML export of the Org buffer will render the label hello
as capitalised
red text.
(org-deflink shout "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export." [:face '(:foreground "red" :weight bold)] (format "<span style=\"color:red\"> %s </span>" (upcase (or o-description o-label))))
A parent keymap inherited by org-deflink keymaps
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; We define a parent keymap that org-deflink keymaps inherit from. ;; We also define a few useful functions that we then bind to this parent map. (defvar org-special-block-extras-mode-map (make-keymap) "A keymap of actions, on link types, that is inherited by all `org-deflink' link keymaps. To learn about keymap inheritance, run: C-h i m elisp RETURN m Inheritance and Keymaps RETURN. This keymap has the following bindings setup: (define-key org-special-block-extras-mode-map (kbd \"C-n\") #'org-this-link-next) (define-key org-special-block-extras-mode-map (kbd \"C-p\") #'org-this-link-previous) (define-key org-special-block-extras-mode-map (kbd \"C-h\") #'org-this-link-show-docs) The use of `C-n' and `C-p' may be a nuisance to some users, since they override `forward-line' and `previous-line' when the cursor is on an org-link type. As such, place something like the following in your initialisation file. ;; Use C-c C-f to move to the next link of the same link-type as the one under the cursor (define-key org-special-block-extras-mode-map (kbd \"C-c C-f\") #'org-this-link-next) Alternatively, if you don't find much value in these basic bindings, you can remove them all: ;; Disable basic org-special-block-extras link keybindings (setcdr org-special-block-extras-mode-map nil) ;; Or, remove a single binding (define-key org-special-block-extras-mode-map (kbd \"C-n\") nil)") (defvar org-special-block-extras-mode-map--link-keymap-docs nil "An alist referencing key bindings for Org links; used in `org-this-link-show-docs'.") (defun org-link-at-point () "Get the Org link type at point, with suffix colon." (interactive) (let ((working-line (line-number-at-pos))) (save-excursion ;; Account for cursour being on anywhere on the links “name:key”. (backward-word 2) (unless (= working-line (line-number-at-pos)) (goto-line working-line)) (let* ((here-to-eol (buffer-substring-no-properties (point) (point-at-eol))) ;; E.g., “kbd:”, the name part of an Org link (link-name (cl-second (s-match "\\([^ ]+:\\).+" here-to-eol)))) link-name)))) (defun org-this-link-next () "Go to the next Org link that is similar to the link at point." (interactive) (re-search-forward (org-link-at-point) nil t)) (defun org-this-link-previous () "Go to the previous Org link that is similar to the link at point." (interactive) (re-search-backward (org-link-at-point) nil t)) (defun org-this-link-show-docs () "Show documentation for the Org link at point in a read-only buffer. Press ‘q’ to kill the resulting buffer and window." (interactive) (let* ((link (s-chop-suffix ":" (org-link-at-point))) (msg (ignore-errors (concat (documentation (intern (format "org-link/%s" link))) "\nKEY BINDINGS:\n" "\nUnless indicated below otherwise..." "\n\tC-h: Shows this helpful message buffer" "\n\tC-n/C-p on the link to jump to next/previous links of this type;" "\n\tC-c C-x C-n/p for moving between arbitrary link types.\n\n" (pp-to-string (cdr (assoc link org-special-block-extras-mode-map--link-keymap-docs)))))) ;; i.e., insist on displaying in a dedicated buffer (max-mini-window-height 0)) (display-message-or-buffer msg) (switch-to-buffer-other-window "*Message*") (rename-buffer (format "Help: Org Link “%s”" link)) (read-only-mode) (local-set-key "q" #'kill-buffer-and-window) (message "Read-only; “q” to kill buffer and window."))) (define-key org-special-block-extras-mode-map (kbd "C-n") #'org-this-link-next) (define-key org-special-block-extras-mode-map (kbd "C-p") #'org-this-link-previous) (define-key org-special-block-extras-mode-map (kbd "C-h") #'org-this-link-show-docs)
Implementation of org-deflink
(cl-defmacro org-deflink (name &optional docstring display &rest body) "Make a new Org-link NAME that exports using form BODY. Since Org links are essentially string-valued functions, a function ‘org-link/NAME’ is created. DOCSTRING is optional; it is visible with (documentation 'org-link/NAME) BODY is a string-valued expression, that may make use of the names o-label, o-description, o-backend. The final one refers to the export backend, such as 'html or 'latex. The first two are obtained from uses: [[name:o-label][o-description]] In particular, the use case “name:o-label” means that o-description is nil. --------------------------------------------------------------------------- Example use: ;; In a Lisp buffer, press “C-x C-e” to load this definition (org-deflink shout (upcase (or o-description o-label))) ;; In an Org-buffer, press “C-c C-e h o” to see how this exports <shout: hello world!> ;; Or using the bracket format [[shout:][hello world!]] [[shout: hello world!]] ;; or using the plain format shout:hello_world Here is a more complex, involved, example that makes use of ‘:let’ for local declarations. For instance, “define:hello” renders as the word “hello” with a tooltip defining the word; the definition is obtained from the command line tool ‘wn’. (org-deflink define \"Define the given word using WordNet, along with synonyms and coordinate terms.\" [:let (definition (shell-command-to-string (format \"wn %s -over -synsn -coorn\" o-label))) :help-echo definition] (--> definition (s-replace-regexp \"\\\\\\\"\" \"''\" it) ;; The presence of ‘\\\"’ in tooltips breaks things, so omit them. (s-replace-regexp \"\\n\" \"<br>\" it) (format \"<abbr class=\\\"tooltip\\\" title=\\\"%s\\\">%s</abbr>\" it o-label))) For HTML tooltips, see `org-ospe-html-export-preserving-whitespace'. More generally, org-special-block-extra's “doc” link type supports, in order of precedence: User definitions, Emacs Lisp documentation of functions & variables, and definitions of English words. For example, “doc:existential_angst” for an entry ‘existential_angst’ whose associated documentation-glossary is user-defined in a ‘#+documentation’ Org-block, or “doc:thread-first” for the Emacs Lisp documentation of the function `thread-first', or “doc:user-mail-address” for the Emacs Lisp documentation of the variable `user-mail-address', or “doc:hello” for the definition of the English word ‘hello’. DISPLAY is a vector consisting of key-value pairs that affects how the link is displayed in Emacs Org buffers. The keys are as follows. + :help-echo is a string-valued expression of the tooltip that should accompany the new link in Org buffers. It has access to o-format being one of ‘plain’, ‘angle’, ‘bracket’ which indicates the format of the link, as shown above. It also has access to o-label and o-description. By default, the tooltip is the link name followed by the documentation of the link, and, finally, the HTML export of the link. That way, upon hover, users can visually see the link contents, know what/how the link exports, and actually see the HTML export. That is to say, for the ‘shout’ example aboce, the default display is essentially: [:help-echo (org-link/shout o-label o-description 'html)] You may want to add the following to your Emacs init file: ;; Nearly instantaneous display of tooltips. (setq tooltip-delay 0) ;; Give user 30 seconds before tooltip automatically disappears. (setq tooltip-hide-delay 300) + :face specifies how should these links be displayed within Emacs. It is a list-valued expression. As usual, it may make use of O-LABEL (but O-DESCRIPTION has value nil). Example: :face '(:underline \"green\") See https://www.gnu.org/software/emacs/manual/html_node/elisp/Face-Attributes.html + [:display 'full] if you do not want bracket links to be folded away in Org buffers; i.e., “[[X][Y]]” does not render as just “Y”. + :follow is a form that is executed when you click on such links; e.g., to open another buffer, browser, or other action. It makes use of (an implicit argument) ‘o-label’. Be aware that ‘o-label’ is a string that may contain spaces; e.g., when the action is to open a URL in a browser. If you are in need of providing similar, related, actions on a single link then your :follow can condition on the current prefix argument via ‘o-prefix’ (which is essentially `current-prefix-arg'). For instance, a user presses “C-u RET” on your link to do one thing but “C-u 72 RET” to do another action. + :keymap is an alternating list of keys and actions to be performed when those keys are pressed while point is on the link. For example: [:keymap (C-h (message-box \"hola\"))] By default, C-n and C-p are for moving to next and previous occruances of the same link type. + :let is a list of alternating variable symbol name and value, which are then used to form a concrete `let*' clause. This is useful for introducing local variables for use in the DISPLAY as well as in the CONTENTS. Such local declarations may make use of O-LABEL and O-DESCRIPTION, as usual." (cl-destructuring-bind (docstring display body) (lf-extract-optionals-from-rest docstring #'stringp display #'vectorp body) (setq display (seq--into-list display)) (let ((org-link/NAME (intern (format "org-link/%s" name))) (navigation "Press “C-h” to see possible actions on this link type.") (lets (cl-loop for (variable value) on (cl-getf display :let) by #'cddr collect (list variable value)))) `(progn ;; Declare the underlying function and documentation (cl-defun ,org-link/NAME ;; function name (o-label o-description o-backend) ;; function args ;; new function documentation ,docstring ;; function body (let* ,lets ,@body)) ;; Construct the Org-link (org-link-set-parameters ,(format "%s" name) :export (quote ,org-link/NAME) ;; How should these links be displayed? ;; (We augment the namespace with the missing o-description that local variables may be using.) :face (lambda (o-label) (let (o-description) (let* ,lets ,(cl-getf display :face)))) ;; When you click on such links, what should happen? ;; (We augment the namespace with the missing o-description that local variables may be using.) :follow (lambda (o-label o-prefix) (let (o-description) (let* ,lets ,(cl-getf display :follow)))) ;; These links should *never* be folded in descriptive display; ;; i.e., “[[example:lable][description]]” will always appear verbatim ;; and not hide the first pair […]. :display (cl-the symbol ,(cl-getf display :display)) ;; e.g.,: 'full ;; Any special keybindings when cursour is on this link type? ;; On ‘NAME:’ links, C-n/p to go to the next/previous such links. :keymap (let ((o-keymap (copy-keymap org-mouse-map)) (pattern (format "%s:" (quote ,name)))) ;; If this Org-link has additional key bindings, then save ;; them in an alist for reference in `org-this-link-show-docs'. (when (quote ,(cl-getf display :keymap)) (push (cons (format "%s" (quote ,name)) (quote ,(cl-getf display :keymap))) org-special-block-extras-mode-map--link-keymap-docs)) ;; Let's inherit some possibly useful key bindings. (set-keymap-parent o-keymap org-special-block-extras-mode-map) ;; Populate the keymap (cl-loop for (key action) on (quote ,(cl-getf display :keymap)) by #'cddr do (define-key o-keymap (kbd (format "%s" key)) `(lambda () (interactive) ,action))) ;; Return the keymap o-keymap) ;; The tooltip alongside a link :help-echo (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :contents-begin :contents-end) (cadr (org-element-context))) (org-format format) (o-label path) (o-description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (or (let* ,lets ,(cl-getf display :help-echo)) (format "%s:%s\n\n%s\nHTML Export:\n\n%s" (quote ,name) (or o-description o-label) ,(concat (or docstring "") "\n\n" navigation "\n") (,org-link/NAME o-label o-description 'html))))))) ;; Return value is the name of the underlying function. ;; We do this to be consistent with `defun'. (quote ,org-link/NAME)))))
Let's have some sanity tests…
(org-deflink shout "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export." [:face '(:foreground "red" :weight bold) ;; :help-echo (org-link/shout o-label o-description 'html) :display 'full :keymap (C-m (message-box "hola")) :follow (message-box "%s and %s" pre current-prefix-arg) ] (format "<span style=\"color:red\"> %s </span>" (upcase (or o-description o-label)))) (deftest "org-deflink makes documented functions" [org-deflink] (⇝ (documentation #'org-link/shout) "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export.")) (deftest "org-deflink works as expected, plain links" [org-deflink] (should (not (null (symbol-function 'org-link/shout)))) (⇝ (⟰ "shout:hello") "<p> <span style=\"color:red\"> HELLO </span></p>")) (deftest "org-deflink works as expected, bracket links" [org-deflink] (⇝ (⟰ "[[shout:hello]]") "<p> <span style=\"color:red\"> HELLO </span></p>") (⇝ (⟰ "[[shout:hello][world!]]") "<p> <span style=\"color:red\"> WORLD! </span></p>")) (deftest "org-deflink works as expected, angle links" [org-deflink] (⇝ (⟰ "<shout: hello world!>") "<p> <span style=\"color:red\"> HELLO WORLD! </span></p>"))
Exercise Here's a useful link we might want: melpa:𝒳
produces a nice badge
for the package 𝒳 that links to the associated Melpa page and shows the version
number of the package, if possible/convenient. For instance,
melpa:org-special-block-extras
and <melpa:s>
become
and .
One possible arcane implementation
(org-deflink melpa "Produce a Melpa badge for a given pacakge O-LABEL, which links to the Melpa page. We try to get the package's version from a constant “⟨O-LABEL⟩-version” if it exists." [:face '(:box "purple" :foreground "purple")] (format (concat "<a href=\"https://melpa.org/#/%s\">" "<img alt=\"MELPA\" src=\"https://img.shields.io/badge/%s-%s-green?logo=Gnu-Emacs\"></img>" "</a>") o-label (s-replace "_" "__" (s-replace "-" "--" o-label)) ;; shields.io conventions (or (ignore-errors (eval (intern (concat o-label "-version")))) "Melpa")))
1.3. The Core Utility: defblock
and friends
To have a unified, and pleasant, interface for declaring new blocks and links, we take the following approach:
- ( Fuse the process of link generation and special block support into one macro, defblock which is like defun. )
The user writes as string-valued function named 𝒳, possibly with arguments, that has access to a
contents
andbackend
variables.‘defblock’ Implementation
(defvar org--supported-blocks nil "Which special blocks, defined with DEFBLOCK, are supported.") (cl-defmacro org-defblock (name kwds &optional link-display docstring &rest body) "Declare a new special block, and link, in the style of DEFUN. A full featured example is at the end of this documentation string. This is an anaphoric macro that provides export support for special blocks *and* links named NAME. Just as an Org-mode src-block consumes as main argument the language for the src block, our special blocks too consume a MAIN-ARG; it may be a symbol or a cons-list consisting of a symbolic name (with which to refer to the main argument in the definition of the block) followed by a default value, then, optionally, any information for a one-time setup of the associated link type. The main arg may be a sequence of symbols separated by spaces, and a few punctuation with the exception of comma ‘,’ since it is a special Lisp operator. In doubt, enclose the main arg in quotes. Then, just as Org-mode src blocks consume key-value pairs, our special blocks consume a number of KWDS, which is a list of the form (key₀ value₀ … keyₙ valueₙ). After that is an optional DOCSTRING, a familar feature of DEFUN. The docstring is displayed as part of the tooltip for the produced link type. Finally, the BODY is a (sequence of) Lisp forms ---no progn needed--- that may refer to the names BACKEND and CONTENTS which refer to the current export backend and the contents of the special block ---or the description clause of a link. CONTENTS refers to an Org-mode parsed string; i.e., Org-markup is acknowledged. In, hopefully, rare circumstances, one may refer to RAW-CONTENTS to look at the fully unparsed contents. Finally, this macro exposes two functions: + ORG-EXPORT: Wrap the argument in an export block for the current backend. + ORG-PARSE: This should ONLY be called within an ORG-EXPORT call, to escape text to Org, and out of the export block. ⇄ We use “@@html:⋯:@@” when altering CONTENTS, but otherwise use raw HTML *around* CONTENTS. ⇄ For example: (format \"<div>%s</div>\" (s-replace \"#+columnbreak:\" \"@@html:<hr>@@\" contents)) ---------------------------------------------------------------------- The relationship between links and special blocks: [ [type:label][description]] ≈ #+begin_type label description #+end_type ---------------------------------------------------------------------- Example declaration, with all possible features shown: ;; We can use variable values when defining new blocks (setq angry-red '(:foreground \"red\" :weight bold)) (org-defblock remark (editor \"Editor Remark\" :face angry-red) (color \"red\" signoff \"\") \"Top level (HTML & LaTeX)O-RESPECT-NEWLINES? editorial remarks; in Emacs they're angry red.\" (format (if (equal backend 'html) \"<strong style=\\\"color: %s;\\\">⟦%s: %s%s⟧</strong>\" \"{\\color{%s}\\bfseries %s: %s%s}\") color editor contents signoff)) ;; I don't want to change the definition, but I'd like to have ;; the following as personalised defaults for the “remark” block. ;; OR, I'd like to set this for links, which do not have argument options. (defblock-header-args remark :main-arg \"Jasim Jameson\" :signoff \"( Aim for success! )\") Three example uses: ;; ⟨0⟩ As a special blocks with arguments given. #+begin_remark Bobbert Barakallah :signoff \"Thank-you for pointing this out!\" :color green I was trying to explain that ${\large (n × (n + 1) \over 2}$ is always an integer. #+end_remark ;; ⟨1⟩ As a terse link, using default values for the args. ;; Notice that Org-mode formatting is recoqgnised even in links. [ [remark:Jasim Jameson][Why are you taking about “$\mathsf{even}$” here?]] ;; ⟨2⟩ So terse that no editor name is provided. [ [remark:][Please improve your transition sentences.]] ;; ⟨★⟩ Unlike 0, examples 1 and 2 will have the default SIGNOFF ;; catenated as well as the default red color." ;; ⇨ The special block support ;; (add-to-list 'org--supported-blocks name) ;; global var ;; TODO: Relocate (defvar org--block--link-display nil "Association list of block name symbols to link display vectors.") ;; Identify which of the optional features is present... (cl-destructuring-bind (link-display docstring body) (lf-extract-optionals-from-rest link-display #'vectorp docstring #'stringp body) `(progn (when ,(not (null link-display)) (push (cons (quote ,name) ,link-display) org--block--link-display)) (list ,(org--create-defmethod-of-defblock name docstring (plist-get kwds :backend) kwds body) ;; ⇨ The link type support (eval (backquote (org-deflink ,name ,(vconcat `[:help-echo (format "%s:%s\n\n%s" (quote ,name) o-label ,docstring)] (or link-display (cdr (assoc name org--block--link-display)))) ;; s-replace-all `((,(format "@@%s:" backend) . "") ("#+end_export" . "") (,(format "#+begin_export %s" backend) . "")) (s-replace-regexp "@@" "" (,(intern (format "org-block/%s" name)) o-backend (or o-description o-label) o-label :o-link? t))))))))) ;; WHERE ... (cl-defmethod org--create-defmethod-of-defblock ((name symbol) docstring backend-type (kwds list) (body list)) "Helper method to produce an associated Lisp function for org-defblock. + NAME: The name of the block type. + DOCSTRING, string|null: Documentation of block. + KWDS: Keyword-value pairs + BODY: Code to be executed" (cl-assert (or (stringp docstring) (null docstring))) (cl-assert (or (symbolp backend-type) (null backend-type))) (let ((main-arg-name (or (cl-first kwds) 'main-arg)) (main-arg-value (cl-second kwds)) (kwds (cddr kwds))) ;; Unless we've already set the docs for the generic function, don't re-declare it. `(if ,(null body) (cl-defgeneric ,(intern (format "org-block/%s" name)) (backend raw-contents &rest _) ,docstring) (cl-defmethod ,(intern (format "org-block/%s" name)) ((backend ,(if backend-type `(eql ,backend-type) t)) (raw-contents string) &optional ,main-arg-name &rest _ &key (o-link? nil) ,@(--reject (keywordp (car it)) (-partition 2 kwds)) &allow-other-keys) ,docstring ;; Use default for main argument (when (and ',main-arg-name (s-blank-p ,main-arg-name)) (--if-let (plist-get (cdr (assoc ',name org--header-args)) :main-arg) (setq ,main-arg-name it) (setq ,main-arg-name ,main-arg-value))) (cl-letf (((symbol-function 'org-export) (lambda (x) "Wrap the given X in an export block for the current backend." (if o-link? x (format "#+begin_export %s \n%s\n#+end_export" backend x)))) ((symbol-function 'org-parse) (lambda (x) "This should ONLY be called within an ORG-EXPORT call." (if o-link? x (format "\n#+end_export\n%s\n#+begin_export %s\n" x backend))))) ;; Use any headers for this block type, if no local value is passed ,@(cl-loop for k in (mapcar #'car (-partition 2 kwds)) collect `(--when-let (plist-get (cdr (assoc ',name org--header-args)) ,(intern (format ":%s" k))) (when (s-blank-p ,k) (setq ,k it)))) (org-export (let ((contents (org-parse raw-contents))) ,@body)))))))
Going forward, it would be nice to have a set of switches that apply to all
special blocks. For instance, :ignore
to simply bypass the user-defined
behaviour of a block type, and :noexport
to zero-out a block upon export. These
are super easy to do —just need a few minutes to breath. It may also be
desirable to provide support for drawers —just as we did to ‘fuse’ the
block-type and link-type approaches used here into one macro.
We tell Org to please look at all special blocks
#+begin_𝒳 main-arg :key₀ value₀ … :keyₙ valueₙ contents #+end_𝒳
Then, before export happens, to replace all such blocks with the result of calling the user's 𝒳 function; i.e., replace them by, essentially,
(𝒳 main-arg :key₀ value₀ … :keyₙ valueₙ :o-contents contents)
Implementing the hooking mechanism
The mechanism that rewrites your source…
(defun org--pp-list (xs) "Given XS as (x₁ x₂ … xₙ), yield the string “x₁ x₂ … xₙ”, no parens. When n = 0, yield the empty string “”." (s-chop-suffix ")" (s-chop-prefix "(" (format "%s" (or xs ""))))) (defvar org--current-backend nil "A message-passing channel updated by org--support-special-blocks-with-args and used by DEFBLOCK.") (defun org--support-special-blocks-with-args (backend) "Remove all headlines in the current buffer. BACKEND is the export back-end being used, as a symbol." (setq org--current-backend backend) (let (blk-start ;; The point at which the user's block begins. header-start ;; The point at which the user's block header & args begin. kwdargs ;; The actual key-value arguments for the header. main-arg ;; The first (non-keyed) value to the block. blk-column ;; The column at which the user's block begins. body-start ;; The starting line of the user's block. blk-contents ;; The actual body string. ;; ⟨blk-start/column⟩#+begin_⟨header-start⟩blk main-arg :key₀ val ₀ … :keyₙ valₙ ;; ⟵ ⟨kwdargs⟩ ;; ⟨body-start⟩ body ;; #+end_blk ) (cl-loop for blk in org--supported-blocks do (goto-char (point-min)) (while (ignore-errors (re-search-forward (format "^\s*\\#\\+begin_%s" blk))) ;; MA: HACK: Instead of a space, it should be any non-whitespace, optionally; ;; otherwise it may accidentlly rewrite blocks with one being a prefix of the other! (setq header-start (point)) ;; Save indentation (re-search-backward (format "\\#\\+begin_%s" blk)) (setq blk-start (point)) (setq blk-column (current-column)) ;; actually process body (goto-char header-start) (setq body-start (1+ (line-end-position))) (thread-last (buffer-substring-no-properties header-start (line-end-position)) (format "(%s)") read (--split-with (not (keywordp it))) (setq kwdargs)) (setq main-arg (org--pp-list (car kwdargs))) (setq kwdargs (cadr kwdargs)) (forward-line -1) (re-search-forward (format "^\s*\\#\\+end_%s" blk)) (setq blk-contents (buffer-substring-no-properties body-start (line-beginning-position))) (kill-region blk-start (point)) (insert (eval `(,(intern (format "org-block/%s" blk)) (quote ,backend) ,blk-contents ,main-arg ,@(--map (list 'quote it) kwdargs)))) ;; See: https://github.com/alhassy/org-special-block-extras/issues/8 ;; (indent-region blk-start (point) blk-column) ;; Actually, this may be needed... ;; (indent-line-to blk-column) ;; #+end... ;; (goto-char blk-start) (indent-line-to blk-column) ;; #+begin... ;; the --map is so that arguments may be passed ;; as "this" or just ‘this’ (raw symbols) ))))
Let's have some sanity tests…
(deftest "pp-list works as desired" (should (equal "1 2 3 4 5" (org--pp-list '(1 2 3 4 5)))) (should (equal "1" (org--pp-list '(1)))) (should (equal "" (org--pp-list nil)))) ;; Using propcheck, we run this test on /arbitrary/ buffer contents. (deftest "No supported blocks means buffer is unchanged" :tags '(core) (let* (org--supported-blocks (propcheck-seed (propcheck-seed)) (buf (propcheck-generate-string nil))) (should (equal buf (with-temp-buffer (insert buf) (org--support-special-blocks-with-args 'html) (buffer-string)))))) (deftest "Constant blocks preserve indentation/enumeration" :expected-result :failed :tags '(core) (org-defblock go nil "doc" "hello") ;; Constantly “hello” (should (equal " 1. item one 2. item two #+begin_export html hello #+end_export 3. item three" (with-temp-buffer (insert " 1. item one 2. item two #+begin_go world #+end_go 3. item three") (org--support-special-blocks-with-args 'html) (buffer-string))))) (deftest "Constant blocks export to LaTex preserves indentation/enumeration" (should (equal "\\begin{enumerate} \\item item one \\item item two hello \\item item three \\end{enumerate} " (org-export-string-as " 1. item one 2. item two #+begin_go world #+end_go 3. item three" 'latex :body-only-please)))) (deftest "Constant blocks export to HTML preserves indentation/enumeration" (should (equal "<ol class=\"org-ol\"> <li>item one</li> <li><p> item two </p> hello</li> <li>item three</li> </ol> " (org-export-string-as " 1. item one 2. item two #+begin_go world #+end_go 3. item three" 'html :body-only-please)))) (deftest "Identity blocks preserve indentation/enumeration" :expected-result :failed :tags '(core) (org-defblock id nil "doc" contents) (should (equal " 1. item one 2. item two #+begin_export html #+end_export world #+begin_export html #+end_export 3. item three" (with-temp-buffer (insert " 1. item one 2. item two #+begin_id world #+end_id 3. item three") (org--support-special-blocks-with-args 'html) (buffer-string))))) (deftest "Identity blocks export to LaTex preserves indentation/enumeration" :expected-result :failed (org-defblock id nil "doc" contents) (should (equal "\\begin{enumerate} \\item item one \\item item two world \\item item three \\end{enumerate} " (org-export-string-as " 1. item one 2. item two #+begin_id world #+end_id 3. item three" 'latex :body-only-please)))) (deftest "Identity blocks export to HTML preserves indentation/enumeration" :expected-result :failed (org-defblock id nil "doc" contents) (should (equal "<ol class=\"org-ol\"> <li>item one</li> <li><p> item two </p> <p> world </p></li> <li>item three</li> </ol> " (org-export-string-as " 1. item one 2. item two #+begin_id world #+end_id 3. item three" 'html :body-only-please))))
When you enable the org-special-block-extras
mode, it is activated…
;; https://orgmode.org/manual/Advanced-Export-Configuration.html (add-hook 'org-export-before-parsing-hook 'org--support-special-blocks-with-args)
When you disable the org-special-block-extras
mode, it is deactivated…
(remove-hook 'org-export-before-parsing-hook 'org--support-special-blocks-with-args)
‘header-args’ Implementation
Then:
(defvar org--header-args nil "Alist (name plist) where “:main-arg” is a special plist key. It serves a similar role to that of Org's src ‘header-args’. See doc of SET-BLOCK-HEADER-ARGS for more information.") (defmacro org-set-block-header-args (blk &rest kvs) "Set default valuts for special block arguments. This is similar to, and inspired by, Org-src block header-args. Example src use: #+PROPERTY: header-args:Language :key value Example block use: (set-block-header-args Block :main-arg mainvalue :key value) A full, working, example can be seen by “C-h o RET defblock”. " `(add-to-list 'org--header-args (list (quote ,blk) ,@kvs)))
This interface is essentially that of Org's src
blocks, with the main-arg
being the first argument to 𝒳 and the only argument not needing to be
preceded by a key name —it is done this way to remain somewhat consistent
with the Org src
interface. The user definition of 𝒳 decides on how optional
the arguments actually are.
Perhaps an example will clarify things …
1.3.1. Example: Jasim providing in-place feedback to Bobbert
Suppose we want to devise a simple special block for editors to provide constructive feedback to authors so that the feedback appears as top-level elements of the resulting exported file —instead of comments that may accidentally not be handled by the author.
In order to showcase the multiple bells and whistles of the system, the snippet below is twice as long than it needs to be, but it is still reasonably small and accessible.
;; We can use variable values when defining new blocks (setq angry-red '(:foreground "red" :weight bold)) ;; This is our 𝒳, “remark”. ;; As a link, it should be shown angry-red; ;; it takes two arguments: “color” and “signoff” ;; with default values being "red" and "". (org-defblock rremark (editor "Editor Remark" color "red" signoff "") (vector :face angry-red) "Top level (HTML & LaTeX) editorial remarks; in Emacs they're angry red." (format (if (equal backend 'html) "<strong style=\"color: %s;\">⟦%s: %s%s⟧</strong>" "{\\color{%s}\\bfseries %s: %s%s}") color editor contents signoff)) ;; I don't want to change the definition, but I'd like to have ;; the following as personalised defaults for the “remark” block. ;; OR, I'd like to set this for links, which do not have argument options. (org-set-block-header-args rremark :main-arg "Jasim Jameson" :signoff "( Aim for success! )")
Example use
The sum of the first $n$ natural numbers is $\sum_{i = 0}^n i = {n × (n + 1) \over 2}$. Note that $n × (n + 1)$ is even. [[rremark:Jasim Jameson][Why are you taking about “$\mathsf{even}$” here?]] #+begin_rremark Bobbert Barakallah :signoff "Thank-you for pointing this out!" :color green I was trying, uh ... Yeah, to explain that ${\large n × (n + 1) \over 2}$ is always an integer. #+end_rremark Hence, we only need to speak about whole numbers. [[rremark:][Then please improve your transition sentences.]]
Resulting rendition
The sum of the first \(n\) natural numbers is \(\sum_{i = 0}^n i = {n × (n + 1) \over 2}\). Note that \(n × (n + 1)\) is even. ⟦Jasim Jameson: Why are you taking about “\(\mathsf{even}\)” here?( Aim for success! )⟧
⟦Bobbert Barakallah:I was trying, uh …
Yeah, to explain that \({\large n × (n + 1) \over 2}\) is always an integer.
Thank-you for pointing this out!⟧Hence, we only need to speak about whole numbers. ⟦Jasim Jameson: Then please improve your transition sentences.( Aim for success! )⟧
Notice that the result contains text —the signoff message— that the user Jasim did not write explicitly.
… Why the stuttered rremark
? Because this package comes with a remark
block that
has more bells and whistles … keep reading ;-)
1.4. Modularity with thread-blockcall
Since defblock let's us pretend block —and link— types are string-valued functions, then one would expect that we can compose blocks modularly as functions compose. Somewhat analogously to funcall and thread-last, we provide a macro thread-blockcall.
Example
(thread-blockcall raw-contents (box name) (details (upcase name) :title-color "green")
=
#+begin_details NAME :title-color "green"
#+begin_box name
contents
#+end_box
#+end_details
Implementation
First, we need to handle the case of one block…
(cl-defmacro org--blockcall (blk &optional main-arg &rest keyword-args-then-contents) "An anaologue to `funcall` but for blocks. Usage: (blockcall blk-name main-arg even-many:key-values raw-contents) One should rarely use this directly; instead use o-thread-blockcall. " `(concat "#+end_export\n" (,(intern (format "org-block/%s" blk)) backend ;; defblock internal ; (format "\n#+begin_export html\n\n%s\n#+end_export\n" ,(car (last keyword-args-then-contents))) ;; contents ,@(last keyword-args-then-contents) ;; contents ,main-arg ,@(-drop-last 1 keyword-args-then-contents)) "\n#+begin_export"))
Using the above sequentially does not work due to the plumbing of
defblock
, so we handle that plumbing below …
(defmacro org-thread-blockcall (body &rest forms) "Thread text through a number of blocks. BODY is likely to be ‘raw-contents’, possibly with user manipulations. Each FORMS is of the shape “(block-name main-argument :key-value-pairs)” (thread-blockcall x) = x (thread-blockcall x (f a)) = (blockcall f a x) (thread-blockcall x f₁ f₂) ≈ (f₂ (f₁ x)) The third is a ‘≈’, and not ‘=’, because the RHS contains ‘blockcall’s as well as massages the export matter between conseqeuctive blockcalls. A full example: (org-defblock nesting (name nil) \"Show text in a box, within details, which contains a box.\" (org-thread-blockcall raw-contents (box name) (details (upcase name) :title-color \"green\") (box (format \"⇨ %s ⇦\" name) :background-color \"blue\") )) " (if (not forms) body `(-let [result (org--blockcall ,@(car forms) ,body)] ,@(cl-loop for b in (cdr forms) collect `(setq result (org--blockcall ,@b (concat "#+begin_export\n" result "\n#+end_export" )))) result)))
1.4.1. Short Example: An opportunity to learn!
The following tiny block is composed from two details blocks and a box block —defined elsewhere in this article. It is intended to give the reader another opportunity to make sure they have tried to solve the puzzle posed in the main text before seeing the answer -—this works well in HTML, not so in LaTeX.
(org-defblock solution (title "Solution" reprimand "Did you actually try? Maybe see the ‘hints’ above!" really "Solution, for real") "Show the answers to a problem, but with a reprimand in case no attempt was made." (org-thread-blockcall raw-contents (details really :title-color "red") (box reprimand :background-color "blue") (details title)))
E.g., what is 1 + 1?
Useless Hint: What is a number?
Solution
Did you actually try? Maybe see the ‘hints’ above!
Solution, for real
The answer is 2.
If you're interested in such ‘fundamental’ questions, consider reading Russel and Whitehead's Principa Mathematica ;-)
The above box was created from:
#+begin_solution The answer is 2. If you're interested in such ‘fundamental’ questions, consider reading Russel and Whitehead's /Principa Mathematica/ ;-) #+end_solution
We will make use of this block below when we get to guided problems ;-)
1.4.2. Longer Example: Demonstrating Org-markup with org-demo
Sometimes, we want to show verbatim source and its resulting rendition —which is a major part of this article! So, let's make a block to mitigate such an error-prone tedium.
Implementation
(org-defblock org-demo (nil nil source "Source" result "Result" source-color "cyan" result-color "cyan" style "parallel" sep (if (equal backend 'html) "@@html:<p><br>@@" "\n\n\n\n") ) "Output the CONTENTS of the block as both parsed Org and unparsed. Label the source text by SOURCE and the result text by RESULT finally, the source-result fragments can be shown in a STYLE that is either “parallel” (default) or “sequential”. SEP is the separator; e.g., a rule ‘<hr>'. " (-let [text (concat ;; Source (thread-last raw-contents (format (if (equal backend 'html) "<div ><pre class=\"src src-org\">%s</pre></div>" "\n\\begin{verbatim}\n%s\n\\end{verbatim}")) org-export (org--blockcall box source :background-color source-color) org-export) ;; Separator sep ;; Result (thread-last raw-contents (org--blockcall box result :background-color result-color) org-export))] (if (equal style "parallel") (org--blockcall parallel "2" :bar nil text) (concat "#+end_export\n" text "\n#+begin_export"))))
Example
This
#+begin_org-demo /italics/ and _underline_ $e^{i \times \pi} + 1 = 0$ #+end_org-demo
Yields
Source
/italics/ and _underline_ $e^{i \times \pi} + 1 = 0$
Result
italics and underline
\(e^{i \times \pi} + 1 = 0\)
(Sequential) Example
This
#+begin_org-demo :style seq /italics/ and _underline_ $e^{i \times \pi} + 1 = 0$ #+end_org-demo
Yields
Source
/italics/ and _underline_ $e^{i \times \pi} + 1 = 0$
Result
italics and underline
\(e^{i \times \pi} + 1 = 0\)
However, since our implementation scheme relies on a preprocessing step before
export, we cannot use org-demo
to show the results of special blocks: They
disappear in the preprocessing step!
E.g., this
#+begin_org-demo #+begin_box There is no special block ‘box’ to touch! #+end_box #+end_org-demo
yields the mess
Source
#+begin_export htmlThere is no special block ‘box’ to touch!
</pre></div> #+endexport
Result
There is no special block ‘box’ to touch!
However, it does work with links!
Source
[[box:][Box-as-link! Boxception!]]
Result
1.5. Practice Problems: Now you try!
A 5-page PDF covering ELisp fundamentals can be found here.
The first problem is to get you going with Lisp, the next two are actually useful blocks. The rename is useful for when you want to change some names or translate some words; spoiler is useful when we want to test a student's understanding, or to subtly hide the answer to a puzzle so the reader has the opportunity to attempt solving it.
1.5.1. Sttutttterrr
Define a block stutter so that the following examples behave as shown.
Hints
- You need at-most 5 lines of Lisp.
- These functions may be useful: s-repeat, numberp, string-to-number
Examples
The following outputs, well, nothing, since we asked for zero repetitions.
#+begin_stutter 0 words more words #+end_stutter
In contrast …
Source
[[stutter:5][woah, I'm repeated 5 times!]]
Result
woah, I'm repeated 5 times!woah, I'm repeated 5 times!woah, I'm repeated 5 times!woah, I'm repeated 5 times!woah, I'm repeated 5 times!
Solution
Did you actually try? Maybe see the ‘hints’ above!
Solution, for real
(org-defblock stutter (reps 2) "Output the CONTENTS of the block REPS many times" (-let [num (if (numberp reps) reps (string-to-number reps))] (s-repeat num contents)))
1.5.2. Textual Substitution —A translation tool
Define a block rename so that the following examples behave as shown.
Hints
- It can be done in less than 10 lines of Lisp.
- First, try to s-replace-all the substitution
'(("Allah" . "God") ("Yacoub". "Jacob") ("Yusuf" . "Joseph"))
only. - Then take out such hard-coded substitutions … these functions may be helpful: --map / -map, s-split, s-trim
Examples
#+begin_rename "Allah to God, Yacoub to Jacob, Yusuf to Joseph" Quran 12-4: *_Yusuf_* said to his father ( _*Yacoub*_ ), /“O my father, indeed I have seen (in a dream) eleven stars and the sun and the moon; I saw them prostrating to me.”/ #+end_rename
Yields…
Quran 12-4: Joseph said to his father ( Jacob ), “O my father, indeed I have seen (in a dream) eleven stars and the sun and the moon; I saw them prostrating to me.”
Source
[[rename:Pharaoh to Firaun, Joseph to Yusuf][Genesis 41-17: Pharaoh said unto Joseph, /In my dream, behold, I stood upon the bank of the river/ …]]
Result
Genesis 41-17: Firaun said unto Yusuf, In my dream, behold, I stood upon the bank of the river …
Solution
Did you actually try? Maybe see the ‘hints’ above!
Solution, for real
(org-defblock rename (list "") "Perform the given LIST of substitutions on the text. The LIST is a comma separated list of ‘to’ separated symbols. In a link, no quotes are needed." (s-replace-all (--map (cons (car it) (cadr it)) (--map (s-split " to " (s-trim it)) (s-split "," list))) contents))
1.5.3. Spoilers! —“fill in the blanks”
Define a block spoiler so that the following examples behave as shown.
Hints
- It can be done in less than 10 lines of Lisp.
You will need the following style setup …
#+html_head: <style> #+html_head: .spoiler {color: grey; background-color:grey;} #+html_head: .spoiler:hover {color: black; background-color:white;} #+html_head: <style> # Example use: <span class="spoiler"> test </span>
- Escape HTML snippets by enclosing them in
@@html: … @@
—as discussed above in the introduction to special blocks. - The functions s-replace-regexp and regexp-quote may be useful.
Examples
#+begin_spoiler ((Yusuf)) said to his father ((Yacoub)), /“O my father, indeed I have seen ((eleven stars)) and ((the sun and the moon)); I saw them prostrating to me.”/ #+end_spoiler
Yusuf said to his father Yacoub , “O my father, indeed I have seen eleven stars and the sun and the moon ; I saw them prostrating to me.”
#+begin_spoiler :left "[" :right "]" [Yusuf] said to his father [Yacoub], /“O my father, indeed I have seen [eleven stars] and [the sun and the moon]; I saw them prostrating to me.”/ #+end_spoiler
Yusuf said to his father Yacoub , “O my father, indeed I have seen eleven stars and the sun and the moon ; I saw them prostrating to me.”
Solution
Did you actually try? Maybe see the ‘hints’ above!
Solution, for real
Rather than having auxiliary #+html_head:
styling settings, we have moved the
styling information to the defblock
declaration and are using the main argument
to colour the spoiler —which defaults to grey ;-)
For example, the next segment of text is in a block #+begin_spoiler pink
…
Yusuf said to his father Yacoub , “O my father, indeed I have seen eleven stars and the sun and the moon ; I saw them prostrating to me.”
Whereas, the following begins with #+begin_spoiler orange
…
Yusuf said to his father Yacoub , “O my father, indeed I have seen eleven stars and the sun and the moon ; I saw them prostrating to me.”
(org-defblock spoiler (color "grey" left "((" right "))") "Hide text enclosed in double parens ((like this)) as if it were spoilers. LEFT and RIGHT may be other kinds of delimiters. The main argument, COLOR, indicates which color to use. For LaTeX, this becomes “fill in the blanks”, with the answers in the footnotes." (if (equal backend 'latex) (s-replace-regexp (concat (regexp-quote left) "\\(.*?\\)" (regexp-quote right)) "@@latex:\\\\fbox{\\\\phantom{\\1}}\\\\footnote{\\1}@@" contents) (-let [id (gensym)] (concat ;; In HTML, a ‘style’ can be, technically, almost anywhere... (format "<style> #%s {color: %s; background-color:%s;} #%s:hover {color: black; background-color:white;} </style> " id color color id) (s-replace-regexp (concat (regexp-quote left) "\\(.*?\\)" (regexp-quote right)) (format "@@html:<span id=\"%s\"> \\1 </span>@@" id) contents)))))
There's actually a problem with this ‘solution’; can you find it?
Hint: Try the link form and see how it breaks!
1.5.4. Judgements: Inference rules and proof trees
Using a mixture of \frac
and \displaystyle
, define a block tree so that the
following examples behave as shown. Hint: org-list-to-lisp and
with-temp-buffer may be useful ;-)
Programming ≈ Proving
Source
#+begin_tree + Function Application :: f(a) : B - a : A - f : A → B + Modus Ponens :: q - p - p ⇒ q #+end_tree
Result
\[\frac{\displaystyle \qquad a : A \qquad f : A → B }{f(a) : B}[\text{Function Application}]\]\[\frac{\displaystyle \qquad p \qquad p ⇒ q}{q}[\text{Modus Ponens}]\]Solution
Did you actually try? Maybe see the ‘hints’ above!
Solution, for real
(defun org--list-to-math (lst) "Get a result LST from ORG-LIST-TO-LISP and render it as a proof tree." (cond ((symbolp lst) "") ((symbolp (car lst)) (org--list-to-math (cadr lst))) (t (-let* (((conclusion₀ children) lst) ((name named?) (s-split " :: " conclusion₀)) (conclusion (or named? conclusion₀))) (if (not children) (if named? (format "\\frac{}{%s}[%s]" conclusion name) conclusion) (format "\\frac{\\displaystyle %s}{%s}%s" (s-join " \\qquad " (mapcar #'org--list-to-math children)) conclusion (if named? (format "[\\text{%s}]" name) ""))))))) (org-defblock tree (main-arg) "Write a proof tree using Org-lists. To get premises₀ … premisesₙ ────────────────────────────[ reason ] conclusion You type #+begin_tree + reason :: conclusion - premises₀ - premises₁ ⋮ - premisesₙ #+end_tree Where each premisesᵢ may, recursively, also have named reasons and (indented) child premises of its own. If there are multiple trees, they are shown one after the other. The text in this block should be considered LaTeX; as such, Org markup is not recognised. A proof tree, derivation, is then just a deeply nested itemisation. For instance, assuming P = Q(X), X = Y, Q(Y) = R, the following proves P = R. #+begin_tree + Trans :: P = R - P = Q(X) + ✓ - Trans :: Q(X) = R + Trans :: Q(X) = Q(Y) - Refl :: Q(X) = Q(X) + ✓ - Leibniz :: Q(X) = Q(Y) + X = Y - ✓ + Sym :: Q(Y) = R - R = Q(Y) - ✓ #+end_tree" (s-join "" (--map (format "\\[%s\\]" (org--list-to-math it)) (cdr (with-temp-buffer (insert raw-contents) (goto-char (point-min)) (org-list-to-lisp))))))
For more on these ‘proof trees’, see ‘Natural Logic’ by Neil Tennant.
\[\] (Warning! For MathJax to activate, you should have some math $...$
somewhere besides the tree
blocks; just \[\]
suffices. )
1.6. What's the rest of this article about?
The rest of the article showcases the special blocks declared with defblock
to
allow the above presentation —with folded regions, coloured boxes, tooltips,
parallel columns of text, etc.
Enjoy ;-)
1.7. Dispatch on backend
—open for extensibility
Consider the source and how it exports …
Source
The link “ [[shout:me the author][hello to the world]] ” exports with bold in HTML and large in LaTeX.
Result
The link “ ” exports with bold in HTML and large in LaTeX.
This could be declared via …
;; Declare the new defblock, along with how it should be displayed as an org link (org-defblock shout0 (speaker) [:face '(:foreground "orange" :weight bold) :display 'full] "Capitalise the contents! Seen in orange bold in Emacs!" (pcase backend ('html (format "Yuck/%s: <strong>%s</strong>" speaker contents)) ('latex (format "Yuck/%s: \\emph{\\LARGE %s}" speaker contents)) (t (error "org-block/shout: Unsupported backend “%s”" backend))))
Rather than defining it with a single defblock declaration and a condition on the backend, we can define it using three defblock declarations: One to declare the block along with top-level documentation and display link information; the second for the HTML export; the third for the LaTeX backend export. (Your favourite backend ℬ can easily be supported by declaring a new defblock in your own code!)
;; Declare the new defblock, along with how it should be displayed as an org link (org-defblock shout (speaker) [:face '(:foreground "orange" :weight bold) :display 'full] "Capitalise the contents! Seen in orange bold in Emacs!") ;; Specialise its definition for when we export to the HTML backend (org-defblock shout (speaker nil :backend html) "To shout is to be bold; i.e., strong." (format "%s: <strong>%s</strong>" speaker contents)) ;; Specialise its definition for when we export to the LaTeX backend (org-defblock shout (speaker nil :backend latex) "Shouting is a what LARGE brutes do" (format "%s: \\emph{\\LARGE %s}" speaker contents))
Try C-h o org-defblock/shout and see two implementations.
- This means that users can always re-define an org special block, or extend it to support a new backend. Neato!
- Free user extensibility!
Such “open methods” are known as “multimethods”; e.g., see cl-defgeneric and cl-defmethod.
2. Folded Details —As well as boxed text and subtle colours
How did we fold away those implementations?
Sometimes there is a remark or a code snippet that is useful to have, but not relevant to the discussion at hand and so we want to fold away such details.
- ‘Conversation-style’ articles, where the author asks the reader questions whose answers are “folded away” so the reader can think about the exercise before seeing the answer.
- Hiding boring but important code snippets, such as a list of import declarations or a tedious implementation.
Requires: ,#+latex_header: \usepackage{tcolorbox}
Implementation
(org-defblock details (title "Details" background-color "#e5f5e5" title-color "green") "Enclose contents in a folded up box, for HTML. For LaTeX, this is just a boring, but centered, box. By default, the TITLE of such blocks is “Details”, its TITLE-COLOR is green, and BACKGROUND-COLOR is “#e5f5e5”. In HTML, we show folded, details, regions with a nice greenish colour. In the future ---i.e., when I have time--- it may be prudent to expose more aspects as arguments. " (pcase backend (`latex (concat (pcase (substring background-color 0 1) ("#" (format "\\definecolor{osbe-bg}{HTML}{%s}" (substring background-color 1))) (_ (format "\\colorlet{osbe-bg}{%s}" background-color))) (pcase (substring title-color 0 1) ("#" (format "\\definecolor{osbe-fg}{HTML}{%s}" (substring title-color 1))) (_ (format "\\colorlet{osbe-fg}{%s}" title-color))) (format "\\begin{quote} \\begin{tcolorbox}[colback=osbe-bg,colframe=osbe-fg,title={%s},sharp corners,boxrule=0.4pt] %s \\end{tcolorbox} \\end{quote}" title contents))) (_ (format "<details class=\"code-details\" style =\"padding: 1em; background-color: %s; border-radius: 15px; color: hsl(157 75% 20%); font-size: 0.9em; box-shadow: 0.05em 0.1em 5px 0.01em #00000057;\"> <summary> <strong> <font face=\"Courier\" size=\"3\" color=\"%s\"> %s </font> </strong> </summary> %s </details>" background-color title-color title contents))))
We could use
\begin{quote}\fbox{\parbox{\linewidth}{\textbf{Details:} ...}}\end{quote}
; however, this does not work well with minted for coloured
source blocks. Instead, we use tcolorbox
.
Let's have some sanity tests…
(deftest "The result is a <details> tag containing the user's title & text." [details] (⇝ (⟰ "#+begin_details TITLE-RIGHT-HERE My aside... #+end_details") "<details" (* anything) "TITLE-RIGHT-HERE" (* anything) "My aside" (* anything) "</details>"))
If you fear that your readers may not click on details boxes in the HTML export,
you could, say, have the details heading “flash pink” whenever the user hovers
over it. This can be accomplished by inserting the following incantation into
your Org file or place it into your org-html-head
variable.
#+html: <style> summary:hover {background:pink;} </style>
2.1. Example: Here's a nifty puzzle, can you figure it out?
Reductions —incidentally also called ‘folds’1— embody primitive recursion and thus computability. For example, what does the following compute when given a whole number 𝓃?
(-reduce #'/ (number-sequence 1.0 𝓃))
Solution
Rather than guess-then-check, let's calculate!
(-reduce #'/ (number-sequence 1.0 𝓃)) = ;; Lisp is strict: Evaluate inner-most expression (-reduce #'/ '(1.0 2.0 3.0 … 𝓃)) = ;; Evaluate left-associating reduction (/ (/ (/ 1.0 2.0) ⋯) 𝓃) =;; Arithmetic: (/ (/ a b) c) = (* (/ a b) (/ 1 c)) = (/ a (* b c)) (/ 1.0 (* 2.0 3.0 … 𝓃))
We have thus found the above Lisp program to compute the inverse factorial of 𝓃; i.e., \(\large \frac{1}{𝓃!}\).
Neato, let's do more super cool stuff ^_^
( In the Emacs Web Wowser, folded regions are displayed unfolded similar to LaTeX. )
2.2. Boxed Text
Folded regions, as implemented above, are displayed in a super neat text box which may be useful to enclose text to make it standout —without having it folded away. As such, we provide the special block box to enclosing text in boxes.
Requires: #+latex_header: \usepackage{tcolorbox}
Implementation
(org-defblock box (title "" background-color nil shadow nil) "Enclose text in a box, possibly with a title. By default, the box's COLOR is green for HTML and red for LaTeX, and it has no TITLE. SHADOW is an alternate style of boxing: It shows the contents in a centred box with a shadow colour being the given non-nil value of SHADOW. If SHADOW is a hexidecimal colour, it should be a string enclosed in double quotes. More accurately, the following are all valid uses: #+begin_box title :shadow t #+begin_box title :shadow inset #+begin_box title :shadow \"pink\" #+begin_box title :shadow pink #+begin_box title :shadow (cyan pink orange yellow) #+begin_box title :shadow (cyan \"inset pink\" orange) #+begin_box title :shadow (:left cyan :right pink :deep-right orange :deep-left yellow) Notice that prefixing colours with ‘inset’ causes the colour to be within the box, rather than spread, with blur, around the box. The use of ‘inset’ swaps left and right. The HTML export uses a padded div, whereas the LaTeX export requires the tcolorbox package. In the future, I will likely expose more arguments." (pcase backend (`latex (apply #'concat `("\\begin{tcolorbox}[title={" ,title "}" ",colback=" ,(pp-to-string (or background-color 'red!5!white)) ",colframe=red!75!black, colbacktitle=yellow!50!red" ",coltitle=red!25!black, fonttitle=\\bfseries," "subtitle style={boxrule=0.4pt, colback=yellow!50!red!25!white}]" ,contents "\\end{tcolorbox}"))) ;; CSS syntax: “box-shadow: specification, specification, ...” ;; where a specification is of the shape “[inset] x_offset y_offset [blur [spread]] color”. (_ (-let [haze (lambda (left right deep-right deep-left) (format "width: 50%%; margin: auto; box-shadow: %s" (thread-last (list (cons right "8px 6px 13px 8px %s") (cons left "-16px 12px 20px 16px %s") (cons deep-right "48px 36px 71px 28px %s") (cons deep-left "-48px -20px 71px 28px %s")) (--filter (car it)) (--map (format (cdr it) (car it))) (s-join ","))))] (format "<div style=\"%s\"> <h3>%s</h3> %s </div>" (s-join ";" `("padding: 1em" ,(format "background-color: %s" (org-subtle-colors (format "%s" (or background-color "green")))) "border-radius: 15px" "font-size: 0.9em" ,(when shadow (cond ((equal shadow t) (funcall haze "hsl(60, 100%, 50%)" "hsl(1, 100%, 50%)" "hsl(180, 100%, 50%)" nil)) ((equal shadow 'inset) (funcall haze "inset hsl(60, 100%, 50%)" "inset hsl(1, 100%, 50%)" "inset hsl(180, 100%, 50%)" nil)) ((or (stringp shadow) (symbolp shadow)) (format "box-shadow: 10px 10px 20px 0px %s; width: 50%%; margin: auto" (pp-to-string shadow))) ((json-plist-p shadow) (-let [(&plist :left X :right Y :deep-right Z :deep-left W) shadow] (funcall haze X Y Z W))) (:otherwise (-let [(X Y Z W) shadow] (funcall haze X Y Z W))))))) title contents)))))
Let's have some sanity tests…
(deftest "We have an HTML box enclosing the user's title (in <h3) and text" [box] (⇝ (⟰ "#+begin_box Pay Attention! This is the key insight... #+end_box") "<div style=\"padding: 1em;background-color: #CCFFCC;border-radius: 15px;" (* anything) "<h3>Pay Attention!</h3>" (* anything) "This is the key insight" (* anything)))
Example: A super boring observation presented obscurely
If you start walking —say, counterclockwise— along the unit circle from its right-most point and walk 180ᵒ, then you will be at its left-most point. That is, \[ e^{i · \pi} \;=\; - 1 \]
Or, with just the header option :shadow t
…
Example: A super boring observation presented obscurely
If you start walking —say, counterclockwise— along the unit circle from its right-most point and walk 180ᵒ, then you will be at its left-most point. That is, \[ e^{i · \pi} \;=\; - 1 \]
Or, with the header option :shadow (:left "inset cyan" :right "inset pink"
:deep-right orange)
…
Example: A super boring observation presented obscurely
If you start walking —say, counterclockwise— along the unit circle from its right-most point and walk 180ᵒ, then you will be at its left-most point. That is, \[ e^{i · \pi} \;=\; - 1 \]
For further header options, see the documentation of box.
In the first example above, how did we get that nice light blue? What is its
HTML code? That's not something I care to remember, so let's make a handy dandy
utility … Now when users request a colour to box
their text, it will be a
‘subtle colour’ ;-)
Implementation for Subtle Colours
1: (defun org-subtle-colors (c) 2: "HTML codes for common colours. 3: 4: Names are very rough approximates. 5: 6: Translations from: https://www.december.com/html/spec/softhues.html" 7: (pcase c 8: ("teal" "#99FFCC") ;; close to aqua 9: ("brown" "#CCCC99") ;; close to moss 10: ("gray" "#CCCCCC") 11: ("purple" "#CCCCFF") 12: ("lime" "#CCFF99") ;; brighter than ‘green’ 13: ("green" "#CCFFCC") 14: ("blue" "#CCFFFF") 15: ("orange" "#FFCC99") 16: ("peach" "#FFCCCC") 17: ("pink" "#FFCCFF") 18: ("yellow" "#FFFF99") 19: ("custard" "#FFFFCC") ;; paler than ‘yellow’ 20: (c c) 21: ))
To use these colour names, you will need the following incantations in your Org file.
Source
#+latex_header: \usepackage{xcolor} #+latex_header: \definecolor{teal} {HTML}{99FFCC} #+latex_header: \definecolor{brown} {HTML}{CCCC99} #+latex_header: \definecolor{gray} {HTML}{CCCCCC} #+latex_header: \definecolor{purple} {HTML}{CCCCFF} #+latex_header: \definecolor{lime} {HTML}{CCFF99} #+latex_header: \definecolor{green} {HTML}{CCFFCC} #+latex_header: \definecolor{blue} {HTML}{CCFFFF} #+latex_header: \definecolor{orange} {HTML}{FFCC99} #+latex_header: \definecolor{peach} {HTML}{FFCCCC} #+latex_header: \definecolor{pink}{HTML}{FFCCFF} #+latex_header: \definecolor{yellow} {HTML}{FFFF99} #+latex_header: \definecolor{custard}{HTML}{FFFFCC} #+latex_header: \definecolor{cyan}{HTML}{00FFFF}
Result
In the future, it'd be nice to account for colours for LaTeX as well. ( E.g.,
\color{blue}
is a nightmare. )
It may be useful to fuse the box
and details
concepts into one. Something for
future me —or another contributor— to think about ;-)
3. Parallel
Articles can get lengthy when vertical whitespace is wasted on thin lines; instead, one could save space by using parallel columns of text.
Requires: #+latex_header: \usepackage{multicol}
Implementation
1: (org-defblock parallel (cols "2" bar nil) 2: "Place ideas side-by-side, possibly with a separator. 3: 4: There are COLS many columns, and they may be seperated by solid 5: vertical rules if BAR is a non-nil (colour) value. 6: 7: + COLS is either a number or a sequence of the shape: 10% 20% 30%. 8: + BAR is either `t', `nil', or a colour such as `red' or `blue'. 9: 10: ---------------------------------------------------------------------- 11: 12: “Soft Columns”: Writing “#+begin_parallel 𝓃 :bar t” will produce 13: 𝒏-many parallel columns, possibly separated by solid rules, or a 14: “bar”. This style allows text to freely move between columns, 15: depending on the size of the browser, which may dynamically 16: shrink and grow. BAR can either be `t', `nil', or 17: any (backend)-valid colour specification; such as `red' or 18: `green'. 19: 20: “Hard Columns”: Alternatively, for non-uniform column widths, 21: COLS may instead be a specification of the widths of the 22: columns. However, this extra flexibility comes at an additional 23: cost: The contents of the block must now contain 𝒏-1 lines 24: consisting of ‘#+columnbreak:’, when the specification determines 25: 𝒏 columns, as shown in the following example. 26: 27: #+begin_parallel 20% 60% 20% :bar green 28: Hello, to the left! 29: 30: #+columnbreak: 31: A super duper wide middle margin! 32: 33: #+columnbreak: 34: Goodbye (“God-be-with-ye”) to the right! 35: #+end_parallel 36: 37: The specification is 𝒏 measurements denoting widths; which may be 38: in any HTML recognisable units; e.g., “5em 20px 30%” is valid. 39: I personally advise only the use of percentage measurements. 40: 41: In the Soft Columns style above, any ‘#+columnbreak:’ are merely ignored. 42: With LaTeX export, the use of ‘#+columnbreak:’ is used to request a column break." 43: (let ((rule (pcase backend 44: (`latex (if bar 2 0)) 45: (_ (format "%s %s" (if bar "solid" "none") (if (string= bar "t") "black" bar))))) 46: (contents′ (s-replace "#+columnbreak:" "\\columnbreak" contents))) 47: (pcase backend 48: (`latex (format "\\par \\setlength{\\columnseprule}{%s pt} 49: \\begin{minipage}[t]{\\linewidth} 50: \\begin{multicols}{%s} 51: %s 52: \\end{multicols}\\end{minipage}" rule cols contents′)) 53: (_ (if (not (s-contains-p "%" cols)) 54: (format "<div style=\"column-rule-style: %s;column-count: %s;\">%s</div>" 55: rule cols contents) 56: ;; Otherwise: cols ≈ "10% 40% 50%", for example. 57: (let ((spec (s-split " " (s-collapse-whitespace (s-trim cols)))) 58: (columnBreak (lambda (width omit-rule?) 59: (format "<div style=\"width: %s; margin: 10px; border-right:4px %s; float: left;\">" width 60: (if omit-rule? "none" rule)))) ) 61: (format "<div>%s%s</div>" 62: (funcall columnBreak (pop spec) nil) 63: (s-replace-regexp (regexp-quote "#+columnbreak:") 64: ;; ‘λ’ since we need the “pop” evaluated for each find-replace instance. 65: ;; We use “not spec” to omit the rule separator when there is NOT anymore elements in SPEC. 66: (lambda (_) (format "@@html:</div>%s@@" (funcall columnBreak (pop spec) (not spec)))) 67: contents))))))))
Let's have some sanity tests…
(deftest "Parallel blocks work as expected - Defaults" [parallel block] (⇝ (⟰ "#+begin_parallel Hello, to the left! #+columnbreak: A super duper wide middle margin! #+columnbreak: Goodbye (“God-be-with-ye”) to the right! #+columnbreak: woah #+end_parallel") ;; The result is 2 columns with no rule between them and it contains the ;; user's text, but ignores the “#+columnbreak”. "<div style=\"column-rule-style: none nil;column-count: 2;\">" (* anything) "Hello, to the left!" (* anything) "A super duper wide middle margin!" (* anything) "Goodbye (“God-be-with-ye”) to the right!" (* anything) "woah")) (deftest "Parallel blocks work as expected - Soft Columns" [parallel block] (⇝ (⟰ "#+begin_parallel 2 :bar t X #+columnbreak: Y Z #+end_parallel") ;; The result is 2 columns with a solid BLACK rule between them and it ;; contains the user's text, but ignores the “#+columnbreak”. "<div style=\"column-rule-style: solid black;column-count: 2;\">" (* anything) "X" (* anything) "Y" (* anything) "Z" (* anything))) (deftest "Parallel blocks work as expected - Hard Columns" [parallel block] (⇝ (⟰ "#+begin_parallel 20% 60% 20% :bar green X #+columnbreak: Y #+columnbreak: Z #+end_parallel") ;; The result is 3 columns with a solid GREEN rule between them and it ;; contains the user's text along with the “#+columnbreak” evaluated at the ;; expected positions. "<div><div style=\"width: 20%; margin: 10px; border-right:4px solid green; float: left;\">" (* anything) "X" (* anything) "</div><div style=\"width: 60%; margin: 10px; border-right:4px solid green; float: left;\">" (* anything) "Y" (* anything) "</div><div style=\"width: 20%; margin: 10px; border-right:4px none; float: left;\">" (* anything) "Z"))
Example
This
#+begin_parallel 2 :bar t X #+columnbreak: Y Z #+end_parallel
Yields
X
Y
Z
( The Emacs Web Wowser, M-x eww
, does not display parallel
environments as
desired. )
4. :fire: HTML Export Styles as Links
It's annoying to have to introduce HTML into your Org —especially when you don't know HTML and don't want to learn it just to have a pretty webpage.
- The annoyance stems from looking around for appropriate CSS files and then dumping links to them into your Org.
- Ideally, you would use Org and declaratively request a theme —from a pre-compiled list of themes, which you can add to.
- Now you can! Just write
html-export-style:𝑻𝑯𝑬𝑴𝑬
anywhere in your Org file.- In Emacs, hover over this new link type to see a list of supported themes.
- By default, we ship themes:
stylish_white solarized_light solarized_dark
simple_gray retro_dark simple_whiteblue rethink_inline imagine_light
comfy_inline default bigblow readtheorg rose latexcss
.
(defvar org--html-export-style-choice "default" "This variable holds the link label declared by users. It is used in the hook to Org's reprocessing; `org--html-export-style-setup'.") (defvar org-html-export-styles `((default . "") (bigblow . "#+SETUPFILE: https://fniessen.github.io/org-html-themes/org/theme-bigblow.setup") (readtheorg . "#+SETUPFILE: https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup") (rose . "#+HTML_HEAD: <link href=\"https://taopeng.me/org-notes-style/css/notes.css\" rel=\"stylesheet\" type=\"text/css\" />") (latexcss . "#+HTML_HEAD: <link rel=\"stylesheet\" href=\"https://latex.now.sh/style.min.css\" />")) "An alist of theme-to-setup pairs, symbols-to-strings, used by `org-link/html-export-style'. For live examples of many of the themes, see https://olmon.gitlab.io/org-themes/. In due time, I would like to add more, such as those linked from the discussion https://news.ycombinator.com/item?id=23130104. A nice, simple, opportunity for someone else to contribute.") ;; ;; Add a bunch more (cl-loop for theme in '(comfy_inline imagine_light rethink_inline simple_whiteblue retro_dark simple_gray solarized_dark solarized_light stylish_white) do (push (cons theme (format "#+SETUPFILE: https://gitlab.com/OlMon/org-themes/-/raw/master/src/%s/%s.theme" theme theme)) org-html-export-styles)) (defun org--html-export-style-setup (_backend) "Insert an HTML theme link setup, according to `org-html-export-styles'." (save-excursion (goto-char (point-min)) (-> (or (assoc org--html-export-style-choice org-html-export-styles) (error "Error: Unknown html-export-style ∷ %s ∉ '%s" org--html-export-style-choice (-cons* 'random 'default (mapcar 'car org-html-export-styles)))) cdr (format "\n %s \n") insert))) (org-deflink html-export-style "Add a dedicated style theme, from `org-html-export-styles'." [:face '(:underline "green") :keymap ("?" ;; Let use select new choice of style with the “?” key. (-let [choice (completing-read "New HTML style: " (cons "random" (--map (pp-to-string (car it)) org-html-export-styles)))] (save-excursion (beginning-of-line) (while (re-search-forward (rx (seq "html-export-style:" (one-or-more word))) nil t) (replace-match (concat "html-export-style:" choice)))))) :help-echo (thread-last (cons "random" (--map (pp-to-string (car it)) org-html-export-styles)) (-partition 4) (--map (s-join " " it)) (s-join "\n") (format "Press “?” to change the theme. \n\n Supported themes include:\n\n%s")) :let (whatdo (progn (setq org--html-export-style-choice (if (equal "random" o-label) (seq-random-elt (mapcar 'car org-html-export-styles)) (intern o-label))) (pcase o-label ;; TODO: Move this to when the mode is enabled/disabled? ("default" (remove-hook 'org-export-before-processing-hook 'org--html-export-style-setup)) (_ (add-hook 'org-export-before-processing-hook 'org--html-export-style-setup))))) ] ;; Result string, nothing. "")
5. Fortune Links: Smiles inside and outside of Emacs
Sometimes we need a smile, or a fortune, and want it with a nice graphic —all
in Emacs. Enter fortune:𝒳
links!
- These yield colourful graphics both in Emacs and in HTML export.
- Click on the link to see it in action, in Emacs.
- Here is an example of
fortune:joke
.________________________________________ / What's the difference between a poorly \ | dressed man on a tricycle and a well | \ dressed man on a bicycle? Attire. / ---------------------------------------- \ \ ,__, | | (oo)\| |___ (__)\| | )\_ | |_w | \ | | || * Cower.... cower
(org-deflink fortune "Print an ASCII animal saying the given link's description, a fortune, or a joke. This is essentially a wrapper around the following command-line incantation fortune | cowsay | lolcat -f We show colourful sayings both in Emacs (when you click on the link) and in HTML export. This requires you have the command-line packages ‘fortune’, ‘cowsay’, ‘lolcat’, and ‘aha’ for converting coloured terminal output into HTML. On MacOS, all of these can be installed with `brew install 𝒳'; better-yet use the Emacs Lisp `system-packages-install' package. The help-echo, hover tooltip, provides useful information on possibly link types and their effects. Example uses: fortune:joke ;; Show a random animal saying a punny joke fortune:random ;; Show a random animal saying a random fortune/phrase fortune:dragon ;; Show a dragon saying a random fortune/phrase ;; Show a the given animal saying the given phrase [[fortune:mutilated][Opps, I broke the thing!]] ;; The ‘fortune’ link grew out of the following link in my work journal [[elisp:(dad-joke-get)][I wanna smile!]] For HTML export, the resulting HTML element has class ‘org-fortune’, to which users may adorn CSS styling. For instance, in an Org file: #+html: <style> .org-fortune {font-style: italic; font-family: Monaco} </style> # Chalkduster font-family is good for one-line sayings, phrases. We intentionally do not fold-up such links when they have associated descriptions. When you click, it takes a seconds to fetch jokes; so await a moment when hovering over joke fortunes." [:display 'full :face '(:box (:color "orange" :style released-button) :underline "green" :overline "green") :let (animals '(blowfish bud-frogs cower default dragon dragon-and-cow flaming-sheep ghostbusters moose mutilated sheep stegosaurus turkey turtle tux) animal (if (string-equal "cow" (s-trim o-label)) 'default (seq-random-elt animals)) animal₀ (if (equal 'default animal) 'cow animal) _loads (progn (require 'dad-joke) (require 'seq) (require 'lolcat)) saying (cond (o-description (format "echo %s" (pp-to-string o-description))) ((equal "joke" (s-trim o-label)) (format "echo %s" (pp-to-string (dad-joke-get)))) (:otherwise "fortune")) result (format "%s\t\t\t%s" (shell-command-to-string (format "%s | cowsay -f %s" saying animal)) animal₀) buf-name (format "fortune:%s" animal₀)) :follow (progn (display-message-or-buffer result) (ignore-errors (kill-buffer buf-name)) (switch-to-buffer-other-window "*Message*") (rename-buffer buf-name) (highlight-regexp (format "%s" animal₀) 'hi-green-b) (lolcat-this-buffer) (local-set-key "q" #'kill-buffer-and-window) (message "“q” to kill buffer and window.")) :help-echo (s-join "\n" (list "“fortune:𝒳” or “[[fortune:𝒳][description 𝒟]]” where 𝒳 is " "⇢ joke ⟦A random animal that says a punny joke⟧" "⇢ random ⟦A random animal that says 𝒟, or a random fortune phrase⟧" "⇢ ⟦This animal says 𝒟, or a random fortune phrase⟧" "\t cow, blowfish, bud-frogs, cower, dragon, dragon-and-cow," "\t flaming-sheep, ghostbusters, moose, mutilated, sheep," "\t stegosaurus, turkey, turtle tux" "\n" "\n" result)) ] (if (equal o-backend 'html) (--> (shell-command-to-string (format "%s | cowsay -f %s | lolcat -f | aha -n" saying animal)) (if (s-starts-with? "/System/Library" it) (s-join "\n" (cdr (s-split "\n" it))) it) (format "%s\t\t\t%s" it animal₀) (format "<pre class=\"org-fortune\"> %s </pre>" it)) result))
6. Editor Comments
“Editor Comments” are intended to be top-level first-class comments in an article that are inline with the surrounding text and are delimited in such a way that they are visible but drawing attention. I first learned about this idea from Wolfram Kahl —who introduced me to Emacs many years ago. We
Example
This
In LaTeX, a remark appears inline with the text surrounding it. #+begin_remark Bobert org-mode is dope, yo! #+replacewith: Org-mode is essentially a path toward enlightenment. #+end_remark Unfortunately, in the HTML rendition, the remark is its own paragraph and thus separated by new lines from its surrounding text.
Yields
In LaTeX, an remark
appears inline with the text surrounding it.
[Bobert: Replace:
org-mode is dope, yo!
With:Org-mode is essentially a path toward enlightenment.
]
Unfortunately, in the HTML rendition, the remark
is its own paragraph and thus
separated by new lines from its surrounding text.
Implementing ‘remark’, from §ection 1, with more bells and whistles
(defvar org-hide-editor-comments nil "Should editor comments be shown in the output or not.") (org-defblock remark (editor "Editor Remark" color "black" signoff "" strong nil) ; :inline-please__see_margin_block_for_a_similar_incantation ; ⇒ crashes! [:face '(:foreground "red" :weight bold)] "Format CONTENTS as an first-class editor comment according to BACKEND. The CONTENTS string has an optional switch: If it contains a line with having only ‘#+replacewith:’, then the text preceding this clause should be replaced by the text after it; i.e., this is what the EDITOR (the person editing) intends and so we fromat the replacement instruction (to the authour) as such. In Emacs, as links, editor remarks are shown with a bold red; but the exported COLOR of a remark is black by default and it is not STRONG ---i.e., bold---. There is an optional SIGNOFF message that is appended to the remark. " (-let* (;; Are we in the html backend? (tex? (equal backend 'latex)) ;; fancy display style (boxed (lambda (x) (if tex? (concat "\\fbox{\\bf " x "}") (concat "<span style=\"border-width:1px" ";border-style:solid;padding:5px\">" "<strong>" x "</strong></span>")))) ;; Is this a replacement clause? ((this that) (s-split "\\#\\+replacewith:" contents)) (replacement-clause? that) ;; There is a ‘that’ (replace-keyword (if tex? "\\underline{Replace:}" " <u>Replace:</u>")) (with-keyword (if tex? "\\underline{With:}" "<u>With:</u>" )) (editor (format "[%s:%s" editor (if replacement-clause? replace-keyword ""))) (contents′ (if replacement-clause? (format "%s %s %s" this (org-export (funcall boxed with-keyword)) that) contents)) ;; “[Editor Comment:” (edcomm-begin (funcall boxed editor)) ;; “]” (edcomm-end (funcall boxed "]"))) (setq org-export-allow-bind-keywords t) ;; So users can use “#+bind” immediately (if org-hide-editor-comments "" (format (pcase backend ('latex (format "{\\color{%%s}%s %%s %%s %%s %%s}" (if strong "\\bfseries" ""))) (_ (format "<%s style=\"color: %%s;\">%%s %%s %%s %%s</%s>" (if strong "strong" "p") (if strong "strong" "p")))) color edcomm-begin contents′ signoff edcomm-end))))
In the HTML export, the edcomm
special block is not in-line with the text
surrounding it —ideally, it would be inline so that existing paragraphs are
not split into multiple paragraphs but instead have an editor's comment
indicating suggested alterations.
Let's have some sanity tests…
(deftest "The user's remark is enclosed in the default delimiters" [remark] (⇝ (⟰ "#+begin_remark Here is some meta-commentary... #+end_remark") (* anything) "[Editor Remark:" (* anything) "Here is some meta-commentary" (* anything) "]")) ;; The other features of remark blocks should be tested; ;; but this is not a pressing, nor interesting, concern.
Example: No optional arguments
[Editor Remark:
Please change this section to be more, ya know, professional.
]Source:
#+begin_remark Please change this section to be more, ya know, professional. #+end_remark
Example: Only providing a main argument ---i.e., the remark author, the editor
[Bobert:
Please change this section to be more, ya know, professional.
]Source:
#+begin_remark Bobert Please change this section to be more, ya know, professional. #+end_remark
Example: Possibly with no contents:
[Bobert: ]
Source:
#+begin_remark Bobert #+end_remark
Example: Empty contents, no authour, nothing
[Editor Remark: ]
Source:
#+begin_remark #+end_remark
Example: Possibly with an empty new line
[Editor Remark: ]
Source:
#+begin_remark #+end_remark
Example: With a “#+replacewith:” clause
[Editor Remark: Replace:
The two-dimensional notation; e.g., \(\sum_{i = 0}^n i^2\)
With:A linear one-dimensional notation; e.g., \((\Sigma i : 0..n \;\bullet\; i^2)\)
]Source:
#+begin_remark The two-dimensional notation; e.g., $\sum_{i = 0}^n i^2$ #+replacewith: A linear one-dimensional notation; e.g., $(\Sigma i : 0..n \;\bullet\; i^2)$ #+end_remark
Example: Possibly “malformed” replacement clauses
Forgot the thing to be replaced…
[Editor Remark: Replace: With:
A linear one-dimensional notation; e.g., \((\Sigma i : 0..n \;\bullet\; i^2)\)
]Forgot the new replacement thing…
[Editor Remark: Replace:
The two-dimensional notation; e.g., \(\sum_{i = 0}^n i^2\)
With: ]Completely lost one's train of thought…
[Editor Remark: Replace: With: ]
Source:
#+begin_remark #+replacewith: #+end_remark
A block to make an editorial comment could be overkill in some cases; luckily defblock automatically provides an associated link type for the declared special blocks.
- Syntax:
[[remark:person_name][editorial remark]]
. - This link type exports the same as the
remark
block type; however, in Emacs it is shown with an ‘angry’ —bold— red face.
Example: Terse remarks via links
[[edcomm:Jasim][Hello, where are you?]]
[Jasim: Hello, where are you? ]
The #+replacewith:
switch —and usual Org markup— also works with these
links:
[[remark:Qasim][/‘j’/ #+replacewith: /‘q’/]]
[Qasim: Replace: ‘j’ With: ‘q’ ]
All editor comments, remarks, are disabled by declaring, in your Org file:
#+bind: org-hide-editor-comments t
The #+bind:
keyword makes Emacs variables buffer-local during export
—it is evaluated after any src
blocks. To use it, one must declare in
their Emacs init file the following line, which our mode
ensures is true.
(setq org-export-allow-bind-keywords t)
( Remember to C-c C-c the #+bind to activate it, the first time it is written. ) |
7. Colours
Let's develop blocks for colouring text and link types for inline colouring; e.g., color and teal.
- E.g.,
[[brown: Hello, World!]]
⇒ Hello, World! - E.g.,
brown:Friends!
⇒ Friends! ( Note the ‘!’ is not coloured; use[[...]]
! )
Use M-x list-colors-display to see a list of defined colour names in Emacs —see xcolor for the LaTeX side and htmlcolorcodes.com for the HTML side, or just visit http://latexcolor.com/ for both.
A Picture and Block Examples
This text is black!
This text is blue!
This text is brown!
This text is darkgray!
This text is gray!
This text is green!
This text is lightgray!
This text is lime!
This text is magenta!
This text is olive!
This text is orange!
This text is pink!
This text is purple!
This text is red!
This text is teal!
This text is violet!
This text is white!
This text is yellow!
Implementation of numerous colour blocks/links
We declare a list of colors that should be available on most systems. Then using this list, we evaluate the code necessary to produce the necessary functions that format special blocks.
By default, Org uses the graphicx
LaTeX package which let's us colour text
—see its documentation here. For example, in an #+begin_export latex
block,
the following produces blue coloured text.
{ \color{blue} This is a sample text in blue. }
(defvar org--ospe-colors '(black blue brown cyan darkgray gray green lightgray lime magenta olive orange pink purple red teal violet white yellow) "Colours that should be available on all systems.") (cl-loop for colour in org--ospe-colors do (eval `(org-defblock ,colour (the-color "black") (vector :face `(:foreground ,(format "%s" (quote ,colour)))) ,(format "Show text in %s color." colour) (let () (format (pcase backend (`latex "\\begingroup\\color{%s}%s\\endgroup\\,") (_ "<span style=\"color:%s;\">%s</span>")) (quote ,colour) contents)))))
Let's have some sanity tests…
(deftest "It is an HTML span styled red that contains the user's text" [color red block] (⇝ (⟰ "#+begin_red My cool thoughts... #+end_red") "<span style=\"color:red;\">" (* anything) "My cool thoughts" (* anything) "</span>")) ;; We have an HTML span styled with the user's color and it contains the user's text (deftest "It works as expected" [color pink block] (⇝ (⟰ "#+begin_color pink My cool thoughts... #+end_color") "<span style=\"color:pink;\">" (* anything) "My cool thoughts" (* anything) "</span>"))
Implementation of ‘color’
For faster experimentation between colours, we provide a generic color
block
that consumes a color argument.
(org-defblock color (color black) (vector :face (lambda (colour) `(:foreground ,(format "%s" colour)))) "Format text according to a given COLOR, which is black by default." (format (pcase backend (`latex "\\begingroup\\color{%s}%s\\endgroup\\,") (`html "<span style=\"color:%s;\">%s</span>")) color contents))
[Posterity] Old version which did not allow wombo
colour:
(org-defblock color (color black :face (lambda (colour) (if (member (intern colour) org--ospe-colors) `(:foreground ,(format "%s" colour)) `(:height 300 :underline (:color "red" :style wave) :overline "red" :strike-through "red")))) nil "Format text according to a given COLOR, which is black by default." (format (pcase backend (`latex "\\begingroup\\color{%s}%s\\endgroup\\,") (`html "<span style=\"color:%s;\">%s</span>")) color contents))
Moreover, we have that the syntax red:text
renders ‘text’ with the colour red
in both the Emacs interface and in exported backends.
Observe: this is super neato, amigos! and this is brown ‘color’ link and this one is an orange ‘color’ link!
Also: If we try to use an unsupported colour ‘wombo’, we render the
descriptive text larger in Emacs along with a tooltip explaining why this is
the case; e.g.,
[[color:wombo][hi]]
.
[[color:#fad][Using Hex colour code!]]
⇒ Using Hex colour code! ;-)
( Markdown does not support colour; go look at the HTML or PDF! )
7.1. latex-definitions
for hiding LaTeX declarations in HTML
Before indicating desirable next steps, let us produce an incidentally useful special block type.
We may use LaTeX-style commands such as {\color{red} x}
by enclosing them in
$
-symbols to obtain \({\color{red}x}\) and other commands to present mathematical
formulae in HTML. This is known as the MathJax tool —Emacs' default HTML
export includes it.
It is common to declare LaTeX definitions for convenience, but such
declarations occur within $
-delimiters and thereby produce undesirable extra
whitespace. We declare the latex_definitions
block type which avoids
displaying such extra whitespace in the resulting HTML.
‘latex-definitions’ Implementation
(org-defblock latex-definitions nil "Declare but do not display the CONTENTS according to the BACKEND." (format (pcase backend ('html "<p style=\"display:none\">\\[%s\\]</p>") (_ "%s")) raw-contents))
Here —which you cannot see, as desired—is an example usage, where we
declare \LL
to produce a violet left parenthesis. We then use these to produce
an example of quantification notation.
\[ {\color{teal}\bigoplus} _{ {\color{violet} x} = {\color{red} a}} ^{\color{cyan} b} {\color{brown}{\,f\, x}} \quad=\quad {\color{brown}{f\,\LL {\color{red} a} \RR}} \;{\color{teal}\oplus}\; {\color{brown}{f \, \LL a + 1 \RR }} \;{\color{teal}\oplus}\; {\color{brown}{f \, \LL a + 2 \RR }} \;{\color{teal}\oplus}\; \cdots \;{\color{teal}\oplus}\; {\color{brown}{f \, \LL {\color{cyan} b} \RR}} \]
⊕ | Loop sequentially with loop-bodies fused using ⊕ |
x | Use x as the name of the current element |
a | Start with x being a |
b | End with x being b |
f x | At each x value, compute f x |
( Markdown does not support MathJax; go look at the HTML or PDF! )
Unfortunately, MathJax does not easily support arbitrary HTML elements to occur
within the $
-delimiters —see this and this for ‘workarounds’. As such, the
MathJax producing the above example is rather ugly whereas its subsequent
explanatory table is prettier on the writer's side.
Going forward, it would be nice to easily have our colour links work within a mathematical special block.
Moreover, it would be nice to extend the color
block type to take multiple
arguments, say, c₁ c₂ … cₙ
such that:
n | Behaviour |
---|---|
0 | No colouring; likewise if no arguments altogether |
1 | Colour all entries using the given colour c₁ |
n | Paragraph –region separated by a new line– i is coloured by cₖ where k = i mod n |
Besides having a colourful article, another usage I envision for this generalisation would be when rendering text in multiple languages; e.g., use red and blue to interleave Arabic poetry with its English translation.
8. Nice Keystroke Renditions: C-h h
Anyone who writes about Emacs will likely want to mention keystrokes in an aesthetically pleasing way, such as C-u 80 - to insert 80 dashes, or C-c C-e h o to export an Org-mode file to HTML, or the useful M-s h ..
kbd:𝒳
will show a tooltip defining 𝒳, as an Emacs Lisp function, if possible. For example,kbd:C-h_h
is C-h h; and likewise<kbd:M-s h .>
is M-s h. (we need to use<...>
since punctuation is not picked up as part of link labels). In contrast,kbd:nope
renders as nope without a tooltip (nor a red border).You can also supply explicit tooltip description:
[[kbd:key sequence][description]]
will show as key sequence; i.e., the key sequence in nice key font along with a tooltip explaining it.
Implementation
(org-deflink kbd "Show keysequence O-LABEL in a nice grey button-like font, along with a tooltip of its documentation, if any. Such links do not get folded in [[bracket]] style, and are rendered as buttons within Emacs. Moreover, O-LABEL may use ‘_’ in-lieu of spaces or [[bracket]] link notation. Examples: [[kbd:C-x C-s]] ≈ <kbd: C-x C-s> ≈ kbd:C-x_C-s" [:display 'full :let (the-label (s-trim (s-replace "_" " " o-label)) lisp-func (ignore-errors (cl-second (help--analyze-key (kbd the-label) the-label))) tooltip (or o-description (ignore-errors (documentation lisp-func)) "") tooltip? (not (equal tooltip "")) style (if tooltip? "border-color: red" "") keystrokes (format "<kbd style=\"%s\">%s</kbd>" style the-label)) ;; o-description is always nil when it comes to deciding the :face. :face (list :inherit 'custom-button :box (if tooltip? "red" t)) :help-echo (format "%s ∷ %s\n%s" the-label (or lisp-func "") tooltip)] (if (equal o-backend 'latex) (format "\\texttt{%s}" the-label) (if tooltip? ;; The style=⋯ is to remove the underlining caused by <abbr>. (format "<abbr class=\"tooltip\" style=\"border: none; text-decoration: none;\" title=\"%s ∷ %s<br>%s\">%s</abbr>" the-label (or lisp-func "") (org-ospe-html-export-preserving-whitespace tooltip) keystrokes) keystrokes)))
The following styling rule is used to make the keystrokes displayed nicely.
(defvar org--ospe-kbd-html-setup nil "Has the necessary keyboard styling HTML beeen added?") (unless org--ospe-kbd-html-setup (setq org--ospe-kbd-html-setup t)) (when org-special-block-add-html-extra (setq org-html-head-extra (concat org-html-head-extra " <style> /* From: https://endlessparentheses.com/public/css/endless.css */ /* See also: https://meta.superuser.com/questions/4788/css-for-the-new-kbd-style */ kbd { -moz-border-radius: 6px; -moz-box-shadow: 0 1px 0 rgba(0,0,0,0.2),0 0 0 2px #fff inset; -webkit-border-radius: 6px; -webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.2),0 0 0 2px #fff inset; background-color: #f7f7f7; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 1px 0 rgba(0,0,0,0.2),0 0 0 2px #fff inset; color: #333; display: inline-block; font-family: 'Droid Sans Mono', monospace; font-size: 80%; font-weight: normal; line-height: inherit; margin: 0 .1em; padding: .08em .4em; text-shadow: 0 1px 0 #fff; word-spacing: -4px; box-shadow: 2px 2px 2px #222; /* MA: An extra I've added. */ } </style>")))
Let's have some sanity tests…
(deftest "It becomes <kbd> tags, but final symbol non-ascii *may* be ignored" [kbd direct-org-links] (⇝ (⟰ "kbd:C-u_80_-∀") "<p>\n<kbd style=\"\">C-u 80</kbd>_-∀</p>")) (deftest "[[It]] becomes <kbd> tags" [kbd square-org-links] (⇝ (⟰ "[[kbd:C-u_80_-]]") "<p>\n<kbd style=\"\">C-u 80 -</kbd></p>")) (deftest "<It> becomes <kbd> tags, and surrounding space is trimmed" [kbd angle-org-links] (⇝ (⟰ "<kbd: C-u 80 - >") "<p>\n<kbd style=\"\">C-u 80 -</kbd></p>")) ;; FIXME: uh-oh! (when nil (deftest "It has a tooltip documenting the underlying Lisp function, when possible" [kbd tooltip] (⇝ (⟰ "<kbd: M-s h .>") "<abbr class=\"tooltip\"" (* anything) "Highlight each instance of the symbol at point.<br>Uses the next face from ‘hi-lock-face-defaults’ without prompting,<br>unless you use a prefix argument.<br>Uses ‘find-tag-default-as-symbol-regexp’ to retrieve the symbol at point.<br><br>This uses Font lock mode if it is enabled; otherwise it uses overlays,<br>in which case the highlighting will not update as you type. The Font<br>Lock mode is considered ''enabled'' in a buffer if its ‘major-mode’<br>causes ‘font-lock-specified-p’ to return non-nil, which means<br>the major mode specifies support for Font Lock." (* anything) "<kbd style=\"border-color: red\">M-s h .</kbd></abbr>")))
9. “Link Here!” & OctoIcons
Use the syntax link-here:name
to create an anchor link that alters the URL with
#name
as in “ ”
—it looks and behaves like the Github generated links for a heading.
Use case: Sometimes you want to explicitly point to a particular location in an
article, such as within a #+begin_details
block, this is a possible way to do so.
Likewise, get OctoIcons with the syntax octoicon:𝒳
where 𝒳
is one of home,
link, mail, report, tag, clock
: , , ,
, , .
- Within
octoicon:𝒳
andlink-here:𝒳
the label𝒳
determines the OctoIcon shown and the name of the local link to be created, respectively.- Descriptions, as in
[[link:label][description]]
, are ignored.
- Descriptions, as in
- Besides the HTML backend, such links are silently omitted.
Six OctoIcons and Implementation
The following SVGs are obtained from: https://primer.style/octicons/
(defvar org--supported-octoicons (-partition 2 '( home "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M16 9l-3-3V2h-2v2L8 1 0 9h2l1 5c0 .55.45 1 1 1h8c.55 0 1-.45 1-1l1-5h2zm-4 5H9v-4H7v4H4L2.81 7.69 8 2.5l5.19 5.19L12 14z\"></path></svg>" link "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg>" mail "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 14 16\" width=\"14\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M0 4v8c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1H1c-.55 0-1 .45-1 1zm13 0L7 9 1 4h12zM1 5.5l4 3-4 3v-6zM2 12l3.5-3L7 10.5 8.5 9l3.5 3H2zm11-.5l-4-3 4-3v6z\"></path></svg>" report "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M0 2a1 1 0 011-1h14a1 1 0 011 1v9a1 1 0 01-1 1H7l-4 4v-4H1a1 1 0 01-1-1V2zm1 0h14v9H6.5L4 13.5V11H1V2zm6 6h2v2H7V8zm0-5h2v4H7V3z\"></path></svg>" tag "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 15 16\" width=\"15\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M7.73 1.73C7.26 1.26 6.62 1 5.96 1H3.5C2.13 1 1 2.13 1 3.5v2.47c0 .66.27 1.3.73 1.77l6.06 6.06c.39.39 1.02.39 1.41 0l4.59-4.59a.996.996 0 000-1.41L7.73 1.73zM2.38 7.09c-.31-.3-.47-.7-.47-1.13V3.5c0-.88.72-1.59 1.59-1.59h2.47c.42 0 .83.16 1.13.47l6.14 6.13-4.73 4.73-6.13-6.15zM3.01 3h2v2H3V3h.01z\"></path></svg>" clock "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 14 16\" width=\"14\" height=\"16\"><path fill-rule=\"evenodd\" d=\"M8 8h3v2H7c-.55 0-1-.45-1-1V4h2v4zM7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 011.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7z\"></path></svg>")) "An association list of supported OctoIcons. Usage: (cadr (assoc 'ICON org--supported-octoicons))")
(org-deflink octoicon "Show an OctoIcon: home, link, mail, report, tag, clock" [:help-echo "Show an OctoIcon: home, link, mail, report, tag, clock"] (unless (member (intern o-label) '(home link mail report tag clock)) (error "octoicon:%s ⇒ This label is not supported!" o-label)) (if (not (equal o-backend 'html)) "" (s-collapse-whitespace (cadr (assoc (intern o-label) org--supported-octoicons))))) (org-deflink link-here "Export a link to the current location in an Org file." [:help-echo (format "This is a local anchor link named “%s”" path)] (if (not (equal o-backend 'html)) "" (format (s-collapse-whitespace "<a class=\"anchor\" aria-hidden=\"true\" id=\"%s\" href=\"#%s\">%s</a>") o-label o-label (cadr (assoc 'link org--supported-octoicons)))))
Going forward, it would be desirable to provide a non-whitespace alternative for the LaTeX rendition. More usefully, before the HTML export hook, we could place such ‘link-here’ links before every org-title produce clickable org-headings, similar to Github's —the necessary ingredients are likely here.
Moreover, it may be useful to have octoicon:𝒳|url
so the resulting
OctoIcon is optionally a link to the given url
. Another direction
would be to support more, if not all, the possible OctoIcons.
Source
link-here:example-location (Click the icon and see the URL has changed!)
Result
(Click the icon and see the URL has changed!)
Tests
(deftest "It works as expected: We have an anchor with the given ID, and the default SVG chain icon." [link:here] (⇝ (⟰ "link-here:example-location (Click the icon and see the URL has changed!)") "<a class=\"anchor\" aria-hidden=\"true\" id=\"example-location\" href=\"#example-location\"><svg" (* anything) "</svg></a> (Click the icon and see the URL has changed!)" (* anything)))
10. Badge Links
Badges provide a quick and colourful summary of key features of a project, such as whether it's maintained, its license, and if it's documented.
Figure 2: An Emacs interface to https://shields.io/
As people who are passionate about writing great code we display "badges" in our code repositories to signal to fellow developers that we set ourselves high standards for the code we write, think of them as the software-equivalent of the brand on your jeans or other reliable product. — repo-badges
Source
What are badges? badge:Let_me_google_that|for_you!|orange|https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir or [[badge: Let me google that | for you! |orange| https://lmgtfy.app/?q=badge+shields.io&iie=1 |Elixir]] or badge:Let_me_*not*_google_that|for_you
Notice that the orange badge points to a URL and so is clickable, whereas the green one does not point to a URL and so is not clickable.
Automated Tests
Let's have some sanity tests…
(deftest "It works when all 5 arguments are provided" [badge] (⇝ (⟰ "badge:Let_me_google_that|for_you!|orange|https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir") "<a href=\"https://lmgtfy.app/?q=badge+shields.io&iie=1\">" "<img src=\"https://img.shields.io/badge/Let_me_google_that-for_you%21-orange?logo=Elixir\"></a>")) (deftest "It works when we use [[link]] syntax with generous spaces and newlines" [badge] (⇝ (⟰ "[[badge: Let me google that | for you! | orange | https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir]]") "<a href=\"https://lmgtfy.app/?q=badge+shields.io&iie=1\">" (* anything) "<img src=\"https://img.shields.io/badge/Let%20me%20google%20that-for%20you%21-orange?logo=Elixir\">")) (deftest "It works when only the first 2 arguments are provided; asterisks are passed unaltered into the first argument" [badge] (⇝ (⟰ "badge:Let_me_*not*_google_that|for_you") "<img src=\"https://img.shields.io/badge/Let_me_%2Anot%2A_google_that-for_you-nil?logo=nil\">")) (deftest "It works when all 5 arguments are provided - URL ‘here’ makes it a local link" [badge] (⇝ (⟰ "badge:key|value|informational|here|Elixir") "<a id=\"key\" href=\"#key\">" "<img src=\"https://img.shields.io/badge/key-value-informational?logo=Elixir\"></a>")) (deftest "We can use spaces, commas, dashes, and percentage symbols in the first argument" [badge] (⇝ (⟰ "badge:example_with_spaces,_-,_and_%|points_right_here|orange|here") "<a id=\"example_with_spaces,_-,_and_%\" href=\"#example_with_spaces,_-,_and_%\">" "<img src=\"https://img.shields.io/badge/example_with_spaces%2C_--%2C_and_%25-points_right_here-orange?logo=nil\"></a>")) (deftest "It works when only first 2 arguments are given: Default colour & logo are green & no logo shown" [badge] (⇝ (⟰ "badge:key|value") "<img src=\"https://img.shields.io/badge/key-value-nil?logo=nil\">")) (deftest "When only a key is provided, the value slot is shown as an empty green stub" [badge] (⇝ (⟰ "badge:key") "<img src=\"https://img.shields.io/badge/key--nil?logo=nil\">")) (deftest "When only a value is provided, only the value is shown in a default green ---no stub for the missing key, yay" [badge] (⇝ (⟰ "badge:|value") "<img src=\"https://img.shields.io/badge/-value-nil?logo=nil\">")) (deftest "It's only a green stub when provided with an empty key and empty value" [badge] (⇝ (⟰ "badge:||green") "<img src=\"https://img.shields.io/badge/--green?logo=nil\">")) (deftest "It's only a green stub when we use a totally [[badge:]]" [badge] (⇝ (⟰ "[[badge:]]") "<img src=\"https://img.shields.io/badge/--nil?logo=nil\">"))
10.1. Example Badges
The general syntax is as follows, with only the first 2 are mandatory,
with the colour defaulting to green, and the url and logo both to nil.
We can thus have badge:label|message
-
⇐
badge:key|value|informational|here|Elixir
- Standard template; with URL pointing to current location which is named
#key
—click on it and look at your browser's URL ;-)
- Standard template; with URL pointing to current location which is named
-
⇐
badge:example_with_spaces,_-,_and_%|points_right_here|orange|here
-
⇐
badge:key|value
. -
⇐
badge:empty_value||informational
-
⇐
badge:|value
⇐
badge:||green
( No key; nor value! )Also: ⇐
[[badge:]]
.
General Syntax
badge:your_key|its_neato_value|some_url|a_logo_as_shown_below [[badge: your key | its neato value | some url | a logo as shown below]] reddit-subscribe-to:exact-name-of-a-subreddit github-stars:user-name/repository-name github-watchers:user-name/repository-name github-forks:user-name/repository-name github-followers:user-name twitter-follow:user-name tweet:url
For now, only badge:
badges redirect to their URL, if any, upon a user's click.
Within Emacs, hovering over a badge displays a tooltip indicating which values
corresponds to which badge fields.
You can make your own badge types with org-make-badge.
org-make-badge
The implementation is a bit lengthy since it attempts to capture a useful portion of the shilelds.io badge interface.
(cl-defmacro org-make-badge (name &optional social-shields-name social-url social-shields-url ) "Make a link NAME whose export is presented as an SVG badge. If the link is intend to be a social badge, then, adhering to shield.io conventions, the appropriate SOCIAL-SHIELDS-NAME must be given. The SOCIAL-URL is a URL that the badge points to and it should have a single ‘%s’ where the link label argument will go. Some social badges have a uniform URL, but otherwise, such as twitter, deviate and so need their own SOCIAL-SHIELDS-URL, which has a single ‘%s’ for the link's label argument. ---------------------------------------------------------------------- E.g., to create a badge named “go”: (org-make-badge \"go\") Then the following exports nicely from an Org file: go:key|value|blue|here|gnu-emacs" `(org-deflink ,(intern name) "Export a Shields.io badge, with general Syntax: badge:key|value|colour|url|logo. Precise details for each argument are shown in the Emacs tooltip for this badge." [:display 'full :follow (--> (s-split "|" path) (or (nth 3 it) o-label) (browse-url it)) :help-echo (-let [ (key value color url logo) (mapcar #'s-trim (s-split "|" o-label)) ] (lf-string "${o-label} \nGeneral Syntax: badge:key|value|colour|url|logo Key : ${key} Value : ${(or value \"\")} Colour : ${(or color \"green\")} URL : ${(or url \"\")} Logo : ${(or logo \"\")} This results in an SVG badge “[key∣value]”, where the ‘key’ is coloured grey and the ‘value’ is coloured ‘color’; for the HTML backend, and otherwise are silently omitted. Descriptions are ignored; i.e., ‘[[badge:label][description]]’ is the same as ‘[[badge:label]]’. ► ‘key’ and ‘value’ have their underscores interpreted as spaces. ⇒ ‘__’ is interpreted as an underscore; Of course, you can write ‘<badge: ⋯>’, then ‘⋯’ may have multiword, spaced, content. ⇒ ‘|’ is not a valid substring, but ‘-, %, ?’ are okay. ► ‘|color|url|logo’ are optional; ⇒ If ‘url’ is not present, the resulting badge is not a hyperlink. ⇒ if ‘url’ is ‘here’ then we have a local link; i.e., the resulting badge behaves like ‘link-here:key’. ⇒ ‘color’ may be: ‘brightgreen’ or ‘success’, ‘red’ or ‘important’, ‘orange’ or ‘critical’, ‘lightgrey’ or ‘inactive’, ‘blue’ or ‘informational’, or ‘green’, ‘yellowgreen’, ‘yellow’, ‘blueviolet’, ‘ff69b4’, etc. Consult https://htmlcolorcodes.com/ to see the HEX codes of other colours. ⇒ ‘logo’ examples and how they look can be found at https://alhassy.github.io/org-special-block-extras/#Example-Badge-Icons See also: https://alhassy.github.io/org-special-block-extras/#Common-Project-Badges"))] ;; :export #'org--link--badge (if (equal o-backend 'latex) "" (-let [ (key value color url logo) (mapcar #'s-trim (s-split "|" o-label)) ] (format (pcase ,(if social-shields-name `(format ,social-url o-label) 'url) ("here" (format "<a id=\"%s\" href=\"#%s\">%%s</a>" (s-replace "%" "%%" key) (s-replace "%" "%%" key))) ("" "%s") ;; e.g., badge:key|value|color||logo ('nil "%s") ;; e.g., badge:key|value|color (_ (format "<a href=\"%s\">%%s</a>" (s-replace "%" "%%" ,(if social-shields-name `(format ,social-url o-label) 'url))))) ,(if social-shields-name (if social-shields-url `(format ,social-shields-url o-label) `(format "<img src=\"https://img.shields.io/%s/%s?style=social\">" ,social-shields-name o-label)) '(format "<img src=\"https://img.shields.io/badge/%s-%s-%s?logo=%s\">" (url-hexify-string (s-replace "-" "--" key)) (url-hexify-string (s-replace "-" "--" (or value ""))) color logo)))))))
Notice that we can have special %
-HTML characters in URLs; e.g.,
<badge:my key|my value| my
colour|https://www.google.com/search?q=hello%20world|gnu-emacs>
⇒
.
Example: Derived Reddit/Github/Twitter social badges
For example, a super simple ‘badge’ link type
(org-make-badge "badge")
A more involved example.
;; Since we're invoking a macro, the twitter-excitement is used lazily; i.e., ;; consulted when needed instead of being evaluated once. (defvar org-link-twitter-excitement "This looks super neat (•̀ᴗ•́)و:" "The string prefixing the URL being shared.") (org-make-badge "tweet" "twitter/url?=url=" (format "https://twitter.com/intent/tweet?text=%s:&url=%%s" org-link-twitter-excitement) "<img src=\"https://img.shields.io/twitter/url?url=%s\">" )
A few more in one, convoluted, go.
;; MA: I don't think this is ideal for long-term maintainability, see ‘:OLD’ below. (cl-loop for (social url name) in '(("reddit/subreddit-subscribers" "https://www.reddit.com/r/%s" "reddit") ("github" "https://www.github.com/%s") ("github/stars" "https://www.github.com/%s/stars") ("github/watchers" "https://www.github.com/%s/watchers") ("github/followers" "https://www.github.com/%s?tab=followers") ("github/forks" "https://www.github.com/%s/fork") ("twitter/follow" "https://twitter.com/intent/follow?screen_name=%s")) for name′ = (or name (s-replace "/" "-" social)) do (eval `(org-make-badge ,name′ ,social ,url)))
Here are some examples.
10.2. Example Colours
Source
+ badge:|red|red badge:|critical|critical + badge:|blue|blue badge:|informational|informational + badge:|brightgreen|brightgreen badge:|success|success + badge:|orange|orange badge:|important|important + badge:|lightgrey|lightgrey badge:|inactive|inactive + badge:|green|green + badge:|yellowgreen|yellowgreen + badge:|yellow|yellow + badge:|blueviolet|blueviolet + badge:|ff69b4|ff69b4 + badge:|9cf|9cf + ... Consult https://htmlcolorcodes.com/ to see the HEX code of any other colour you wish to use; e.g., badge:|1d8348|1d8348
Result
- …
Consult https://htmlcolorcodes.com/ to see the HEX code of any other colour you wish to use; e.g.,
10.3. Example Badge Icons
Here are a few free SVG icons for popular brands from https://simpleicons.org/.
To use any of the names below, just write, say,
badge:||grey||SVG-NAME-HERE
.
- “Fire”
- Elixir
- tinder
- codeigniter
- prometheus
- sparkpost
- “Messaging”
- quip
- google-hangouts
- hackhands
- google-messages
- Slack
- “Emacs”
- gnu-emacs
- spacemacs
- vim
- neovim
- gnu
- github
- acm
- wikipedia
- microsoft-excel
- microsoft-word
- dropbox
- google-scholar
- google-translate
- ghost
- helm
- apache-openoffice
- buffer
- adobe-fonts
- google-calendar
- “Social”
- google-cast
- youtube
- discord
- google-hangouts
- skype
- stack-overflow
- stack-exchange
- jabber
- “Lightbulb”
- lighthouse
- google-keep
- minds
- “Programming”
- git
- ruby
- scala
- OCaml
- javascript
- gnu-bash
- powershell
- LaTeX
- java
- kotlin
- haskell
- coffeescript
- purescript
- rust
- typescript
- css3
- python
- c
- clojure
- lua
- adobe-acrobat-reader
- perl
- “Miscellaneous”
- read-the-docs
- buy-me-a-coffee
- gimp
- mega
- nintendo-3ds
- paypal
- pinboard
- mocha
- Gitea
- instacart
- openStreetMap
- amazon
- svg
- rss
- swagger
- pastebin
- skyliner
- iTunes
- gulp
- leaflet
- youtube-gaming
- GIMP
- atom
10.4. Common Project Badges —with copyable source
-
src
badge:Emacs|23/26/28|green|https://www.gnu.org/software/emacs|gnu-emacs
-
src
badge:Org|9.3.6|blue|https://orgmode.org|gnu
-
src
[[badge:org-special-block-extras|1.0|informational|https://alhassy.github.io/org-special-block-extras/README.html|Gnu-Emacs][org-special-block-extras badge]]
-
src
[[badge:melpa|pending|critical|https://github.com/alhassy/emacs.d#use-package-the-start-of-initel|github][melpa badge]]
-
src
[[badge:docs|literate|success|https://github.com/alhassy/emacs.d#what-does-literate-programming-look-like|read-the-docs][read-the-docs badge]]
src
badge:wiki|github|informational|here|wikipedia
-
src
badge:code_coverage|88%|green|here|codecov
src
badge:build|passing|success|here|azure-pipelines
-
src
badge:author|musa_al-hassy|purple|https://alhassy.github.io/|nintendo-3ds
-
src
badge:author|musa_al-hassy|purple|https://alhassy.github.io/|gimp
-
src
[[badge:license|GNU_3|informational|https://www.gnu.org/licenses/gpl-3.0.en.html|read-the-docs][gnu 3 license badge]]
-
src
badge:issue_tracking|github|informational|here|github
-
src
badge:help_forum|discourse|informational|here|discourse
-
src
badge:social_chat|gitter|informational|https://gitter.im/explore|gitter
-
src
badge:Maintained?|yes|success
src
badge:Maintained?|no|critical
src
badge:No_Maintenance_Intended|×|critical|http://unmaintained.tech/
-
src
badge:website|up|success
src
badge:website|down|critical
-
src
badge:Ask_me|anything|1abc9c
src
badge:contributions|welcome|green|https://github.com/alhassy/org-special-block-extras/issues
-
src
badge:Made_with|Python,_LaTeX,_MathJax,_and_Emacs_Org-mode|1f425
10.5. Next Steps
Going forward, it would be desirable to provide non-whitespace alternatives for the LaTeX backend.
[Author: That is why no examples are shown in the PDF ]
It may be useful to colour the|
-separated fields of a badge link.
11. Tooltips for Glossaries, Dictionaries, and Documentation
Let's make a link type doc
that shows a tooltip documentation —e.g., glossary
or abbreviation— for a given label.
E.g., user-declared Category Theory and Emacs-retrieved loop and thread-last ^_^
Eye-candy ---A gallery of images
Figure 3: Tooltips for documentation and glossary items –in the browser!
Figure 4: Tooltips for documentation and glossary items –in Emacs!
Figure 5: Tooltips for documentation and glossary items –in the PDF!
Full Example
User enters …
#+begin_documentation Existential Angst :label "ex-angst" A negative feeling arising from freedom and responsibility. Also known as 1. Existential Dread, and 2. Existential Anxiety. Perhaps a distraction, such as [[https://www.w3schools.com][visiting W3Schools]], may help ;-) Then again, coding can be frustrating at times, maybe have a slice of pie with maths by reading “$e^{i×π} + 1 = 0$” as a poem ;-) #+end_documentation
Then… doc:ex-angst
gives us Existential Angst,
or using a description: “existence is pain”?
( [[doc:ex-angst][“existence is pain”?]]
)
As it stands, Emacs tooltips only appear after an export has happened: The export updates the dictionary variable which is used for the tooltips utility.
The
:label
is optional; when not provided it defaults to the name of the entry with spaces replaced by ‘_’. For more, see org-block/documentation.Entry names do not need to be unique, but labels do! ( The labels are like the addresses used to look up a person's home. )
‼TODO More Examples
Supported | Example | Source |
---|---|---|
No description | Category Theory | doc:cat |
With description and code font | polymorphism |
[[doc:natural-transformation][=polymorphism=]] |
Fallback: Arbitrary ELisp Docs (function) | thread-first | doc:thread-first |
Fallback: Arbitrary ELisp Docs (variable) | user-mail-address | doc:user-mail-address |
Fallback: Arbitrary English word | user | doc:user |
Notice how hovering over items makes them appear, but to make them disappear you should click on them or scroll away. This is ideal when one wants to have multiple ‘definitions’ visible ;-)
Below are the entries in my personal glossary ;-)
‼ TODO
In the export, it looks like some names are repeated, but in the source one would notice that we have labels in both upper and lower cases and with underscores ;-) They just happen to be referring to the same named entry.
Tests
(deftest "It gives a tooltip whose title is the Lisp docs of APPLY" [doc] (⇝ (⟰ "doc:apply") "<abbr class=\"tooltip\" title=\"" (literal (s-replace "\n" "<br>" (documentation #'apply))) "\">apply</abbr>")) (setq angst (⟰ "#+begin_documentation Existential Angst :label \"ex-angst\" A negative feeling arising from freedom and responsibility. Also known as 1. /Existential Dread/, and 2. *Existential Anxiety*. Perhaps a distraction, such as [[https://www.w3schools.com][visiting W3Schools]], may help ;-) Then again, ~coding~ can be frustrating at times, maybe have a slice of pie with maths by reading “$e^{i×π} + 1 = 0$” as a poem ;-) #+end_documentation We now use this as doc:ex-angst.")) (deftest "Documentation blocks are not exported; they produce a new osbe--docs entry" [doc documentation o--name&doc] (should (org-docs-get "ex-angst"))) ;; Upon export, the #+begin_documentation is /not/ present. ;; We have the text outside that block only. ;; Along, with a htmlified tooltip of the new entry. (deftest "The osbe--docs entry of the documentation block appears within a tooltip" [doc documentation o--name&doc] (⇝ angst " <p> We now use this as <abbr class=\"tooltip\" title=\"" (literal (org-ospe-html-export-preserving-whitespace (cl-second (org-docs-get "ex-angst")))) "\">Existential Angst</abbr>.</p> "))
11.1. Implementation Details: doc
link, documentation
block, and tooltipster
We begin by making use of a list of documentation-glossary entries —a lightweight database of information, if you will.
Overview of relevant Lisp
- org--docs is the global variable that holds the glossary/database for the current session. Entries may be inserted with org-docs-set and retrieved with org-docs-get.
- org-docs-fallback is a fallback method for retriving documentation-glossary
entries from elsewhere besides the current buffer; e.g., using Emacs'
documentation function or the
wn
WordNet command for definitions of English words. We use org-docs-load-libraries to load documentation-glossary entries from external files. - org-docs-insert for interactively inserting links for documented-glossary words.
(defvar org--docs nil "An alist of (LABEL NAME DESCRIPTION) entries; our glossary. Example setter: 0. (org-docs-set \"os\" \"Emacs\" \"A place wherein I do all of my computing.\") Example getters: 0. (org-docs-get LABEL) 1. (-let [(name description) (cdr (assoc LABEL o--docs))] ⋯) See also `org--docs-from-libraries' and `org-docs-load-libraries'.") (cl-defun org-docs-set (label name description) "Add a new documentation-glossary entry, if it is not already present. We associate LABEL to have title NAME and glossary value DESCRIPTION. Example usage: (org-docs-set \"cat\" \"Category Theory\" \"A theory of typed composition; e.g., typed monoids.\")" (add-to-list 'org--docs (list label name description))) (cl-defun org-docs-get (label) "Return the name and documentation-glossary values associated with LABEL. It returns a list of length 2. Example uses: ;; Get the Lisp documentation of `thread-last' (org-docs-get \"thread-last\") ;; Get the English definition of ‘computing’ (org-docs-get \"computing\") We look for LABEL from within the current buffer first, using `org--docs', and otherwise look among the loaded libraries, using `org--docs-from-libraries', and, finally, look for the documentation entry using `org-docs-fallback'." (cdr (or (assoc label org--docs) (assoc label org--docs-from-libraries) (funcall org-docs-fallback label) (error "Error: No documentation-glossary entry for “%s”!" label)))) (cl-defun org-docs-insert () "Insert a “doc:𝒳” link from user's documentation-glossary database. It can be tricky to remember what you have, or what documentation entries mention, and so this command gives a searchable way to insert doc links." (interactive) (thread-last (cl-remove-duplicates (-concat org--docs org--docs-from-libraries) :test (lambda (x y) (cl-equalp (car x) (car y)))) (--map (format "%s ∷ %s" (car it) (cl-third it))) (completing-read "Insert doc link ∷ ") (s-split "∷") car (concat "doc:") (insert)))
We may wish to use Emacs' documentation (or lf-documentation) command to retrieve entries —this is useful for an online article that refers to unfamiliar Emacs terms ;-) To avoid copy-pasting documentation entries from one location to another, users may declare a fallback method. Besides Emacs' documentation, the fallback can be refer to a user's personal ‘global glossary’ variable —which may live in their Emacs' init file—, for more see: org-docs-load-libraries
(defvar org-docs-fallback (lambda (label) (list ;; label label ;; name label ;; documentation (or (ignore-errors (documentation (intern label))) (ignore-errors (documentation-property (intern label) 'variable-documentation)) (-let [it (shell-command-to-string (format "wn %s -over -synsn" label))] (if (s-blank-p it) (error "Error: No documentation-glossary entry for “%s”!" label) it))))) "The fallback method to retriving documentation or glossary entries. We try to retrive the Emacs Lisp function documentation of the given LABEL, if possible, otherwise we try to retrive the Emacs Lisp variable documentation, and if that fails then we look up the word in the English dictionary. The English definition is obtained from the command line tool ‘wn’, WordNet.")
Implementation matter for user libraries
(defvar org-docs-libraries nil "List of Org files that have ‘#+begin_documentation’ blocks that should be loaded for use with the ‘doc:𝒳’ link type.")
In your init, you could do the following. ( See the installation instructions below, for more! )
(setq org-docs-libraries '("~/org-special-block-extras/documentation.org"))
My personal documentation library can be seen here: or .
(cl-defun org-docs-load-libraries (&optional (libs org-docs-libraries)) "Load documentation-glossary libraries LIBS. If no LIBS are provided, simply use those declared org-docs-libraries. See `org-docs-from-libraries'." (interactive) (cl-loop for lib in libs do (with-temp-buffer (insert-file-contents lib) ;; doc only activates after an export (-let [org-export-with-broken-links t] (org-html-export-as-html)) (kill-buffer) (delete-window) (setq org--docs-from-libraries (-concat org--docs org--docs-from-libraries)) (setq org--docs nil)))) (defvar org--docs-from-libraries nil "The alist of (label name description) entries loaded from the libraries. The ‘doc:𝒳’ link will load the libraries, possibly setting this variable to ‘nil’, then make use of this variable when looking for documentation strings. Interactively call `org-docs-load-libraries' to force your documentation libraries to be reloaded. See also `org-docs-libraries'.")
When the mode is actually enabled, then we load the user's libraries.
;; Ensure user's documentation libraries have loaded (unless org--docs-from-libraries (org-docs-load-libraries))
Let's keep track of where documentation comes from —either the current article or from the fallback— so that we may process it later on.
(defvar org--docs-actually-used nil "Which words are actually cited in the current article. We use this listing to actually print a glossary using ‘show:GLOSSARY’.")
Now HTML exporting such links as tooltips and displaying them in Emacs as
tooltips happens in two stages: First we check the documentation, if there is no
entry, we try the fallback —if that falls, an error is reported at export
time. E.g., upon export doc:wombo
will produce a no-entry error.
‘doc’ link implementation
(org-deflink doc "Export O-LABEL as itself, or as the provided O-DESCRIPTION, along with a tooltip that shows the user's documentation-glossary for o-LABEL and using that entry's name when no O-DESCRIPTION is provided." [:let (entry (org-docs-get o-label) name (cl-first entry) docs (cl-second entry) display-name (or o-description name)) :help-echo (format "[%s] %s :: %s" o-label name docs) :face '(custom-button)] (add-to-list 'org--docs-actually-used (list o-label name docs)) (pcase o-backend (`html (format "<abbr class=\"tooltip\" title=\"%s\">%s</abbr>" (org-ospe-html-export-preserving-whitespace docs) display-name)) ;; Make the current word refer to its glosary entry; ;; also declare the location that the glossary should refer back to. (`latex (format (concat "\\hyperref" "[o-glossary-%s]{%s}" "\\label{o-glossary" "-declaration-site-%s}") label display-name label)))) ;; WHERE ... (defun org-ospe-html-export-preserving-whitespace (s) "Make Org-markup'd string S ready for HTML presentation, preserving whitespace. This is orthogonal to the `org-export-string-as' command; i.e., (org-export-string-as s 'html :body-only-please) does something else! In particular, what this yields is an HTML rendition that does not account for whitespace, such as indentation and newlines. " ;; Make it look pretty! (thread-last s (s-replace-regexp "\\#\\+begin_src [^\n]*\n" "<pre class='tooltip'>") (s-replace-regexp "\\( \\)*\\#\\+end_src\n" "</pre>") (s-replace-regexp "\\#\\+begin_export [^\n]*\n" "") (s-replace-regexp "\\( \\)*\\#\\+end_export" "") (s-replace " " " ") ; Preserve newlines (s-replace "\n" "<br>") ; Preserve whitespace (s-replace-regexp "\\#\\+begin_example<br>" "") (s-replace-regexp "\\#\\+end_example<br>" "") ; (s-replace-regexp "\\#\\+begin_src \\(.\\)*\\#\\+end_src)" "<pre>\1</pre>") ; (s-replace-regexp "\\#\\+begin_src [^<]*<br>" "<pre>") ; (s-replace-regexp "<br>\\( \\)*\\#\\+end_src<br>" "<br>\1</pre>") ;; Translate Org markup ;; Only replace /.*/ by <em>.*<em> when it does not have an alphanum,:,/,< before it. (s-replace-regexp "\\([^a-z0-9A-Z:/<]\\)/\\(.+?\\)/" "\\1<em>\\2</em>") (s-replace-regexp "\\*\\(.+?\\)\\*" "<strong>\\1</strong>") (s-replace-regexp "\\~\\([^ ].*?\\)\\~" "<code>\\1</code>") ;; No, sometimes we want equalities. ;; (s-replace-regexp "=\\([^ \n].*?\\)=" "<code>\\1</code>") (s-replace-regexp "\\$\\(.+?\\)\\$" "<em>\\1</em>") (s-replace-regexp "\\[\\[\\(.*\\)\\]\\[\\(.*\\)\\]\\]" "\\2 (\\1)") ;; 5+ dashes result in a horizontal line (s-replace-regexp "-----+" "<hr>") ;; Spacing in math mode (s-replace-regexp "\\\\quad" " ") (s-replace-regexp "\\\\," " ") ;; en space (s-replace-regexp "\\\\;" " ") ;; em space ;; The presence of ‘\"’ in tooltips breaks things, so omit them. (s-replace-regexp "\\\"" "''")))
Since tooltips are not clickable and do not support MathJax, we have manually transpiled what are perhaps the most common instances of Org-markup: Bold, emphasises, code/verbatim, math, and links.
Things look great at the HTML side and on the Emacs side for consuming
documented text. Besides being inconvenient, we cannot with good conscious
force the average user to write Lisp as we did for the doc:cat
entry. We turn to
the problem of producing documentation entries with a block type interface…
‘documentation’ Implementation: An Example of Mandatory Arguments and Using ‘raw-contents’
(org-defblock documentation (name (error "Documentation block: Name must be provided") label nil show nil color "green") "Register the dictionary entries in CONTENTS to the dictionary variable. The dictionary variable is ‘org--docs’. A documentation entry may have its LABEL, its primary identifier, be: 1. Omitted 2. Given as a single symbol 3. Given as a many aliases '(lbl₀ lbl₁ … lblₙ) The third case is for when there is no canonical label to refer to an entry, or it is convenient to use multiple labels for the same entry. In all of the above cases, two additional labels are included: The entry name with spaces replaced by underscores, and again but all lower case. Documentation blocks are not shown upon export; unless SHOW is non-nil, in which case they are shown using the ‘box’ block, with the provided COLOR passed to it. In the futture, it may be nice to have an option to render tooltips. That'd require the ‘doc:𝒳’ link construction be refactored via a ‘defun’." (unless (consp label) (setq label (list label))) (push (s-replace " " "_" name) label) (push (downcase (s-replace " " "_" name)) label) (cl-loop for l in label do (add-to-list 'org--docs (mapcar #'s-trim (list (format "%s" l) name (substring-no-properties raw-contents))))) ;; Should the special block show something upon export? (if show (org--blockcall box name :background-color color raw-contents) ""))
Thus far, Org entities are converted into HTML tags such as <i>
for italicised
text. However, HTML's default tooltip utility —using title="⋯"
in a div
—
does not render arbitrary HTML elements. Moreover, the default tooltip utility
is rather slow. As such, we move to using tooltipster. The incantation below
sets up the required magic to make this happen.
Fast, Sleek, and Beautiful Tooltips: Touching ‘org-html-head-extra’
(defvar org--tooltip-html-setup nil "Has the necessary HTML beeen added?") (unless org--tooltip-html-setup (setq org--tooltip-html-setup t)) (when org-special-block-add-html-extra (setq org-html-head-extra (concat org-html-head-extra " <link rel=\"stylesheet\" type=\"text/css\" href=\"https://alhassy.github.io/org-special-block-extras/tooltipster/dist/css/tooltipster.bundle.min.css\"/> <link rel=\"stylesheet\" type=\"text/css\" href=\"https://alhassy.github.io/org-special-block-extras/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-punk.min.css\" /> <script type=\"text/javascript\"> if (typeof jQuery == 'undefined') { document.write(unescape('%3Cscript src=\"https://code.jquery.com/jquery-1.10.0.min.js\"%3E%3C/script%3E')); } </script> <script type=\"text/javascript\" src=\"https://alhassy.github.io/org-special-block-extras/tooltipster/dist/js/tooltipster.bundle.min.js\"></script> <script> $(document).ready(function() { $('.tooltip').tooltipster({ theme: 'tooltipster-punk', contentAsHTML: true, animation: 'grow', delay: [100,500], // trigger: 'click' trigger: 'custom', triggerOpen: { mouseenter: true }, triggerClose: { originClick: true, scroll: true } }); }); </script> <style> abbr {color: red;} .tooltip { border-bottom: 1px dotted #000; color:red; text-decoration: none;} </style> ")))
Note that we have a conditional in the middle to avoid loading jQuery multiple
times —e.g., when one uses the read-the-org
them in combination with this
library. Multiple imports lead to broken features.
11.2. Wait, what about the LaTeX?
A PDF is essentially a fancy piece of paper, so tooltips will take on the
form of glossary entries: Using doc:𝒳
will result in the word 𝒳
being printed
as a hyperlink to a glossary entry, which you the user will eventually
declare using show:GLOSSARY
; moreover, the glossary entry will also have a
link back to where the doc:𝒳
was declared. E.g., defmacro and
lambda.
We make a show:𝒳
link type to print the value of the variable 𝒳
as follows, with GLOSSARY
being a reserved name.
Implementation of ‘show’
(org-deflink show "Yield the value of the expression O-LABEL, with =GLOSSARY= being a reserved name. Example uses: show:user-full-name <show: (* 2 (+ 3 4 (- pi))) > Note that there is `elisp' links with Emacs, out of the box. However, they only serve to evaluate Lisp expressions; for example, to make “link buttons” that do useful things, as follows. [[elisp:(find-file user-init-file)][Init]] In particular, `elisp' links do not export the value of their expression. That is what we accomplish with this new `show' link type." [:face '(:underline "green") :let (o-value (if (equal o-label "GLOSSARY") (pp-to-string (mapcar #'cl-second org--docs-actually-used)) (pp-to-string (eval (car (read-from-string o-label))))) o-expr (if (equal o-label "GLOSSARY") (concat "GLOSSARY ---i.e., org--docs-actually-used" "\n\nWe erase the glossary not on the first export, but on the second export." "\nThe first export collects all citations, which are used in the second export.") o-label)) :help-echo (format (concat "Upon export, the following will be placed literally" "\n\t%s" "\nWhich is the value of the expression:\n\t%s") o-value o-expr)] (cond ((not (equal o-label "GLOSSARY")) o-value) ;; Otherwise O-LABEL is glossary, which we print in HTML & LaTeX ((equal 'html o-backend) (s-join " " (--map (format "<abbr class=\"tooltip\" title=\"%s\">%s</abbr>" (org-ospe-html-export-preserving-whitespace (cl-third it)) (cl-second it)) ;; Ignore duplicates; i.e., entries with the same name/title. (cl-remove-duplicates org--docs-actually-used :test (lambda (x y) (cl-equalp (cl-second x) (cl-second y))))))) (:otherwise (s-join "\n\n" (cl-loop for (label name doc) in org--docs-actually-used collect (format (concat "\\vspace{1em}\\phantomsection" "\\textbf{%s}\\quad" "\\label{o-glossary-%s}" "%s See page " "\\pageref{org-special-block-extras" "-glossary-declaration-site-%s}") name o-label (when doc (thread-last doc ;; preserve whitespace (s-replace "&" "\\&") ;; Hack! (s-replace " " " \\quad ") (s-replace "\n" " \\newline{\\color{white}.}"))) o-label))))))
As an example, we know have generic sentences:
My name is show:user-full-name and I am using Emacs show:emacs-version ^_^ |
---|
My name is "Musa Al-hassy" and I am using Emacs "29.0.90" ^_^ |
For example, here is a word whose documentation is obtained from Emacs rather than me writing it: thread-last. If you click on it, in the LaTeX output, you will be directed to the glossary at the end of this article —glossaries are not printed in HTML rendering.
Neato! The whitespace in the documentation is preserved in the LaTeX output as is the case for HTML.
If we decide to erase a doc:𝒳
, then it should not longer appear in the
printed glossary listing. Likewise, a documentation
block has its Org markup
exported according to the backend being exported to, hence if we decide to
switch between different backends then only the first rendition will be used
unless we erase the database each time after export.
(defvar org--docs-empty! (list nil t) "An indicator of when glossary entries should be erased. We erase the glossary not on the first export, but on the second export. The first export collects all citations, which are used in the second export.") (setcdr (last org--docs-empty!) org--docs-empty!) ;; It's an infinite cyclic list. ;; Actual used glossary entries depends on the buffer; so clean up after each export (advice-add #'org-export-dispatch :after (lambda (&rest _) (when (pop org--docs-empty!) (setq org--docs-actually-used nil ;; The 𝒳 of each “doc:𝒳” that appears in the current buffer. org--docs nil)))) ;; The “#+begin_documentation ⋯ :label 𝒳” of the current buffer.
11.3. Next Steps
Going forward, it'd be nice to have URLs work well upon export for documentation
block types; whereas they currently break the HTML export.
- If an entry is referenced multiple times, such as Category Theory, then it would be nice if the glossary referred to the pages of all such locations rather than just the final one.
- The glossary current prints in order of appearance; we may want to have the option to print it in a sorted fashion.
- Perhaps use the line activation feature to provide link tooltips immediately rather than rely on exportation.
TheIt does ;-)show
link type could accept an arbitrary Lisp expression as a bracketed link.- When one clicks on a
doc
documentation link, it would be nice to ‘jump’ to its associated#+begin_documentation
declaration block in the current buffer, if possible.
12. Marginal, “one-off”, remarks
(Scroll to make tooltips disappear!)
The use of doc:𝒳
— and documentation — are for explanatory remarks, personal
documentation, or glossary, that is intended to be used in multiple places.
However, sometimes we want a “one-off” remark containing, say, references or
additional explanation; for that we provide margin. For example:
Allah°  does not burden a soul beyond what it can bear. — Quran 2:286
Source:
AllahThe God of Abraham; known as Elohim in the Bible does not burden a soul beyond what it can bear. --- Quran 2:286
In HTML, such “marginal remarks” appear as tooltips; in LaTeX, they appear in the margin.
Tests
(setq margin (⟰ "/Allah[[margin:][The God of Abraham; known as Elohim in the Bible]] does not burden a soul beyond what it can bear./ --- Quran 2:286")) (deftest "It exports into an <abbr> tooltip" [margin] (⇝ margin "<abbr class=\"tooltip\"")) (deftest "It mentions the margin link's key before the tooltip" [margin] (⇝ margin "Allah<abbr class=\"tooltip\"")) (deftest "Our personalised marginal remark appears in an <abbr> title" [margin] (⇝ margin "title=\"The God of Abraham; known as Elohim" "<br>        in the Bible\">")) (deftest "The marginal remark appears in a tiny circle" [margin] (⇝ margin "<abbr" (* anything) "°</abbr>"))
Implementation
(org-defblock margin (marker nil color "gray!80" counter "footnote" width "\\paperwidth - \\textwidth - \\oddsidemargin - 1in - 3ex") ;; Width: https://tex.stackexchange.com/a/101861/69371 (vector :display 'full :face '(:foreground "grey" :weight bold :underline "orange" :overline "orange")) "Produce an HTML tooltip or a LaTeX margin note. The ‘margin’ block is intended for “one-off” (mostly optional) remarks. For notes that you want to use repeatedly, in multiple articles or in multiple locations in the same article, consider using ‘documentation’ to declare them and ‘doc’ to invoke them. For LaTeX, place ‘#+resize:’ to have the remainder of a block be resized, for now 1.3 the margin width ---requires \\usepackage{adjustbox}. ---------------------------------------------------------------------- WIDTH, COUNTER, and COLOR are LaTeX specfic. When no label, marker, is used for a marginal note, we rely on a COUNTER, such as ‘footnote’ (default) or ‘sidenote.’ Since HTML has no margin per se, we use “∘” as default marker: Users hover over it to read the marginal note. Marginal notes have their labels, markers, in black and the notes themselves have COLOR being grey!80. In Emacs, margin links appear grey with an orange tinted boarder. Regarding LaTeX, since verbatim environments do not in general work well as arguments to other commands, such as ‘\\marginpar’, we save the contents of the special block in a ‘minipage’ within a LaTeX ‘box’; then we can unfold such a box in the margin. Hence, ‘src’ blocks can appear within ‘margin’ blocks (•̀ᴗ•́)و The WIDTH argument is the width of the margin; i.e., the width of the underlying minipage. One could use \\maxsizebox{.25\\textwidth}{\\textheight}{ ... } which only resizes the content if its natural size is larger than the given 〈width〉 or 〈height〉. We don't use this, since maxsizebox does not natively allow linebreaks (e.g., one would wrap contents in a tabular environment then use ‘\\\\’, two backslashes, to request a line break; but this crashes if one wants to also use verbatim environments.) In LaTeX, it may be useful to invoke ‘\\dotfill’." (-let [stepcounter (if marker "" (format "\\stepcounter{%s}" counter))] (pcase backend (`latex (setq marker (or marker (format "{\\the%s}" counter))) ;; "\\circ" (format "\\!\\!${}^{\\textnormal{%s}}$ \\newsavebox{\\OrgSpecialBlockExtrasMarginBox} \\begin{lrbox}{\\OrgSpecialBlockExtrasMarginBox} \\begin{minipage}{%s} \\raggedright \\iffalse Otherwise default alignment is fully justified. \\fi \\footnotesize \\setminted{fontsize=\\footnotesize, breaklines} \\iffalse HACK! \\fi \\color{%s} {\\color{black}${}^{\\textnormal{%s}}$}\n\\normalfont\n %s \\end{minipage} \\end{lrbox} \\marginpar{\\usebox{\\OrgSpecialBlockExtrasMarginBox}%s} \\hspace{-1.9ex} \\global\\let\\OrgSpecialBlockExtrasMarginBox\\relax" marker width color marker (if (s-contains? "#+resize:" contents) (s-concat (s-replace "#+resize:" "#+latex: \\maxsizebox{1.3\\textwidth}{\\textheight}{\\begin{tabular}{l}\n" (s-trim contents)) "\n\\end{tabular}}") (s-trim contents)) stepcounter)) (_ (setq marker (or marker "°")) (format "<abbr class=\"tooltip\" title=\"%s\">%s</abbr> " (org-ospe-html-export-preserving-whitespace contents) ; MA: FIXME: (org-export-string-as contents 'html :body-only-please) marker)))))
Example: Links, without and with explict lables
This °  works!
This look!  works!
Source:
Example: Middle of a sentence, no explicit label
Hello this
° is super neat!
Source:
Hello this #+begin_margin Extra details about whatever it is I'm talking about. #+end_margin is super neat!
Example: Middle of a sentence, explicit (full word) label
Hello
this is super neat!
Source:
Hello #+begin_margin this Extra details about whatever it is I'm talking about. #+end_margin is super neat!
★ Example: A ‘margin’ with an ‘export’ in the middle
An HTML “marquee”
° is a scrolling piece of text.
Source:
An HTML “marquee” #+begin_margin An example is best: #+begin_export html <marquee>Catch me if you can!</marquee> #+end_export It is pronounced “mar key”. #+end_margin is a scrolling piece of text.
Example: Brief code snippet in tooltip/margin
It's a
simple 
exercise to show that ℕ is fixedpoint of Maybe
.
Source:
It's a #+begin_margin simple Proof Sketch: “Squint” your eyes to see zero : ℕ and nothing : Maybe ℕ as essentially the same, and “squint” your eyes to see that suc is essentially the same as just. More formally, here's one direction: #+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n #+end_src The rest is an arduous exercise if you don't know what's going on. #+end_margin exercise to show that ℕ is fixedpoint of Maybe.
★ Example: Lengthy ‘margin’ with multiple code blocks!
It's a
simple 
exercise to show that ℕ is fixedpoint of Maybe
.
Source:
It's a #+begin_margin simple We show that there is an isomorphism; i.e., a non-lossy protocol between Maybe ℕ and ℕ in stages. First, let's recall the definitions ... in Agda ... #+begin_src haskell :tangle no :tangle no data Maybe (A : Set) : Set₁ where nothing : Maybe A just : A → Maybe A data ℕ : Set where zero : ℕ suc : ℕ → ℕ #+end_src We can show Maybe ℕ ≅ ℕ by writing two functions... #+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n from : ℕ → Maybe ℕ from zero = nothing from (suc n) = just n #+end_src ...and, finally, checking that the two functions undo each other... #+begin_src emacs-lisp to∘from : ∀ {n} → to (from n) ≡ n to∘from n = {! try it! } from∘to : ∀ {m} → from (to m) ≡ m from∘to m = {! try it! } #+end_src This is “simple”, but involved! #+end_margin exercise to show that ℕ is fixedpoint of Maybe.
For the tooltips containing code snippets, I've declared the following to make the code look nice.
#+html: <style> pre.tooltip {color: black; background-color:Snow;} </style>
Going forward, it would be nice to get full code colouring of source blocks in the HTML tooltips —which can be done using org-export-string-as, just need the time.
13. Equational Proofs
Sometimes you just want to have a quick, yet detailed, explanation and so we can use org-lists as equational proofs —thanks to the calc block!
Implementation
(defun org--list-to-calc (lst rel hint-format NL-length color) "Get a result from org-list-to-lisp and render it as a calculational proof. LST is an expression, possibly with a hint and dedicated relation. The expression may contain multiple lines, as may the hints. REL is the default relation in the left-most column. HINT_FORMAT is the formatting string for hints; e.g., \"\\color{maroon}{\\langle\\large\\substack{\\text{ %s }}⟩}\" NL-length is how long the explicit vertical space, \\, should be. The number refers to the vspace occupied nearly by the height of a single normal sized letter. COLOR is the colour of the hints." (cond ((symbolp lst) "") ((symbolp (car lst)) (org--list-to-calc (cadr lst))) (:otherwise (-let* (((conclusion₀ children) lst) ((expr₀ hint) (s-split "--" conclusion₀)) ((op₀ expr₁) (cdr (s-match "^\\[\\(.*\\)\\]\\(.*\\)" expr₀))) (op (or op₀ rel)) (expr (or expr₁ expr₀))) (if (not children) (if hint (format "\n %s \\;\\; & \\qquad \\color{%s}{%s} \n \\\\ & \\begin{split}%s\\end{split}" op color ;; the hfill is so that we do not use substack's default ;; centering, but instead left-align justificatiion ;; hints. (format (s-replace "%s" "{\\large\\substack{\\text{ %s } \\hfill\\\\\n}}" hint-format) (s-replace "\n" " } \\hfill\\\\\n\\text{ " (s-replace "\n\n" (s-repeat (* 6 NL-length) "\n $\\,$") (s-trim hint)))) expr) (format "\\begin{split}%s\\end{split} \n" expr)) ;; MA: The following could be improved. (format "\n %s \\;\\; & \\qquad \\color{%s}{%s} \n \\\\ & \\begin{split}%s\\end{split}" op color ;; BEGIN similar as above (format (s-replace "%s" "{\\large\\substack{\\text{ %s } \\hfill\\\\ \\begin{split} & %s \n\\end{split}\\hfill\\\\\n}}" hint-format) (s-replace "\n" " } \\hfill\\\\\n\\text{ " (s-replace "\n\n" (s-repeat (* 6 NL-length) "\n $\\,$") (s-trim hint))) ;; END similar (s-chop-prefix "\\\\" (s-join "\\\\" (--map (format "%s" (org--list-to-calc it rel hint-format NL-length color)) children)))) expr)))))) (org-defblock calc (main-arg nil rel "=" hint-format "\\left[ %s \\right." explicit-vspace 2 color "maroon") "Render an Org-list as an equational proof. Sometimes the notation delimiting justification hints may clash with the domain topic, so we can change the hint format, e.g., to \"\\left\\langle %s \\right\\rangle\". Set HINT-FORMAT to the empty string, \"\", to comment-out all hints in the exported version. The hint is the text immediately after a “--”, if there are multiple such delimiters only the first is shown; this can be useful if we want to have multiple alternatives, say for extra details in the source but not so much in the export. Line breaks are taken literally in the hints, but not in the math; where one uses \\\\. For math with multiple lines, use ‘&’ as an alignment marker; otherwise math is right-justified. For HTML, to use an TeX it must be enclosed in $, since that is what is required by MathJaX." (thread-last (with-temp-buffer (insert raw-contents) (goto-char (point-min)) (org-list-to-lisp)) cdr (--map (format "%s" (org--list-to-calc it rel hint-format explicit-vspace color))) (s-join "\\\\") (format "$$\\begin{align*} & %s \n\\end{align*}$$")))
“Programming Comments ≈ Proof Hints”
Let's prove that \(x ≤ z\)…
Source:
#+begin_calc :hint-format "\\left\\{ %s\\right." + x + y -- Explanation of why $x \;=\; y$ Actually, let me explain: * x * x′ -- hint 1 * y -- hint 2 No words can appear (in the export) after a nested calculation, for now. + [≤] z -- Explanation of why $y \;\leq\; z$ -- explain it more, this is ignored from export ;-) #+end_calc
Result:
$$\begin{align*} & \begin{split}x\end{split} \\ = \;\; & \qquad \color{maroon}{\left\{ {\large\substack{\text{ Explanation of why $x \;=\; y$ } \hfill\\ \text{ Actually, let me explain: } \hfill\\ \begin{split} & \begin{split}x\end{split} \\ = \;\; & \qquad \color{maroon}{\left\{ {\large\substack{\text{ hint 1 } \hfill\\ }}\right.} \\ & \begin{split}x′ \end{split}\\ = \;\; & \qquad \color{maroon}{\left\{ {\large\substack{\text{ hint 2 } \hfill\\ }}\right.} \\ & \begin{split}y \end{split} \end{split}\hfill\\ }}\right.} \\ & \begin{split} y \end{split}\\ ≤ \;\; & \qquad \color{maroon}{\left\{ {\large\substack{\text{ Explanation of why $y \;\leq\; z$ } \hfill\\ }}\right.} \\ & \begin{split} z\end{split} \end{align*}$$Benefits of Org-lists for proofs:
- Nested proofs can be folded-away with TAB.
- This provides a nice proof outline!
- All Org support for lists active; e.g., M-↑,↓ for moving proof steps (list items) and M-RET for new proof steps.
- Intentional explanations can make the proof argument more convincing, or accurate.
Examples below: Rational root equivales perfect power, Handshaking lemma, calculating train length, and last, but not least, “1 + 1 = 2”.
Align the proof hints with M-x align-regexp RET -- RET …let's calculate that \(a ⊆ e\)…
Source:
#+begin_calc + a + b -- reason for the equality + [⊆] c -- reason for the inclusion + d -- reason for the equality + e -- final justification #+end_calc
Result:
$$\begin{align*} & \begin{split}a\end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ reason for the equality } \hfill\\ }} \right.} \\ & \begin{split}b \end{split}\\ ⊆ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ reason for the inclusion } \hfill\\ }} \right.} \\ & \begin{split} c \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ reason for the equality } \hfill\\ }} \right.} \\ & \begin{split}d \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ final justification } \hfill\\ }} \right.} \\ & \begin{split}e \end{split} \end{align*}$$Tests
(setq calc (⟰ "#+begin_calc :hint-format \"\\\\left\\{ %s\\\\right.\" + x + y -- Explanation of why $x \\;=\\; y$ Actually, let me explain: * x * x′ -- hint 1 * y -- hint 2 No words can appear (in the export) *after* a nested calculation, for now. + [≤] z -- Explanation of why $y \\;\\leq\\; z$ -- explain it more, this is ignored from export ;-) #+end_calc")) (deftest "It's an align environment, in displayed math mode" [calc] (⇝ calc "$$\\begin{align*}" (* anything) "\\end{align*}$$")) (deftest "The calculation has 4 proof steps" [calc] ;; The number of steps in a calculation is the number of items in each nesting, minus 1 (at each nesting). ;; Above we have depth 2 with 3 items in each depth, for a total of (3-1) + (3-1) = 2 + 2 = 4. (should (= 4 (s-count-matches (rx (seq "\\" (* whitespace) (any "=" "≤"))) calc)))) (deftest "Of our 4 steps, 3 of them are equalities and one is an inclusion." [calc] (should (= 3 (s-count-matches (rx "= \\;\\;") calc))) (should (= 1 (s-count-matches (rx "≤ \\;\\;") calc)))) (deftest "All of the hints actually appear in the calculational proof" [calc] (mapc (lambda (hint) (should (s-contains? hint calc))) '("Explanation of why $x \\;=\\; y$" "Actually, let me explain:" "hint 1" "hint 2" "Explanation of why $y \\;\\leq\\; z$")))
Examples…
★ Proof of “1 + 1 = 2” with source
$$\begin{align*} & \begin{split}1 + 1\end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ $\def\suc{\mathsf{suc}\,} \def\zero{\mathsf{zero}}$ } \hfill\\ \text{ “1” abbreviates “$\suc \zero$” } \hfill\\ }} \right.} \\ & \begin{split}\suc \zero + \suc \zero \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Addition is defined by moving the $\suc$-s from the left argument to the } \hfill\\ \text{ right. } \hfill\\ \text{ $\def\eqn#1#2{\begin{equation} #2 \tag{#1} \label{#1} \end{equation}}$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$That is, we define $x + y$ by considering the possible $shape$ of $x$, } \hfill\\ \text{ which could only be a $\zero$ or a $\suc$-cessor. } \hfill\\ \text{ When $x$ is zero, there are no $\suc$-s to move to the right, so we yield the right. } \hfill\\ \text{ $\eqn{Base Case}{\zero + n \; = \; n}$ } \hfill\\ \text{ When $x$ is a $\suc$, we move the $\suc$ to the right, and continue to try adding. } \hfill\\ \text{ $\eqn{Inductive Step}{\suc m \,+\, n \; = \; m \,+\, \suc n}$ } \hfill\\ \text{ Since we're moving a $\suc$ at each step, eventually we'll hit the } \hfill\\ \text{ base case and be done adding. } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$For now, we apply the \eqref{Inductive Step}. } \hfill\\ }} \right.} \\ & \begin{split}\zero + \suc (\suc \zero) \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Using the \eqref{Base Case} definition of addition. } \hfill\\ }} \right.} \\ & \begin{split}\suc (\suc \zero) \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Switching to ‘conventional’ notation } \hfill\\ }} \right.} \\ & \begin{split}2 \end{split} \end{align*}$$
Source:
#+begin_calc + 1 + 1 + \suc \zero + \suc \zero -- $\def\suc{\mathsf{suc}\,} \def\zero{\mathsf{zero}}$ “1” abbreviates “$\suc \zero$” + \zero + \suc (\suc \zero) -- Addition is defined by moving the $\suc$-s from the left argument to the right. $\def\eqn#1#2{\begin{equation} #2 \tag{#1} \label{#1} \end{equation}}$ That is, we define $x + y$ by considering the possible $shape$ of $x$, which could only be a $\zero$ or a $\suc$-cessor. When $x$ is zero, there are no $\suc$-s to move to the right, so we yield the right. $\eqn{Base Case}{\zero + n \; = \; n}$ When $x$ is a $\suc$, we move the $\suc$ to the right, and continue to try adding. $\eqn{Inductive Step}{\suc m \,+\, n \; = \; m \,+\, \suc n}$ Since we're moving a $\suc$ at each step, eventually we'll hit the base case and be done adding. For now, we apply the \eqref{Inductive Step}. + \suc (\suc \zero) -- Using the \eqref{Base Case} definition of addition. + 2 -- Switching to ‘conventional’ notation #+end_calc
“Calculating” the length of a train
calculate the length of a train, that is travelling at a speed of 60 km/hr and, on its way, it crosses a pole in 9 seconds.
$$\begin{align*} & \begin{split}\text{ the length of the train } \end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Express length as a difference of distance } \hfill\\ }} \right.} \\ & \begin{split}\text{ the distance from the end of the train to its front } \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ distance = time × speed } \hfill\\ }} \right.} \\ & \begin{split}& \left(\begin{split} &\text{ difference in time from when the train's front} \\ &\text{ crosses the pole until its back crosses the pole } \end{split}\right) \;\times\; \text{speed of the train} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Rephrase } \hfill\\ }} \right.} \\ & \begin{split}& \text{ how long it takes the train to cross the pole } \;\times\; \text{speed of the train} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ $\def\meters{\,\mathsf{meters}\,} \def\seconds{\,\mathsf{seconds}\,}$ } \hfill\\ \text{ The train crosses the pole in 9 seconds; } \hfill\\ \text{ and the train is travelling at the speed of 60km/hr. } \hfill\\ }} \right.} \\ & \begin{split}9 \text{ seconds } \times 60 \text{ km / hr} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Express things in common units, such as seconds; i.e.,: } \hfill\\ \begin{split} & \begin{split}1 \text{ km / hr}\end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Be more explicit } \hfill\\ }} \right.} \\ & \begin{split}1 \text{ km } /\; 1 \text{ hr } \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ A kilometre is 1000 meters } \hfill\\ }} \right.} \\ & \begin{split}1000 \meters /\, 1 \text{ hr } \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Express hours in terms of seconds: } \hfill\\ \begin{split} & \begin{split}1 \text{ hr }\end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Express in minutes } \hfill\\ }} \right.} \\ & \begin{split}60 \,\mathsf{minutes} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Be more explicit } \hfill\\ }} \right.} \\ & \begin{split}60 \,\times\, 1 \,\mathsf{minutes} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ A minute has 60 seconds } \hfill\\ }} \right.} \\ & \begin{split}60 \,\times\, 60 \,\seconds \end{split} \end{split}\hfill\\ }} \right.} \\ & \begin{split}1000 \meters /\, (60 × 60) \seconds \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Collecting like-terms } \hfill\\ }} \right.} \\ & \begin{split}(1000 / 60 × 60) \meters / \seconds \end{split} \end{split}\hfill\\ }} \right.} \\ & \begin{split}9 \seconds \times 60 × (1000 / 60 × 60) \meters / \seconds \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Division cancels multiplication: $b × (a / b) = a$ } \hfill\\ }} \right.} \\ & \begin{split}9 \seconds \times (1000 / 60) \meters / \seconds \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Collect like-terms; i.e., commutativity of × } \hfill\\ }} \right.} \\ & \begin{split}(1000 / 60) × 9 \seconds × (\meters / \seconds) \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Division cancels multiplication: $b × (a / b) = a$ } \hfill\\ }} \right.} \\ & \begin{split}(1000 / 60) × 9 \meters \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Arithmetic } \hfill\\ }} \right.} \\ & \begin{split}150 \meters \end{split} \end{align*}$$ Let usEuler's Handshaking Lemma
At a party of 7 people, is it possible that everybody knows exactly 3 other people?
We could consider all possible cases, which is not particularly enlightening; or we could calculate…
$$\begin{align*} & \begin{split}\text{“Obviously”, the sum of everyone's friendship count is twice the total number of friendships}\end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Formalise... } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$Let $f(p)$ denote the number of friends that person $p$ has, } \hfill\\ \text{ which is 0 if $p$ has no friends. } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$Let $F$ be the total number of friendships (between any 2 people). } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$Since friendships are always between two people, we have } \hfill\\ \text{ that $\sum_p f(p) = 2 × F$: Counting-up the number of friends } \hfill\\ \text{ that everyone has will always be twice the total number of relationships } \hfill\\ \begin{split} & \begin{split}\text{sum of everyone's friendship count}\end{split} \\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Formalise, where $p$ ranges over people } \hfill\\ }} \right]} \\ & \begin{split}\sum_p f(p) \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Definition of $f$: } \hfill\\ \text{ Let $𝑭𝒓_{p, r}$ be 1 exactly when $p$ participates in } \hfill\\ \text{ friendship/relationship $r$, } \hfill\\ \text{ and 0 otherwise, then $f(p) = \sum_r 𝑭𝒓_{p, r}$. } \hfill\\ }} \right]} \\ & \begin{split}\sum_p \sum_r 𝑭𝒓_{p, r} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Quantifier interchange; i.e., Fubini's Principle } \hfill\\ }} \right]} \\ & \begin{split}\sum_r \sum_p 𝑭𝒓_{p, r} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Every relationship $r$ is between 2 people; } \hfill\\ \text{ i.e., $\sum_p 𝑭𝒓_{p, r} = 2$ } \hfill\\ }} \right]} \\ & \begin{split}\sum_r 2 \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Factoring } \hfill\\ }} \right]} \\ & \begin{split}2 × \sum_r 1 \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ The total number of friendships is $F$ } \hfill\\ }} \right]} \\ & \begin{split}2 × F \end{split} \end{split}\hfill\\ }} \right]} \\ & \begin{split}∑_p f(p) = 2×F \end{split}\\ ⇒ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Equals for equals; } \hfill\\ \text{ i.e., $if$ those numbers are equal $then$ their parities are equal } \hfill\\ \text{ $\def\even#1{\mathsf{even}\,\left(#1\right)}$ } \hfill\\ \text{ $\def\odd#1{\mathsf{odd}\,\left(#1\right)}$ } \hfill\\ }} \right]} \\ & \begin{split} \even{∑_p f(p)} ≡ \even{2 × F} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ The right side is clearly true; } \hfill\\ \text{ (‘true’ is the identity of ‘≡’) } \hfill\\ }} \right]} \\ & \begin{split}\even{∑_p f(p)} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ $\even{-}$ distributes over sums turning them into equivalences ‘≡’ } \hfill\\ }} \right]} \\ & \begin{split}{\LARGE ≡}_p \;\even{f(p)} \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ By definition, a chain of equivalences is true exactly when } \hfill\\ \text{ it has an even number of false values } \hfill\\ }} \right]} \\ & \begin{split}\even{ ♯\{p ∣ ¬ \even{f(p)}\} } \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ A non-even number is precisely an odd number } \hfill\\ }} \right]} \\ & \begin{split}\even{ ♯\{p ∣ \odd{f(p)} \} } \end{split}\\ = \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Un-formalise } \hfill\\ }} \right]} \\ & \begin{split}\text{an even number of people have an odd number of friends} \end{split} \end{align*}$$Back to the problem: At a party of 7 people, is it possible that everybody knows exactly 3 other people?
If such a party were possible, the number of people with an odd friend count must be even, but 7 is not even, and so no such party is possible. ( Alternatively, if such a party were possible, then by the Handshaking Lemma, \(F = {∑_p f(p) \over 2} = {7 × 3 \over 2} = 10.5\): This is absurd since there cannot be a ‘.5’ relationship! Hence, no such party is possible. )
- Likewise, if exactly 5 people in a room each claim to have shaken 3 hands, then someone is lying.
( Rephrased: The sum of the degrees of the vertices of a (total) Graph is twice the number of edges of the graph; moreover, an even number of vertices have an odd degree.
The party problem is then: Is there a graph of 7 nodes with each node being connected to exactly 3 other nodes? Apparently not, and we didn't need to draw any graphs at all to prove it.
For more on Graph Theory, here are some lecture notes. )
- The power of shaking hands has a few non-trivial applications of this lemma.
★ Rational root equivales perfect power
$$\begin{align*} & \begin{split}\sqrt[n]{k} \text{ is a rational number } \end{split} \\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ A rational number is the fraction of two integers. } \hfill\\ \text{ Let variables $a,\, b$ range over integer numbers. } \hfill\\ }} \right.} \\ & \begin{split}∃\, a, b •\; \sqrt[n]{k} = {a \over b} \end{split}\\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Use arithmetic to eliminate the $n$-th root operator. } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$$\def\exp#1#2{\mathsf{exp}_#1\,#2}$ } \hfill\\ \text{ $\def\eq{\;=\;}$ } \hfill\\ }} \right.} \\ & \begin{split}∃\, a, b •\; k · a ^n = b ^n \end{split}\\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Let $\exp{m} x$ be the number of times that $m$ divides $x$. } \hfill\\ \text{ For example, $\exp{2} 48 \eq 4$ and $\exp{2} 49 \eq 0$. } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$The numbers $p$ with $∀ m : ℤ⁺ \;•\; \exp{m}p \,≠\, 0 \,≡\, m \,=\, p$ } \hfill\\ \text{ are called $prime$ numbers. Let variable $p$ range over prime numbers. } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$ } \hfill\\ \text{ $\,$Fundamental theorem of arithmetic: Numbers are determined by their prime powers. } \hfill\\ \text{ That is, $\big(∀ \,p\, •\;\; \exp{p} x \eq f(p)\big) \;\;≡\;\; x \eq \big(Π\, p\, •\; p^{f(p)}\big)$ for any $f$. } \hfill\\ \text{ As such, every number is the product of its prime powers: } \hfill\\ \text{ $\qquad x \eq \big(Π \,p\, •\; p^{\exp{p} x}\big)$. } \hfill\\ \text{ And so, any two numbers are the same precisely when they have the same primes: } \hfill\\ \text{ $\qquad x \eq y \;\;≡\;\; \big(∀ p \,•\, \exp{p} x \eq \exp{p} y\big)$. } \hfill\\ }} \right.} \\ & \begin{split}∃\, a, b •\; ∀\, p •\; \exp{p}(k · a ^n) \eq \exp{p}(b ^n ) \end{split}\\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ When $p$ is prime, $\exp{p}(x · y) \eq \exp{p} x \,+\, \exp{p} y$. } \hfill\\ \text{ Aside: In general, $\exp{p}(Π \,i\, \,•\, x_i) \eq (Σ \,i\, \,•\, \exp{p} x_i)$. } \hfill\\ }} \right.} \\ & \begin{split}∃\, a, b •\; ∀\, p •\; \exp{p} k + n · \exp{p} a \eq n · \exp{p} b \end{split}\\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Use arithmetic to collect similar terms. } \hfill\\ }} \right.} \\ & \begin{split}∃\, a, b •\; ∀\, p •\; \exp{p} k \eq n · \Big(\exp{p} b - \exp{p} a\Big) \end{split}\\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ (⇒) is the definition of multiplicity; } \hfill\\ \text{ (⇐) take $a \,≔\, 1$ and define $b$ by its prime powers: } \hfill\\ \text{ $\qquad ∀\, p \,•\, \exp{p} b \,≔\, {\exp{p} k \,/\, n}$ } \hfill\\ }} \right.} \\ & \begin{split}∀\, p •\; \exp{p} k \text{ is a multiple of } n \end{split}\\ ⇔ \;\; & \qquad \color{maroon}{\left[ {\large\substack{\text{ Fundamental theorem of arithmetic and definition of ‘perfect’ } \hfill\\ }} \right.} \\ & \begin{split}k \text{ is a perfect $n$-th power; i.e., of the shape } x^n \end{split} \end{align*}$$
Possibly Useful links:
- TeX Commands available in MathJax
- MathJax crashes if
\label
names are not unique —e.g., for labelled equations, see “1 + 1 = 2” example above.
★ What if I want to do this in LaTeX (e.g., for math.stackexchange.com)?
Copy-paste the following…
$ % set up \step command \def\BEGINstep{ \left\langle } \def\ENDstep{ \right\rangle } \newcommand{\step}[2][=]{ \\ #1 \;\; & \qquad \color{maroon}{\BEGINstep\text{ #2 } \ENDstep} \\ & } % multi-line step with many lines of text \newcommand{\line}[1]{ \text{#1}\hfill\\ } \newcommand{\stepmany}[2][=]{ \\ #1 \;\; & \qquad \color{maroon}{\BEGINstep \large\substack{ #2 } \ENDstep} \\ & } % multi-line step with 4 lines of text \newcommand{\stepfour}[5][=]{ \stepmany[#1]{\line{#2} \line{#3} \line{#4} \line{#5}} } \newenvironment{calc}{\begin{align*} & }{\end{align*}} $
Then, use it as follows…
\begin{calc} x \step{reason for equality} y \step[\leq]{reason for inclusion} z \end{calc}
Graph org--calc org-export-string-as org--margin lambda lf-documentation org-docs-insert org-docs-load-libraries org-docs-fallback org-docs-get org-docs-set org--docs user user-mail-address Natural Transformation org-block/documentation Existential Angst loop Category Theory org-make-badge org--teal org--color org--parallel cl-defmethod cl-defgeneric with-temp-buffer org-list-to-lisp org--tree regexp-quote s-replace-regexp s-trim s-split -map --map s-replace-all string-to-number numberp s-repeat org--stutter org--spoiler org--rename org--box org--details org-thread-blockcall thread-last funcall documentation org-link-parameters org-add-link-type org-link-set-parameters cl-defun org-deflink defmacro repeat-complex-command Hussain family thread-first org-link/octoicon org-narrow-to-element org-table-create-or-convert-from-region defun org-defblock
Created: 2023-07-20 Thu 20:41