@caupulican/pi-tui 0.81.42 → 0.84.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/dist/autocomplete.d.ts +1 -1
- package/dist/autocomplete.d.ts.map +1 -1
- package/dist/autocomplete.js +15 -23
- package/dist/autocomplete.js.map +1 -1
- package/dist/bracketed-paste.d.ts +33 -0
- package/dist/bracketed-paste.d.ts.map +1 -0
- package/dist/bracketed-paste.js +96 -0
- package/dist/bracketed-paste.js.map +1 -0
- package/dist/components/box.d.ts.map +1 -1
- package/dist/components/box.js +2 -7
- package/dist/components/box.js.map +1 -1
- package/dist/components/cancellable-loader.d.ts.map +1 -1
- package/dist/components/cancellable-loader.js +4 -3
- package/dist/components/cancellable-loader.js.map +1 -1
- package/dist/components/editor.d.ts +20 -8
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +172 -310
- package/dist/components/editor.js.map +1 -1
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +12 -35
- package/dist/components/image.js.map +1 -1
- package/dist/components/input.d.ts +8 -8
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +29 -75
- package/dist/components/input.js.map +1 -1
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js +7 -9
- package/dist/components/loader.js.map +1 -1
- package/dist/components/markdown.d.ts +3 -11
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +48 -124
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js +4 -9
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +4 -12
- package/dist/components/settings-list.js.map +1 -1
- package/dist/components/spacer.d.ts.map +1 -1
- package/dist/components/spacer.js +0 -1
- package/dist/components/spacer.js.map +1 -1
- package/dist/components/text-layout.d.ts +49 -0
- package/dist/components/text-layout.d.ts.map +1 -0
- package/dist/components/text-layout.js +103 -0
- package/dist/components/text-layout.js.map +1 -0
- package/dist/components/text.d.ts +2 -11
- package/dist/components/text.d.ts.map +1 -1
- package/dist/components/text.js +16 -83
- package/dist/components/text.js.map +1 -1
- package/dist/components/truncated-text.d.ts.map +1 -1
- package/dist/components/truncated-text.js +0 -3
- package/dist/components/truncated-text.js.map +1 -1
- package/dist/editing-actions.d.ts +17 -0
- package/dist/editing-actions.d.ts.map +1 -0
- package/dist/editing-actions.js +34 -0
- package/dist/editing-actions.js.map +1 -0
- package/dist/editor-component.d.ts +9 -0
- package/dist/editor-component.d.ts.map +1 -1
- package/dist/editor-component.js +4 -1
- package/dist/editor-component.js.map +1 -1
- package/dist/fuzzy.d.ts.map +1 -1
- package/dist/fuzzy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +2 -2
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +4 -6
- package/dist/keybindings.js.map +1 -1
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/kill-ring.d.ts.map +1 -1
- package/dist/kill-ring.js +2 -4
- package/dist/kill-ring.js.map +1 -1
- package/dist/native-loader.d.ts +3 -0
- package/dist/native-loader.d.ts.map +1 -0
- package/dist/native-loader.js +25 -0
- package/dist/native-loader.js.map +1 -0
- package/dist/native-modifiers.d.ts.map +1 -1
- package/dist/native-modifiers.js +5 -20
- package/dist/native-modifiers.js.map +1 -1
- package/dist/stdin-buffer.d.ts +8 -2
- package/dist/stdin-buffer.d.ts.map +1 -1
- package/dist/stdin-buffer.js +38 -32
- package/dist/stdin-buffer.js.map +1 -1
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +47 -72
- package/dist/terminal.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +26 -31
- package/dist/tui.js.map +1 -1
- package/dist/undo-stack.d.ts.map +1 -1
- package/dist/undo-stack.js +2 -4
- package/dist/undo-stack.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +197 -275
- package/dist/utils.js.map +1 -1
- package/dist/word-navigation.d.ts.map +1 -1
- package/dist/word-navigation.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Component } from "../tui.ts";
|
|
2
|
+
import { CachedTextComponent } from "./text-layout.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Default text styling for markdown content.
|
|
4
5
|
* Applied to all text unless overridden by markdown formatting.
|
|
@@ -44,22 +45,12 @@ export interface MarkdownOptions {
|
|
|
44
45
|
/** Preserve source ordered-list markers instead of normalizing them from the list start. */
|
|
45
46
|
preserveOrderedListMarkers?: boolean;
|
|
46
47
|
}
|
|
47
|
-
export declare class Markdown implements Component {
|
|
48
|
-
private text;
|
|
49
|
-
private _renderRevision;
|
|
50
|
-
get renderRevision(): number;
|
|
51
|
-
private paddingX;
|
|
52
|
-
private paddingY;
|
|
48
|
+
export declare class Markdown extends CachedTextComponent implements Component {
|
|
53
49
|
private defaultTextStyle?;
|
|
54
50
|
private theme;
|
|
55
51
|
private options;
|
|
56
52
|
private defaultStylePrefix?;
|
|
57
|
-
private cachedText?;
|
|
58
|
-
private cachedWidth?;
|
|
59
|
-
private cachedLines?;
|
|
60
53
|
constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, options?: MarkdownOptions);
|
|
61
|
-
setText(text: string): void;
|
|
62
|
-
invalidate(): void;
|
|
63
54
|
render(width: number): string[];
|
|
64
55
|
/**
|
|
65
56
|
* Apply default text style to a string.
|
|
@@ -68,6 +59,7 @@ export declare class Markdown implements Component {
|
|
|
68
59
|
* to ensure it extends to the full line width.
|
|
69
60
|
*/
|
|
70
61
|
private applyDefaultStyle;
|
|
62
|
+
private applyConfiguredDefaultStyle;
|
|
71
63
|
private getDefaultStylePrefix;
|
|
72
64
|
private getStylePrefix;
|
|
73
65
|
private getDefaultInlineStyleContext;
|
|
@@ -1 +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 Tokens } from \"marked\";\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(?=[^~]|$)/;\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\nfunction trimPartialClosingFences(tokens: readonly Token[]): void {\n\tconst token = tokens[tokens.length - 1];\n\tif (token?.type === \"list\") {\n\t\ttrimPartialClosingFences(token.items[token.items.length - 1]?.tokens ?? []);\n\t\treturn;\n\t}\n\tif (token?.type === \"blockquote\") {\n\t\ttrimPartialClosingFences(token.tokens ?? []);\n\t\treturn;\n\t}\n\n\tif (token?.type !== \"code\") {\n\t\treturn;\n\t}\n\n\t// Markdown fenced code blocks can use either backticks or tildes, with length 3 or more.\n\t// Capture the opening fence marker so a streamed partial closing fence shorter than\n\t// the opener can be removed from code content below. This prevents content from\n\t// shrinking when the closing fence completes during streaming.\n\tconst opener = /^(?<marker>`{3,}|~{3,})[^\\n]*(?:\\n|$)/.exec(token.raw);\n\tconst marker = opener?.groups?.marker;\n\tif (!marker) {\n\t\treturn;\n\t}\n\n\tconst fenceChar = marker[0];\n\tconst partialClosingFence = new RegExp(`\\\\n${fenceChar}{1,${marker.length - 1}}$`);\n\tif (partialClosingFence.test(token.raw)) {\n\t\ttoken.text = token.text.replace(partialClosingFence, \"\");\n\t}\n}\n\nconst markdownParser = new Marked();\nmarkdownParser.setOptions({\n\ttokenizer: new StrictStrikethroughTokenizer(),\n});\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}\n\ninterface InlineStyleContext {\n\tapplyText: (text: string) => string;\n\tstylePrefix: string;\n}\n\nexport class Markdown implements Component {\n\tprivate text: string;\n\tprivate _renderRevision = 0;\n\tget renderRevision(): number {\n\t\treturn this._renderRevision;\n\t}\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\tthis._renderRevision++;\n\t}\n\n\trender(width: number): string[] {\n\t\t// Check cache\n\t\tif (this.cachedLines && this.cachedText === this.text && this.cachedWidth === width) {\n\t\t\treturn this.cachedLines.slice();\n\t\t}\n\t\tthis._renderRevision++;\n\n\t\t// Calculate available width for content (subtract horizontal padding)\n\t\tconst contentWidth = Math.max(1, width - this.paddingX * 2);\n\n\t\t// Don't render anything if there's no actual text\n\t\tif (!this.text || this.text.trim() === \"\") {\n\t\t\tconst result: string[] = [];\n\t\t\t// Update cache\n\t\t\tthis.cachedText = this.text;\n\t\t\tthis.cachedWidth = width;\n\t\t\tthis.cachedLines = result;\n\t\t\treturn result.slice();\n\t\t}\n\n\t\t// Replace tabs with 3 spaces for consistent rendering\n\t\tconst normalizedText = this.text.replace(/\\t/g, \" \");\n\n\t\t// Parse markdown to HTML-like tokens\n\t\tconst tokens = markdownParser.lexer(normalizedText);\n\t\ttrimPartialClosingFences(tokens);\n\n\t\t// Convert tokens to styled terminal output\n\t\tconst renderedLines: string[] = [];\n\n\t\tfor (let i = 0; i < tokens.length; i++) {\n\t\t\tconst token = tokens[i];\n\t\t\tconst nextToken = tokens[i + 1];\n\t\t\tconst tokenLines = this.renderToken(token, contentWidth, nextToken?.type);\n\t\t\tfor (const tokenLine of tokenLines) {\n\t\t\t\trenderedLines.push(tokenLine);\n\t\t\t}\n\t\t}\n\n\t\t// Wrap lines (NO padding, NO background yet)\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} else {\n\t\t\t\tfor (const wrappedLine of wrapTextWithAnsi(line, contentWidth)) {\n\t\t\t\t\twrappedLines.push(wrappedLine);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add margins and background to each wrapped line\n\t\tconst leftMargin = \" \".repeat(this.paddingX);\n\t\tconst rightMargin = \" \".repeat(this.paddingX);\n\t\tconst bgFn = this.defaultTextStyle?.bgColor;\n\t\tconst contentLines: string[] = [];\n\n\t\tfor (const line of wrappedLines) {\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 = leftMargin + line + rightMargin;\n\n\t\t\tif (bgFn) {\n\t\t\t\tcontentLines.push(applyBackgroundToLine(lineWithMargins, width, bgFn));\n\t\t\t} else {\n\t\t\t\t// No background - just pad to width\n\t\t\t\tconst visibleLen = visibleWidth(lineWithMargins);\n\t\t\t\tconst paddingNeeded = Math.max(0, width - visibleLen);\n\t\t\t\tcontentLines.push(lineWithMargins + \" \".repeat(paddingNeeded));\n\t\t\t}\n\t\t}\n\n\t\t// Add top/bottom padding (empty lines)\n\t\tconst emptyLine = \" \".repeat(width);\n\t\tconst emptyLines: string[] = [];\n\t\tfor (let i = 0; i < this.paddingY; i++) {\n\t\t\tconst line = bgFn ? applyBackgroundToLine(emptyLine, width, bgFn) : emptyLine;\n\t\t\temptyLines.push(line);\n\t\t}\n\n\t\t// Combine top padding, content, and bottom padding\n\t\tconst result = emptyLines.concat(contentLines, emptyLines);\n\n\t\t// Update cache\n\t\tthis.cachedText = this.text;\n\t\tthis.cachedWidth = width;\n\t\tthis.cachedLines = result;\n\n\t\treturn result.length > 0 ? result.slice() : [\"\"];\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 renderToken(\n\t\ttoken: Token,\n\t\twidth: number,\n\t\tnextTokenType?: string,\n\t\tstyleContext?: InlineStyleContext,\n\t): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tswitch (token.type) {\n\t\t\tcase \"heading\": {\n\t\t\t\tconst headingLevel = token.depth;\n\t\t\t\tconst headingPrefix = `${\"#\".repeat(headingLevel)} `;\n\n\t\t\t\t// Build a heading-specific style context so inline tokens (codespan, bold, etc.)\n\t\t\t\t// restore heading styling after their own ANSI resets instead of falling back to\n\t\t\t\t// the default text style.\n\t\t\t\tlet headingStyleFn: (text: string) => string;\n\t\t\t\tif (headingLevel === 1) {\n\t\t\t\t\theadingStyleFn = (text: string) => this.theme.heading(this.theme.bold(this.theme.underline(text)));\n\t\t\t\t} else {\n\t\t\t\t\theadingStyleFn = (text: string) => this.theme.heading(this.theme.bold(text));\n\t\t\t\t}\n\n\t\t\t\tconst headingStyleContext: InlineStyleContext = {\n\t\t\t\t\tapplyText: headingStyleFn,\n\t\t\t\t\tstylePrefix: this.getStylePrefix(headingStyleFn),\n\t\t\t\t};\n\n\t\t\t\tconst headingText = this.renderInlineTokens(token.tokens || [], headingStyleContext);\n\t\t\t\tconst styledHeading = headingLevel >= 3 ? headingStyleFn(headingPrefix) + headingText : headingText;\n\t\t\t\tlines.push(styledHeading);\n\t\t\t\tif (nextTokenType && nextTokenType !== \"space\") {\n\t\t\t\t\tlines.push(\"\"); // Add spacing after headings (unless space token follows)\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"paragraph\": {\n\t\t\t\tconst paragraphText = this.renderInlineTokens(token.tokens || [], styleContext);\n\t\t\t\tlines.push(paragraphText);\n\t\t\t\t// Don't add spacing if next token is space or list\n\t\t\t\tif (nextTokenType && nextTokenType !== \"list\" && nextTokenType !== \"space\") {\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"text\":\n\t\t\t\tlines.push(this.renderInlineTokens([token], styleContext));\n\t\t\t\tbreak;\n\n\t\t\tcase \"code\": {\n\t\t\t\tconst indent = this.theme.codeBlockIndent ?? \" \";\n\t\t\t\tlines.push(this.theme.codeBlockBorder(`\\`\\`\\`${token.lang || \"\"}`));\n\t\t\t\tif (this.theme.highlightCode) {\n\t\t\t\t\tconst highlightedLines = this.theme.highlightCode(token.text, token.lang);\n\t\t\t\t\tfor (const hlLine of highlightedLines) {\n\t\t\t\t\t\tlines.push(`${indent}${hlLine}`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Split code by newlines and style each line\n\t\t\t\t\tconst codeLines = token.text.split(\"\\n\");\n\t\t\t\t\tfor (const codeLine of codeLines) {\n\t\t\t\t\t\tlines.push(`${indent}${this.theme.codeBlock(codeLine)}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlines.push(this.theme.codeBlockBorder(\"```\"));\n\t\t\t\tif (nextTokenType && nextTokenType !== \"space\") {\n\t\t\t\t\tlines.push(\"\"); // Add spacing after code blocks (unless space token follows)\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"list\": {\n\t\t\t\tconst listLines = this.renderList(token as Tokens.List, 0, width, styleContext);\n\t\t\t\tlines.push(...listLines);\n\t\t\t\t// Don't add spacing after lists if a space token follows\n\t\t\t\t// (the space token will handle it)\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"table\": {\n\t\t\t\tconst tableLines = this.renderTable(token as Tokens.Table, width, nextTokenType, styleContext);\n\t\t\t\tlines.push(...tableLines);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"blockquote\": {\n\t\t\t\tconst quoteStyle = (text: string) => this.theme.quote(this.theme.italic(text));\n\t\t\t\tconst quoteStylePrefix = this.getStylePrefix(quoteStyle);\n\t\t\t\tconst applyQuoteStyle = (line: string): string => {\n\t\t\t\t\tif (!quoteStylePrefix) {\n\t\t\t\t\t\treturn quoteStyle(line);\n\t\t\t\t\t}\n\t\t\t\t\tconst lineWithReappliedStyle = line.replace(/\\x1b\\[0m/g, `\\x1b[0m${quoteStylePrefix}`);\n\t\t\t\t\treturn quoteStyle(lineWithReappliedStyle);\n\t\t\t\t};\n\n\t\t\t\t// Calculate available width for quote content (subtract border \"│ \" = 2 chars)\n\t\t\t\tconst quoteContentWidth = Math.max(1, width - 2);\n\n\t\t\t\t// Blockquotes contain block-level tokens (paragraph, list, code, etc.), so render\n\t\t\t\t// children with renderToken() instead of renderInlineTokens().\n\t\t\t\t// Default message style should not apply inside blockquotes.\n\t\t\t\tconst quoteInlineStyleContext: InlineStyleContext = {\n\t\t\t\t\tapplyText: (text: string) => text,\n\t\t\t\t\tstylePrefix: quoteStylePrefix,\n\t\t\t\t};\n\t\t\t\tconst quoteTokens = token.tokens || [];\n\t\t\t\tconst renderedQuoteLines: string[] = [];\n\t\t\t\tfor (let i = 0; i < quoteTokens.length; i++) {\n\t\t\t\t\tconst quoteToken = quoteTokens[i];\n\t\t\t\t\tconst nextQuoteToken = quoteTokens[i + 1];\n\t\t\t\t\trenderedQuoteLines.push(\n\t\t\t\t\t\t...this.renderToken(quoteToken, quoteContentWidth, nextQuoteToken?.type, quoteInlineStyleContext),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Avoid rendering an extra empty quote line before the outer blockquote spacing.\n\t\t\t\twhile (renderedQuoteLines.length > 0 && renderedQuoteLines[renderedQuoteLines.length - 1] === \"\") {\n\t\t\t\t\trenderedQuoteLines.pop();\n\t\t\t\t}\n\n\t\t\t\tfor (const quoteLine of renderedQuoteLines) {\n\t\t\t\t\tconst styledLine = applyQuoteStyle(quoteLine);\n\t\t\t\t\tconst wrappedLines = wrapTextWithAnsi(styledLine, quoteContentWidth);\n\t\t\t\t\tfor (const wrappedLine of wrappedLines) {\n\t\t\t\t\t\tlines.push(this.theme.quoteBorder(\"│ \") + wrappedLine);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (nextTokenType && nextTokenType !== \"space\") {\n\t\t\t\t\tlines.push(\"\"); // Add spacing after blockquotes (unless space token follows)\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"hr\":\n\t\t\t\tlines.push(this.theme.hr(\"─\".repeat(Math.min(width, 80))));\n\t\t\t\tif (nextTokenType && nextTokenType !== \"space\") {\n\t\t\t\t\tlines.push(\"\"); // Add spacing after horizontal rules (unless space token follows)\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"html\":\n\t\t\t\t// Render HTML as plain text (escaped for terminal)\n\t\t\t\tif (\"raw\" in token && typeof token.raw === \"string\") {\n\t\t\t\t\tlines.push(this.applyDefaultStyle(token.raw.trim()));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"space\":\n\t\t\t\t// Space tokens represent blank lines in markdown\n\t\t\t\tlines.push(\"\");\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// Handle any other token types as plain text\n\t\t\t\tif (\"text\" in token && typeof token.text === \"string\") {\n\t\t\t\t\tlines.push(token.text);\n\t\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\tprivate renderInlineTokens(tokens: Token[], styleContext?: InlineStyleContext): string {\n\t\tlet result = \"\";\n\t\tconst resolvedStyleContext = styleContext ?? this.getDefaultInlineStyleContext();\n\t\tconst { applyText, stylePrefix } = resolvedStyleContext;\n\t\tconst applyTextWithNewlines = (text: string): string => {\n\t\t\tconst segments: string[] = text.split(\"\\n\");\n\t\t\treturn segments.map((segment: string) => applyText(segment)).join(\"\\n\");\n\t\t};\n\n\t\tfor (const token of tokens) {\n\t\t\tswitch (token.type) {\n\t\t\t\tcase \"text\":\n\t\t\t\t\t// Text tokens in list items can have nested tokens for inline formatting\n\t\t\t\t\tif (token.tokens && token.tokens.length > 0) {\n\t\t\t\t\t\tresult += this.renderInlineTokens(token.tokens, resolvedStyleContext);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult += applyTextWithNewlines(token.text);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"paragraph\":\n\t\t\t\t\t// Paragraph tokens contain nested inline tokens\n\t\t\t\t\tresult += this.renderInlineTokens(token.tokens || [], resolvedStyleContext);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"strong\": {\n\t\t\t\t\tconst boldContent = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);\n\t\t\t\t\tresult += this.theme.bold(boldContent) + stylePrefix;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"em\": {\n\t\t\t\t\tconst italicContent = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);\n\t\t\t\t\tresult += this.theme.italic(italicContent) + stylePrefix;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"codespan\":\n\t\t\t\t\tresult += this.theme.code(token.text) + stylePrefix;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"link\": {\n\t\t\t\t\tconst linkText = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);\n\t\t\t\t\tconst styledLink = this.theme.link(this.theme.underline(linkText));\n\t\t\t\t\tif (getCapabilities().hyperlinks) {\n\t\t\t\t\t\t// OSC 8: render as a clickable hyperlink. The URL is not printed inline,\n\t\t\t\t\t\t// so we always show only the link text regardless of whether it matches href.\n\t\t\t\t\t\tresult += hyperlink(styledLink, token.href) + stylePrefix;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Fallback: print URL in parentheses when text differs from href.\n\t\t\t\t\t\t// Compare raw token.text (not styled) against href for the equality check.\n\t\t\t\t\t\t// For mailto: links strip the prefix (autolinked emails use text=\"foo@bar.com\"\n\t\t\t\t\t\t// but href=\"mailto:foo@bar.com\").\n\t\t\t\t\t\tconst hrefForComparison = token.href.startsWith(\"mailto:\") ? token.href.slice(7) : token.href;\n\t\t\t\t\t\tif (token.text === token.href || token.text === hrefForComparison) {\n\t\t\t\t\t\t\tresult += styledLink + stylePrefix;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresult += styledLink + this.theme.linkUrl(` (${token.href})`) + stylePrefix;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"br\":\n\t\t\t\t\tresult += \"\\n\";\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"del\": {\n\t\t\t\t\tconst delContent = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);\n\t\t\t\t\tresult += this.theme.strikethrough(delContent) + stylePrefix;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"html\":\n\t\t\t\t\t// Render inline HTML as plain text\n\t\t\t\t\tif (\"raw\" in token && typeof token.raw === \"string\") {\n\t\t\t\t\t\tresult += applyTextWithNewlines(token.raw);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\t// Handle any other inline token types as plain text\n\t\t\t\t\tif (\"text\" in token && typeof token.text === \"string\") {\n\t\t\t\t\t\tresult += applyTextWithNewlines(token.text);\n\t\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\twhile (stylePrefix && result.endsWith(stylePrefix)) {\n\t\t\tresult = result.slice(0, -stylePrefix.length);\n\t\t}\n\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\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 indent = \" \".repeat(depth);\n\t\t// Use the list's start property (defaults to 1 for ordered lists)\n\t\tconst startNumber = typeof token.start === \"number\" ? token.start : 1;\n\n\t\tfor (let i = 0; i < token.items.length; i++) {\n\t\t\tconst item = token.items[i];\n\t\t\tconst bullet = token.ordered\n\t\t\t\t? this.options.preserveOrderedListMarkers\n\t\t\t\t\t? (this.getOrderedListMarker(item) ?? `${startNumber + i}. `)\n\t\t\t\t\t: `${startNumber + i}. `\n\t\t\t\t: \"- \";\n\t\t\tconst taskMarker = item.task ? `[${item.checked ? \"x\" : \" \"}] ` : \"\";\n\t\t\tconst marker = bullet + taskMarker;\n\t\t\tconst firstPrefix = indent + this.theme.listBullet(marker);\n\t\t\tconst continuationPrefix = indent + \" \".repeat(visibleWidth(marker));\n\t\t\tconst itemWidth = Math.max(1, width - visibleWidth(firstPrefix));\n\t\t\tlet renderedAnyLine = false;\n\n\t\t\tfor (const itemToken of item.tokens) {\n\t\t\t\tif (itemToken.type === \"list\") {\n\t\t\t\t\tlines.push(...this.renderList(itemToken as Tokens.List, depth + 1, width, styleContext));\n\t\t\t\t\trenderedAnyLine = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst itemLines = this.renderToken(itemToken, itemWidth, undefined, styleContext);\n\t\t\t\tfor (const line of itemLines) {\n\t\t\t\t\tfor (const wrappedLine of wrapTextWithAnsi(line, itemWidth)) {\n\t\t\t\t\t\tconst linePrefix = renderedAnyLine ? continuationPrefix : firstPrefix;\n\t\t\t\t\t\tlines.push(linePrefix + wrappedLine);\n\t\t\t\t\t\trenderedAnyLine = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!renderedAnyLine) {\n\t\t\t\tlines.push(firstPrefix);\n\t\t\t}\n\t\t}\n\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 renderTable(\n\t\ttoken: Tokens.Table,\n\t\tavailableWidth: number,\n\t\tnextTokenType?: string,\n\t\tstyleContext?: InlineStyleContext,\n\t): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst numCols = token.header.length;\n\n\t\tif (numCols === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate border overhead: \"│ \" + (n-1) * \" │ \" + \" │\"\n\t\t// = 2 + (n-1) * 3 + 2 = 3n + 1\n\t\tconst borderOverhead = 3 * numCols + 1;\n\t\tconst availableForCells = availableWidth - borderOverhead;\n\t\tif (availableForCells < numCols) {\n\t\t\t// Too narrow to render a stable table. Fall back to raw markdown.\n\t\t\tconst fallbackLines = token.raw ? wrapTextWithAnsi(token.raw, availableWidth) : [];\n\t\t\tif (nextTokenType && nextTokenType !== \"space\") {\n\t\t\t\tfallbackLines.push(\"\");\n\t\t\t}\n\t\t\treturn fallbackLines;\n\t\t}\n\n\t\tconst maxUnbrokenWordWidth = 30;\n\n\t\t// Calculate natural column widths (what each column needs without constraints)\n\t\tconst naturalWidths: number[] = [];\n\t\tconst minWordWidths: number[] = [];\n\t\tfor (let i = 0; i < numCols; i++) {\n\t\t\tconst headerText = this.renderInlineTokens(token.header[i].tokens || [], styleContext);\n\t\t\tnaturalWidths[i] = visibleWidth(headerText);\n\t\t\tminWordWidths[i] = Math.max(1, this.getLongestWordWidth(headerText, maxUnbrokenWordWidth));\n\t\t}\n\t\tfor (const row of token.rows) {\n\t\t\tfor (let i = 0; i < row.length; i++) {\n\t\t\t\tconst cellText = this.renderInlineTokens(row[i].tokens || [], styleContext);\n\t\t\t\tnaturalWidths[i] = Math.max(naturalWidths[i] || 0, visibleWidth(cellText));\n\t\t\t\tminWordWidths[i] = Math.max(\n\t\t\t\t\tminWordWidths[i] || 1,\n\t\t\t\t\tthis.getLongestWordWidth(cellText, maxUnbrokenWordWidth),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tlet minColumnWidths = minWordWidths;\n\t\tlet minCellsWidth = minColumnWidths.reduce((a, b) => a + b, 0);\n\n\t\tif (minCellsWidth > availableForCells) {\n\t\t\tminColumnWidths = new Array(numCols).fill(1);\n\t\t\tconst remaining = availableForCells - numCols;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\tconst totalWeight = minWordWidths.reduce((total, width) => total + Math.max(0, width - 1), 0);\n\t\t\t\tconst growth = minWordWidths.map((width) => {\n\t\t\t\t\tconst weight = Math.max(0, width - 1);\n\t\t\t\t\treturn totalWeight > 0 ? Math.floor((weight / totalWeight) * remaining) : 0;\n\t\t\t\t});\n\n\t\t\t\tfor (let i = 0; i < numCols; i++) {\n\t\t\t\t\tminColumnWidths[i] += growth[i] ?? 0;\n\t\t\t\t}\n\n\t\t\t\tconst allocated = growth.reduce((total, width) => total + width, 0);\n\t\t\t\tlet leftover = remaining - allocated;\n\t\t\t\tfor (let i = 0; leftover > 0 && i < numCols; i++) {\n\t\t\t\t\tminColumnWidths[i]++;\n\t\t\t\t\tleftover--;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tminCellsWidth = minColumnWidths.reduce((a, b) => a + b, 0);\n\t\t}\n\n\t\t// Calculate column widths that fit within available width\n\t\tconst totalNaturalWidth = naturalWidths.reduce((a, b) => a + b, 0) + borderOverhead;\n\t\tlet columnWidths: number[];\n\n\t\tif (totalNaturalWidth <= availableWidth) {\n\t\t\t// Everything fits naturally\n\t\t\tcolumnWidths = naturalWidths.map((width, index) => Math.max(width, minColumnWidths[index]));\n\t\t} else {\n\t\t\t// Need to shrink columns to fit\n\t\t\tconst totalGrowPotential = naturalWidths.reduce((total, width, index) => {\n\t\t\t\treturn total + Math.max(0, width - minColumnWidths[index]);\n\t\t\t}, 0);\n\t\t\tconst extraWidth = Math.max(0, availableForCells - minCellsWidth);\n\t\t\tcolumnWidths = minColumnWidths.map((minWidth, index) => {\n\t\t\t\tconst naturalWidth = naturalWidths[index];\n\t\t\t\tconst minWidthDelta = Math.max(0, naturalWidth - minWidth);\n\t\t\t\tlet grow = 0;\n\t\t\t\tif (totalGrowPotential > 0) {\n\t\t\t\t\tgrow = Math.floor((minWidthDelta / totalGrowPotential) * extraWidth);\n\t\t\t\t}\n\t\t\t\treturn minWidth + grow;\n\t\t\t});\n\n\t\t\t// Adjust for rounding errors - distribute remaining space\n\t\t\tconst allocated = columnWidths.reduce((a, b) => a + b, 0);\n\t\t\tlet remaining = availableForCells - allocated;\n\t\t\twhile (remaining > 0) {\n\t\t\t\tlet grew = false;\n\t\t\t\tfor (let i = 0; i < numCols && remaining > 0; i++) {\n\t\t\t\t\tif (columnWidths[i] < naturalWidths[i]) {\n\t\t\t\t\t\tcolumnWidths[i]++;\n\t\t\t\t\t\tremaining--;\n\t\t\t\t\t\tgrew = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!grew) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Render top border\n\t\tconst topBorderCells = columnWidths.map((w) => \"─\".repeat(w));\n\t\tlines.push(`┌─${topBorderCells.join(\"─┬─\")}─┐`);\n\n\t\t// Render header with wrapping\n\t\tconst headerCellLines: string[][] = token.header.map((cell, i) => {\n\t\t\tconst text = this.renderInlineTokens(cell.tokens || [], styleContext);\n\t\t\treturn this.wrapCellText(text, columnWidths[i]);\n\t\t});\n\t\tconst headerLineCount = Math.max(...headerCellLines.map((c) => c.length));\n\n\t\tfor (let lineIdx = 0; lineIdx < headerLineCount; lineIdx++) {\n\t\t\tconst rowParts = headerCellLines.map((cellLines, colIdx) => {\n\t\t\t\tconst text = cellLines[lineIdx] || \"\";\n\t\t\t\tconst padded = text + \" \".repeat(Math.max(0, columnWidths[colIdx] - visibleWidth(text)));\n\t\t\t\treturn this.theme.bold(padded);\n\t\t\t});\n\t\t\tlines.push(`│ ${rowParts.join(\" │ \")} │`);\n\t\t}\n\n\t\t// Render separator\n\t\tconst separatorCells = columnWidths.map((w) => \"─\".repeat(w));\n\t\tconst separatorLine = `├─${separatorCells.join(\"─┼─\")}─┤`;\n\t\tlines.push(separatorLine);\n\n\t\t// Render rows with wrapping\n\t\tfor (let rowIndex = 0; rowIndex < token.rows.length; rowIndex++) {\n\t\t\tconst row = token.rows[rowIndex];\n\t\t\tconst rowCellLines: string[][] = row.map((cell, i) => {\n\t\t\t\tconst text = this.renderInlineTokens(cell.tokens || [], styleContext);\n\t\t\t\treturn this.wrapCellText(text, columnWidths[i]);\n\t\t\t});\n\t\t\tconst rowLineCount = Math.max(...rowCellLines.map((c) => c.length));\n\n\t\t\tfor (let lineIdx = 0; lineIdx < rowLineCount; lineIdx++) {\n\t\t\t\tconst rowParts = rowCellLines.map((cellLines, colIdx) => {\n\t\t\t\t\tconst text = cellLines[lineIdx] || \"\";\n\t\t\t\t\treturn text + \" \".repeat(Math.max(0, columnWidths[colIdx] - visibleWidth(text)));\n\t\t\t\t});\n\t\t\t\tlines.push(`│ ${rowParts.join(\" │ \")} │`);\n\t\t\t}\n\n\t\t\tif (rowIndex < token.rows.length - 1) {\n\t\t\t\tlines.push(separatorLine);\n\t\t\t}\n\t\t}\n\n\t\t// Render bottom border\n\t\tconst bottomBorderCells = columnWidths.map((w) => \"─\".repeat(w));\n\t\tlines.push(`└─${bottomBorderCells.join(\"─┴─\")}─┘`);\n\n\t\tif (nextTokenType && nextTokenType !== \"space\") {\n\t\t\tlines.push(\"\"); // Add spacing after table\n\t\t}\n\t\treturn lines;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/components/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Marked, Tokenizer } from "marked";
|
|
2
2
|
import { getCapabilities, hyperlink, isImageLine } from "../terminal-image.js";
|
|
3
|
-
import {
|
|
3
|
+
import { visibleWidth, wrapTextWithAnsi } from "../utils.js";
|
|
4
|
+
import { CachedTextComponent, frameTextLines } from "./text-layout.js";
|
|
4
5
|
const STRICT_STRIKETHROUGH_REGEX = /^(~~)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/;
|
|
5
6
|
class StrictStrikethroughTokenizer extends Tokenizer {
|
|
6
7
|
del(src) {
|
|
@@ -49,68 +50,26 @@ const markdownParser = new Marked();
|
|
|
49
50
|
markdownParser.setOptions({
|
|
50
51
|
tokenizer: new StrictStrikethroughTokenizer(),
|
|
51
52
|
});
|
|
52
|
-
export class Markdown {
|
|
53
|
-
text;
|
|
54
|
-
_renderRevision = 0;
|
|
55
|
-
get renderRevision() {
|
|
56
|
-
return this._renderRevision;
|
|
57
|
-
}
|
|
58
|
-
paddingX; // Left/right padding
|
|
59
|
-
paddingY; // Top/bottom padding
|
|
60
|
-
defaultTextStyle;
|
|
61
|
-
theme;
|
|
62
|
-
options;
|
|
63
|
-
defaultStylePrefix;
|
|
64
|
-
// Cache for rendered output
|
|
65
|
-
cachedText;
|
|
66
|
-
cachedWidth;
|
|
67
|
-
cachedLines;
|
|
53
|
+
export class Markdown extends CachedTextComponent {
|
|
68
54
|
constructor(text, paddingX, paddingY, theme, defaultTextStyle, options) {
|
|
69
|
-
|
|
70
|
-
this.paddingX = paddingX;
|
|
71
|
-
this.paddingY = paddingY;
|
|
55
|
+
super(text, paddingX, paddingY);
|
|
72
56
|
this.theme = theme;
|
|
73
57
|
this.defaultTextStyle = defaultTextStyle;
|
|
74
58
|
this.options = options ? { ...options } : {};
|
|
75
59
|
}
|
|
76
|
-
setText(text) {
|
|
77
|
-
this.text = text;
|
|
78
|
-
this.invalidate();
|
|
79
|
-
}
|
|
80
|
-
invalidate() {
|
|
81
|
-
this.cachedText = undefined;
|
|
82
|
-
this.cachedWidth = undefined;
|
|
83
|
-
this.cachedLines = undefined;
|
|
84
|
-
this._renderRevision++;
|
|
85
|
-
}
|
|
86
60
|
render(width) {
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
this._renderRevision++;
|
|
92
|
-
// Calculate available width for content (subtract horizontal padding)
|
|
93
|
-
const contentWidth = Math.max(1, width - this.paddingX * 2);
|
|
94
|
-
// Don't render anything if there's no actual text
|
|
95
|
-
if (!this.text || this.text.trim() === "") {
|
|
96
|
-
const result = [];
|
|
97
|
-
// Update cache
|
|
98
|
-
this.cachedText = this.text;
|
|
99
|
-
this.cachedWidth = width;
|
|
100
|
-
this.cachedLines = result;
|
|
101
|
-
return result.slice();
|
|
102
|
-
}
|
|
103
|
-
// Replace tabs with 3 spaces for consistent rendering
|
|
104
|
-
const normalizedText = this.text.replace(/\t/g, " ");
|
|
61
|
+
const start = this.renderState.begin(this.text, width, this.paddingX);
|
|
62
|
+
if (start.kind === "complete")
|
|
63
|
+
return start.lines;
|
|
105
64
|
// Parse markdown to HTML-like tokens
|
|
106
|
-
const tokens = markdownParser.lexer(normalizedText);
|
|
65
|
+
const tokens = markdownParser.lexer(start.prepared.normalizedText);
|
|
107
66
|
trimPartialClosingFences(tokens);
|
|
108
67
|
// Convert tokens to styled terminal output
|
|
109
68
|
const renderedLines = [];
|
|
110
69
|
for (let i = 0; i < tokens.length; i++) {
|
|
111
70
|
const token = tokens[i];
|
|
112
71
|
const nextToken = tokens[i + 1];
|
|
113
|
-
const tokenLines = this.renderToken(token, contentWidth, nextToken?.type);
|
|
72
|
+
const tokenLines = this.renderToken(token, start.prepared.contentWidth, nextToken?.type);
|
|
114
73
|
for (const tokenLine of tokenLines) {
|
|
115
74
|
renderedLines.push(tokenLine);
|
|
116
75
|
}
|
|
@@ -122,46 +81,20 @@ export class Markdown {
|
|
|
122
81
|
wrappedLines.push(line);
|
|
123
82
|
}
|
|
124
83
|
else {
|
|
125
|
-
for (const wrappedLine of wrapTextWithAnsi(line, contentWidth)) {
|
|
84
|
+
for (const wrappedLine of wrapTextWithAnsi(line, start.prepared.contentWidth)) {
|
|
126
85
|
wrappedLines.push(wrappedLine);
|
|
127
86
|
}
|
|
128
87
|
}
|
|
129
88
|
}
|
|
130
|
-
// Add margins and background to each wrapped line
|
|
131
|
-
const leftMargin = " ".repeat(this.paddingX);
|
|
132
|
-
const rightMargin = " ".repeat(this.paddingX);
|
|
133
89
|
const bgFn = this.defaultTextStyle?.bgColor;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
contentLines.push(applyBackgroundToLine(lineWithMargins, width, bgFn));
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// No background - just pad to width
|
|
146
|
-
const visibleLen = visibleWidth(lineWithMargins);
|
|
147
|
-
const paddingNeeded = Math.max(0, width - visibleLen);
|
|
148
|
-
contentLines.push(lineWithMargins + " ".repeat(paddingNeeded));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
// Add top/bottom padding (empty lines)
|
|
152
|
-
const emptyLine = " ".repeat(width);
|
|
153
|
-
const emptyLines = [];
|
|
154
|
-
for (let i = 0; i < this.paddingY; i++) {
|
|
155
|
-
const line = bgFn ? applyBackgroundToLine(emptyLine, width, bgFn) : emptyLine;
|
|
156
|
-
emptyLines.push(line);
|
|
157
|
-
}
|
|
158
|
-
// Combine top padding, content, and bottom padding
|
|
159
|
-
const result = emptyLines.concat(contentLines, emptyLines);
|
|
160
|
-
// Update cache
|
|
161
|
-
this.cachedText = this.text;
|
|
162
|
-
this.cachedWidth = width;
|
|
163
|
-
this.cachedLines = result;
|
|
164
|
-
return result.length > 0 ? result.slice() : [""];
|
|
90
|
+
const result = frameTextLines(wrappedLines, {
|
|
91
|
+
width,
|
|
92
|
+
paddingX: this.paddingX,
|
|
93
|
+
paddingY: this.paddingY,
|
|
94
|
+
background: bgFn,
|
|
95
|
+
isPassthrough: isImageLine,
|
|
96
|
+
});
|
|
97
|
+
return this.renderState.finish(this.text, width, result);
|
|
165
98
|
}
|
|
166
99
|
/**
|
|
167
100
|
* Apply default text style to a string.
|
|
@@ -173,22 +106,28 @@ export class Markdown {
|
|
|
173
106
|
if (!this.defaultTextStyle) {
|
|
174
107
|
return text;
|
|
175
108
|
}
|
|
109
|
+
return this.applyConfiguredDefaultStyle(text);
|
|
110
|
+
}
|
|
111
|
+
applyConfiguredDefaultStyle(text) {
|
|
112
|
+
const style = this.defaultTextStyle;
|
|
113
|
+
if (!style)
|
|
114
|
+
return text;
|
|
176
115
|
let styled = text;
|
|
177
116
|
// Apply foreground color (NOT background - that's applied at padding stage)
|
|
178
|
-
if (
|
|
179
|
-
styled =
|
|
117
|
+
if (style.color) {
|
|
118
|
+
styled = style.color(styled);
|
|
180
119
|
}
|
|
181
120
|
// Apply text decorations using this.theme
|
|
182
|
-
if (
|
|
121
|
+
if (style.bold) {
|
|
183
122
|
styled = this.theme.bold(styled);
|
|
184
123
|
}
|
|
185
|
-
if (
|
|
124
|
+
if (style.italic) {
|
|
186
125
|
styled = this.theme.italic(styled);
|
|
187
126
|
}
|
|
188
|
-
if (
|
|
127
|
+
if (style.strikethrough) {
|
|
189
128
|
styled = this.theme.strikethrough(styled);
|
|
190
129
|
}
|
|
191
|
-
if (
|
|
130
|
+
if (style.underline) {
|
|
192
131
|
styled = this.theme.underline(styled);
|
|
193
132
|
}
|
|
194
133
|
return styled;
|
|
@@ -201,22 +140,7 @@ export class Markdown {
|
|
|
201
140
|
return this.defaultStylePrefix;
|
|
202
141
|
}
|
|
203
142
|
const sentinel = "\u0000";
|
|
204
|
-
|
|
205
|
-
if (this.defaultTextStyle.color) {
|
|
206
|
-
styled = this.defaultTextStyle.color(styled);
|
|
207
|
-
}
|
|
208
|
-
if (this.defaultTextStyle.bold) {
|
|
209
|
-
styled = this.theme.bold(styled);
|
|
210
|
-
}
|
|
211
|
-
if (this.defaultTextStyle.italic) {
|
|
212
|
-
styled = this.theme.italic(styled);
|
|
213
|
-
}
|
|
214
|
-
if (this.defaultTextStyle.strikethrough) {
|
|
215
|
-
styled = this.theme.strikethrough(styled);
|
|
216
|
-
}
|
|
217
|
-
if (this.defaultTextStyle.underline) {
|
|
218
|
-
styled = this.theme.underline(styled);
|
|
219
|
-
}
|
|
143
|
+
const styled = this.applyConfiguredDefaultStyle(sentinel);
|
|
220
144
|
const sentinelIndex = styled.indexOf(sentinel);
|
|
221
145
|
this.defaultStylePrefix = sentinelIndex >= 0 ? styled.slice(0, sentinelIndex) : "";
|
|
222
146
|
return this.defaultStylePrefix;
|
|
@@ -374,7 +298,7 @@ export class Markdown {
|
|
|
374
298
|
return lines;
|
|
375
299
|
}
|
|
376
300
|
renderInlineTokens(tokens, styleContext) {
|
|
377
|
-
|
|
301
|
+
const parts = [];
|
|
378
302
|
const resolvedStyleContext = styleContext ?? this.getDefaultInlineStyleContext();
|
|
379
303
|
const { applyText, stylePrefix } = resolvedStyleContext;
|
|
380
304
|
const applyTextWithNewlines = (text) => {
|
|
@@ -386,28 +310,28 @@ export class Markdown {
|
|
|
386
310
|
case "text":
|
|
387
311
|
// Text tokens in list items can have nested tokens for inline formatting
|
|
388
312
|
if (token.tokens && token.tokens.length > 0) {
|
|
389
|
-
|
|
313
|
+
parts.push(this.renderInlineTokens(token.tokens, resolvedStyleContext));
|
|
390
314
|
}
|
|
391
315
|
else {
|
|
392
|
-
|
|
316
|
+
parts.push(applyTextWithNewlines(token.text));
|
|
393
317
|
}
|
|
394
318
|
break;
|
|
395
319
|
case "paragraph":
|
|
396
320
|
// Paragraph tokens contain nested inline tokens
|
|
397
|
-
|
|
321
|
+
parts.push(this.renderInlineTokens(token.tokens || [], resolvedStyleContext));
|
|
398
322
|
break;
|
|
399
323
|
case "strong": {
|
|
400
324
|
const boldContent = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);
|
|
401
|
-
|
|
325
|
+
parts.push(this.theme.bold(boldContent), stylePrefix);
|
|
402
326
|
break;
|
|
403
327
|
}
|
|
404
328
|
case "em": {
|
|
405
329
|
const italicContent = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);
|
|
406
|
-
|
|
330
|
+
parts.push(this.theme.italic(italicContent), stylePrefix);
|
|
407
331
|
break;
|
|
408
332
|
}
|
|
409
333
|
case "codespan":
|
|
410
|
-
|
|
334
|
+
parts.push(this.theme.code(token.text), stylePrefix);
|
|
411
335
|
break;
|
|
412
336
|
case "link": {
|
|
413
337
|
const linkText = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);
|
|
@@ -415,7 +339,7 @@ export class Markdown {
|
|
|
415
339
|
if (getCapabilities().hyperlinks) {
|
|
416
340
|
// OSC 8: render as a clickable hyperlink. The URL is not printed inline,
|
|
417
341
|
// so we always show only the link text regardless of whether it matches href.
|
|
418
|
-
|
|
342
|
+
parts.push(hyperlink(styledLink, token.href), stylePrefix);
|
|
419
343
|
}
|
|
420
344
|
else {
|
|
421
345
|
// Fallback: print URL in parentheses when text differs from href.
|
|
@@ -424,39 +348,39 @@ export class Markdown {
|
|
|
424
348
|
// but href="mailto:foo@bar.com").
|
|
425
349
|
const hrefForComparison = token.href.startsWith("mailto:") ? token.href.slice(7) : token.href;
|
|
426
350
|
if (token.text === token.href || token.text === hrefForComparison) {
|
|
427
|
-
|
|
351
|
+
parts.push(styledLink, stylePrefix);
|
|
428
352
|
}
|
|
429
353
|
else {
|
|
430
|
-
|
|
354
|
+
parts.push(styledLink, this.theme.linkUrl(` (${token.href})`), stylePrefix);
|
|
431
355
|
}
|
|
432
356
|
}
|
|
433
357
|
break;
|
|
434
358
|
}
|
|
435
359
|
case "br":
|
|
436
|
-
|
|
360
|
+
parts.push("\n");
|
|
437
361
|
break;
|
|
438
362
|
case "del": {
|
|
439
363
|
const delContent = this.renderInlineTokens(token.tokens || [], resolvedStyleContext);
|
|
440
|
-
|
|
364
|
+
parts.push(this.theme.strikethrough(delContent), stylePrefix);
|
|
441
365
|
break;
|
|
442
366
|
}
|
|
443
367
|
case "html":
|
|
444
368
|
// Render inline HTML as plain text
|
|
445
369
|
if ("raw" in token && typeof token.raw === "string") {
|
|
446
|
-
|
|
370
|
+
parts.push(applyTextWithNewlines(token.raw));
|
|
447
371
|
}
|
|
448
372
|
break;
|
|
449
373
|
default:
|
|
450
374
|
// Handle any other inline token types as plain text
|
|
451
375
|
if ("text" in token && typeof token.text === "string") {
|
|
452
|
-
|
|
376
|
+
parts.push(applyTextWithNewlines(token.text));
|
|
453
377
|
}
|
|
454
378
|
}
|
|
455
379
|
}
|
|
456
|
-
while (stylePrefix &&
|
|
457
|
-
|
|
380
|
+
while (stylePrefix && parts[parts.length - 1] === stylePrefix) {
|
|
381
|
+
parts.pop();
|
|
458
382
|
}
|
|
459
|
-
return
|
|
383
|
+
return parts.join("");
|
|
460
384
|
}
|
|
461
385
|
getOrderedListMarker(item) {
|
|
462
386
|
const match = /^(?: {0,3})(\d{1,9}[.)])[ \t]+/.exec(item.raw);
|