Projects About AlBasmala
build status

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

Article image
  

Ξ  

⭐ 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;
  • error refuses a tagged section that has any, naming both headings; and
  • recurse wraps 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.

3. Tags are functions of Org sections: org-deftag

Article image

Just as a special block is a function on a region of text, so too a tag is construable as a function on an Org section (Reddit post). By way of example, the following renders any section tagged :quote: as a <blockquote>.

(org-deftag quote (&optional (color "black") &key cite)
  "HTML export a section as a <blockquote>, its text coloured COLOR, crediting CITE."
  (insert "\n#+html:" (format "<blockquote style=\"color: %s\">" color))
  (org-next-visible-heading 1)
  (insert (if cite (format "\n#+html: <cite>%s</cite>" cite) "") "\n#+html: </blockquote>"))

As with cl-defun, the &optional arguments are positional: the underscore-separated items after the tag's name bind them in order. Any the tag omits are read from the section's property of the same name, and then default. The &key arguments are read from properties only —handy for lots of named info.

* Tags are functions of Org sections        :quote_blue:
* Tags are functions of Org sections        :quote:
:PROPERTIES:
:COLOR: blue
:CITE: Musa
:END:

When both the tag and a property give an argument, the tag wins.

Convenience: The arglist may be flattened into name-default pairs, all positional; (org-deftag quote (color "black") …) is the above, sans cite.

The body runs at export time, with point where the heading used to be —it has been erased, along with its property drawer, so we reinsert o-heading if we want it. The body may mention

  • o-backend —the export backend, such as html or latex;
  • o-heading —the heading's text, sans the tag;
  • o-properties —all of the section's properties, as a plist such as (:color "blue"); and
  • o-drawer —the erased property drawer, as text, should we want it back.

Sections without the tag are left untouched, property drawers and all.

Often, all we want is for a section to export as one of our special blocks; for that, we need no tag of our own —see the :tag t option of org-defblock, above. By way of example, :details_Hint: folds a section's text away under the title Hint.

The full story is at C-h o org-deftag.

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 graph 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 dot changes what C-c C-c does;
  • honours C-u via current-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 graph 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.


Generated by Emacs and Org-mode (•̀ᴗ•́)و
Creative Commons License
Life & Computing Science by Musa Al-hassy is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License