Something went wrong. Try again.
A reference for terminal control sequences whose examples run live in libghostty-vt compiled to WebAssembly.
Something went wrong. Try again.
5.4 kB · 113 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
// Draws the syntax diagram at the top of a sequence page: each piece of the// sequence as a tile with its name on top and its bytes underneath. The// page carries the sequence as text in `data-sequence`, so it reads fine// with no script at all.
const C0 = [ "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",];
const INTRODUCERS = { CSI: { bytes: [0x1b, 0x5b], title: "Control Sequence Introducer" }, OSC: { bytes: [0x1b, 0x5d], title: "Operating System Command" }, DCS: { bytes: [0x1b, 0x50], title: "Device Control String" }, ST: { bytes: [0x1b, 0x5c], title: "String Terminator" },};
// Only the names that mean the same thing wherever they appear. Pc is a// column to CUP, a color to OSC 4 and a selection to OSC 52, and Pl a line// to CUP, a level to DECSCL and a lock to DECUDK, so those are left to the// page.const PARAMETERS = { n: "a number", s: "a number selecting a behavior", t: "text", m: "a list of numbers",};
// DEC's edges, as a rectangle (DECCARA's Pt ; Pl ; Pb ; Pr) or as margins// (DECSTBM's Pt ; Pb, DECSLRM's Pl ; Pr), where Pt is the top line rather// than text.const EDGES = { Pt: "the top line", Pl: "the left column", Pb: "the bottom line", Pr: "the right column",};
// `parameters` names words to draw as parameters whatever they look like:// a single lowercase letter is otherwise the byte itself.export function tokenize(text, parameters = []) { const edges = /\bPt ; (Pl ; )?Pb\b|\bPl ; Pr\b/.test(text); return text.trim().split(/\s+/).map((word) => { if (parameters.includes(word)) return { kind: "param", label: word, bytes: [], title: `${word}: as the page describes` }; if (word in INTRODUCERS) return { kind: "intro", label: word, ...INTRODUCERS[word] }; const c0 = C0.indexOf(word); if (c0 >= 0) return { kind: "control", label: word, bytes: [c0], title: `C0 control ${word}` }; if (word === "DEL") return { kind: "control", label: word, bytes: [0x7f], title: "Delete" }; if (word === "SP") return { kind: "control", label: word, bytes: [0x20], title: "a space, here an intermediate byte" }; if (word === "..." || word === "…") return { kind: "more", label: "…", bytes: [], title: "repeated" }; // DEC writes a reply's hexadecimal digits as D…D, as in DECCKSR. if (/^D(…|\.\.\.)D$/.test(word)) return { kind: "param", label: "D…D", bytes: [], title: "hexadecimal digits" }; // A choice of final byte, as in a mode's `h/l`: one tile showing both. // Only between single characters, so that DECUDK's `Ky1/St1` stays as // it is. const choice = /^(.)\/(.)$/.exec(word); if (choice) { const [, a, b] = choice; const title = word === "h/l" ? "h sets the mode, l resets it" : `either ${a} or ${b}`; return { kind: "literal", label: word, bytes: [], alternatives: [a, b].map((ch) => [ch.charCodeAt(0)]), title }; } // A word of two or more lowercase letters names what goes there: // ReGIS's `commands`, an OSC's `text` or `payload`, `key=value`, sixel's // `s…s`. A single letter, such as the `p` that ends ReGIS's // introducer, is the byte itself. if (/^[a-z][a-z…=]*[a-z…]$/.test(word)) return { kind: "param", label: word, bytes: [], title: `${word}: as the page describes` }; const param = /^P([a-z0-9]+)$/.exec(word); if (param) { if (edges && word in EDGES) return { kind: "param", label: word, bytes: [], title: `${word}: ${EDGES[word]}` }; return { kind: "param", label: word, bytes: [], title: `${word}: ${PARAMETERS[param[1]] ?? "as the page describes"}` }; } // DEC's names for the parts of a string, such as DECDLD's Dscs and // Sxbp1 or DECUDK's Ky1/St1: a capital, then lowercase letters and digits. if (/^[A-Z][a-z][a-z0-9]*(\/[A-Z][a-z][a-z0-9]*)?$/.test(word)) { return { kind: "param", label: word, bytes: [], title: `${word}: as the page describes` }; } return { kind: "literal", label: word, bytes: Array.from(new TextEncoder().encode(word)), title: `the characters ${word}` }; });}
function hex(bytes) { return bytes.map((b) => b.toString(16).toUpperCase().padStart(2, "0")).join(" ");}
function render(figure) { const tokens = tokenize(figure.dataset.sequence, (figure.dataset.parameters ?? "").split(/\s+/).filter(Boolean)); const list = document.createElement("ol"); list.className = "wire"; for (const t of tokens) { const li = document.createElement("li"); li.className = `tile ${t.kind}`; li.title = t.title; const label = document.createElement("span"); label.className = "label"; label.textContent = t.label; const bytes = document.createElement("span"); bytes.className = "hex"; bytes.textContent = t.alternatives ? t.alternatives.map(hex).join(" / ") : t.kind === "param" ? "· · ·" : t.kind === "more" ? "" : hex(t.bytes); li.append(label, bytes); list.append(li); } figure.replaceChildren(list); figure.setAttribute("aria-label", `Syntax: ${figure.dataset.sequence}`);}
if (typeof document !== "undefined") { for (const figure of document.querySelectorAll("figure.syntax[data-sequence]")) render(figure);}