Skip to content

Highlighting

A highlighter colours your code blocks, and you have two to choose from. Both emit the same thing - CSS classes over the theme's tokens, never inline styles - so a page looks the same whichever one produced it, and switching is one line of your site.

PluginReadsGood for
TextMate.tmLanguage.json grammars, the ones VS Code shipsFifty languages in the box, and a rich ecosystem of grammars beyond them
tree-sittertree-sitter grammars and their queriesPrecision: names coloured by what they are, not by what they look like

Which one

TextMate knows about fifty languages the moment you register it, and most languages that are not in the box have a .tmLanguage.json published somewhere - VS Code extensions are full of them.

tree-sitter parses instead of matching patterns, so it tells a type from a function from a parameter where a pattern sees three identifiers. Twelve languages come inside the package, and you name any other by its repository. In F#, types, functions, parameters and union cases each get their own colour.

Both are one line in your site and emit identical classes, so swapping one for the other needs no change to your theme.

Or both

Register both: the last one registered is asked first, and the one before it covers whatever the later one does not know.

|> TextMate.register     // fifty languages
|> TreeSitter.register   // twelve, done properly

tree-sitter colours F#, JSON, YAML and the rest of its twelve, and a Python snippet falls through to TextMate. Swap the two lines for the opposite preference.

Colours

Highlighted code carries classes - tok-keyword, tok-string - and the theme decides what they mean. The default theme ships two schemes, Atom One Light and One Dark Pro, as custom properties. Override one and every block on the site follows, whichever highlighter drew it:

static/custom.css
:root {
    --tok-keyword: #a626a4;
    --tok-string: #50a14f;
}

:root[data-theme="dark"] {
    --tok-keyword: #ff7b72;
    --tok-string: #a5d6ff;
}

The vocabulary is small on purpose, and every grammar's captures land somewhere in it:

Wordstok-keyword, tok-operator, tok-punctuation
Namestok-type, tok-constructor, tok-function, tok-namespace, tok-variable, tok-parameter, tok-property
Valuestok-string, tok-escape, tok-number, tok-constant
Markuptok-tag, tok-attribute, tok-heading, tok-bold, tok-italic
The resttok-comment, tok-preprocessor, tok-invalid, tok-inserted, tok-deleted

A capture that matches nothing falls back down its dotted name - variable.parameter to tok-parameter, then to tok-variable - so a grammar with a vocabulary of its own still ends up somewhere sensible.

Edit this page