MishkaCmsCore.Runtime.PageBuilder.Highlight (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Colour for the code the editor shows, driven by tokenizers rather than by regular expressions.

Why not a regular expression

The drawer coloured a line grey when it started with <!-- or /*, and left everything else one colour. Anything better with patterns runs into the cases that make highlighting worth having in the first place: a # inside a string is not a comment, " inside a heredoc does not end it, ` interpolation is code inside a string, and a<in a text node is not a tag. A tokenizer already knows all four, and this project ships two of them. ## What it costs, measured:elixir_tokenizeron this machine: **64 µs** for an eight-line snippet, **4.5 ms** for 320 lines. That is the whole reason this is scoped to what the drawer shows — a selected node's markup, one handler's body — and not to a whole page's source. 64 µs per render is nothing; 4.5 ms on every keystroke is a stutter somebody would feel, and the editor re-renders on every selection change. Anything this cannot tokenize comes back as one plain segment rather than raising. Colour is the least important thing on the screen and must never be the reason a panel fails to draw. ## The shape[{text, colour}]per line, so a caller renders one<span>` per segment and the line numbers stay the caller's business.

Summary

Functions

CSS as coloured segments. Small enough not to need a tokenizer of its own: a declaration is a property, a colon and a value, and the only thing worth telling apart is a comment and a selector.

Elixir source as coloured segments, one list per line.

HEEx or HTML as coloured segments, through the project's own tokenizer — the same one the tree is built with, so the drawer cannot disagree with the canvas about where a tag ends.

Text nobody should pretend to have parsed, in the plain colour, one list per line.

Types

segment()

@type segment() :: {String.t(), String.t()}

Functions

css(source)

@spec css(String.t() | nil) :: [[segment()]]

CSS as coloured segments. Small enough not to need a tokenizer of its own: a declaration is a property, a colon and a value, and the only thing worth telling apart is a comment and a selector.

elixir(source)

@spec elixir(String.t() | nil) :: [[segment()]]

Elixir source as coloured segments, one list per line.

Positions come from :elixir_tokenizer, and the text is sliced out of the ORIGINAL source rather than rebuilt from the tokens: a tokenizer drops whitespace and comments, so anything rebuilt from its output would come back reflowed and missing the comments — which is most of what a reader is looking at.

heex(source)

@spec heex(String.t() | nil) :: [[segment()]]

HEEx or HTML as coloured segments, through the project's own tokenizer — the same one the tree is built with, so the drawer cannot disagree with the canvas about where a tag ends.

prose(source)

@spec prose(String.t() | nil) :: [[segment()]]

Text nobody should pretend to have parsed, in the plain colour, one list per line.

For the panes that hold a sentence rather than a program — the drawer's JS tab points at Runtime → JS hooks — where running a tokenizer over prose only invents keywords out of ordinary words.