@teammates/consolonia 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +48 -0
  2. package/dist/__tests__/ansi.test.d.ts +1 -0
  3. package/dist/__tests__/ansi.test.js +520 -0
  4. package/dist/__tests__/chat-view.test.d.ts +4 -0
  5. package/dist/__tests__/chat-view.test.js +480 -0
  6. package/dist/__tests__/drawing.test.d.ts +4 -0
  7. package/dist/__tests__/drawing.test.js +426 -0
  8. package/dist/__tests__/input.test.d.ts +5 -0
  9. package/dist/__tests__/input.test.js +911 -0
  10. package/dist/__tests__/layout.test.d.ts +4 -0
  11. package/dist/__tests__/layout.test.js +689 -0
  12. package/dist/__tests__/pixel.test.d.ts +1 -0
  13. package/dist/__tests__/pixel.test.js +674 -0
  14. package/dist/__tests__/render.test.d.ts +1 -0
  15. package/dist/__tests__/render.test.js +400 -0
  16. package/dist/__tests__/styled.test.d.ts +4 -0
  17. package/dist/__tests__/styled.test.js +149 -0
  18. package/dist/__tests__/widgets.test.d.ts +5 -0
  19. package/dist/__tests__/widgets.test.js +924 -0
  20. package/dist/ansi/esc.d.ts +61 -0
  21. package/dist/ansi/esc.js +85 -0
  22. package/dist/ansi/output.d.ts +66 -0
  23. package/dist/ansi/output.js +192 -0
  24. package/dist/ansi/strip.d.ts +16 -0
  25. package/dist/ansi/strip.js +74 -0
  26. package/dist/app.d.ts +68 -0
  27. package/dist/app.js +297 -0
  28. package/dist/drawing/clip.d.ts +23 -0
  29. package/dist/drawing/clip.js +67 -0
  30. package/dist/drawing/context.d.ts +77 -0
  31. package/dist/drawing/context.js +275 -0
  32. package/dist/index.d.ts +48 -0
  33. package/dist/index.js +63 -0
  34. package/dist/input/escape-matcher.d.ts +27 -0
  35. package/dist/input/escape-matcher.js +253 -0
  36. package/dist/input/events.d.ts +49 -0
  37. package/dist/input/events.js +17 -0
  38. package/dist/input/index.d.ts +15 -0
  39. package/dist/input/index.js +14 -0
  40. package/dist/input/matcher.d.ts +23 -0
  41. package/dist/input/matcher.js +14 -0
  42. package/dist/input/mouse-matcher.d.ts +27 -0
  43. package/dist/input/mouse-matcher.js +142 -0
  44. package/dist/input/paste-matcher.d.ts +23 -0
  45. package/dist/input/paste-matcher.js +104 -0
  46. package/dist/input/processor.d.ts +51 -0
  47. package/dist/input/processor.js +145 -0
  48. package/dist/input/raw-mode.d.ts +13 -0
  49. package/dist/input/raw-mode.js +24 -0
  50. package/dist/input/text-matcher.d.ts +14 -0
  51. package/dist/input/text-matcher.js +32 -0
  52. package/dist/layout/box.d.ts +33 -0
  53. package/dist/layout/box.js +92 -0
  54. package/dist/layout/column.d.ts +21 -0
  55. package/dist/layout/column.js +90 -0
  56. package/dist/layout/control.d.ts +73 -0
  57. package/dist/layout/control.js +215 -0
  58. package/dist/layout/row.d.ts +21 -0
  59. package/dist/layout/row.js +95 -0
  60. package/dist/layout/stack.d.ts +18 -0
  61. package/dist/layout/stack.js +64 -0
  62. package/dist/layout/types.d.ts +27 -0
  63. package/dist/layout/types.js +4 -0
  64. package/dist/pixel/background.d.ts +16 -0
  65. package/dist/pixel/background.js +16 -0
  66. package/dist/pixel/box-pattern.d.ts +38 -0
  67. package/dist/pixel/box-pattern.js +57 -0
  68. package/dist/pixel/buffer.d.ts +25 -0
  69. package/dist/pixel/buffer.js +51 -0
  70. package/dist/pixel/color.d.ts +48 -0
  71. package/dist/pixel/color.js +92 -0
  72. package/dist/pixel/foreground.d.ts +31 -0
  73. package/dist/pixel/foreground.js +64 -0
  74. package/dist/pixel/pixel.d.ts +21 -0
  75. package/dist/pixel/pixel.js +38 -0
  76. package/dist/pixel/symbol.d.ts +38 -0
  77. package/dist/pixel/symbol.js +192 -0
  78. package/dist/render/regions.d.ts +54 -0
  79. package/dist/render/regions.js +102 -0
  80. package/dist/render/render-target.d.ts +42 -0
  81. package/dist/render/render-target.js +118 -0
  82. package/dist/styled.d.ts +113 -0
  83. package/dist/styled.js +176 -0
  84. package/dist/widgets/border.d.ts +34 -0
  85. package/dist/widgets/border.js +121 -0
  86. package/dist/widgets/chat-view.d.ts +239 -0
  87. package/dist/widgets/chat-view.js +993 -0
  88. package/dist/widgets/interview.d.ts +87 -0
  89. package/dist/widgets/interview.js +187 -0
  90. package/dist/widgets/markdown.d.ts +87 -0
  91. package/dist/widgets/markdown.js +611 -0
  92. package/dist/widgets/panel.d.ts +19 -0
  93. package/dist/widgets/panel.js +35 -0
  94. package/dist/widgets/scroll-view.d.ts +43 -0
  95. package/dist/widgets/scroll-view.js +182 -0
  96. package/dist/widgets/styled-text.d.ts +38 -0
  97. package/dist/widgets/styled-text.js +183 -0
  98. package/dist/widgets/syntax.d.ts +37 -0
  99. package/dist/widgets/syntax.js +670 -0
  100. package/dist/widgets/text-input.d.ts +121 -0
  101. package/dist/widgets/text-input.js +618 -0
  102. package/dist/widgets/text.d.ts +34 -0
  103. package/dist/widgets/text.js +168 -0
  104. package/package.json +45 -0
