@greppleai/grip-tui 0.0.0-stage → 0.0.1
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.
- package/README.md +818 -2
- package/dist/autocomplete.d.ts +48 -0
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +566 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/components/alt-screen-flash.d.ts +13 -0
- package/dist/components/alt-screen-flash.d.ts.map +1 -0
- package/dist/components/alt-screen-flash.js +37 -0
- package/dist/components/alt-screen-flash.js.map +1 -0
- package/dist/components/box.d.ts +22 -0
- package/dist/components/box.d.ts.map +1 -0
- package/dist/components/box.js +104 -0
- package/dist/components/box.js.map +1 -0
- package/dist/components/cancellable-loader.d.ts +22 -0
- package/dist/components/cancellable-loader.d.ts.map +1 -0
- package/dist/components/cancellable-loader.js +35 -0
- package/dist/components/cancellable-loader.js.map +1 -0
- package/dist/components/editor.d.ts +292 -0
- package/dist/components/editor.d.ts.map +1 -0
- package/dist/components/editor.js +1887 -0
- package/dist/components/editor.js.map +1 -0
- package/dist/components/h-stack.d.ts +7 -0
- package/dist/components/h-stack.d.ts.map +1 -0
- package/dist/components/h-stack.js +43 -0
- package/dist/components/h-stack.js.map +1 -0
- package/dist/components/image.d.ts +32 -0
- package/dist/components/image.d.ts.map +1 -0
- package/dist/components/image.js +76 -0
- package/dist/components/image.js.map +1 -0
- package/dist/components/input.d.ts +46 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +352 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/loader.d.ts +32 -0
- package/dist/components/loader.d.ts.map +1 -0
- package/dist/components/loader.js +80 -0
- package/dist/components/loader.js.map +1 -0
- package/dist/components/markdown.d.ts +132 -0
- package/dist/components/markdown.d.ts.map +1 -0
- package/dist/components/markdown.js +717 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/scroll-view.d.ts +53 -0
- package/dist/components/scroll-view.d.ts.map +1 -0
- package/dist/components/scroll-view.js +173 -0
- package/dist/components/scroll-view.js.map +1 -0
- package/dist/components/select-list.d.ts +50 -0
- package/dist/components/select-list.d.ts.map +1 -0
- package/dist/components/select-list.js +159 -0
- package/dist/components/select-list.js.map +1 -0
- package/dist/components/settings-list.d.ts +52 -0
- package/dist/components/settings-list.d.ts.map +1 -0
- package/dist/components/settings-list.js +187 -0
- package/dist/components/settings-list.js.map +1 -0
- package/dist/components/spacer.d.ts +12 -0
- package/dist/components/spacer.d.ts.map +1 -0
- package/dist/components/spacer.js +23 -0
- package/dist/components/spacer.js.map +1 -0
- package/dist/components/stack.d.ts +32 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +122 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/text.d.ts +19 -0
- package/dist/components/text.d.ts.map +1 -0
- package/dist/components/text.js +89 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/truncated-text.d.ts +13 -0
- package/dist/components/truncated-text.d.ts.map +1 -0
- package/dist/components/truncated-text.js +51 -0
- package/dist/components/truncated-text.js.map +1 -0
- package/dist/components/v-stack.d.ts +8 -0
- package/dist/components/v-stack.d.ts.map +1 -0
- package/dist/components/v-stack.js +27 -0
- package/dist/components/v-stack.js.map +1 -0
- package/dist/editor-component.d.ts +39 -0
- package/dist/editor-component.d.ts.map +1 -0
- package/dist/editor-component.js +2 -0
- package/dist/editor-component.js.map +1 -0
- package/dist/fuzzy.d.ts +16 -0
- package/dist/fuzzy.d.ts.map +1 -0
- package/dist/fuzzy.js +101 -0
- package/dist/fuzzy.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/keybindings.d.ts +243 -0
- package/dist/keybindings.d.ts.map +1 -0
- package/dist/keybindings.js +209 -0
- package/dist/keybindings.js.map +1 -0
- package/dist/keys.d.ts +184 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +1053 -0
- package/dist/keys.js.map +1 -0
- package/dist/kill-ring.d.ts +30 -0
- package/dist/kill-ring.d.ts.map +1 -0
- package/dist/kill-ring.js +37 -0
- package/dist/kill-ring.js.map +1 -0
- package/dist/latex.d.ts +10 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +1406 -0
- package/dist/latex.js.map +1 -0
- package/dist/layout-node.d.ts +43 -0
- package/dist/layout-node.d.ts.map +1 -0
- package/dist/layout-node.js +6 -0
- package/dist/layout-node.js.map +1 -0
- package/dist/layout.d.ts +40 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +386 -0
- package/dist/layout.js.map +1 -0
- package/dist/native-modifiers.d.ts +3 -0
- package/dist/native-modifiers.d.ts.map +1 -0
- package/dist/native-modifiers.js +53 -0
- package/dist/native-modifiers.js.map +1 -0
- package/dist/stdin-buffer.d.ts +58 -0
- package/dist/stdin-buffer.d.ts.map +1 -0
- package/dist/stdin-buffer.js +318 -0
- package/dist/stdin-buffer.js.map +1 -0
- package/dist/terminal-control.d.ts +5 -0
- package/dist/terminal-control.d.ts.map +1 -0
- package/dist/terminal-control.js +26 -0
- package/dist/terminal-control.js.map +1 -0
- package/dist/terminal-image.d.ts +112 -0
- package/dist/terminal-image.d.ts.map +1 -0
- package/dist/terminal-image.js +508 -0
- package/dist/terminal-image.js.map +1 -0
- package/dist/terminal.d.ts +117 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +490 -0
- package/dist/terminal.js.map +1 -0
- package/dist/tui-alt-screen.d.ts +114 -0
- package/dist/tui-alt-screen.d.ts.map +1 -0
- package/dist/tui-alt-screen.js +950 -0
- package/dist/tui-alt-screen.js.map +1 -0
- package/dist/tui-main-screen.d.ts +52 -0
- package/dist/tui-main-screen.d.ts.map +1 -0
- package/dist/tui-main-screen.js +511 -0
- package/dist/tui-main-screen.js.map +1 -0
- package/dist/tui.d.ts +295 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +868 -0
- package/dist/tui.js.map +1 -0
- package/dist/undo-stack.d.ts +17 -0
- package/dist/undo-stack.d.ts.map +1 -0
- package/dist/undo-stack.js +25 -0
- package/dist/undo-stack.js.map +1 -0
- package/dist/utils.d.ts +96 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +1106 -0
- package/dist/utils.js.map +1 -0
- package/dist/word-navigation.d.ts +25 -0
- package/dist/word-navigation.d.ts.map +1 -0
- package/dist/word-navigation.js +94 -0
- package/dist/word-navigation.js.map +1 -0
- package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/package.json +47 -5
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Component } from "../tui.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Default text styling for markdown content.
|
|
4
|
+
* Applied to all text unless overridden by markdown formatting.
|
|
5
|
+
*/
|
|
6
|
+
export interface DefaultTextStyle {
|
|
7
|
+
/** Foreground color function */
|
|
8
|
+
color?: (text: string) => string;
|
|
9
|
+
/** Background color function */
|
|
10
|
+
bgColor?: (text: string) => string;
|
|
11
|
+
/** Bold text */
|
|
12
|
+
bold?: boolean;
|
|
13
|
+
/** Italic text */
|
|
14
|
+
italic?: boolean;
|
|
15
|
+
/** Strikethrough text */
|
|
16
|
+
strikethrough?: boolean;
|
|
17
|
+
/** Underline text */
|
|
18
|
+
underline?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Theme functions for markdown elements.
|
|
22
|
+
* Each function takes text and returns styled text with ANSI codes.
|
|
23
|
+
*/
|
|
24
|
+
export interface MarkdownTheme {
|
|
25
|
+
heading: (text: string) => string;
|
|
26
|
+
link: (text: string) => string;
|
|
27
|
+
linkUrl: (text: string) => string;
|
|
28
|
+
code: (text: string) => string;
|
|
29
|
+
codeBlock: (text: string) => string;
|
|
30
|
+
codeBlockBorder: (text: string) => string;
|
|
31
|
+
quote: (text: string) => string;
|
|
32
|
+
quoteBorder: (text: string) => string;
|
|
33
|
+
hr: (text: string) => string;
|
|
34
|
+
listBullet: (text: string) => string;
|
|
35
|
+
bold: (text: string) => string;
|
|
36
|
+
italic: (text: string) => string;
|
|
37
|
+
strikethrough: (text: string) => string;
|
|
38
|
+
underline: (text: string) => string;
|
|
39
|
+
highlightCode?: (code: string, lang?: string) => string[];
|
|
40
|
+
/** Prefix applied to each rendered code block line (default: " ") */
|
|
41
|
+
codeBlockIndent?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface MarkdownOptions {
|
|
44
|
+
/** Preserve source ordered-list markers instead of normalizing them from the list start. */
|
|
45
|
+
preserveOrderedListMarkers?: boolean;
|
|
46
|
+
/** Preserve source backslash escapes instead of normalizing escaped punctuation. */
|
|
47
|
+
preserveBackslashEscapes?: boolean;
|
|
48
|
+
/** Transform source Markdown before parsing, with the exact width available for content. */
|
|
49
|
+
transform?: (markdown: string, availableWidth: number) => string;
|
|
50
|
+
/** Render supported LaTeX math expressions as Unicode text (default: true). */
|
|
51
|
+
renderLatex?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare class Markdown implements Component {
|
|
54
|
+
private text;
|
|
55
|
+
private paddingX;
|
|
56
|
+
private paddingY;
|
|
57
|
+
private defaultTextStyle?;
|
|
58
|
+
private theme;
|
|
59
|
+
private options;
|
|
60
|
+
private defaultStylePrefix?;
|
|
61
|
+
private cachedText?;
|
|
62
|
+
private cachedWidth?;
|
|
63
|
+
private cachedLines?;
|
|
64
|
+
constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, options?: MarkdownOptions);
|
|
65
|
+
setText(text: string): void;
|
|
66
|
+
invalidate(): void;
|
|
67
|
+
private renderMarkdownTokens;
|
|
68
|
+
private wrapRenderedLines;
|
|
69
|
+
private padRenderedLines;
|
|
70
|
+
private createVerticalPadding;
|
|
71
|
+
private cacheRenderedLines;
|
|
72
|
+
render(width: number): string[];
|
|
73
|
+
/**
|
|
74
|
+
* Apply default text style to a string.
|
|
75
|
+
* This is the base styling applied to all text content.
|
|
76
|
+
* NOTE: Background color is NOT applied here - it's applied at the padding stage
|
|
77
|
+
* to ensure it extends to the full line width.
|
|
78
|
+
*/
|
|
79
|
+
private applyDefaultStyle;
|
|
80
|
+
private getDefaultStylePrefix;
|
|
81
|
+
private getStylePrefix;
|
|
82
|
+
private getDefaultInlineStyleContext;
|
|
83
|
+
private shouldAppendBlockSpacing;
|
|
84
|
+
private renderHeadingToken;
|
|
85
|
+
private renderParagraphToken;
|
|
86
|
+
private renderLatexBlockToken;
|
|
87
|
+
private renderCodeToken;
|
|
88
|
+
private renderBlockquoteToken;
|
|
89
|
+
private renderHorizontalRule;
|
|
90
|
+
private renderHtmlBlock;
|
|
91
|
+
private renderFallbackToken;
|
|
92
|
+
private renderToken;
|
|
93
|
+
private applyInlineText;
|
|
94
|
+
private renderInlineLinkToken;
|
|
95
|
+
private renderInlineLatexToken;
|
|
96
|
+
private renderInlineTextToken;
|
|
97
|
+
private renderInlineHtmlToken;
|
|
98
|
+
private renderInlineFallbackToken;
|
|
99
|
+
private renderInlineToken;
|
|
100
|
+
private renderInlineTokens;
|
|
101
|
+
private getOrderedListMarker;
|
|
102
|
+
private getListItemMarker;
|
|
103
|
+
private renderListItem;
|
|
104
|
+
/**
|
|
105
|
+
* Render a list with proper nesting support
|
|
106
|
+
*/
|
|
107
|
+
private renderList;
|
|
108
|
+
/**
|
|
109
|
+
* Get the visible width of the longest word in a string.
|
|
110
|
+
*/
|
|
111
|
+
private getLongestWordWidth;
|
|
112
|
+
/**
|
|
113
|
+
* Wrap a table cell to fit into a column.
|
|
114
|
+
*
|
|
115
|
+
* Delegates to wrapTextWithAnsi() so ANSI codes + long tokens are handled
|
|
116
|
+
* consistently with the rest of the renderer.
|
|
117
|
+
*/
|
|
118
|
+
private wrapCellText;
|
|
119
|
+
/**
|
|
120
|
+
* Render a table with width-aware cell wrapping.
|
|
121
|
+
* Cells that don't fit are wrapped to multiple lines.
|
|
122
|
+
*/
|
|
123
|
+
private measureTableColumns;
|
|
124
|
+
private fitMinimumTableColumnWidths;
|
|
125
|
+
private distributeRemainingTableWidth;
|
|
126
|
+
private calculateTableColumnWidths;
|
|
127
|
+
private renderTableBorder;
|
|
128
|
+
private renderTableCellLines;
|
|
129
|
+
private appendTableRow;
|
|
130
|
+
private renderTable;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/components/markdown.ts"],"names":[],"mappings":"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","sourcesContent":["import { Marked, type Token, Tokenizer, type TokenizerExtension, type Tokens } from \"marked\";\nimport { renderLatex } from \"../latex.ts\";\nimport { getCapabilities, hyperlink, isImageLine } from \"../terminal-image.ts\";\nimport type { Component } from \"../tui.ts\";\nimport { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from \"../utils.ts\";\n\nconst STRICT_STRIKETHROUGH_REGEX = /^(~~)(?=[^\\s~])((?:\\\\.|[^\\\\])*?(?:\\\\.|[^\\s~\\\\]))\\1(?=[^~]|$)/;\nconst HTML_COMMENT_REGEX = /<!--[\\s\\S]*?-->/g;\n\nfunction stripHtmlComments(html: string): string {\n\treturn html.replace(HTML_COMMENT_REGEX, \"\");\n}\n\nclass StrictStrikethroughTokenizer extends Tokenizer {\n\toverride del(src: string): Tokens.Del | undefined {\n\t\tconst match = STRICT_STRIKETHROUGH_REGEX.exec(src);\n\t\tif (!match) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst text = match[2];\n\t\treturn {\n\t\t\ttype: \"del\",\n\t\t\traw: match[0],\n\t\t\ttext,\n\t\t\ttokens: this.lexer.inlineTokens(text),\n\t\t};\n\t}\n}\n\ntype LatexTokenType = \"latex\" | \"latexBlock\";\n\ninterface LatexToken extends Tokens.Generic {\n\ttype: LatexTokenType;\n\ttext: string;\n\tpending?: boolean;\n}\n\nfunction isEscaped(source: string, index: number): boolean {\n\tlet backslashes = 0;\n\tfor (let position = index - 1; position >= 0 && source[position] === \"\\\\\"; position--) {\n\t\tbackslashes++;\n\t}\n\treturn backslashes % 2 === 1;\n}\n\nfunction findClosingDelimiter(source: string, closing: string, start: number): number {\n\tlet index = source.indexOf(closing, start);\n\twhile (index >= 0 && isEscaped(source, index)) {\n\t\tindex = source.indexOf(closing, index + closing.length);\n\t}\n\treturn index;\n}\n\nfunction looksLikePendingDollarMath(source: string): boolean {\n\treturn /\\\\[A-Za-z]+|[_^=+*/<>()[\\]|±≤≥≠≈∈→⇒∞∫∑√-]/.test(source);\n}\n\nfunction looksLikeShellVariableChain(content: string, following: string): boolean {\n\treturn /^[A-Z_][A-Z0-9_]*(?:[/:.-][A-Za-z0-9_./:-]*)*$/.test(content) && /^[A-Za-z_][A-Za-z0-9_]*/.test(following);\n}\n\nfunction tokenizeInlineLatex(source: string): LatexToken | undefined {\n\tlet opening = \"\";\n\tlet closing = \"\";\n\tif (source.startsWith(\"$$\")) {\n\t\topening = \"$$\";\n\t\tclosing = \"$$\";\n\t} else if (source.startsWith(\"\\\\(\")) {\n\t\topening = \"\\\\(\";\n\t\tclosing = \"\\\\)\";\n\t} else if (source.startsWith(\"\\\\[\")) {\n\t\topening = \"\\\\[\";\n\t\tclosing = \"\\\\]\";\n\t} else if (source.startsWith(\"$\") && !/^\\$\\s/.test(source)) {\n\t\topening = \"$\";\n\t\tclosing = \"$\";\n\t} else {\n\t\treturn undefined;\n\t}\n\n\tconst closingIndex = findClosingDelimiter(source, closing, opening.length);\n\tif (\n\t\tclosingIndex >= 0 &&\n\t\topening === \"$\" &&\n\t\t(/\\s$/.test(source.slice(opening.length, closingIndex)) ||\n\t\t\t/^\\d/.test(source.slice(closingIndex + 1)) ||\n\t\t\tlooksLikeShellVariableChain(\n\t\t\t\tsource.slice(opening.length, closingIndex),\n\t\t\t\tsource.slice(closingIndex + closing.length),\n\t\t\t) ||\n\t\t\tsource.slice(opening.length, closingIndex).includes(\"`\"))\n\t) {\n\t\treturn undefined;\n\t}\n\n\tif (closingIndex < 0) {\n\t\tconst pendingSource = source.slice(opening.length);\n\t\tif (opening.startsWith(\"\\\\\") || looksLikePendingDollarMath(pendingSource)) {\n\t\t\treturn { type: \"latex\", raw: source, text: pendingSource, pending: true };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tconst text = source.slice(opening.length, closingIndex);\n\tif (!text || text.includes(\"\\n\")) {\n\t\treturn undefined;\n\t}\n\n\tconst raw = source.slice(0, closingIndex + closing.length);\n\treturn { type: \"latex\", raw, text };\n}\n\nfunction tokenizeBlockLatex(source: string): LatexToken | undefined {\n\tconst dollarMatch = /^ {0,3}\\$\\$[ \\t]*(?:\\n)?([\\s\\S]*?)\\$\\$[ \\t]*(?:\\n|$)/.exec(source);\n\tif (dollarMatch?.[1]) {\n\t\treturn { type: \"latexBlock\", raw: dollarMatch[0], text: dollarMatch[1].trim() };\n\t}\n\n\tconst bracketMatch = /^ {0,3}\\\\\\[[ \\t]*(?:\\n)?([\\s\\S]*?)\\\\\\][ \\t]*(?:\\n|$)/.exec(source);\n\tif (bracketMatch?.[1]) {\n\t\treturn { type: \"latexBlock\", raw: bracketMatch[0], text: bracketMatch[1].trim() };\n\t}\n\n\tconst pendingBracket = /^ {0,3}\\\\\\[[ \\t]*(?:\\n)?([\\s\\S]*)$/.exec(source);\n\tif (pendingBracket) {\n\t\treturn { type: \"latexBlock\", raw: pendingBracket[0], text: pendingBracket[1], pending: true };\n\t}\n\tconst pendingDollar = /^ {0,3}\\$\\$[ \\t]*(?:\\n)?([\\s\\S]*)$/.exec(source);\n\tif (pendingDollar?.[1] && looksLikePendingDollarMath(pendingDollar[1])) {\n\t\treturn { type: \"latexBlock\", raw: pendingDollar[0], text: pendingDollar[1], pending: true };\n\t}\n\treturn undefined;\n}\n\nconst LATEX_MARKDOWN_EXTENSIONS: readonly TokenizerExtension[] = [\n\t{\n\t\tname: \"latexBlock\",\n\t\tlevel: \"block\",\n\t\tstart(source) {\n\t\t\tconst match = /(?:^|\\n) {0,3}(?:\\$\\$|\\\\\\[)/.exec(source);\n\t\t\treturn match ? match.index + (match[0].startsWith(\"\\n\") ? 1 : 0) : undefined;\n\t\t},\n\t\ttokenizer: tokenizeBlockLatex,\n\t},\n\t{\n\t\tname: \"latex\",\n\t\tlevel: \"inline\",\n\t\tstart(source) {\n\t\t\tconst indices = [source.indexOf(\"$\"), source.indexOf(\"\\\\(\"), source.indexOf(\"\\\\[\")].filter(\n\t\t\t\t(index) => index >= 0,\n\t\t\t);\n\t\t\treturn indices.length > 0 ? Math.min(...indices) : undefined;\n\t\t},\n\t\ttokenizer: tokenizeInlineLatex,\n\t},\n];\n\nconst markdownParser = new Marked();\nmarkdownParser.setOptions({\n\ttokenizer: new StrictStrikethroughTokenizer(),\n});\nmarkdownParser.use({ extensions: [...LATEX_MARKDOWN_EXTENSIONS] });\n\n/**\n * Default text styling for markdown content.\n * Applied to all text unless overridden by markdown formatting.\n */\nexport interface DefaultTextStyle {\n\t/** Foreground color function */\n\tcolor?: (text: string) => string;\n\t/** Background color function */\n\tbgColor?: (text: string) => string;\n\t/** Bold text */\n\tbold?: boolean;\n\t/** Italic text */\n\titalic?: boolean;\n\t/** Strikethrough text */\n\tstrikethrough?: boolean;\n\t/** Underline text */\n\tunderline?: boolean;\n}\n\n/**\n * Theme functions for markdown elements.\n * Each function takes text and returns styled text with ANSI codes.\n */\nexport interface MarkdownTheme {\n\theading: (text: string) => string;\n\tlink: (text: string) => string;\n\tlinkUrl: (text: string) => string;\n\tcode: (text: string) => string;\n\tcodeBlock: (text: string) => string;\n\tcodeBlockBorder: (text: string) => string;\n\tquote: (text: string) => string;\n\tquoteBorder: (text: string) => string;\n\thr: (text: string) => string;\n\tlistBullet: (text: string) => string;\n\tbold: (text: string) => string;\n\titalic: (text: string) => string;\n\tstrikethrough: (text: string) => string;\n\tunderline: (text: string) => string;\n\thighlightCode?: (code: string, lang?: string) => string[];\n\t/** Prefix applied to each rendered code block line (default: \" \") */\n\tcodeBlockIndent?: string;\n}\n\nexport interface MarkdownOptions {\n\t/** Preserve source ordered-list markers instead of normalizing them from the list start. */\n\tpreserveOrderedListMarkers?: boolean;\n\t/** Preserve source backslash escapes instead of normalizing escaped punctuation. */\n\tpreserveBackslashEscapes?: boolean;\n\t/** Transform source Markdown before parsing, with the exact width available for content. */\n\ttransform?: (markdown: string, availableWidth: number) => string;\n\t/** Render supported LaTeX math expressions as Unicode text (default: true). */\n\trenderLatex?: boolean;\n}\n\ninterface InlineStyleContext {\n\tapplyText: (text: string) => string;\n\tstylePrefix: string;\n}\n\ninterface TableColumnMeasurements {\n\tnaturalWidths: number[];\n\tminimumWidths: number[];\n}\n\nexport class Markdown implements Component {\n\tprivate text: string;\n\tprivate paddingX: number; // Left/right padding\n\tprivate paddingY: number; // Top/bottom padding\n\tprivate defaultTextStyle?: DefaultTextStyle;\n\tprivate theme: MarkdownTheme;\n\tprivate options: MarkdownOptions;\n\tprivate defaultStylePrefix?: string;\n\n\t// Cache for rendered output\n\tprivate cachedText?: string;\n\tprivate cachedWidth?: number;\n\tprivate cachedLines?: string[];\n\n\tconstructor(\n\t\ttext: string,\n\t\tpaddingX: number,\n\t\tpaddingY: number,\n\t\ttheme: MarkdownTheme,\n\t\tdefaultTextStyle?: DefaultTextStyle,\n\t\toptions?: MarkdownOptions,\n\t) {\n\t\tthis.text = text;\n\t\tthis.paddingX = paddingX;\n\t\tthis.paddingY = paddingY;\n\t\tthis.theme = theme;\n\t\tthis.defaultTextStyle = defaultTextStyle;\n\t\tthis.options = options ? { ...options } : {};\n\t}\n\n\tsetText(text: string): void {\n\t\tthis.text = text;\n\t\tthis.invalidate();\n\t}\n\n\tinvalidate(): void {\n\t\tthis.cachedText = undefined;\n\t\tthis.cachedWidth = undefined;\n\t\tthis.cachedLines = undefined;\n\t}\n\n\tprivate renderMarkdownTokens(text: string, contentWidth: number): string[] {\n\t\tconst tokens = markdownParser.lexer(text);\n\t\tconst renderedLines: string[] = [];\n\t\tfor (let index = 0; index < tokens.length; index++) {\n\t\t\tconst tokenLines = this.renderToken(tokens[index], contentWidth, tokens[index + 1]?.type);\n\t\t\tfor (const tokenLine of tokenLines) renderedLines.push(tokenLine);\n\t\t}\n\t\treturn renderedLines;\n\t}\n\n\tprivate wrapRenderedLines(renderedLines: string[], contentWidth: number): string[] {\n\t\tconst wrappedLines: string[] = [];\n\t\tfor (const line of renderedLines) {\n\t\t\tif (isImageLine(line)) {\n\t\t\t\twrappedLines.push(line);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const wrappedLine of wrapTextWithAnsi(line, contentWidth)) wrappedLines.push(wrappedLine);\n\t\t}\n\t\treturn wrappedLines;\n\t}\n\n\tprivate padRenderedLines(lines: string[], width: number, paddingX: number): string[] {\n\t\tconst margin = \" \".repeat(paddingX);\n\t\tconst bgFn = this.defaultTextStyle?.bgColor;\n\t\tconst contentLines: string[] = [];\n\t\tfor (const line of lines) {\n\t\t\tif (isImageLine(line)) {\n\t\t\t\tcontentLines.push(line);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst lineWithMargins = margin + line + margin;\n\t\t\tif (bgFn) {\n\t\t\t\tcontentLines.push(applyBackgroundToLine(lineWithMargins, width, bgFn));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst paddingNeeded = Math.max(0, width - visibleWidth(lineWithMargins));\n\t\t\tcontentLines.push(lineWithMargins + \" \".repeat(paddingNeeded));\n\t\t}\n\t\treturn contentLines;\n\t}\n\n\tprivate createVerticalPadding(width: number): string[] {\n\t\tconst bgFn = this.defaultTextStyle?.bgColor;\n\t\tconst emptyLine = \" \".repeat(width);\n\t\tconst emptyLines: string[] = [];\n\t\tfor (let index = 0; index < this.paddingY; index++) {\n\t\t\temptyLines.push(bgFn ? applyBackgroundToLine(emptyLine, width, bgFn) : emptyLine);\n\t\t}\n\t\treturn emptyLines;\n\t}\n\n\tprivate cacheRenderedLines(width: number, lines: string[]): void {\n\t\tthis.cachedText = this.text;\n\t\tthis.cachedWidth = width;\n\t\tthis.cachedLines = lines;\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.cachedLines && this.cachedText === this.text && this.cachedWidth === width) {\n\t\t\treturn this.cachedLines;\n\t\t}\n\n\t\tconst effectivePaddingX = Math.min(this.paddingX, Math.floor(Math.max(0, width - 1) / 2));\n\t\tconst contentWidth = Math.max(1, width - effectivePaddingX * 2);\n\t\tconst text = this.options.transform?.(this.text, contentWidth) ?? this.text;\n\t\tif (!text || text.trim() === \"\") {\n\t\t\tconst result: string[] = [];\n\t\t\tthis.cacheRenderedLines(width, result);\n\t\t\treturn result;\n\t\t}\n\n\t\tconst normalizedText = text.replace(/\\t/g, \" \");\n\t\tconst renderedLines = this.renderMarkdownTokens(normalizedText, contentWidth);\n\t\tconst wrappedLines = this.wrapRenderedLines(renderedLines, contentWidth);\n\t\tconst contentLines = this.padRenderedLines(wrappedLines, width, effectivePaddingX);\n\t\tconst verticalPadding = this.createVerticalPadding(width);\n\t\tconst result = verticalPadding.concat(contentLines, verticalPadding);\n\t\tthis.cacheRenderedLines(width, result);\n\t\treturn result.length > 0 ? result : [\"\"];\n\t}\n\n\t/**\n\t * Apply default text style to a string.\n\t * This is the base styling applied to all text content.\n\t * NOTE: Background color is NOT applied here - it's applied at the padding stage\n\t * to ensure it extends to the full line width.\n\t */\n\tprivate applyDefaultStyle(text: string): string {\n\t\tif (!this.defaultTextStyle) {\n\t\t\treturn text;\n\t\t}\n\n\t\tlet styled = text;\n\n\t\t// Apply foreground color (NOT background - that's applied at padding stage)\n\t\tif (this.defaultTextStyle.color) {\n\t\t\tstyled = this.defaultTextStyle.color(styled);\n\t\t}\n\n\t\t// Apply text decorations using this.theme\n\t\tif (this.defaultTextStyle.bold) {\n\t\t\tstyled = this.theme.bold(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.italic) {\n\t\t\tstyled = this.theme.italic(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.strikethrough) {\n\t\t\tstyled = this.theme.strikethrough(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.underline) {\n\t\t\tstyled = this.theme.underline(styled);\n\t\t}\n\n\t\treturn styled;\n\t}\n\n\tprivate getDefaultStylePrefix(): string {\n\t\tif (!this.defaultTextStyle) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tif (this.defaultStylePrefix !== undefined) {\n\t\t\treturn this.defaultStylePrefix;\n\t\t}\n\n\t\tconst sentinel = \"\\u0000\";\n\t\tlet styled = sentinel;\n\n\t\tif (this.defaultTextStyle.color) {\n\t\t\tstyled = this.defaultTextStyle.color(styled);\n\t\t}\n\n\t\tif (this.defaultTextStyle.bold) {\n\t\t\tstyled = this.theme.bold(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.italic) {\n\t\t\tstyled = this.theme.italic(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.strikethrough) {\n\t\t\tstyled = this.theme.strikethrough(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.underline) {\n\t\t\tstyled = this.theme.underline(styled);\n\t\t}\n\n\t\tconst sentinelIndex = styled.indexOf(sentinel);\n\t\tthis.defaultStylePrefix = sentinelIndex >= 0 ? styled.slice(0, sentinelIndex) : \"\";\n\t\treturn this.defaultStylePrefix;\n\t}\n\n\tprivate getStylePrefix(styleFn: (text: string) => string): string {\n\t\tconst sentinel = \"\\u0000\";\n\t\tconst styled = styleFn(sentinel);\n\t\tconst sentinelIndex = styled.indexOf(sentinel);\n\t\treturn sentinelIndex >= 0 ? styled.slice(0, sentinelIndex) : \"\";\n\t}\n\n\tprivate getDefaultInlineStyleContext(): InlineStyleContext {\n\t\treturn {\n\t\t\tapplyText: (text: string) => this.applyDefaultStyle(text),\n\t\t\tstylePrefix: this.getDefaultStylePrefix(),\n\t\t};\n\t}\n\n\tprivate shouldAppendBlockSpacing(nextTokenType?: string): boolean {\n\t\treturn Boolean(nextTokenType && nextTokenType !== \"space\");\n\t}\n\n\tprivate renderHeadingToken(token: Tokens.Heading, nextTokenType?: string): string[] {\n\t\tconst headingPrefix = `${\"#\".repeat(token.depth)} `;\n\t\tconst headingStyle =\n\t\t\ttoken.depth === 1\n\t\t\t\t? (text: string) => this.theme.heading(this.theme.bold(this.theme.underline(text)))\n\t\t\t\t: (text: string) => this.theme.heading(this.theme.bold(text));\n\t\tconst styleContext: InlineStyleContext = {\n\t\t\tapplyText: headingStyle,\n\t\t\tstylePrefix: this.getStylePrefix(headingStyle),\n\t\t};\n\t\tconst headingText = this.renderInlineTokens(token.tokens || [], styleContext);\n\t\tconst styledHeading = token.depth >= 3 ? headingStyle(headingPrefix) + headingText : headingText;\n\t\treturn this.shouldAppendBlockSpacing(nextTokenType) ? [styledHeading, \"\"] : [styledHeading];\n\t}\n\n\tprivate renderParagraphToken(\n\t\ttoken: Tokens.Paragraph,\n\t\tnextTokenType?: string,\n\t\tstyleContext?: InlineStyleContext,\n\t): string[] {\n\t\tconst lines = [this.renderInlineTokens(token.tokens || [], styleContext)];\n\t\tif (nextTokenType && nextTokenType !== \"list\" && nextTokenType !== \"space\") lines.push(\"\");\n\t\treturn lines;\n\t}\n\n\tprivate renderLatexBlockToken(token: LatexToken, width: number, nextTokenType?: string): string[] {\n\t\tconst candidate =\n\t\t\t!token.pending && this.options.renderLatex !== false ? renderLatex(token.text, { display: true }) : undefined;\n\t\tconst rendered = candidate?.split(\"\\n\").every((line) => visibleWidth(line) <= width)\n\t\t\t? candidate\n\t\t\t: token.raw.trim();\n\t\tconst lines = rendered.split(\"\\n\").map((line) => this.applyDefaultStyle(line));\n\t\tif (this.shouldAppendBlockSpacing(nextTokenType)) lines.push(\"\");\n\t\treturn lines;\n\t}\n\n\tprivate renderCodeToken(token: Tokens.Code, nextTokenType?: string): string[] {\n\t\tconst indent = this.theme.codeBlockIndent ?? \" \";\n\t\tconst lines = [this.theme.codeBlockBorder(`\\`\\`\\`${token.lang || \"\"}`)];\n\t\tif (this.theme.highlightCode) {\n\t\t\tfor (const highlightedLine of this.theme.highlightCode(token.text, token.lang)) {\n\t\t\t\tlines.push(`${indent}${highlightedLine}`);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const codeLine of token.text.split(\"\\n\")) {\n\t\t\t\tlines.push(`${indent}${this.theme.codeBlock(codeLine)}`);\n\t\t\t}\n\t\t}\n\t\tlines.push(this.theme.codeBlockBorder(\"```\"));\n\t\tif (this.shouldAppendBlockSpacing(nextTokenType)) lines.push(\"\");\n\t\treturn lines;\n\t}\n\n\tprivate renderBlockquoteToken(token: Tokens.Blockquote, width: number, nextTokenType?: string): string[] {\n\t\tconst quoteStyle = (text: string) => this.theme.quote(this.theme.italic(text));\n\t\tconst quoteStylePrefix = this.getStylePrefix(quoteStyle);\n\t\tconst quoteContentWidth = Math.max(1, width - 2);\n\t\tconst quoteStyleContext: InlineStyleContext = {\n\t\t\tapplyText: (text: string) => text,\n\t\t\tstylePrefix: quoteStylePrefix,\n\t\t};\n\t\tconst renderedQuoteLines: string[] = [];\n\t\tconst quoteTokens = token.tokens || [];\n\t\tfor (let index = 0; index < quoteTokens.length; index++) {\n\t\t\trenderedQuoteLines.push(\n\t\t\t\t...this.renderToken(quoteTokens[index], quoteContentWidth, quoteTokens[index + 1]?.type, quoteStyleContext),\n\t\t\t);\n\t\t}\n\t\twhile (renderedQuoteLines.length > 0 && renderedQuoteLines[renderedQuoteLines.length - 1] === \"\") {\n\t\t\trenderedQuoteLines.pop();\n\t\t}\n\n\t\tconst lines: string[] = [];\n\t\tfor (const quoteLine of renderedQuoteLines) {\n\t\t\tconst lineWithReappliedStyle = quoteStylePrefix\n\t\t\t\t? quoteLine.replace(/\\x1b\\[0m/g, `\\x1b[0m${quoteStylePrefix}`)\n\t\t\t\t: quoteLine;\n\t\t\tfor (const wrappedLine of wrapTextWithAnsi(quoteStyle(lineWithReappliedStyle), quoteContentWidth)) {\n\t\t\t\tlines.push(this.theme.quoteBorder(\"│ \") + wrappedLine);\n\t\t\t}\n\t\t}\n\t\tif (this.shouldAppendBlockSpacing(nextTokenType)) lines.push(\"\");\n\t\treturn lines;\n\t}\n\n\tprivate renderHorizontalRule(width: number, nextTokenType?: string): string[] {\n\t\tconst lines = [this.theme.hr(\"─\".repeat(Math.min(width, 80)))];\n\t\tif (this.shouldAppendBlockSpacing(nextTokenType)) lines.push(\"\");\n\t\treturn lines;\n\t}\n\n\tprivate renderHtmlBlock(token: Token): string[] {\n\t\tif (!(\"raw\" in token) || typeof token.raw !== \"string\") return [];\n\t\tconst visibleHtml = stripHtmlComments(token.raw).trim();\n\t\treturn visibleHtml ? [this.applyDefaultStyle(visibleHtml)] : [];\n\t}\n\n\tprivate renderFallbackToken(token: Token): string[] {\n\t\treturn \"text\" in token && typeof token.text === \"string\" ? [token.text] : [];\n\t}\n\n\tprivate renderToken(\n\t\ttoken: Token,\n\t\twidth: number,\n\t\tnextTokenType?: string,\n\t\tstyleContext?: InlineStyleContext,\n\t): string[] {\n\t\tswitch (token.type) {\n\t\t\tcase \"heading\":\n\t\t\t\treturn this.renderHeadingToken(token as Tokens.Heading, nextTokenType);\n\t\t\tcase \"paragraph\":\n\t\t\t\treturn this.renderParagraphToken(token as Tokens.Paragraph, nextTokenType, styleContext);\n\t\t\tcase \"text\":\n\t\t\t\treturn [this.renderInlineTokens([token], styleContext)];\n\t\t\tcase \"latexBlock\":\n\t\t\t\treturn this.renderLatexBlockToken(token as LatexToken, width, nextTokenType);\n\t\t\tcase \"code\":\n\t\t\t\treturn this.renderCodeToken(token as Tokens.Code, nextTokenType);\n\t\t\tcase \"list\":\n\t\t\t\treturn this.renderList(token as Tokens.List, 0, width, styleContext);\n\t\t\tcase \"table\":\n\t\t\t\treturn this.renderTable(token as Tokens.Table, width, nextTokenType, styleContext);\n\t\t\tcase \"blockquote\":\n\t\t\t\treturn this.renderBlockquoteToken(token as Tokens.Blockquote, width, nextTokenType);\n\t\t\tcase \"hr\":\n\t\t\t\treturn this.renderHorizontalRule(width, nextTokenType);\n\t\t\tcase \"html\":\n\t\t\t\treturn this.renderHtmlBlock(token);\n\t\t\tcase \"space\":\n\t\t\t\treturn [\"\"];\n\t\t\tdefault:\n\t\t\t\treturn this.renderFallbackToken(token);\n\t\t}\n\t}\n\n\tprivate applyInlineText(text: string, styleContext: InlineStyleContext): string {\n\t\treturn text\n\t\t\t.split(\"\\n\")\n\t\t\t.map((segment) => styleContext.applyText(segment))\n\t\t\t.join(\"\\n\");\n\t}\n\n\tprivate renderInlineLinkToken(token: Tokens.Link, styleContext: InlineStyleContext): string {\n\t\tconst linkText = this.renderInlineTokens(token.tokens || [], styleContext);\n\t\tconst styledLink = this.theme.link(this.theme.underline(linkText));\n\t\tif (getCapabilities().hyperlinks) {\n\t\t\t// OSC 8: render as a clickable hyperlink. The URL is not printed inline,\n\t\t\t// so we always show only the link text regardless of whether it matches href.\n\t\t\treturn hyperlink(styledLink, token.href) + styleContext.stylePrefix;\n\t\t}\n\n\t\t// Fallback: print URL in parentheses when text differs from href.\n\t\t// Compare raw token.text (not styled) against href for the equality check.\n\t\t// For mailto: links strip the prefix (autolinked emails use text=\"foo@bar.com\"\n\t\t// but href=\"mailto:foo@bar.com\").\n\t\tconst hrefForComparison = token.href.startsWith(\"mailto:\") ? token.href.slice(7) : token.href;\n\t\tif (token.text === token.href || token.text === hrefForComparison) {\n\t\t\treturn styledLink + styleContext.stylePrefix;\n\t\t}\n\t\treturn styledLink + this.theme.linkUrl(` (${token.href})`) + styleContext.stylePrefix;\n\t}\n\n\tprivate renderInlineLatexToken(token: LatexToken, styleContext: InlineStyleContext): string {\n\t\tconst rendered =\n\t\t\t!token.pending && this.options.renderLatex !== false ? (renderLatex(token.text) ?? token.raw) : token.raw;\n\t\treturn this.applyInlineText(rendered, styleContext);\n\t}\n\n\tprivate renderInlineTextToken(token: Tokens.Text, styleContext: InlineStyleContext): string {\n\t\t// Text tokens in list items can have nested tokens for inline formatting.\n\t\treturn token.tokens?.length\n\t\t\t? this.renderInlineTokens(token.tokens, styleContext)\n\t\t\t: this.applyInlineText(token.text, styleContext);\n\t}\n\n\tprivate renderInlineHtmlToken(token: Token, styleContext: InlineStyleContext): string {\n\t\tif (!(\"raw\" in token) || typeof token.raw !== \"string\") return \"\";\n\t\tconst visibleHtml = stripHtmlComments(token.raw);\n\t\treturn visibleHtml ? this.applyInlineText(visibleHtml, styleContext) : \"\";\n\t}\n\n\tprivate renderInlineFallbackToken(token: Token, styleContext: InlineStyleContext): string {\n\t\tif (!(\"text\" in token) || typeof token.text !== \"string\") return \"\";\n\t\treturn this.applyInlineText(token.text, styleContext);\n\t}\n\n\tprivate renderInlineToken(token: Token, styleContext: InlineStyleContext): string {\n\t\tconst { stylePrefix } = styleContext;\n\t\tswitch (token.type) {\n\t\t\tcase \"latex\":\n\t\t\t\treturn this.renderInlineLatexToken(token as LatexToken, styleContext);\n\t\t\tcase \"escape\":\n\t\t\t\treturn this.applyInlineText(this.options.preserveBackslashEscapes ? token.raw : token.text, styleContext);\n\t\t\tcase \"text\":\n\t\t\t\treturn this.renderInlineTextToken(token as Tokens.Text, styleContext);\n\t\t\tcase \"paragraph\":\n\t\t\t\treturn this.renderInlineTokens(token.tokens || [], styleContext);\n\t\t\tcase \"strong\":\n\t\t\t\treturn this.theme.bold(this.renderInlineTokens(token.tokens || [], styleContext)) + stylePrefix;\n\t\t\tcase \"em\":\n\t\t\t\treturn this.theme.italic(this.renderInlineTokens(token.tokens || [], styleContext)) + stylePrefix;\n\t\t\tcase \"codespan\":\n\t\t\t\treturn this.theme.code(token.text) + stylePrefix;\n\t\t\tcase \"link\":\n\t\t\t\treturn this.renderInlineLinkToken(token as Tokens.Link, styleContext);\n\t\t\tcase \"br\":\n\t\t\t\treturn \"\\n\";\n\t\t\tcase \"del\":\n\t\t\t\treturn this.theme.strikethrough(this.renderInlineTokens(token.tokens || [], styleContext)) + stylePrefix;\n\t\t\tcase \"html\":\n\t\t\t\treturn this.renderInlineHtmlToken(token, styleContext);\n\t\t\tdefault:\n\t\t\t\treturn this.renderInlineFallbackToken(token, styleContext);\n\t\t}\n\t}\n\n\tprivate renderInlineTokens(tokens: Token[], styleContext?: InlineStyleContext): string {\n\t\tconst resolvedStyleContext = styleContext ?? this.getDefaultInlineStyleContext();\n\t\tlet result = \"\";\n\t\tfor (const token of tokens) {\n\t\t\tresult += this.renderInlineToken(token, resolvedStyleContext);\n\t\t}\n\n\t\twhile (resolvedStyleContext.stylePrefix && result.endsWith(resolvedStyleContext.stylePrefix)) {\n\t\t\tresult = result.slice(0, -resolvedStyleContext.stylePrefix.length);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate getOrderedListMarker(item: Tokens.ListItem): string | undefined {\n\t\tconst match = /^(?: {0,3})(\\d{1,9}[.)])[ \\t]+/.exec(item.raw);\n\t\treturn match ? `${match[1]} ` : undefined;\n\t}\n\n\tprivate getListItemMarker(token: Tokens.List, item: Tokens.ListItem, index: number, startNumber: number): string {\n\t\tif (!token.ordered) return \"- \";\n\t\tif (this.options.preserveOrderedListMarkers) {\n\t\t\treturn this.getOrderedListMarker(item) ?? `${startNumber + index}. `;\n\t\t}\n\t\treturn `${startNumber + index}. `;\n\t}\n\n\tprivate renderListItem(\n\t\ttoken: Tokens.List,\n\t\titem: Tokens.ListItem,\n\t\tindex: number,\n\t\tstartNumber: number,\n\t\tdepth: number,\n\t\twidth: number,\n\t\tstyleContext?: InlineStyleContext,\n\t): string[] {\n\t\tconst indent = \" \".repeat(depth);\n\t\tconst bullet = this.getListItemMarker(token, item, index, startNumber);\n\t\tconst taskMarker = item.task ? `[${item.checked ? \"x\" : \" \"}] ` : \"\";\n\t\tconst marker = bullet + taskMarker;\n\t\tconst firstPrefix = indent + this.theme.listBullet(marker);\n\t\tconst continuationPrefix = indent + \" \".repeat(visibleWidth(marker));\n\t\tconst itemWidth = Math.max(1, width - visibleWidth(firstPrefix));\n\t\tconst lines: string[] = [];\n\t\tlet renderedAnyLine = false;\n\n\t\tfor (const itemToken of item.tokens) {\n\t\t\tif (itemToken.type === \"list\") {\n\t\t\t\tlines.push(...this.renderList(itemToken as Tokens.List, depth + 1, width, styleContext));\n\t\t\t\trenderedAnyLine = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst itemLines = this.renderToken(itemToken, itemWidth, undefined, styleContext);\n\t\t\tconst wrappedItemLines = itemLines.flatMap((line) => wrapTextWithAnsi(line, itemWidth));\n\t\t\tfor (const wrappedLine of wrappedItemLines) {\n\t\t\t\tconst linePrefix = renderedAnyLine ? continuationPrefix : firstPrefix;\n\t\t\t\tlines.push(linePrefix + wrappedLine);\n\t\t\t\trenderedAnyLine = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!renderedAnyLine) lines.push(firstPrefix);\n\t\treturn lines;\n\t}\n\n\t/**\n\t * Render a list with proper nesting support\n\t */\n\tprivate renderList(token: Tokens.List, depth: number, width: number, styleContext?: InlineStyleContext): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst startNumber = typeof token.start === \"number\" ? token.start : 1;\n\t\tfor (let index = 0; index < token.items.length; index++) {\n\t\t\tlines.push(...this.renderListItem(token, token.items[index], index, startNumber, depth, width, styleContext));\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/**\n\t * Get the visible width of the longest word in a string.\n\t */\n\tprivate getLongestWordWidth(text: string, maxWidth?: number): number {\n\t\tconst words = text.split(/\\s+/).filter((word) => word.length > 0);\n\t\tlet longest = 0;\n\t\tfor (const word of words) {\n\t\t\tlongest = Math.max(longest, visibleWidth(word));\n\t\t}\n\t\tif (maxWidth === undefined) {\n\t\t\treturn longest;\n\t\t}\n\t\treturn Math.min(longest, maxWidth);\n\t}\n\n\t/**\n\t * Wrap a table cell to fit into a column.\n\t *\n\t * Delegates to wrapTextWithAnsi() so ANSI codes + long tokens are handled\n\t * consistently with the rest of the renderer.\n\t */\n\tprivate wrapCellText(text: string, maxWidth: number): string[] {\n\t\treturn wrapTextWithAnsi(text, Math.max(1, maxWidth));\n\t}\n\n\t/**\n\t * Render a table with width-aware cell wrapping.\n\t * Cells that don't fit are wrapped to multiple lines.\n\t */\n\tprivate measureTableColumns(\n\t\ttoken: Tokens.Table,\n\t\tstyleContext: InlineStyleContext | undefined,\n\t): TableColumnMeasurements {\n\t\tconst naturalWidths: number[] = [];\n\t\tconst minimumWidths: number[] = [];\n\t\tconst maxUnbrokenWordWidth = 30;\n\t\tfor (let column = 0; column < token.header.length; column++) {\n\t\t\tconst headerText = this.renderInlineTokens(token.header[column].tokens || [], styleContext);\n\t\t\tnaturalWidths[column] = visibleWidth(headerText);\n\t\t\tminimumWidths[column] = Math.max(1, this.getLongestWordWidth(headerText, maxUnbrokenWordWidth));\n\t\t}\n\t\tfor (const row of token.rows) {\n\t\t\tfor (let column = 0; column < row.length; column++) {\n\t\t\t\tconst cellText = this.renderInlineTokens(row[column].tokens || [], styleContext);\n\t\t\t\tnaturalWidths[column] = Math.max(naturalWidths[column] || 0, visibleWidth(cellText));\n\t\t\t\tminimumWidths[column] = Math.max(\n\t\t\t\t\tminimumWidths[column] || 1,\n\t\t\t\t\tthis.getLongestWordWidth(cellText, maxUnbrokenWordWidth),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn { naturalWidths, minimumWidths };\n\t}\n\n\tprivate fitMinimumTableColumnWidths(minimumWidths: number[], availableForCells: number): number[] {\n\t\tif (minimumWidths.reduce((total, width) => total + width, 0) <= availableForCells) return minimumWidths;\n\t\tconst fittedWidths = new Array(minimumWidths.length).fill(1);\n\t\tconst remaining = availableForCells - minimumWidths.length;\n\t\tif (remaining <= 0) return fittedWidths;\n\t\tconst totalWeight = minimumWidths.reduce((total, width) => total + Math.max(0, width - 1), 0);\n\t\tconst growth = minimumWidths.map((width) => {\n\t\t\tconst weight = Math.max(0, width - 1);\n\t\t\treturn totalWeight > 0 ? Math.floor((weight / totalWeight) * remaining) : 0;\n\t\t});\n\t\tfor (let column = 0; column < minimumWidths.length; column++) fittedWidths[column] += growth[column] ?? 0;\n\t\tconst allocated = growth.reduce((total, width) => total + width, 0);\n\t\tlet leftover = remaining - allocated;\n\t\tfor (let column = 0; leftover > 0 && column < minimumWidths.length; column++) {\n\t\t\tfittedWidths[column]++;\n\t\t\tleftover--;\n\t\t}\n\t\treturn fittedWidths;\n\t}\n\n\tprivate distributeRemainingTableWidth(\n\t\tcolumnWidths: number[],\n\t\tnaturalWidths: number[],\n\t\tremainingWidth: number,\n\t): void {\n\t\tlet remaining = remainingWidth;\n\t\twhile (remaining > 0) {\n\t\t\tlet grew = false;\n\t\t\tfor (let column = 0; column < columnWidths.length && remaining > 0; column++) {\n\t\t\t\tif (columnWidths[column] >= naturalWidths[column]) continue;\n\t\t\t\tcolumnWidths[column]++;\n\t\t\t\tremaining--;\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t\tif (!grew) break;\n\t\t}\n\t}\n\n\tprivate calculateTableColumnWidths(\n\t\tnaturalWidths: number[],\n\t\tminimumWidths: number[],\n\t\tavailableForCells: number,\n\t\tborderOverhead: number,\n\t): number[] {\n\t\tconst fittedMinimumWidths = this.fitMinimumTableColumnWidths(minimumWidths, availableForCells);\n\t\tconst totalNaturalWidth = naturalWidths.reduce((total, width) => total + width, 0) + borderOverhead;\n\t\tif (totalNaturalWidth <= availableForCells + borderOverhead) {\n\t\t\treturn naturalWidths.map((width, column) => Math.max(width, fittedMinimumWidths[column]));\n\t\t}\n\t\tconst minimumCellsWidth = fittedMinimumWidths.reduce((total, width) => total + width, 0);\n\t\tconst totalGrowPotential = naturalWidths.reduce(\n\t\t\t(total, width, column) => total + Math.max(0, width - fittedMinimumWidths[column]),\n\t\t\t0,\n\t\t);\n\t\tconst extraWidth = Math.max(0, availableForCells - minimumCellsWidth);\n\t\tconst columnWidths = fittedMinimumWidths.map((minimumWidth, column) => {\n\t\t\tconst growthPotential = Math.max(0, naturalWidths[column] - minimumWidth);\n\t\t\tconst growth = totalGrowPotential > 0 ? Math.floor((growthPotential / totalGrowPotential) * extraWidth) : 0;\n\t\t\treturn minimumWidth + growth;\n\t\t});\n\t\tconst allocated = columnWidths.reduce((total, width) => total + width, 0);\n\t\tthis.distributeRemainingTableWidth(columnWidths, naturalWidths, availableForCells - allocated);\n\t\treturn columnWidths;\n\t}\n\n\tprivate renderTableBorder(columnWidths: number[], left: string, junction: string, right: string): string {\n\t\treturn `${left}─${columnWidths.map((width) => \"─\".repeat(width)).join(`─${junction}─`)}─${right}`;\n\t}\n\n\tprivate renderTableCellLines(\n\t\tcells: Tokens.TableCell[],\n\t\tcolumnWidths: number[],\n\t\tstyleContext: InlineStyleContext | undefined,\n\t): string[][] {\n\t\treturn cells.map((cell, column) => {\n\t\t\tconst text = this.renderInlineTokens(cell.tokens || [], styleContext);\n\t\t\treturn this.wrapCellText(text, columnWidths[column]);\n\t\t});\n\t}\n\n\t// grip-ignore noExcessiveCollectionIterations: Every rendered table line must format each column once, so this rectangular traversal is linear in the emitted cell slots. @class:229-925\n\tprivate appendTableRow(lines: string[], cellLines: string[][], columnWidths: number[], bold: boolean): void {\n\t\tconst lineCount = Math.max(...cellLines.map((cell) => cell.length));\n\t\tfor (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {\n\t\t\tconst rowParts = cellLines.map((columnLines, column) => {\n\t\t\t\tconst text = columnLines[lineIndex] || \"\";\n\t\t\t\tconst padded = text + \" \".repeat(Math.max(0, columnWidths[column] - visibleWidth(text)));\n\t\t\t\treturn bold ? this.theme.bold(padded) : padded;\n\t\t\t});\n\t\t\tlines.push(`│ ${rowParts.join(\" │ \")} │`);\n\t\t}\n\t}\n\n\tprivate renderTable(\n\t\ttoken: Tokens.Table,\n\t\tavailableWidth: number,\n\t\tnextTokenType?: string,\n\t\tstyleContext?: InlineStyleContext,\n\t): string[] {\n\t\tconst columnCount = token.header.length;\n\t\tif (columnCount === 0) return [];\n\t\tconst borderOverhead = 3 * columnCount + 1;\n\t\tconst availableForCells = availableWidth - borderOverhead;\n\t\tif (availableForCells < columnCount) {\n\t\t\tconst fallbackLines = token.raw ? wrapTextWithAnsi(token.raw, availableWidth) : [];\n\t\t\tif (nextTokenType && nextTokenType !== \"space\") fallbackLines.push(\"\");\n\t\t\treturn fallbackLines;\n\t\t}\n\n\t\tconst measurements = this.measureTableColumns(token, styleContext);\n\t\tconst columnWidths = this.calculateTableColumnWidths(\n\t\t\tmeasurements.naturalWidths,\n\t\t\tmeasurements.minimumWidths,\n\t\t\tavailableForCells,\n\t\t\tborderOverhead,\n\t\t);\n\t\tconst lines = [this.renderTableBorder(columnWidths, \"┌\", \"┬\", \"┐\")];\n\t\tthis.appendTableRow(\n\t\t\tlines,\n\t\t\tthis.renderTableCellLines(token.header, columnWidths, styleContext),\n\t\t\tcolumnWidths,\n\t\t\ttrue,\n\t\t);\n\t\tconst separator = this.renderTableBorder(columnWidths, \"├\", \"┼\", \"┤\");\n\t\tlines.push(separator);\n\t\tfor (let rowIndex = 0; rowIndex < token.rows.length; rowIndex++) {\n\t\t\tthis.appendTableRow(\n\t\t\t\tlines,\n\t\t\t\tthis.renderTableCellLines(token.rows[rowIndex], columnWidths, styleContext),\n\t\t\t\tcolumnWidths,\n\t\t\t\tfalse,\n\t\t\t);\n\t\t\tif (rowIndex < token.rows.length - 1) lines.push(separator);\n\t\t}\n\t\tlines.push(this.renderTableBorder(columnWidths, \"└\", \"┴\", \"┘\"));\n\t\tif (nextTokenType && nextTokenType !== \"space\") lines.push(\"\");\n\t\treturn lines;\n\t}\n}\n"]}
|