org-special-block-extras: Make your own blocks and links

⭐ The code lives at github.com/alhassy/org-special-block-extras —star it, or discuss it there!
Every block and link above is made with the very tools below, so a new one of our own is a
defun away.
1. Make your own blocks with org-defblock
We declare a block just as we declare a function with cl-defun: the block's main
argument —written right after #+begin_NAME— is its &optional argument, and its
header arguments are its &key arguments. In the body, backend names the current
export backend and contents the block's Org-parsed contents.
(org-defblock note (&optional (title "Note") &key (color "teal"))
"Call out CONTENTS under TITLE, tinted COLOR."
(pcase backend
('html (format "<div style=\"border-left: 4px solid %s; padding-left: 1em\"><b>%s</b>%s</div>"
color title contents))
(_ (format "\\textbf{\\color{%s}%s} %s" color title contents))))
Now the following binds title to "Heads up" and color to red; omit either, and
its default is used. The main argument may also be given by its name, as a keyword:
#+begin_note :title "Heads up" is the same block.
#+begin_note "Heads up" :color red Mind the gap. #+end_note
For any backend derived from HTML or LaTeX —org-reveal's, ox-hugo's, ox-gfm's, beamer's, or
our own— backend is html or latex, so one rendering serves them all. To single one out,
we ask Org's own org-export-current-backend; by way of example, a docsy alert in Hugo:
(org-defblock noteblock (&optional (title "Note") &key (color "primary"))
"A note: a docsy alert in Hugo, a noteblock environment in LaTeX, a box elsewhere in HTML."
(cond ((org-export-derived-backend-p org-export-current-backend 'hugo)
(format "{{%% alert title=\"%s\" color=\"%s\" %%}}\n%s\n{{%% /alert %%}}" title color raw-contents))
((equal backend 'latex) (format "\\begin{noteblock}\n%s\n\\end{noteblock}" contents))
(t (format "<div class=\"note\">%s</div>" contents))))
In similar spirit, (org-export-markdown-p) and (org-export-beamer-p) single out Markdown and
beamer.
Every block is also a link, with the label as main argument and the description as
contents: [[note:Heads up][Mind the gap.]].
A block names what its document's head needs, added once, by :latex-head and :html-head; say,
(org-defblock thm (&optional title :latex-head ("amsthm" "\\newtheorem{theorem}{Theorem}")) …). A
LaTeX item is a package name or any preamble line; an HTML item is HTML, a symbol holding it, or
mathjax. An export that uses the block adds each item its head lacks; one that does not, adds none.
The lists go unquoted; e.g., :html-head (mathjax my-html-head "<style>.aside {color: gray}</style>").
Name a package, "amsthm", rather than write "\\usepackage{amsthm}": a name is left be when the
document loads it already, options and all —say, \usepackage[thmmarks]{amsthm}— whereas a line
is left be only when the document has that very line; else the package loads twice, and differing
options clash.
Even Org's own blocks ---quote, verse, center, example, src, export, and comment—
are fair game: an (org-defblock verse …) overrides every #+begin_verse, saying so in
*Messages*. To leave some to Org, a handler hands them back; by way of example, a verse
signed by its poet, else Org's own:
(org-defblock verse (&optional poet)
"A verse, signed by POET; sans one, Org's own verse."
(if (org-string-nw-p poet)
(format "\\begin{verse}\n%s\n\\end{verse}\n--- %s" contents poet)
(org-parse (org-builtin-block "verse" raw-contents))))
Our own quote does just this: given a citation, it is ours; else, Org's own. Moreover, defaults may change long after the
block is defined, just as a src block's header-args do —there is nothing new to learn. A
NAME block reads them as a src block of language NAME would; from weakest to strongest:
(setq org-babel-default-header-args:note '((:color . "orange"))), then
#+property: header-args:note :color orange for the whole file, then :header-args:note: in
a heading's drawer for its subtree ---:header-args:note+: adds to it— then #+header:
lines just above the block, and finally the block's own line. By way of example, every note
in this section is orange, and the second is boxed too:
* Exercises :PROPERTIES: :header-args:note: :color orange :END: #+begin_note Hint Try induction. #+end_note #+header: :boxed t #+begin_note Answer By induction on n. #+end_note
A main argument is given by its name, as in :title Hint; and switches, such as :let and
:folded, are inherited alike —a src block of language LANG even takes them from Babel's
own header-args:LANG. See C-h f org--block-header-args.
One definition gives three uses: a block, a link, and a tag. A section tagged :note:
exports as a note block around its own text, up to the next heading; the tag's
underscore-separated items form the main argument —tags cannot hold spaces— and the
section's properties supply keyword arguments by name. So all three of these agree:
#+begin_note "Heads up" :color red Mind the gap. #+end_note [[note:Heads up][Mind the gap.]] * Why bother? :note_Heads_up: :PROPERTIES: :COLOR: red :END: Mind the gap.
Likewise, the package's own blocks are links and tags too; e.g., :details_Hint: folds a
section's text away under the title Hint. Try tagging a heading :red: —or :blue:,
:teal:, … any of the 19 colours— and its text exports in that colour.
Should we not want all three, the options :link nil and :tag nil decline the link and
the tag, whereas :block nil keeps only the link and tag —e.g.,
(org-defblock note (&optional (title "Note") :tag nil) …). The variable
org-defblock-links-and-tags sets whether blocks come with links and tags by default; its
documentation weighs the trade-off —chiefly namespacing: every block claims the link type
NAME: and the tag :NAME:. So, should :red: already mean something else in our
headings, we set org-defblock-links-and-tags to nil before loading this package. For just a
link, or just a tag, there are org-deflink and org-deftag, below.
A tag wraps only its section's own text, since a block cannot hold a heading. By way of
example, in * Abstract :abstract: with a ** Aside beneath it, the aside stays outside
the box. Declaring :tag [:subsections MODE] picks what becomes of subsections:
ignore—the default, as with:tag t— leaves them outside the block;errorrefuses a tagged section that has any, naming both headings; andrecursewraps each subsection's own text in a block of its own, with the same arguments, unless the subsection carries the tag itself.
A block may also say how it looks in our Org buffers, via the option :fontify: below,
#+begin_proof shows as Proof., #+end_proof as ⏹, and the contents in italics too.
Each part is optional; the text itself is untouched, only displayed differently.
(org-defblock proof (&optional title :fontify [:begin "Proof." :end "⏹" :face 'italic]) …)
Moreover, :keywords shows patterns within the contents in a face of their choosing; e.g.,
an align block shows each :tag-blue: as a tag, in blue, by
:fontify [:keywords '((":tag-\\([^: \t\n]+\\):" 0 org--align-tag-face))]
where a face that is a function is called, with the match data at hand, for the face.
Convenience: For the terse-minded, the arglist may be flattened into the main argument and its default, followed by key-value pairs. The block above is then
(org-defblock note (title "Note" color "teal") …)
The full story is at C-h o org-defblock.
2. Make your own links with org-deflink
An Org link [[NAME:label][description]] is a function of its label and description;
so, we declare one as we would a cl-defun, then say how it exports and how it looks
within Emacs.
(org-deflink shout (label &optional description backend)
"Shout the description, else the label."
:face '(:weight bold :foreground "red")
:help-echo (format "I'll shout %s" (or description label))
:export (format (if (eq backend 'latex) "\\textbf{%s}" "<b>%s</b>")
(upcase (or description label))))
Then shout:hello and [[shout:hello][hey there]] export as HELLO and HEY THERE.
The arglist names the label, description, and export backend, in that order; any may be
omitted from the end, and (description "…") gives a default. Besides :export, the
keys :face, :help-echo, :follow, :keymap, :display and :let customise the link
within Emacs; C-h o org-deflink has the details.
Convenience: The arglist may be omitted, whence the anaphoric names o-label,
o-description and o-backend are used instead:
(org-deflink shout
"Shout the description, else the label."
:export (upcase (or o-description o-label)))
4. C-c C-c for your own blocks
Org's C-c C-c does the obvious thing at point: it runs a src block, ticks a checkbox,
recalculates a table. Our special blocks may join in; one org-def-action gives every
#+begin_NAME … #+end_NAME block a C-c C-c action, as one org-defblock gives it an export.
For a single block, the :C-c_C-c switch suffices: #+begin_quiz :C-c_C-c my/check-answers
makes C-c C-c within that block call my/check-answers; a Lisp form, rather than a
function name, is evaluated instead. For every block of a kind, we define an action:
(org-defblock shout () "Upcase the contents." (upcase contents))
(org-def-action shout (&optional greeting &key (volume 1))
"Announce the shout's GREETING, at VOLUME."
(message "%s, at %s: %s" greeting volume raw-contents))
Then C-c C-c within #+begin_shout Hello :volume 11 … #+end_shout says Hello, at 11: ….
The arglist is the block's, in either of org-defblock's styles; the body sees the
arguments by name —those the block inherits, by header-args:shout, too— and the
block's text as raw-contents, with point within the block. A non-nil value claims the key
press, so Org does nothing else with it; nil declines it, leaving it to the block's
:C-c_C-c switch, if any.
For a complete worked example, we may mimic the graphRender an Org list + tables as a Graphviz dot graph.
MAIN-ARG is the graph’s title, its label.
The header’s other keys are graph attributes, such as :rankdir LR; :node.KEY and :edge.KEY set node and edge defaults;
:directed nil makes an undirected graph; :wrap N fills labels to N characters a line; :unflatten N staggers
wide rows of leaves over N ranks; and :unknown-tags random-templates gives an unknown tag a made-up template,
rather than erring. Pass :file foo.png on the
#+begin_graph line to fix the output filename; else a #+name: foo line above the block makes it foo.png, and the graph
a figure that [[foo]] reaches and #+caption: captions; otherwise the name is graph-HASH.png, HASH being that of the
dot source, so re-exporting an unchanged graph rewrites one file rather than adding another.
For HTML export: if :svg t is set, embeds an inline <svg> element (tooltips
are preserved as <title> attributes and appear on hover in browsers).
Otherwise, renders to an inline base64 PNG.
For Markdown, it writes the image file and links it, as for a named graph.
For other backends it emits a #+begin_src dot block. In HTML, a node’s [[*Section]] link leads to that section.
Within the block body:
#+graph: table ↦ global graph[ ] attributes
#+node: table ↦ default node[ ] attributes
#+edge: table ↦ default edge[ ] attributes
#+rank: n1 n2 ↦ { rank=same; n1; n2 } for nodes without a common parent; ids may be [[links]]
Under an item, #+graph:, #+node:, #+edge:, and #+rank: reach its whole subtree, as Org’s inherited properties do.
#+begin_cluster TITLE :KEY V … #+end_cluster ↦ subgraph cluster_TITLE { … }; nests, and may sit in an item
:NAME: … :END: ↦ a drawer of any name, folding such directives away
- :T: :: MEANING ↦ at the top, a legend entry: a sample of template T, captioned MEANING
A #+name: table outside any graph is a template every graph in the buffer may use; the graph’s own wins.
A section tagged :graph: exports as a graph of its text, its :PROPERTIES: drawer serving as the header line.
Each list item declares a node. Nesting encodes edges (parent → child); ordered children, 1. 2. 3., share a rank
in the order written. An item’s text may hold a <<target>>, its id; [[TARGET]] or [[TARGET][LABEL]] links, extra arcs;
and *bold*, /italic/, _underline_, +strike+, =code=, or ~pre~, which make its label an HTML-like one.
A description tag, ‘+ L :: CHILD’, labels the arc into CHILD; trailing ‘:T:’ tags splice #+name: templates;
an inline footnote, [fn::NOTE], is the node’s tooltip, as is a note: a paragraph past the item’s first, or a
#+begin_note block ---with ‘#+begin_note :edge’, the arc’s---, shown beneath the label instead under :notes inline;
a web, file, or [[*Section]] link, its URL; an [[file:IMAGE.png]], its image;
a checkbox, [X], [-], or [ ], applies the template checked, partial, or unchecked, when one exists; and a ‘\\’
breaks the label’s line ---in a table value or #+record: cell, too.
Org’s {{{macros}}} expand, by the buffer’s #+macro: lines.
A two-column inline table on a list item supplies node attributes.
| key | val | ↦ a Graphviz attribute of the node; values are quoted for you.
| :id | alias | ↦ cross-reference name for this node.
| :edge.KEY | val | ↦ attribute of the arc from the parent; a parallel arc is a second child item.
| :edge[T].KEY | val | ↦ attribute of this item’s arc to T, from a [[T]] link; "rec::port" reaches a record’s port.
| :inherit | T₁ T₂ | ↦ inherit the #+name: templates’ rows; explicit rows win. Also :edge.:inherit, and :T₁:T₂: tags:
after an item, its node’s; after a reference, or a description term, its arc’s.
A child item that is a lone [[T]] link is a reference to T: an arc to it, styled by the child’s :edge.KEY rows.
| :children-rank | same | ↦ constrain immediate children to the same rank;
also accepts min, max, source, sink.
| shape | cluster | ↦ make this node an inline subgraph cluster_ID { … }.
#+record: table ↦ the node’s <TABLE> label, as laid out: a cell’s <<p>> is port p, its [[T]] an arc from there;
^ or an empty cell spans, a <l>/<c>/<r> row aligns, and hlines rule.
Example:
#+begin_graph :rankdir LR
+ A
+ calls :: B
+ returns :: C
#+end_graph
Regarding the Lisp function:
+ BACKEND refers to the current export backend.
+ RAW-CONTENTS refers to the text as the user wrote it, sans its least indentation.
⇒ You may mention CONTENTS to refer to the ‘org parsed’ version of user text.
⇒ CONTENTS and RAW-CONTENTS are identical whenever key CONTENTS-OCCUR-AS-LINK-DESCRIPTION is non-nil.
For example, upon LaTeX export, the Org special block
#+begin_graph
Hello, world
#+end_graph
is rewritten to the result of the call
(org-block/graph ‘latex "Hello, world") block's handler,
org-graph–cc-handler. It
- finds its block via
(re-search-backward "^[ \t]*#\\+begin_graph\\b")then(org-special-block-after-point "graph"); - reads the block's keyword arguments, so
#+begin_graph :C-c_C-c dotchanges whatC-c C-cdoes; - honours
C-uviacurrent-prefix-arg—showing the dot source rather than rendering a PNG; - inserts a
#+RESULTS:section after#+end_graph, just as src blocks do; and - returns
t, claiming the keypress.
It is the graph's action, at the end of org-graph-block.el:
(org-def-action graph ()
"Render the graph at point, or show its dot source; see `org-graph--cc-handler'."
(org-graph--cc-handler))
The dispatcher itself —a single function on org-ctrl-c-ctrl-c-hook— lives in
org-ctrl-c-ctrl-c-for-special-block.el.
5. C-c ' to edit a block in its own buffer
Just as C-c ' edits a src block in a buffer of its language, so too it edits every special
block —ours, or any #+begin_𝒳 at all— in an Org buffer; C-c ' again writes the contents
back. Org keeps all it already does: a table, a link, or a src block within a special block is
edited as ever.
A block may instead choose its buffer's mode: the option :edit-indirect-mode MODE makes
C-c ' open the block's contents in a buffer in MODE. By way of example, the graphRender an Org list + tables as a Graphviz dot graph.
MAIN-ARG is the graph’s title, its label.
The header’s other keys are graph attributes, such as :rankdir LR; :node.KEY and :edge.KEY set node and edge defaults;
:directed nil makes an undirected graph; :wrap N fills labels to N characters a line; :unflatten N staggers
wide rows of leaves over N ranks; and :unknown-tags random-templates gives an unknown tag a made-up template,
rather than erring. Pass :file foo.png on the
#+begin_graph line to fix the output filename; else a #+name: foo line above the block makes it foo.png, and the graph
a figure that [[foo]] reaches and #+caption: captions; otherwise the name is graph-HASH.png, HASH being that of the
dot source, so re-exporting an unchanged graph rewrites one file rather than adding another.
For HTML export: if :svg t is set, embeds an inline <svg> element (tooltips
are preserved as <title> attributes and appear on hover in browsers).
Otherwise, renders to an inline base64 PNG.
For Markdown, it writes the image file and links it, as for a named graph.
For other backends it emits a #+begin_src dot block. In HTML, a node’s [[*Section]] link leads to that section.
Within the block body:
#+graph: table ↦ global graph[ ] attributes
#+node: table ↦ default node[ ] attributes
#+edge: table ↦ default edge[ ] attributes
#+rank: n1 n2 ↦ { rank=same; n1; n2 } for nodes without a common parent; ids may be [[links]]
Under an item, #+graph:, #+node:, #+edge:, and #+rank: reach its whole subtree, as Org’s inherited properties do.
#+begin_cluster TITLE :KEY V … #+end_cluster ↦ subgraph cluster_TITLE { … }; nests, and may sit in an item
:NAME: … :END: ↦ a drawer of any name, folding such directives away
- :T: :: MEANING ↦ at the top, a legend entry: a sample of template T, captioned MEANING
A #+name: table outside any graph is a template every graph in the buffer may use; the graph’s own wins.
A section tagged :graph: exports as a graph of its text, its :PROPERTIES: drawer serving as the header line.
Each list item declares a node. Nesting encodes edges (parent → child); ordered children, 1. 2. 3., share a rank
in the order written. An item’s text may hold a <<target>>, its id; [[TARGET]] or [[TARGET][LABEL]] links, extra arcs;
and *bold*, /italic/, _underline_, +strike+, =code=, or ~pre~, which make its label an HTML-like one.
A description tag, ‘+ L :: CHILD’, labels the arc into CHILD; trailing ‘:T:’ tags splice #+name: templates;
an inline footnote, [fn::NOTE], is the node’s tooltip, as is a note: a paragraph past the item’s first, or a
#+begin_note block ---with ‘#+begin_note :edge’, the arc’s---, shown beneath the label instead under :notes inline;
a web, file, or [[*Section]] link, its URL; an [[file:IMAGE.png]], its image;
a checkbox, [X], [-], or [ ], applies the template checked, partial, or unchecked, when one exists; and a ‘\\’
breaks the label’s line ---in a table value or #+record: cell, too.
Org’s {{{macros}}} expand, by the buffer’s #+macro: lines.
A two-column inline table on a list item supplies node attributes.
| key | val | ↦ a Graphviz attribute of the node; values are quoted for you.
| :id | alias | ↦ cross-reference name for this node.
| :edge.KEY | val | ↦ attribute of the arc from the parent; a parallel arc is a second child item.
| :edge[T].KEY | val | ↦ attribute of this item’s arc to T, from a [[T]] link; "rec::port" reaches a record’s port.
| :inherit | T₁ T₂ | ↦ inherit the #+name: templates’ rows; explicit rows win. Also :edge.:inherit, and :T₁:T₂: tags:
after an item, its node’s; after a reference, or a description term, its arc’s.
A child item that is a lone [[T]] link is a reference to T: an arc to it, styled by the child’s :edge.KEY rows.
| :children-rank | same | ↦ constrain immediate children to the same rank;
also accepts min, max, source, sink.
| shape | cluster | ↦ make this node an inline subgraph cluster_ID { … }.
#+record: table ↦ the node’s <TABLE> label, as laid out: a cell’s <<p>> is port p, its [[T]] an arc from there;
^ or an empty cell spans, a <l>/<c>/<r> row aligns, and hlines rule.
Example:
#+begin_graph :rankdir LR
+ A
+ calls :: B
+ returns :: C
#+end_graph
Regarding the Lisp function:
+ BACKEND refers to the current export backend.
+ RAW-CONTENTS refers to the text as the user wrote it, sans its least indentation.
⇒ You may mention CONTENTS to refer to the ‘org parsed’ version of user text.
⇒ CONTENTS and RAW-CONTENTS are identical whenever key CONTENTS-OCCUR-AS-LINK-DESCRIPTION is non-nil.
For example, upon LaTeX export, the Org special block
#+begin_graph
Hello, world
#+end_graph
is rewritten to the result of the call
(org-block/graph ‘latex "Hello, world") block
is declared with :edit-indirect-mode org-graph-edit-mode, an Org mode that completes Graphviz
attributes.
(org-defblock poem (&optional title :edit-indirect-mode text-mode)
"Set CONTENTS in italics, under TITLE."
(format "<h3>%s</h3><em>%s</em>" title contents))
For an action beyond an edit buffer, (org-defblock-add-edit-special-handler "poem" #'my-handler)
registers any function of no arguments, just as C-c C-c handlers are registered —see
org-special-block-indirect-edit.el.
6. C-c C-v C-d to split a block in two
Just as C-c C-v C-d splits a src block at point, so too it splits any special block, after
Kaushal Modi's trick; each piece keeps the block's whole #+begin_ line, header arguments
and all. By way of example, with point at the start of Two.,
#+begin_tip Mind One. Two. #+end_tip
becomes
#+begin_tip Mind One. #+end_tip #+begin_tip Mind Two. #+end_tip
At a line's end, the split falls after that line; an active region is split off as a piece of
its own; and C-u C-c C-v C-d puts a heading between the pieces. In a src block within a
special block, it is the src block that is split, as ever. The command is
org-special-block-split, should you want it on a key of its own.
7. Hover for a block's arguments: eldoc
With point on a #+begin_NAME or #+end_NAME line, or on a NAME: link, the echo area
—or eldoc-box, should we use it— shows how the block is called, defaults and all, along
with the first line of its docstring:
#+begin_details TITLE="Details" :background-color "#e5f5e5" :title-color "green": Enclose contents in a folded up box, for HTML.
This needs no setup beyond org-special-block-extras-mode, and works for our own blocks too.
For the full story of a block, C-h o org-block/NAME.
Likewise, completing on a #+begin_ line ---M-TAB, or any completion popup— offers every
block's name beside the first line of its docstring, and Org's own blocks beside them; a popup's
documentation pane shows the block's whole docstring.
Life & Computing Science by Musa Al-hassy is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