@@ -0,0 +1,670 @@
1
+ /**
2
+ * Syntax highlighting — plugin-driven tokenizer for code blocks.
3
+ *
4
+ * Each language plugin implements `SyntaxHighlighter` which splits a
5
+ * line of source code into `SyntaxToken[]`. The markdown renderer
6
+ * maps token types to `TextStyle` via `SyntaxTheme`.
7
+ *
8
+ * Built-in plugins: JavaScript/TypeScript, Python, C#.
9
+ *
10
+ * Register custom languages:
11
+ * import { registerHighlighter } from "@teammates/consolonia";
12
+ * registerHighlighter({ name: "ruby", aliases: ["rb"], tokenize: ... });
13
+ */
14
+ import { BLUE, CYAN, GRAY, GREEN, MAGENTA, WHITE, YELLOW, } from "../pixel/color.js";
15
+ export const DEFAULT_SYNTAX_THEME = {
16
+ keyword: { fg: MAGENTA },
17
+ string: { fg: GREEN },
18
+ number: { fg: YELLOW },
19
+ comment: { fg: GRAY, italic: true },
20
+ operator: { fg: CYAN },
21
+ punctuation: { fg: WHITE },
22
+ type: { fg: CYAN },
23
+ function: { fg: BLUE },
24
+ variable: { fg: WHITE },
25
+ constant: { fg: YELLOW, bold: true },
26
+ decorator: { fg: YELLOW },
27
+ attribute: { fg: CYAN },
28
+ text: { fg: WHITE },
29
+ };
30
+ // ── Registry ─────────────────────────────────────────────────────
31
+ const registry = new Map();
32
+ /** Register a syntax highlighter for one or more language aliases. */
33
+ export function registerHighlighter(highlighter) {
34
+ registry.set(highlighter.name.toLowerCase(), highlighter);
35
+ for (const alias of highlighter.aliases) {
36
+ registry.set(alias.toLowerCase(), highlighter);
37
+ }
38
+ }
39
+ /** Look up a highlighter by language name/alias. Returns null if not found. */
40
+ export function getHighlighter(lang) {
41
+ return registry.get(lang.toLowerCase()) ?? null;
42
+ }
43
+ /** Tokenize a line using the registered highlighter for the given language. */
44
+ export function highlightLine(lang, line) {
45
+ const h = getHighlighter(lang);
46
+ if (!h)
47
+ return [{ text: line, type: "text" }];
48
+ return h.tokenize(line);
49
+ }
50
+ /**
51
+ * Build a simple highlighter from an ordered list of regex rules.
52
+ * Each rule's pattern is tested against the remaining input at each
53
+ * position. First match wins. Unmatched characters become "text".
54
+ */
55
+ function regexHighlighter(name, aliases, rules) {
56
+ return {
57
+ name,
58
+ aliases,
59
+ tokenize(line) {
60
+ const tokens = [];
61
+ let pos = 0;
62
+ while (pos < line.length) {
63
+ let matched = false;
64
+ const remaining = line.slice(pos);
65
+ for (const rule of rules) {
66
+ const m = rule.pattern.exec(remaining);
67
+ if (m && m.index === 0 && m[0].length > 0) {
68
+ tokens.push({ text: m[0], type: rule.type });
69
+ pos += m[0].length;
70
+ matched = true;
71
+ break;
72
+ }
73
+ }
74
+ if (!matched) {
75
+ // Accumulate unmatched char into text
76
+ const last = tokens[tokens.length - 1];
77
+ if (last && last.type === "text") {
78
+ last.text += line[pos];
79
+ }
80
+ else {
81
+ tokens.push({ text: line[pos], type: "text" });
82
+ }
83
+ pos++;
84
+ }
85
+ }
86
+ return tokens;
87
+ },
88
+ };
89
+ }
90
+ // ── Built-in: JavaScript / TypeScript ────────────────────────────
91
+ const JS_KEYWORDS = [
92
+ "abstract",
93
+ "as",
94
+ "async",
95
+ "await",
96
+ "break",
97
+ "case",
98
+ "catch",
99
+ "class",
100
+ "const",
101
+ "continue",
102
+ "debugger",
103
+ "default",
104
+ "delete",
105
+ "do",
106
+ "else",
107
+ "enum",
108
+ "export",
109
+ "extends",
110
+ "finally",
111
+ "for",
112
+ "from",
113
+ "function",
114
+ "get",
115
+ "if",
116
+ "implements",
117
+ "import",
118
+ "in",
119
+ "instanceof",
120
+ "interface",
121
+ "let",
122
+ "new",
123
+ "of",
124
+ "package",
125
+ "private",
126
+ "protected",
127
+ "public",
128
+ "readonly",
129
+ "return",
130
+ "set",
131
+ "static",
132
+ "super",
133
+ "switch",
134
+ "this",
135
+ "throw",
136
+ "try",
137
+ "type",
138
+ "typeof",
139
+ "var",
140
+ "void",
141
+ "while",
142
+ "with",
143
+ "yield",
144
+ ];
145
+ const JS_CONSTANTS = ["true", "false", "null", "undefined", "NaN", "Infinity"];
146
+ const JS_TYPES = [
147
+ "Array",
148
+ "Boolean",
149
+ "Date",
150
+ "Error",
151
+ "Function",
152
+ "Map",
153
+ "Number",
154
+ "Object",
155
+ "Promise",
156
+ "RegExp",
157
+ "Set",
158
+ "String",
159
+ "Symbol",
160
+ "WeakMap",
161
+ "WeakSet",
162
+ "any",
163
+ "boolean",
164
+ "never",
165
+ "number",
166
+ "string",
167
+ "unknown",
168
+ "void",
169
+ "bigint",
170
+ ];
171
+ const jsHighlighter = regexHighlighter("javascript", ["js", "jsx", "ts", "tsx", "typescript", "mjs", "cjs"], [
172
+ // Comments
173
+ { type: "comment", pattern: /^\/\/.*/ },
174
+ { type: "comment", pattern: /^\/\*[\s\S]*?\*\// },
175
+ // Strings
176
+ { type: "string", pattern: /^`(?:[^`\\]|\\.)*`/ },
177
+ { type: "string", pattern: /^"(?:[^"\\]|\\.)*"/ },
178
+ { type: "string", pattern: /^'(?:[^'\\]|\\.)*'/ },
179
+ // Template literal fragments (unclosed — just highlight to end of line)
180
+ { type: "string", pattern: /^`[^`]*$/ },
181
+ // Decorators
182
+ { type: "decorator", pattern: /^@\w+/ },
183
+ // Numbers
184
+ { type: "number", pattern: /^0[xX][0-9a-fA-F_]+n?/ },
185
+ { type: "number", pattern: /^0[bB][01_]+n?/ },
186
+ { type: "number", pattern: /^0[oO][0-7_]+n?/ },
187
+ { type: "number", pattern: /^\d[\d_]*(?:\.[\d_]*)?(?:[eE][+-]?\d+)?n?/ },
188
+ // Constants
189
+ {
190
+ type: "constant",
191
+ pattern: new RegExp(`^\\b(?:${JS_CONSTANTS.join("|")})\\b`),
192
+ },
193
+ // Types (capitalized or TS built-in)
194
+ { type: "type", pattern: new RegExp(`^\\b(?:${JS_TYPES.join("|")})\\b`) },
195
+ // Keywords
196
+ {
197
+ type: "keyword",
198
+ pattern: new RegExp(`^\\b(?:${JS_KEYWORDS.join("|")})\\b`),
199
+ },
200
+ // Function calls
201
+ { type: "function", pattern: /^\b[a-zA-Z_$]\w*(?=\s*\()/ },
202
+ // Operators
203
+ {
204
+ type: "operator",
205
+ pattern: /^(?:=>|===|!==|==|!=|<=|>=|&&|\|\||<<|>>>|>>|\?\?|\?\.|[+\-*/%&|^~!<>=?:])/,
206
+ },
207
+ // Punctuation
208
+ { type: "punctuation", pattern: /^[{}()[\];,.]/ },
209
+ // Identifiers
210
+ { type: "variable", pattern: /^[a-zA-Z_$]\w*/ },
211
+ // Whitespace
212
+ { type: "text", pattern: /^\s+/ },
213
+ ]);
214
+ registerHighlighter(jsHighlighter);
215
+ // ── Built-in: Python ─────────────────────────────────────────────
216
+ const PY_KEYWORDS = [
217
+ "and",
218
+ "as",
219
+ "assert",
220
+ "async",
221
+ "await",
222
+ "break",
223
+ "class",
224
+ "continue",
225
+ "def",
226
+ "del",
227
+ "elif",
228
+ "else",
229
+ "except",
230
+ "finally",
231
+ "for",
232
+ "from",
233
+ "global",
234
+ "if",
235
+ "import",
236
+ "in",
237
+ "is",
238
+ "lambda",
239
+ "nonlocal",
240
+ "not",
241
+ "or",
242
+ "pass",
243
+ "raise",
244
+ "return",
245
+ "try",
246
+ "while",
247
+ "with",
248
+ "yield",
249
+ "match",
250
+ "case",
251
+ ];
252
+ const PY_CONSTANTS = ["True", "False", "None"];
253
+ const PY_TYPES = [
254
+ "int",
255
+ "float",
256
+ "str",
257
+ "bool",
258
+ "list",
259
+ "dict",
260
+ "tuple",
261
+ "set",
262
+ "frozenset",
263
+ "bytes",
264
+ "bytearray",
265
+ "complex",
266
+ "range",
267
+ "type",
268
+ "object",
269
+ "Exception",
270
+ "ValueError",
271
+ "TypeError",
272
+ "KeyError",
273
+ "IndexError",
274
+ "AttributeError",
275
+ "ImportError",
276
+ "RuntimeError",
277
+ "StopIteration",
278
+ "Generator",
279
+ "Callable",
280
+ "Optional",
281
+ "Union",
282
+ "Any",
283
+ "List",
284
+ "Dict",
285
+ "Tuple",
286
+ "Set",
287
+ ];
288
+ const pyHighlighter = regexHighlighter("python", ["py", "python3", "py3"], [
289
+ // Comments
290
+ { type: "comment", pattern: /^#.*/ },
291
+ // Strings (triple-quoted)
292
+ { type: "string", pattern: /^"""[\s\S]*?"""/ },
293
+ { type: "string", pattern: /^'''[\s\S]*?'''/ },
294
+ // Strings
295
+ { type: "string", pattern: /^f"(?:[^"\\]|\\.)*"/ },
296
+ { type: "string", pattern: /^f'(?:[^'\\]|\\.)*'/ },
297
+ { type: "string", pattern: /^r"(?:[^"\\]|\\.)*"/ },
298
+ { type: "string", pattern: /^r'(?:[^'\\]|\\.)*'/ },
299
+ { type: "string", pattern: /^b"(?:[^"\\]|\\.)*"/ },
300
+ { type: "string", pattern: /^b'(?:[^'\\]|\\.)*'/ },
301
+ { type: "string", pattern: /^"(?:[^"\\]|\\.)*"/ },
302
+ { type: "string", pattern: /^'(?:[^'\\]|\\.)*'/ },
303
+ // Decorators
304
+ { type: "decorator", pattern: /^@\w[\w.]*/ },
305
+ // Numbers
306
+ { type: "number", pattern: /^0[xX][0-9a-fA-F_]+/ },
307
+ { type: "number", pattern: /^0[bB][01_]+/ },
308
+ { type: "number", pattern: /^0[oO][0-7_]+/ },
309
+ { type: "number", pattern: /^\d[\d_]*(?:\.[\d_]*)?(?:[eE][+-]?\d+)?j?/ },
310
+ // Constants
311
+ {
312
+ type: "constant",
313
+ pattern: new RegExp(`^\\b(?:${PY_CONSTANTS.join("|")})\\b`),
314
+ },
315
+ // Types
316
+ { type: "type", pattern: new RegExp(`^\\b(?:${PY_TYPES.join("|")})\\b`) },
317
+ // Keywords
318
+ {
319
+ type: "keyword",
320
+ pattern: new RegExp(`^\\b(?:${PY_KEYWORDS.join("|")})\\b`),
321
+ },
322
+ // Function calls
323
+ { type: "function", pattern: /^\b[a-zA-Z_]\w*(?=\s*\()/ },
324
+ // self/cls
325
+ { type: "variable", pattern: /^\b(?:self|cls)\b/ },
326
+ // Operators
327
+ {
328
+ type: "operator",
329
+ pattern: /^(?:->|:=|\*\*|\/\/|==|!=|<=|>=|<<|>>|[+\-*/%&|^~!<>=@])/,
330
+ },
331
+ // Punctuation
332
+ { type: "punctuation", pattern: /^[{}()[\]:;,.]/ },
333
+ // Identifiers
334
+ { type: "variable", pattern: /^[a-zA-Z_]\w*/ },
335
+ // Whitespace
336
+ { type: "text", pattern: /^\s+/ },
337
+ ]);
338
+ registerHighlighter(pyHighlighter);
339
+ // ── Built-in: C# ────────────────────────────────────────────────
340
+ const CS_KEYWORDS = [
341
+ "abstract",
342
+ "as",
343
+ "async",
344
+ "await",
345
+ "base",
346
+ "bool",
347
+ "break",
348
+ "byte",
349
+ "case",
350
+ "catch",
351
+ "char",
352
+ "checked",
353
+ "class",
354
+ "const",
355
+ "continue",
356
+ "decimal",
357
+ "default",
358
+ "delegate",
359
+ "do",
360
+ "double",
361
+ "else",
362
+ "enum",
363
+ "event",
364
+ "explicit",
365
+ "extern",
366
+ "finally",
367
+ "fixed",
368
+ "float",
369
+ "for",
370
+ "foreach",
371
+ "get",
372
+ "goto",
373
+ "if",
374
+ "implicit",
375
+ "in",
376
+ "int",
377
+ "interface",
378
+ "internal",
379
+ "is",
380
+ "lock",
381
+ "long",
382
+ "namespace",
383
+ "new",
384
+ "object",
385
+ "operator",
386
+ "out",
387
+ "override",
388
+ "params",
389
+ "partial",
390
+ "private",
391
+ "protected",
392
+ "public",
393
+ "readonly",
394
+ "record",
395
+ "ref",
396
+ "required",
397
+ "return",
398
+ "sbyte",
399
+ "sealed",
400
+ "set",
401
+ "short",
402
+ "sizeof",
403
+ "stackalloc",
404
+ "static",
405
+ "string",
406
+ "struct",
407
+ "switch",
408
+ "this",
409
+ "throw",
410
+ "try",
411
+ "typeof",
412
+ "uint",
413
+ "ulong",
414
+ "unchecked",
415
+ "unsafe",
416
+ "ushort",
417
+ "using",
418
+ "var",
419
+ "virtual",
420
+ "void",
421
+ "volatile",
422
+ "when",
423
+ "where",
424
+ "while",
425
+ "yield",
426
+ "init",
427
+ "global",
428
+ "dynamic",
429
+ "value",
430
+ "nameof",
431
+ ];
432
+ const CS_CONSTANTS = ["true", "false", "null"];
433
+ const CS_TYPES = [
434
+ "Task",
435
+ "List",
436
+ "Dictionary",
437
+ "HashSet",
438
+ "IEnumerable",
439
+ "IList",
440
+ "ICollection",
441
+ "IDictionary",
442
+ "Action",
443
+ "Func",
444
+ "Nullable",
445
+ "String",
446
+ "Int32",
447
+ "Int64",
448
+ "Boolean",
449
+ "Double",
450
+ "Float",
451
+ "Decimal",
452
+ "Object",
453
+ "Type",
454
+ "Exception",
455
+ "Console",
456
+ "Math",
457
+ "Span",
458
+ "Memory",
459
+ "ReadOnlySpan",
460
+ "ValueTask",
461
+ ];
462
+ const csHighlighter = regexHighlighter("csharp", ["cs", "c#"], [
463
+ // Comments
464
+ { type: "comment", pattern: /^\/\/.*/ },
465
+ { type: "comment", pattern: /^\/\*[\s\S]*?\*\// },
466
+ // Strings
467
+ { type: "string", pattern: /^\$@"(?:[^"\\]|\\.|"")*"/ },
468
+ { type: "string", pattern: /^@"(?:[^"\\]|"")*"/ },
469
+ { type: "string", pattern: /^\$"(?:[^"\\]|\\.)*"/ },
470
+ { type: "string", pattern: /^"(?:[^"\\]|\\.)*"/ },
471
+ { type: "string", pattern: /^'(?:[^'\\]|\\.)'/ },
472
+ // Attributes
473
+ { type: "attribute", pattern: /^\[[\w.]+(?:\(.*?\))?\]/ },
474
+ // Numbers
475
+ { type: "number", pattern: /^0[xX][0-9a-fA-F_]+[uUlLfFdDmM]?/ },
476
+ { type: "number", pattern: /^0[bB][01_]+[uUlLfFdDmM]?/ },
477
+ {
478
+ type: "number",
479
+ pattern: /^\d[\d_]*(?:\.[\d_]*)?(?:[eE][+-]?\d+)?[uUlLfFdDmM]?/,
480
+ },
481
+ // Constants
482
+ {
483
+ type: "constant",
484
+ pattern: new RegExp(`^\\b(?:${CS_CONSTANTS.join("|")})\\b`),
485
+ },
486
+ // Types (built-in + PascalCase)
487
+ { type: "type", pattern: new RegExp(`^\\b(?:${CS_TYPES.join("|")})\\b`) },
488
+ { type: "type", pattern: /^\b[A-Z][a-zA-Z0-9]*(?=\s*[<{(])/ },
489
+ // Keywords
490
+ {
491
+ type: "keyword",
492
+ pattern: new RegExp(`^\\b(?:${CS_KEYWORDS.join("|")})\\b`),
493
+ },
494
+ // Generic type parameters
495
+ { type: "type", pattern: /^<[A-Z]\w*(?:\s*,\s*[A-Z]\w*)*>/ },
496
+ // Method calls
497
+ { type: "function", pattern: /^\b[a-zA-Z_]\w*(?=\s*[<(])/ },
498
+ // Operators
499
+ {
500
+ type: "operator",
501
+ pattern: /^(?:=>|&&|\|\||\?\?|\?\.|\?\[|==|!=|<=|>=|<<|>>|[+\-*/%&|^~!<>=?:])/,
502
+ },
503
+ // Punctuation
504
+ { type: "punctuation", pattern: /^[{}()[\];,.]/ },
505
+ // Identifiers
506
+ { type: "variable", pattern: /^[a-zA-Z_@]\w*/ },
507
+ // Whitespace
508
+ { type: "text", pattern: /^\s+/ },
509
+ ]);
510
+ registerHighlighter(csHighlighter);
511
+ // ── Built-in: Bash / Shell ───────────────────────────────────────
512
+ const BASH_KEYWORDS = [
513
+ "if",
514
+ "then",
515
+ "else",
516
+ "elif",
517
+ "fi",
518
+ "for",
519
+ "while",
520
+ "until",
521
+ "do",
522
+ "done",
523
+ "case",
524
+ "esac",
525
+ "in",
526
+ "function",
527
+ "select",
528
+ "time",
529
+ "coproc",
530
+ "return",
531
+ "exit",
532
+ "break",
533
+ "continue",
534
+ "local",
535
+ "declare",
536
+ "typeset",
537
+ "export",
538
+ "readonly",
539
+ "unset",
540
+ "shift",
541
+ "source",
542
+ "eval",
543
+ "exec",
544
+ "trap",
545
+ ];
546
+ const BASH_BUILTINS = [
547
+ "echo",
548
+ "printf",
549
+ "read",
550
+ "cd",
551
+ "pwd",
552
+ "pushd",
553
+ "popd",
554
+ "dirs",
555
+ "set",
556
+ "test",
557
+ "true",
558
+ "false",
559
+ "command",
560
+ "type",
561
+ "which",
562
+ "alias",
563
+ "unalias",
564
+ "bg",
565
+ "fg",
566
+ "jobs",
567
+ "wait",
568
+ "kill",
569
+ "history",
570
+ "getopts",
571
+ "hash",
572
+ "ulimit",
573
+ "umask",
574
+ ];
575
+ const bashHighlighter = regexHighlighter("bash", ["sh", "shell", "zsh", "fish", "ksh"], [
576
+ // Comments
577
+ { type: "comment", pattern: /^#.*/ },
578
+ // Strings
579
+ { type: "string", pattern: /^"(?:[^"\\]|\\.)*"/ },
580
+ { type: "string", pattern: /^'[^']*'/ },
581
+ { type: "string", pattern: /^\$'(?:[^'\\]|\\.)*'/ },
582
+ // Here-string
583
+ { type: "string", pattern: /^<<<\s*\S+/ },
584
+ // Variable expansions
585
+ { type: "variable", pattern: /^\$\{[^}]*\}/ },
586
+ { type: "variable", pattern: /^\$[A-Za-z_]\w*/ },
587
+ { type: "variable", pattern: /^\$[0-9@#?*!$-]/ },
588
+ // Command substitution
589
+ { type: "function", pattern: /^\$\(/ },
590
+ // Numbers
591
+ { type: "number", pattern: /^\b\d+\b/ },
592
+ // Builtins
593
+ {
594
+ type: "function",
595
+ pattern: new RegExp(`^\\b(?:${BASH_BUILTINS.join("|")})\\b`),
596
+ },
597
+ // Keywords
598
+ {
599
+ type: "keyword",
600
+ pattern: new RegExp(`^\\b(?:${BASH_KEYWORDS.join("|")})\\b`),
601
+ },
602
+ // Operators and redirections
603
+ { type: "operator", pattern: /^(?:&&|\|\||>>|<<|[<>|&;])/ },
604
+ {
605
+ type: "operator",
606
+ pattern: /^(?:==|!=|-eq|-ne|-lt|-gt|-le|-ge|-z|-n|-f|-d|-e|-r|-w|-x)/,
607
+ },
608
+ // Punctuation
609
+ { type: "punctuation", pattern: /^[{}()[\]]/ },
610
+ // Flags
611
+ { type: "constant", pattern: /^--?[a-zA-Z][\w-]*/ },
612
+ // Identifiers
613
+ { type: "variable", pattern: /^[a-zA-Z_]\w*/ },
614
+ // Whitespace
615
+ { type: "text", pattern: /^\s+/ },
616
+ ]);
617
+ registerHighlighter(bashHighlighter);
618
+ // ── Built-in: JSON ───────────────────────────────────────────────
619
+ const jsonHighlighter = regexHighlighter("json", ["jsonc", "json5"], [
620
+ // Comments (JSONC)
621
+ { type: "comment", pattern: /^\/\/.*/ },
622
+ { type: "comment", pattern: /^\/\*[\s\S]*?\*\// },
623
+ // Property keys (quoted string before colon)
624
+ { type: "type", pattern: /^"(?:[^"\\]|\\.)*"(?=\s*:)/ },
625
+ // String values
626
+ { type: "string", pattern: /^"(?:[^"\\]|\\.)*"/ },
627
+ // Numbers
628
+ { type: "number", pattern: /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/ },
629
+ // Constants
630
+ { type: "constant", pattern: /^\b(?:true|false|null)\b/ },
631
+ // Punctuation
632
+ { type: "punctuation", pattern: /^[{}()[\]:,]/ },
633
+ // Whitespace
634
+ { type: "text", pattern: /^\s+/ },
635
+ ]);
636
+ registerHighlighter(jsonHighlighter);
637
+ // ── Built-in: YAML ───────────────────────────────────────────────
638
+ const yamlHighlighter = regexHighlighter("yaml", ["yml"], [
639
+ // Comments
640
+ { type: "comment", pattern: /^#.*/ },
641
+ // Document markers
642
+ { type: "operator", pattern: /^(?:---|\.\.\.)$/ },
643
+ // Keys (word before colon at start of line or after indent)
644
+ { type: "type", pattern: /^[\w][\w.-]*(?=\s*:)/ },
645
+ // Anchors and aliases
646
+ { type: "decorator", pattern: /^[&*]\w+/ },
647
+ // Tags
648
+ { type: "attribute", pattern: /^![\w!./-]+/ },
649
+ // Strings
650
+ { type: "string", pattern: /^"(?:[^"\\]|\\.)*"/ },
651
+ { type: "string", pattern: /^'(?:[^'\\]|\\.)*'/ },
652
+ // Block scalar indicators
653
+ { type: "operator", pattern: /^[|>][+-]?(?=\s|$)/ },
654
+ // Constants
655
+ {
656
+ type: "constant",
657
+ pattern: /^\b(?:true|false|null|yes|no|on|off|True|False|Null|Yes|No|On|Off|TRUE|FALSE|NULL|YES|NO|ON|OFF)\b/,
658
+ },
659
+ // Numbers
660
+ { type: "number", pattern: /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/ },
661
+ { type: "number", pattern: /^0[xX][0-9a-fA-F]+/ },
662
+ { type: "number", pattern: /^0[oO][0-7]+/ },
663
+ // Punctuation
664
+ { type: "punctuation", pattern: /^[{}[\]:,\-?]/ },
665
+ // Plain scalars (unquoted values)
666
+ { type: "variable", pattern: /^[^\s#:,[\]{}]+/ },
667
+ // Whitespace
668
+ { type: "text", pattern: /^\s+/ },
669
+ ]);
670
+ registerHighlighter(yamlHighlighter);