@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,43 @@
1
+ /**
2
+ * Vertical scrollable container.
3
+ *
4
+ * Wraps a single child control and clips rendering to a visible
5
+ * window. Supports mouse wheel scrolling and arrow-key scrolling
6
+ * when the child does not consume the events.
7
+ */
8
+ import type { DrawingContext } from "../drawing/context.js";
9
+ import type { InputEvent } from "../input/events.js";
10
+ import { Control } from "../layout/control.js";
11
+ import type { Constraint, Rect, Size } from "../layout/types.js";
12
+ export interface ScrollViewOptions {
13
+ child?: Control;
14
+ maxHeight?: number;
15
+ }
16
+ export declare class ScrollView extends Control {
17
+ private _child;
18
+ private _scrollOffset;
19
+ private _maxHeight;
20
+ /** The child's full measured height (updated after measure). */
21
+ private _contentHeight;
22
+ constructor(options?: ScrollViewOptions);
23
+ get child(): Control | null;
24
+ set child(value: Control | null);
25
+ get scrollOffset(): number;
26
+ set scrollOffset(value: number);
27
+ get maxHeight(): number;
28
+ set maxHeight(value: number);
29
+ /** Total height of the child content. */
30
+ get contentHeight(): number;
31
+ /** Currently visible line range (0-based, inclusive top, exclusive bottom). */
32
+ get visibleRange(): {
33
+ top: number;
34
+ bottom: number;
35
+ };
36
+ /** Scroll so that the given y position (in child coordinates) is visible. */
37
+ scrollTo(y: number): void;
38
+ measure(constraint: Constraint): Size;
39
+ arrange(rect: Rect): void;
40
+ render(ctx: DrawingContext): void;
41
+ handleInput(event: InputEvent): boolean;
42
+ private _clampOffset;
43
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Vertical scrollable container.
3
+ *
4
+ * Wraps a single child control and clips rendering to a visible
5
+ * window. Supports mouse wheel scrolling and arrow-key scrolling
6
+ * when the child does not consume the events.
7
+ */
8
+ import { Control } from "../layout/control.js";
9
+ export class ScrollView extends Control {
10
+ _child;
11
+ _scrollOffset = 0;
12
+ _maxHeight;
13
+ /** The child's full measured height (updated after measure). */
14
+ _contentHeight = 0;
15
+ constructor(options = {}) {
16
+ super();
17
+ this._child = options.child ?? null;
18
+ this._maxHeight = options.maxHeight ?? Infinity;
19
+ if (this._child) {
20
+ this.children.push(this._child);
21
+ }
22
+ }
23
+ // ── Properties ────────────────────────────────────────────────
24
+ get child() {
25
+ return this._child;
26
+ }
27
+ set child(value) {
28
+ if (this._child) {
29
+ const idx = this.children.indexOf(this._child);
30
+ if (idx >= 0)
31
+ this.children.splice(idx, 1);
32
+ }
33
+ this._child = value;
34
+ if (value) {
35
+ this.children.push(value);
36
+ }
37
+ this._scrollOffset = 0;
38
+ this.invalidate();
39
+ }
40
+ get scrollOffset() {
41
+ return this._scrollOffset;
42
+ }
43
+ set scrollOffset(value) {
44
+ const clamped = this._clampOffset(value);
45
+ if (this._scrollOffset !== clamped) {
46
+ this._scrollOffset = clamped;
47
+ this.invalidate();
48
+ }
49
+ }
50
+ get maxHeight() {
51
+ return this._maxHeight;
52
+ }
53
+ set maxHeight(value) {
54
+ if (this._maxHeight !== value) {
55
+ this._maxHeight = value;
56
+ this.invalidate();
57
+ }
58
+ }
59
+ /** Total height of the child content. */
60
+ get contentHeight() {
61
+ return this._contentHeight;
62
+ }
63
+ /** Currently visible line range (0-based, inclusive top, exclusive bottom). */
64
+ get visibleRange() {
65
+ const bounds = this.bounds;
66
+ const visibleH = bounds ? bounds.height : 0;
67
+ return {
68
+ top: this._scrollOffset,
69
+ bottom: this._scrollOffset + visibleH,
70
+ };
71
+ }
72
+ // ── Public methods ────────────────────────────────────────────
73
+ /** Scroll so that the given y position (in child coordinates) is visible. */
74
+ scrollTo(y) {
75
+ const bounds = this.bounds;
76
+ const visibleH = bounds ? bounds.height : 0;
77
+ if (y < this._scrollOffset) {
78
+ this.scrollOffset = y;
79
+ }
80
+ else if (y >= this._scrollOffset + visibleH) {
81
+ this.scrollOffset = y - visibleH + 1;
82
+ }
83
+ }
84
+ // ── Layout ────────────────────────────────────────────────────
85
+ measure(constraint) {
86
+ if (!this._child) {
87
+ this._contentHeight = 0;
88
+ return { width: 0, height: 0 };
89
+ }
90
+ // Let the child measure with unconstrained height
91
+ const _childConstraint = {
92
+ minWidth: constraint.minWidth,
93
+ minHeight: 0,
94
+ maxWidth: constraint.maxWidth,
95
+ maxHeight: Infinity,
96
+ };
97
+ // The child should have been measured by the layout engine;
98
+ // we read its desired size.
99
+ const childSize = this._child.desiredSize ?? { width: 0, height: 0 };
100
+ this._contentHeight = childSize.height;
101
+ const visibleHeight = Math.min(childSize.height, this._maxHeight, constraint.maxHeight);
102
+ return {
103
+ width: Math.min(childSize.width, constraint.maxWidth),
104
+ height: visibleHeight,
105
+ };
106
+ }
107
+ arrange(rect) {
108
+ if (!this._child)
109
+ return;
110
+ // Give the child its full content height, not the clamped visible height
111
+ this._child.arrange({
112
+ x: rect.x,
113
+ y: rect.y - this._scrollOffset,
114
+ width: rect.width,
115
+ height: this._contentHeight,
116
+ });
117
+ // Re-clamp scroll offset in case content height changed
118
+ this._scrollOffset = this._clampOffset(this._scrollOffset);
119
+ }
120
+ render(ctx) {
121
+ const bounds = this.bounds;
122
+ if (!bounds || !this._child)
123
+ return;
124
+ // Push a clip rectangle so the child only renders within visible area
125
+ ctx.pushClip(bounds);
126
+ // The child was arranged with y offset accounting for scroll,
127
+ // so it renders correctly within the clip.
128
+ this._child.render(ctx);
129
+ ctx.popClip();
130
+ }
131
+ // ── Input handling ────────────────────────────────────────────
132
+ handleInput(event) {
133
+ // Let child handle first
134
+ if (this._child) {
135
+ const childHandled = this._child.handleInput(event);
136
+ if (childHandled)
137
+ return true;
138
+ }
139
+ if (event.type === "mouse") {
140
+ const me = event.event;
141
+ if (me.type === "wheelup") {
142
+ this.scrollOffset = this._scrollOffset - 3;
143
+ return true;
144
+ }
145
+ if (me.type === "wheeldown") {
146
+ this.scrollOffset = this._scrollOffset + 3;
147
+ return true;
148
+ }
149
+ }
150
+ if (event.type === "key") {
151
+ const ke = event.event;
152
+ if (ke.key === "up") {
153
+ this.scrollOffset = this._scrollOffset - 1;
154
+ return true;
155
+ }
156
+ if (ke.key === "down") {
157
+ this.scrollOffset = this._scrollOffset + 1;
158
+ return true;
159
+ }
160
+ if (ke.key === "pageup") {
161
+ const bounds = this.bounds;
162
+ const pageSize = bounds ? bounds.height : 10;
163
+ this.scrollOffset = this._scrollOffset - pageSize;
164
+ return true;
165
+ }
166
+ if (ke.key === "pagedown") {
167
+ const bounds = this.bounds;
168
+ const pageSize = bounds ? bounds.height : 10;
169
+ this.scrollOffset = this._scrollOffset + pageSize;
170
+ return true;
171
+ }
172
+ }
173
+ return false;
174
+ }
175
+ // ── Internal helpers ──────────────────────────────────────────
176
+ _clampOffset(offset) {
177
+ const bounds = this.bounds;
178
+ const visibleH = bounds ? bounds.height : 0;
179
+ const maxOffset = Math.max(0, this._contentHeight - visibleH);
180
+ return Math.max(0, Math.min(offset, maxOffset));
181
+ }
182
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * StyledText — text display widget that renders multi-styled lines.
3
+ *
4
+ * Unlike Text (which applies one TextStyle to the whole block), StyledText
5
+ * accepts StyledSpan lines where each segment can have its own color,
6
+ * bold, italic, etc. Plain strings are also accepted and rendered with
7
+ * a default style.
8
+ *
9
+ * Supports word wrapping, but wrapping is computed on the plain-text
10
+ * representation and styles are carried across wrap boundaries.
11
+ */
12
+ import type { DrawingContext, TextStyle } from "../drawing/context.js";
13
+ import { Control } from "../layout/control.js";
14
+ import type { Constraint, Size } from "../layout/types.js";
15
+ import { type StyledSpan } from "../styled.js";
16
+ /** A line of styled text: either a StyledSpan or a plain string. */
17
+ export type StyledLine = StyledSpan | string;
18
+ export interface StyledTextOptions {
19
+ lines?: StyledLine[];
20
+ defaultStyle?: TextStyle;
21
+ wrap?: boolean;
22
+ }
23
+ export declare class StyledText extends Control {
24
+ private _lines;
25
+ private _defaultStyle;
26
+ private _wrap;
27
+ /** Cached wrapped lines from the last measure pass. */
28
+ private _wrapped;
29
+ constructor(options?: StyledTextOptions);
30
+ get lines(): StyledLine[];
31
+ set lines(value: StyledLine[]);
32
+ get defaultStyle(): TextStyle;
33
+ set defaultStyle(value: TextStyle);
34
+ get wrap(): boolean;
35
+ set wrap(value: boolean);
36
+ measure(constraint: Constraint): Size;
37
+ render(ctx: DrawingContext): void;
38
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * StyledText — text display widget that renders multi-styled lines.
3
+ *
4
+ * Unlike Text (which applies one TextStyle to the whole block), StyledText
5
+ * accepts StyledSpan lines where each segment can have its own color,
6
+ * bold, italic, etc. Plain strings are also accepted and rendered with
7
+ * a default style.
8
+ *
9
+ * Supports word wrapping, but wrapping is computed on the plain-text
10
+ * representation and styles are carried across wrap boundaries.
11
+ */
12
+ import { Control } from "../layout/control.js";
13
+ import { charWidth, stringDisplayWidth } from "../pixel/symbol.js";
14
+ import { spanLength } from "../styled.js";
15
+ // ── Widget ───────────────────────────────────────────────────────
16
+ export class StyledText extends Control {
17
+ _lines;
18
+ _defaultStyle;
19
+ _wrap;
20
+ /** Cached wrapped lines from the last measure pass. */
21
+ _wrapped = [];
22
+ constructor(options = {}) {
23
+ super();
24
+ this._lines = options.lines ?? [];
25
+ this._defaultStyle = options.defaultStyle ?? {};
26
+ this._wrap = options.wrap ?? false;
27
+ }
28
+ // ── Properties ─────────────────────────────────────────────────
29
+ get lines() {
30
+ return this._lines;
31
+ }
32
+ set lines(value) {
33
+ this._lines = value;
34
+ this.invalidate();
35
+ }
36
+ get defaultStyle() {
37
+ return this._defaultStyle;
38
+ }
39
+ set defaultStyle(value) {
40
+ this._defaultStyle = value;
41
+ this.invalidate();
42
+ }
43
+ get wrap() {
44
+ return this._wrap;
45
+ }
46
+ set wrap(value) {
47
+ if (this._wrap !== value) {
48
+ this._wrap = value;
49
+ this.invalidate();
50
+ }
51
+ }
52
+ // ── Layout ─────────────────────────────────────────────────────
53
+ measure(constraint) {
54
+ if (this._lines.length === 0) {
55
+ this._wrapped = [];
56
+ return { width: 0, height: 0 };
57
+ }
58
+ if (!this._wrap) {
59
+ this._wrapped = this._lines;
60
+ const maxW = this._lines.reduce((max, line) => Math.max(max, lineLength(line)), 0);
61
+ return {
62
+ width: Math.min(maxW, constraint.maxWidth),
63
+ height: Math.min(this._lines.length, constraint.maxHeight),
64
+ };
65
+ }
66
+ // Word-wrap mode
67
+ const maxW = constraint.maxWidth;
68
+ if (maxW <= 0) {
69
+ this._wrapped = [];
70
+ return { width: 0, height: 0 };
71
+ }
72
+ const wrapped = [];
73
+ for (const line of this._lines) {
74
+ const sub = wrapStyledLine(line, maxW);
75
+ wrapped.push(...sub);
76
+ }
77
+ this._wrapped = wrapped;
78
+ const longestW = wrapped.reduce((max, line) => Math.max(max, lineLength(line)), 0);
79
+ return {
80
+ width: Math.min(longestW, constraint.maxWidth),
81
+ height: Math.min(wrapped.length, constraint.maxHeight),
82
+ };
83
+ }
84
+ render(ctx) {
85
+ const bounds = this.bounds;
86
+ if (!bounds || this._wrapped.length === 0)
87
+ return;
88
+ for (let i = 0; i < this._wrapped.length && i < bounds.height; i++) {
89
+ const line = this._wrapped[i];
90
+ if (typeof line === "string") {
91
+ ctx.drawText(bounds.x, bounds.y + i, line, this._defaultStyle);
92
+ }
93
+ else {
94
+ // Apply defaultStyle as fallback for segments without explicit colors
95
+ const merged = line.map((seg) => ({
96
+ text: seg.text,
97
+ style: mergeStyles(this._defaultStyle, seg.style),
98
+ }));
99
+ ctx.drawStyledText(bounds.x, bounds.y + i, merged);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ // ── Helpers ──────────────────────────────────────────────────────
105
+ function lineLength(line) {
106
+ if (typeof line === "string")
107
+ return stringDisplayWidth(line);
108
+ return spanLength(line);
109
+ }
110
+ /** Merge a default style with a segment's explicit style. */
111
+ function mergeStyles(base, over) {
112
+ return {
113
+ fg: over.fg ?? base.fg,
114
+ bg: over.bg ?? base.bg,
115
+ bold: over.bold ?? base.bold,
116
+ italic: over.italic ?? base.italic,
117
+ underline: over.underline ?? base.underline,
118
+ strikethrough: over.strikethrough ?? base.strikethrough,
119
+ };
120
+ }
121
+ /**
122
+ * Wrap a styled line to fit within maxWidth.
123
+ * For plain strings, standard word-wrap. For StyledSpans,
124
+ * we wrap on the concatenated text and split segments at boundaries.
125
+ */
126
+ function wrapStyledLine(line, maxWidth) {
127
+ if (typeof line === "string") {
128
+ return wrapPlainLine(line, maxWidth);
129
+ }
130
+ // Flatten to a stream of { char, style } for wrap calculation
131
+ const chars = [];
132
+ for (const seg of line) {
133
+ for (const ch of seg.text) {
134
+ chars.push({ char: ch, style: seg.style });
135
+ }
136
+ }
137
+ if (chars.length <= maxWidth)
138
+ return [line];
139
+ // Simple hard-wrap at maxWidth (word-aware wrap is complex for styled text)
140
+ const result = [];
141
+ for (let start = 0; start < chars.length; start += maxWidth) {
142
+ const slice = chars.slice(start, start + maxWidth);
143
+ // Coalesce consecutive chars with same style into segments
144
+ const segments = [];
145
+ let cur = null;
146
+ for (const { char, style } of slice) {
147
+ if (cur && cur.style === style) {
148
+ cur.text += char;
149
+ }
150
+ else {
151
+ if (cur)
152
+ segments.push(cur);
153
+ cur = { text: char, style };
154
+ }
155
+ }
156
+ if (cur)
157
+ segments.push(cur);
158
+ result.push(segments);
159
+ }
160
+ return result;
161
+ }
162
+ function wrapPlainLine(text, maxWidth) {
163
+ if (stringDisplayWidth(text) <= maxWidth)
164
+ return [text];
165
+ const result = [];
166
+ let current = "";
167
+ let currentWidth = 0;
168
+ for (const char of text) {
169
+ const w = charWidth(char.codePointAt(0));
170
+ if (currentWidth + w > maxWidth && current.length > 0) {
171
+ result.push(current);
172
+ current = char;
173
+ currentWidth = w;
174
+ }
175
+ else {
176
+ current += char;
177
+ currentWidth += w;
178
+ }
179
+ }
180
+ if (current)
181
+ result.push(current);
182
+ return result;
183
+ }
@@ -0,0 +1,37 @@
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 type { TextStyle } from "../drawing/context.js";
15
+ /** Semantic token types produced by highlighters. */
16
+ export type SyntaxTokenType = "keyword" | "string" | "number" | "comment" | "operator" | "punctuation" | "type" | "function" | "variable" | "constant" | "decorator" | "attribute" | "text";
17
+ export interface SyntaxToken {
18
+ text: string;
19
+ type: SyntaxTokenType;
20
+ }
21
+ /** Maps token types to text styles. */
22
+ export type SyntaxTheme = Record<SyntaxTokenType, TextStyle>;
23
+ export declare const DEFAULT_SYNTAX_THEME: SyntaxTheme;
24
+ export interface SyntaxHighlighter {
25
+ /** Canonical language name. */
26
+ name: string;
27
+ /** Language aliases (e.g. ["js", "jsx", "ts", "tsx"]). */
28
+ aliases: string[];
29
+ /** Tokenize a single line of source code. */
30
+ tokenize(line: string): SyntaxToken[];
31
+ }
32
+ /** Register a syntax highlighter for one or more language aliases. */
33
+ export declare function registerHighlighter(highlighter: SyntaxHighlighter): void;
34
+ /** Look up a highlighter by language name/alias. Returns null if not found. */
35
+ export declare function getHighlighter(lang: string): SyntaxHighlighter | null;
36
+ /** Tokenize a line using the registered highlighter for the given language. */
37
+ export declare function highlightLine(lang: string, line: string): SyntaxToken[];