ArcMoon 1.1.0

Start

Language

Reference

Nothing matches “”.

↑↓ move Enter open Esc close
← All tools

arcmoon-highlight

v1.2.1 npm GitHub

Syntax highlighting for .arcm code: highlighted HTML in Node.js or the browser, and a live editor that shows errors as you type.

Install

npm install arcmoon-highlight
pnpm add arcmoon-highlight
bun add arcmoon-highlight

arcmoon-highlight colors ArcMoon code: as HTML in Node.js or the browser, or live in an editor that also shows errors. It reads code with ArcMoon's own lexer, so it sees it exactly the way ArcMoon does, including the JavaScript in ${ }$ and the CSS in [style]. You don't need to install arcmoon.

What's in it

  • arcmoon-highlight: staticHighlight, tokenize, defaultTokens, highlightJs, decorateJs.
  • arcmoon-highlight/diagnostics: diagnose.
  • arcmoon-highlight/dynamic: attachHighlighter, the live editor (needs CodeMirror, see below).

staticHighlight(src, options)

Turns ArcMoon code into highlighted HTML. Every token becomes a <span> with an inline color from the built-in dark theme:

static.js
import { staticHighlight } from "arcmoon-highlight";

const html = staticHighlight(`[div = class: "hero"]
  [h1]Hello[end]
[end]`);

document.querySelector("pre").innerHTML = html;
  • It never throws: half-written code is highlighted up to the problem, and the rest is plain text.
  • Text is escaped, so the result is safe to put in innerHTML.
  • JavaScript in ${ }$ and runtime ${ }$, and CSS in [style], style:, css. and -- props, get their own colors.

Choosing colors

Each token is drawn by the first of these that answers: onToken, then tokens[type], then other, then the built-in colors:

options.js
import { staticHighlight } from "arcmoon-highlight";

const src = "[b]Hi[end]";

// tokens: a style per token type
staticHighlight(src, {
  tokens: {
    IDENTIFIER: "#ffcc00",                    // a color
    TEXT: { color: "#22c55e", bold: true },  // color, bold, italic
    END_KEYWORD: null                        // plain text, no span
  }
});
// … <span style="color:#ffcc00">b</span> … <span style="color:#22c55e;font-weight:bold">Hi</span> … end …

// render: your own HTML for a token type
staticHighlight(src, { tokens: { TEXT: { render: (value) => `<mark>${value}</mark>` } } });

// onToken: decide for any token, with the ones around it; undefined falls through
staticHighlight(src, {
  onToken: ({ prev, current, next }) => (current.type === "IDENTIFIER" ? `<b class="tag">${current.value}</b>` : undefined)
});

// other: the style for every token type you didn't list in tokens
staticHighlight(src, { tokens: { IDENTIFIER: "#ffcc00" }, other: "#888888" });
  • tokens: a style per token type. A style is a color ("#ffcc00"), an object ({ color, bold, italic }), { render } for your own HTML, or null for plain text.
  • onToken({ prev, current, next }): decide for any token, looking at its neighbours. Return HTML, or undefined to let the next rule decide.
  • other: one style for every token type not in tokens.

Colors from your CSS

Inline colors can't follow light and dark themes. With classPrefix, every token gets a class instead, and your CSS colors it:

classes.js
import { staticHighlight } from "arcmoon-highlight";

