Render code to ANSI, HTML or Hiccup, syntax highlighted the same way Helix does it github.com/waddie/dathan/releases
html hiccup helix-editor css syntax-highlighting
0

Configure Feed

Select the types of activity you want to include in your feed.

Rust 100.0%
26 1 0

Clone this repository

https://tangled.org/tomwaddington.dev/dathan https://tangled.org/did:plc:7l5tahawnzji6l43s7z4hdto
git@tangled.org:tomwaddington.dev/dathan git@tangled.org:did:plc:7l5tahawnzji6l43s7z4hdto

For self-hosted knots, clone URLs may differ based on your setup.



README.md

dathan#

Highlight source code to HTML, Hiccup or ANSI, using Helix’s tree-sitter grammars and queries.

dathan reads the compiled grammars and highlights.scm / injections.scm / locals.scm queries from a Helix runtime, so it covers whatever languages that runtime provides. Grammar loading, query inherits resolution, injections, and locals are handled by tree-house, the same library Helix uses.

Build#

cargo build --release

The binary is target/release/dathan.

Or install directly with:

cargo install --path .

Usage#

dathan [OPTIONS] [FILE]

If FILE is omitted, source is read from stdin. With stdin there is no filename to detect from, so pass --lang (or rely on a #! shebang line).

Options:

--format <FORMAT>            Output format. Default: terminal. See below.
--inline                     For class-based formats, emit theme-resolved inline styles.
--lang <name>                Force a language instead of detecting it.
--runtime <path>             Extra runtime root, highest priority. Repeatable.
--languages <path>           Base languages.toml. The user config is still merged on top.
--theme <path|name>          theme.toml path, or a bare name resolved against the runtime themes/ dirs.
--emit-css                   Write a CSS stylesheet from the theme and exit. Ignores FILE.
-o, --output <path>          Output file. Default: stdout.

Formats:

--format Output
terminal ANSI escape codes, 24-bit colour (default).
html <pre><code> with hierarchical class.
edn-hiccup Clojure/EDN Hiccup with hierarchical :class.
json-hiccup JSON Hiccup arrays with hierarchical class.

The three class-based formats (html, edn-hiccup, json-hiccup) name spans with hierarchical classes for styling via an external stylesheet (see --emit-css).

Passing --inline instead resolves each scope to an inline style from the theme and puts a base ui.text / ui.background style on the container.

--inline and the terminal format resolve colours and modifiers from the theme directly. They use --theme if given, otherwise the same theme discovery as --emit-css, and fall back to the bundled default theme when none is found.

--theme takes either a path to a theme.toml or a bare name. A name that isn’t an existing path is resolved to <name>.toml in the runtime themes/ dirs (--runtime themes/, then ~/.config/helix/themes, then each remaining runtime root’s themes/), so --theme acid picks up acid.toml from the runtime.

A scope’s style is resolved by longest dotted prefix (e.g. function.builtin falls back to function), matching Helix.

Examples:

dathan src/main.rs
dathan --format html src/main.rs -o main.html
cat src/main.rs | dathan --lang rust
dathan --emit-css --theme acid -o theme.css
dathan --format terminal --theme ~/.config/helix/themes/acid.toml src/main.rs
dathan --theme acid src/main.rs
dathan --format html --inline src/main.rs -o main.html

From Helix#

From Helix, you can pipe selections through dathan to your clipboard management program:

space.B.d = ":pipe-to dathan --format html --lang %{language} | pbcopy"

Replace pbcopy with xclip/xsel/clip/etc. depending on your platform.

Runtime discovery#

Runtime roots are searched in this order, first match wins:

  1. --runtime paths
  2. ~/.config/helix/runtime
  3. $HELIX_RUNTIME

The base language registry is --languages if given, otherwise a languages.toml next to the $HELIX_RUNTIME tree (as in a Helix source checkout), otherwise ~/.config/helix/languages.toml. When the base comes from --languages or $HELIX_RUNTIME, ~/.config/helix/languages.toml is merged over it by language name.

Language detection uses file-types globs, then the file extension, then a #! shebang line. Override with --lang.

Output#

The class-based formats name spans by scope. A dotted scope becomes space-separated hierarchical classes, so keyword.control.conditional is rendered as keyword keyword-control keyword-control-conditional.

HTML:

<div class="dathan">
  <pre><code><span class="keyword keyword-function">fn</span></code></pre>
</div>

EDN Hiccup:

[:div.dathan [:pre [:code [:span {:class "keyword keyword-function"} "fn"] " " ]]]

JSON Hiccup:

["div",{"class":"dathan"},["pre",{},["code",{},["span",{"class":"keyword keyword-function"},"fn"]," ",]]]

CSS from --emit-css targets the most specific class, for example .keyword-control { color: …; }. Palette names in the theme are resolved.

With --inline, the same class-based formats bake the resolved style into each span instead. Inline HTML:

<div class="dathan">
  <pre style="color: #a4a0e8; background-color: #3b224c"><code>
    <span style="color: #eccdba">fn</span>  </code></pre>
</div>

terminal emits the same spans as ANSI SGR escape codes (24-bit colour), with a base colour from the theme’s ui.text / ui.background.

Tests#

cargo test

License#

Copyright © 2026 Tom Waddington

Distributed under the MIT License. See LICENSE file for details.