/** * CodeMirror 6 language support for Decodal. * * This module exports the Decodal language extension, parser metadata, and the * bundled highlight style used by the official Decodal playground. Add * {@link decodal} to a CodeMirror extension set to enable highlighting, * indentation, and folding for Decodal documents. * * @module */ import { HighlightStyle, LRLanguage, LanguageSupport, delimitedIndent, foldNodeProp, indentNodeProp, syntaxHighlighting } from 'npm:@codemirror/language@^6.12.4'; import { styleTags, tags as t } from 'npm:@lezer/highlight@^1.2.3'; import { parser } from './decodal-parser-jsr.js'; const parserWithMetadata = parser.configure({ props: [ styleTags({ 'Let In Match Import Default As': t.keyword, 'True False': t.bool, Identifier: t.variableName, String: t.string, Regex: t.regexp, 'Integer Float': t.number, Comment: t.lineComment, 'Plus Minus Star Slash PlusPlus Ellipsis Equal EqualEqual Bang BangEqual Amp AmpAmp PipePipe SlashSlash Gt Gte Lt Lte Arrow': t.operator, 'LBrace RBrace': t.brace, 'LBracket RBracket': t.squareBracket, 'LParen RParen': t.paren, 'Semicolon Comma Dot Colon': t.punctuation, }), indentNodeProp.add({ Object: delimitedIndent({ closing: '}', align: false }), MapConstraint: delimitedIndent({ closing: '}', align: false }), Array: delimitedIndent({ closing: ']', align: false }), ArrayConstraint: delimitedIndent({ closing: ']', align: false }), MatchExpression: delimitedIndent({ closing: '}', align: false }), LetExpression: context => context.column(context.node.from) + context.unit, }), foldNodeProp.add({ Object: foldDelimited('{', '}'), MapConstraint: foldDelimited('{', '}'), Array: foldDelimited('[', ']'), ArrayConstraint: foldDelimited('[', ']'), MatchExpression: foldDelimited('{', '}'), }), ], }); function foldDelimited(open: string, close: string) { return (node: { from: number; to: number }, state: { doc: { sliceString(from: number, to: number): string } }) => { const text = state.doc.sliceString(node.from, node.to); const openIndex = text.indexOf(open); const closeIndex = text.lastIndexOf(close); if (openIndex < 0 || closeIndex <= openIndex) return null; const from = node.from + openIndex + open.length; const to = node.from + closeIndex; return from < to ? { from, to } : null; }; } /** Decodal LR language definition for CodeMirror 6. */ export const decodalLanguage: LRLanguage = LRLanguage.define({ parser: parserWithMetadata, languageData: { commentTokens: { line: '#' }, closeBrackets: { brackets: ['(', '[', '{', '"'] }, }, }); /** Default Decodal highlight style used by the playground. */ export const decodalHighlightStyle: HighlightStyle = HighlightStyle.define([ { tag: t.keyword, color: '#93c5fd', fontWeight: '700' }, { tag: t.variableName, color: 'inherit' }, { tag: t.bool, color: '#fbbf24' }, { tag: t.number, color: '#fbbf24' }, { tag: t.string, color: '#86efac' }, { tag: t.regexp, color: '#86efac' }, { tag: t.lineComment, color: '#94a3b8', fontStyle: 'italic' }, { tag: t.operator, color: '#f9a8d4' }, { tag: [t.brace, t.squareBracket, t.paren, t.punctuation], color: '#f9a8d4' }, ]); /** Options for {@link decodal}. */ export interface DecodalOptions { /** Include the bundled {@link decodalHighlightStyle}. Defaults to `true`. */ highlight?: boolean; } /** * Create CodeMirror language support for Decodal. * * @param options - Optional language support options. * @returns A CodeMirror extension bundle containing the Decodal language and, * unless disabled, the default highlight style. */ export function decodal(options: DecodalOptions = {}): LanguageSupport { const { highlight = true } = options; return new LanguageSupport( decodalLanguage, highlight ? [syntaxHighlighting(decodalHighlightStyle)] : [], ); }