staticHighlight('[b = id: "x"]Hi ${ count }$[end]', { classPrefix: "arcm-" });
// <span class="arcm-open-bracket">[</span><span class="arcm-identifier">b</span>
// <span class="arcm-key">id</span><span class="arcm-colon">:</span> <span class="arcm-string">"x"</span> …
// <span class="arcm-logic"> <span class="arcm-js-name">count</span> </span> …
theme.css
/* one stylesheet, both themes */
:root {
  --arcm-tag: #c2185b;
  --arcm-key: #0369a1;
  --arcm-string: #15803d;
}
@media (prefers-color-scheme: dark) {
  :root { --arcm-tag: #ff7a8c; --arcm-key: #7cc4ff; --arcm-string: #8fdba8; }
}
.arcm-identifier { color: var(--arcm-tag); }
.arcm-key, .arcm-js-property { color: var(--arcm-key); }
.arcm-string, .arcm-js-string { color: var(--arcm-string); }
.arcm-comment { font-style: italic; opacity: 0.6; }
.arcm-error { text-decoration: underline wavy #f43f5e; }
  • ArcMoon tokens: the prefix plus the token type in lowercase, with dashes: arcm-identifier, arcm-open-bracket, arcm-end-keyword.
  • JavaScript: arcm-js-keyword, arcm-js-name, arcm-js-call, arcm-js-string, arcm-js-number, arcm-js-operator and more.
  • CSS: arcm-css-property, arcm-css-class, arcm-css-value, arcm-css-color and more.
  • Errors: arcm-error, plus arcm-error-arcmoon, arcm-error-javascript or arcm-error-css.

This site colors all its code this way.

Token types

tokenize gives the tokens staticHighlight colors, and defaultTokens the built-in colors for each type:

tokenize.js
import { tokenize, defaultTokens } from "arcmoon-highlight";

tokenize('[p = class: "x"]Hi[end]');
// [{ type: "OPEN_BRACKET", value: "[" }, { type: "IDENTIFIER", value: "p" },
//  { type: "WHITESPACE", value: " " }, { type: "EQUAL", value: "=" }, …]

// the built-in dark colors: start from them and change a few
const tokens = { ...defaultTokens, IDENTIFIER: "#ffcc00" };
  • Markup: OPEN_BRACKET, CLOSE_BRACKET, IDENTIFIER, END_KEYWORD, EXCLAMATION_MARK, IMPORT, SLOT, FOR_EACH, TEXT, ESCAPE, WHITESPACE.
  • Props: KEY, EQUAL, COLON, COMMA, STRING, NUMBER, BOOLEAN, WORD.
  • Code: RUNTIME_KEYWORD, LOGIC_OPEN, LOGIC, LOGIC_CLOSE.
  • Comments: COMMENT, COMMENT_BLOCK.
  • CSS: CSS_SELECTOR, CSS_CLASS, CSS_ID, CSS_PSEUDO, CSS_ATTRIBUTE, CSS_AT_RULE, CSS_PROPERTY, CSS_VARIABLE, CSS_VALUE, CSS_NUMBER, CSS_COLOR, CSS_FUNCTION, CSS_STRING, CSS_IMPORTANT, CSS_COMMENT, CSS_PUNCTUATION, CSS_TEXT.

JavaScript alone

highlightJs(code, { classPrefix }) colors plain JavaScript, the same way as inside ${ }$. decorateJs does the same for editors, as ranges instead of HTML.

js.js
import { highlightJs } from "arcmoon-highlight";

highlightJs('const n = add(1, "x");', { classPrefix: "arcm-" });
// <span class="arcm-js-keyword">const</span> <span class="arcm-js-name">n</span>
// <span class="arcm-js-operator">=</span> <span class="arcm-js-call">add</span>…
// <span class="arcm-js-number">1</span>… <span class="arcm-js-string">"x"</span>…

Finding errors: diagnose(src, options)

Finds what is wrong in ArcMoon code, without running it, each at its exact spot:

  • ArcMoon: syntax errors from ArcMoon's lexer and parser, and ArcMoon's rules for code, like export in runtime code or a Node.js module in the browser.
  • JavaScript: every ${ }$ is parsed, like the compiler does.
  • CSS: braces, syntax and missing colons are errors; unknown properties and bad values are warnings. Values with var(), env() or attr() aren't checked.
diagnose.js
import { diagnose } from "arcmoon-highlight/diagnostics";

diagnose(`[p]\${ const = 1 }$[end]
[style] .a { colr: red } [end]
runtime \${ export const x = 1; }$`);
// [
//   { line: 1, column: 13, text: "=", severity: "error", source: "JavaScript",
//     message: "Unexpected token", from: 12, to: 13 },
//   { line: 2, column: 14, text: "colr", severity: "warning", source: "CSS",
//     message: "\"colr\" is not a CSS property (did you mean \"color\"?)", from: 37, to: 41 },
//   { line: 3, column: 12, text: "export const x = 1;", severity: "error", source: "ArcMoon",
//     message: "export is not allowed in runtime code", from: 66, to: 85 }
// ]

diagnose(src, { rules: false });   // only syntax errors, not ArcMoon's rules
  • Each problem has line and column (from 1), from / to (offsets), the underlined text, severity ("error" or "warning"), source and message.
  • rules: false checks syntax only. Rules that need the whole project (like bundle in the config) are left to the language server.

Pass the list to staticHighlight as errors, and each problem gets a wavy underline, with its message as a tooltip:

errors.js
import { staticHighlight } from "arcmoon-highlight";
import { diagnose } from "arcmoon-highlight/diagnostics";

const src = "[p]${ 1 + }$[end]";
staticHighlight(src, { errors: diagnose(src) });
// … <span style="text-decoration:underline wavy #f59e0b;…" title="JavaScript: the code ends too early">+</span> …

staticHighlight(src, { classPrefix: "arcm-", errors: diagnose(src) });
// … <span class="arcm-error arcm-error-javascript" title="JavaScript: the code ends too early">…</span> …

Live editor: attachHighlighter(element, options)

A CodeMirror 6 editor that highlights as you type and shows errors with wavy lines and dots by the line numbers. Install CodeMirror next to it:

npm install @codemirror/view @codemirror/state @codemirror/commands @codemirror/lint
editor.js
import { attachHighlighter } from "arcmoon-highlight/dynamic";

const editor = attachHighlighter(document.getElementById("editor"), {
  classPrefix: "arcm-",          // or tokens / other / onToken, like staticHighlight
  caretColor: "#e8eaf0",
  showLineNumbers: true,
  showErrors: true,              // wavy lines and dots in the gutter
  rules: true,                   // also check ArcMoon's rules, not only syntax
  onDiagnostics: (list) => {},   // after every check: the full list, like diagnose()
  onError: (text) => {}          // after every check: the first problem as text, or null
});

editor.setValue("[h1]Hello[end]");
editor.getValue();                          // "[h1]Hello[end]"
editor.onUpdate((code) => save(code));      // after every change
editor.goTo({ from: 4, to: 9 });            // select, scroll there and focus
editor.view;                                // the CodeMirror EditorView, for anything else
editor.destroy();                           // remove it
  • Colors: the same classPrefix, tokens, other and onToken as staticHighlight.
  • Errors are checked 250 ms after typing stops. showErrors: false still checks and calls onDiagnostics / onError, but draws nothing.
  • Methods: setValue, getValue, onUpdate, goTo (for example to jump to an error), destroy, and view for anything else CodeMirror can do.

From a CDN

Two ready bundles with a global ArcMoonHighlight. Nothing else to install:

index.html
<!-- highlighting only: staticHighlight, tokenize, highlightJs, … (8 KB gzipped) -->
<script src="https://cdn.jsdelivr.net/npm/arcmoon-highlight/dist/arcmoon-highlight.js"></script>

<!-- everything: also diagnose and the live editor, CodeMirror inside (202 KB gzipped) -->
<script src="https://cdn.jsdelivr.net/npm/arcmoon-highlight/dist/arcmoon-highlight.full.js"></script>

<script>
  const { staticHighlight, diagnose, attachHighlighter } = ArcMoonHighlight;
</script>