org.nvim

Diagrams (ob-mermaid, ob-dot, ob-plantuml)

Stability: experimental (org-extensions-stability)

diagrams renders diagram source blocks to images: mermaid (with mermaid-cli's mmdc, like the third-party ob-mermaid), dot (Graphviz, like Emacs's ob-dot) and the built-in plantuml port (org-babel-languages):

require("org").setup({
  extensions = { diagrams = { render_on_save = true } },
})

C-c C-c on a block writes the diagram to its :file and inserts a [[file:...]] result, as for any babel language (both languages default to :results file :exports results):

#+begin_src mermaid :file flow.png :theme dark
flowchart LR
  A --> B
#+end_src

#+begin_src dot :file deps.svg :var color="red"
digraph { a [color=$color]; a -> b }
#+end_src

mermaid takes ob-mermaid's header arguments :theme, :background-color, :width, :height, :scale, :mermaid-config-file, :css-file and :puppeteer-config-file (ob-mermaid's spelling :pupeteer-config-file also works). dot runs CMD IN CMDLINE -o OUT like ob-dot: :cmd (default dot), :cmdline (default -T plus the extension of :file) and $NAME replaced by :var NAME. plantuml keeps its own options in babel.languages.plantuml; when no jar_path is set but a plantuml command is installed, that command is used.

Beyond Emacs

  • A block without :file gets output_dir/LANG-HASH8.EXT (HASH8 starts the content hash; EXT is :file-ext or format), relative to the org file's directory or :dir. Emacs errors instead; set auto_file = false for that.
  • Rendered diagrams are cached in cache_dir by a hash of the language, the command and options, the output format and the expanded body. An unchanged diagram is copied from the cache instead of being rendered again. For plantuml the key also covers the files the text pulls in with !include (!include_once, !include_many, !includesub; local files, four levels deep), so editing one renders again. :Org diagrams_rerender renders the block at point without the cache and :Org diagrams_clear_cache empties it. At startup the cache drops entries unused for cache_max_age days, then the least recently used beyond cache_max_size megabytes. Both only touch cache entries (files named by the hash); other files in cache_dir are left alone.
  • With auto_preview (the default) the result image is shown inline after the block runs, when an image backend is available (org-images; SVG needs ImageMagick).
  • With render_on_save every diagram block of an org buffer is rendered after the buffer is written, one block after another in the background, so Neovim stays responsive while mmdc or dot run. When they are done and nothing but the results changed, the buffer is written again, so the file on disk has the new results; when you edited it meanwhile, the buffer is left modified for you to save. Blocks run without the evaluation prompt, except those with :eval no, never or query, which are skipped. Unchanged diagrams come from the cache at once.
  • :Org diagrams_clean deletes the generated files (LANG-HASH8.EXT) in the current file's output_dir that no org file of that directory links to or names (open buffers count with their unsaved text), after asking; :Org diagrams_clean dry only lists them. Files you named yourself with :file are never touched.
  • The tools run without a shell (an argument list) unless the command is a shell fragment (a string of several words) or the block sets :cmd or :cmdline, which are shell text as in ob-dot.

Actions

  diagrams_render         render the diagram block at point (what
                            C-c C-c does there); not applicable elsewhere
  diagrams_rerender       the same, ignoring the cache
  diagrams_render_buffer  render every diagram block of the buffer
  diagrams_clear_cache    delete the cached renders
  diagrams_clean          delete generated diagrams nothing links to
No default keys; use :Org <action> or bind them in mappings.org.

Options (defaults in parentheses)

  languages        handled languages ({ "mermaid", "dot", "plantuml" });
                   a language left out keeps its core behaviour
  mermaid          { command ("mmdc"; a string runs through the shell,
                   so "npx -y @mermaid-js/mermaid-cli" works, or a list of
                   words), args ({}), theme, background,
                   config_file, puppeteer_config } (defaults for the
                   header arguments above)
  dot              { command ("dot"), args ({}) }
  auto_file        generate a :file when there is none (true)
  output_dir       directory of generated names ("diagrams")
  format           extension of generated names ("png")
  cache            use the cache (true)
  cache_dir        stdpath("cache") .. "/org/diagrams"
  auto_preview     preview the result image (true)
  render_on_save   render diagram blocks after writing (false)
  cache_max_age    days an unused cache entry is kept (90; false: ever)
  cache_max_size   megabytes the cache is pruned to (200; false: no
                   limit)

:checkhealth org shows which renderers (mmdc, dot, plantuml or the jar and java) are installed, whether results can be previewed, and the cache directory. None of the tools is needed to load the extension; a block whose tool is missing reports it and inserts no result.

Limits

  • Generated names change with the body, so an edited diagram leaves its old file behind in output_dir until diagrams_clean.
  • Only plantuml includes are followed: a mermaid config or CSS file, and dot's image= or imagepath files, are not part of the cache key; use diagrams_rerender after changing them.
  • Without Graphviz installed only fake tools were run in the tests; the argument order follows ob-dot (dot IN -TEXT [args] -o OUT).
  • Only file results are cached. plantuml's text output (:results verbatim, no :file) is left to the core port.