@mariozechner/pi-tui 0.7.28 → 0.8.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/components/editor.d.ts +7 -4
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +11 -13
- package/dist/components/editor.js.map +1 -1
- package/dist/components/input.d.ts +1 -0
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +3 -0
- package/dist/components/input.js.map +1 -1
- package/dist/components/loader.d.ts +3 -1
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js +6 -3
- package/dist/components/loader.js.map +1 -1
- package/dist/components/markdown.d.ts +27 -9
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +39 -76
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/select-list.d.ts +12 -2
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js +27 -12
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/spacer.d.ts +1 -0
- package/dist/components/spacer.d.ts.map +1 -1
- package/dist/components/spacer.js +3 -0
- package/dist/components/spacer.js.map +1 -1
- package/dist/components/text.d.ts +4 -11
- package/dist/components/text.d.ts.map +1 -1
- package/dist/components/text.js +13 -10
- package/dist/components/text.js.map +1 -1
- package/dist/components/truncated-text.d.ts +1 -0
- package/dist/components/truncated-text.d.ts.map +1 -1
- package/dist/components/truncated-text.js +34 -13
- package/dist/components/truncated-text.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tui.d.ts +6 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +5 -0
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +2 -9
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -15
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"../tui.js\";\nimport { visibleWidth } from \"../utils.js\";\n\n/**\n * Input component - single-line text input with horizontal scrolling\n */\nexport class Input implements Component {\n\tprivate value: string = \"\";\n\tprivate cursor: number = 0; // Cursor position in the value\n\tpublic onSubmit?: (value: string) => void;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tgetValue(): string {\n\t\treturn this.value;\n\t}\n\n\tsetValue(value: string): void {\n\t\tthis.value = value;\n\t\tthis.cursor = Math.min(this.cursor, value.length);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\n\t\tif (data.includes(\"\\x1b[200~\")) {\n\t\t\tthis.isInPaste = true;\n\t\t\tthis.pasteBuffer = \"\";\n\t\t\tdata = data.replace(\"\\x1b[200~\", \"\");\n\t\t}\n\n\t\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tif (remaining) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Handle special keys\n\t\tif (data === \"\\r\" || data === \"\\n\") {\n\t\t\t// Enter - submit\n\t\t\tif (this.onSubmit) {\n\t\t\t\tthis.onSubmit(this.value);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x7f\" || data === \"\\x08\") {\n\t\t\t// Backspace\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tthis.value = this.value.slice(0, this.cursor - 1) + this.value.slice(this.cursor);\n\t\t\t\tthis.cursor--;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x1b[D\") {\n\t\t\t// Left arrow\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tthis.cursor--;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x1b[C\") {\n\t\t\t// Right arrow\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tthis.cursor++;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x1b[3~\") {\n\t\t\t// Delete\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + 1);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x01\") {\n\t\t\t// Ctrl+A - beginning of line\n\t\t\tthis.cursor = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x05\") {\n\t\t\t// Ctrl+E - end of line\n\t\t\tthis.cursor = this.value.length;\n\t\t\treturn;\n\t\t}\n\n\t\t// Regular character input\n\t\tif (data.length === 1 && data >= \" \" && data <= \"~\") {\n\t\t\tthis.value = this.value.slice(0, this.cursor) + data + this.value.slice(this.cursor);\n\t\t\tthis.cursor++;\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\");\n\n\t\t// Insert at cursor position\n\t\tthis.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);\n\t\tthis.cursor += cleanText.length;\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\treturn [prompt];\n\t\t}\n\n\t\tlet visibleText = \"\";\n\t\tlet cursorDisplay = this.cursor;\n\n\t\tif (this.value.length < availableWidth) {\n\t\t\t// Everything fits (leave room for cursor at end)\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\n\t\t\t// Reserve one character for cursor if it's at the end\n\t\t\tconst scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;\n\t\t\tconst halfWidth = Math.floor(scrollWidth / 2);\n\n\t\t\tif (this.cursor < halfWidth) {\n\t\t\t\t// Cursor near start\n\t\t\t\tvisibleText = this.value.slice(0, scrollWidth);\n\t\t\t\tcursorDisplay = this.cursor;\n\t\t\t} else if (this.cursor > this.value.length - halfWidth) {\n\t\t\t\t// Cursor near end\n\t\t\t\tvisibleText = this.value.slice(this.value.length - scrollWidth);\n\t\t\t\tcursorDisplay = scrollWidth - (this.value.length - this.cursor);\n\t\t\t} else {\n\t\t\t\t// Cursor in middle\n\t\t\t\tconst start = this.cursor - halfWidth;\n\t\t\t\tvisibleText = this.value.slice(start, start + scrollWidth);\n\t\t\t\tcursorDisplay = halfWidth;\n\t\t\t}\n\t\t}\n\n\t\t// Build line with fake cursor\n\t\t// Insert cursor character at cursor position\n\t\tconst beforeCursor = visibleText.slice(0, cursorDisplay);\n\t\tconst atCursor = visibleText[cursorDisplay] || \" \"; // Character at cursor, or space if at end\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + 1);\n\n\t\t// Use inverse video to show cursor\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal\n\t\tconst textWithCursor = beforeCursor + cursorChar + afterCursor;\n\n\t\t// Calculate visual width\n\t\tconst visualLength = visibleWidth(textWithCursor);\n\t\tconst padding = \" \".repeat(Math.max(0, availableWidth - visualLength));\n\t\tconst line = prompt + textWithCursor + padding;\n\n\t\treturn [line];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"../tui.js\";\nimport { visibleWidth } from \"../utils.js\";\n\n/**\n * Input component - single-line text input with horizontal scrolling\n */\nexport class Input implements Component {\n\tprivate value: string = \"\";\n\tprivate cursor: number = 0; // Cursor position in the value\n\tpublic onSubmit?: (value: string) => void;\n\n\t// Bracketed paste mode buffering\n\tprivate pasteBuffer: string = \"\";\n\tprivate isInPaste: boolean = false;\n\n\tgetValue(): string {\n\t\treturn this.value;\n\t}\n\n\tsetValue(value: string): void {\n\t\tthis.value = value;\n\t\tthis.cursor = Math.min(this.cursor, value.length);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Handle bracketed paste mode\n\t\t// Start of paste: \\x1b[200~\n\t\t// End of paste: \\x1b[201~\n\n\t\t// Check if we're starting a bracketed paste\n\t\tif (data.includes(\"\\x1b[200~\")) {\n\t\t\tthis.isInPaste = true;\n\t\t\tthis.pasteBuffer = \"\";\n\t\t\tdata = data.replace(\"\\x1b[200~\", \"\");\n\t\t}\n\n\t\t// If we're in a paste, buffer the data\n\t\tif (this.isInPaste) {\n\t\t\t// Check if this chunk contains the end marker\n\t\t\tthis.pasteBuffer += data;\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(\"\\x1b[201~\");\n\t\t\tif (endIndex !== -1) {\n\t\t\t\t// Extract the pasted content\n\t\t\t\tconst pasteContent = this.pasteBuffer.substring(0, endIndex);\n\n\t\t\t\t// Process the complete paste\n\t\t\t\tthis.handlePaste(pasteContent);\n\n\t\t\t\t// Reset paste state\n\t\t\t\tthis.isInPaste = false;\n\n\t\t\t\t// Handle any remaining input after the paste marker\n\t\t\t\tconst remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \\x1b[201~\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tif (remaining) {\n\t\t\t\t\tthis.handleInput(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Handle special keys\n\t\tif (data === \"\\r\" || data === \"\\n\") {\n\t\t\t// Enter - submit\n\t\t\tif (this.onSubmit) {\n\t\t\t\tthis.onSubmit(this.value);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x7f\" || data === \"\\x08\") {\n\t\t\t// Backspace\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tthis.value = this.value.slice(0, this.cursor - 1) + this.value.slice(this.cursor);\n\t\t\t\tthis.cursor--;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x1b[D\") {\n\t\t\t// Left arrow\n\t\t\tif (this.cursor > 0) {\n\t\t\t\tthis.cursor--;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x1b[C\") {\n\t\t\t// Right arrow\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tthis.cursor++;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x1b[3~\") {\n\t\t\t// Delete\n\t\t\tif (this.cursor < this.value.length) {\n\t\t\t\tthis.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + 1);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x01\") {\n\t\t\t// Ctrl+A - beginning of line\n\t\t\tthis.cursor = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"\\x05\") {\n\t\t\t// Ctrl+E - end of line\n\t\t\tthis.cursor = this.value.length;\n\t\t\treturn;\n\t\t}\n\n\t\t// Regular character input\n\t\tif (data.length === 1 && data >= \" \" && data <= \"~\") {\n\t\t\tthis.value = this.value.slice(0, this.cursor) + data + this.value.slice(this.cursor);\n\t\t\tthis.cursor++;\n\t\t}\n\t}\n\n\tprivate handlePaste(pastedText: string): void {\n\t\t// Clean the pasted text - remove newlines and carriage returns\n\t\tconst cleanText = pastedText.replace(/\\r\\n/g, \"\").replace(/\\r/g, \"\").replace(/\\n/g, \"\");\n\n\t\t// Insert at cursor position\n\t\tthis.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);\n\t\tthis.cursor += cleanText.length;\n\t}\n\n\tinvalidate(): void {\n\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\t// Calculate visible window\n\t\tconst prompt = \"> \";\n\t\tconst availableWidth = width - prompt.length;\n\n\t\tif (availableWidth <= 0) {\n\t\t\treturn [prompt];\n\t\t}\n\n\t\tlet visibleText = \"\";\n\t\tlet cursorDisplay = this.cursor;\n\n\t\tif (this.value.length < availableWidth) {\n\t\t\t// Everything fits (leave room for cursor at end)\n\t\t\tvisibleText = this.value;\n\t\t} else {\n\t\t\t// Need horizontal scrolling\n\t\t\t// Reserve one character for cursor if it's at the end\n\t\t\tconst scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;\n\t\t\tconst halfWidth = Math.floor(scrollWidth / 2);\n\n\t\t\tif (this.cursor < halfWidth) {\n\t\t\t\t// Cursor near start\n\t\t\t\tvisibleText = this.value.slice(0, scrollWidth);\n\t\t\t\tcursorDisplay = this.cursor;\n\t\t\t} else if (this.cursor > this.value.length - halfWidth) {\n\t\t\t\t// Cursor near end\n\t\t\t\tvisibleText = this.value.slice(this.value.length - scrollWidth);\n\t\t\t\tcursorDisplay = scrollWidth - (this.value.length - this.cursor);\n\t\t\t} else {\n\t\t\t\t// Cursor in middle\n\t\t\t\tconst start = this.cursor - halfWidth;\n\t\t\t\tvisibleText = this.value.slice(start, start + scrollWidth);\n\t\t\t\tcursorDisplay = halfWidth;\n\t\t\t}\n\t\t}\n\n\t\t// Build line with fake cursor\n\t\t// Insert cursor character at cursor position\n\t\tconst beforeCursor = visibleText.slice(0, cursorDisplay);\n\t\tconst atCursor = visibleText[cursorDisplay] || \" \"; // Character at cursor, or space if at end\n\t\tconst afterCursor = visibleText.slice(cursorDisplay + 1);\n\n\t\t// Use inverse video to show cursor\n\t\tconst cursorChar = `\\x1b[7m${atCursor}\\x1b[27m`; // ESC[7m = reverse video, ESC[27m = normal\n\t\tconst textWithCursor = beforeCursor + cursorChar + afterCursor;\n\n\t\t// Calculate visual width\n\t\tconst visualLength = visibleWidth(textWithCursor);\n\t\tconst padding = \" \".repeat(Math.max(0, availableWidth - visualLength));\n\t\tconst line = prompt + textWithCursor + padding;\n\n\t\treturn [line];\n\t}\n}\n"]}
|
|
@@ -4,12 +4,14 @@ import { Text } from "./text.js";
|
|
|
4
4
|
* Loader component that updates every 80ms with spinning animation
|
|
5
5
|
*/
|
|
6
6
|
export declare class Loader extends Text {
|
|
7
|
+
private spinnerColorFn;
|
|
8
|
+
private messageColorFn;
|
|
7
9
|
private message;
|
|
8
10
|
private frames;
|
|
9
11
|
private currentFrame;
|
|
10
12
|
private intervalId;
|
|
11
13
|
private ui;
|
|
12
|
-
constructor(ui: TUI, message?: string);
|
|
14
|
+
constructor(ui: TUI, spinnerColorFn: (str: string) => string, messageColorFn: (str: string) => string, message?: string);
|
|
13
15
|
render(width: number): string[];
|
|
14
16
|
start(): void;
|
|
15
17
|
stop(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,qBAAa,MAAO,SAAQ,IAAI;IAQ9B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,OAAO;IAThB,OAAO,CAAC,MAAM,CAAsD;IACpE,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,EAAE,CAAoB;IAE9B,YACC,EAAE,EAAE,GAAG,EACC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EACvC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EACvC,OAAO,GAAE,MAAqB,EAKtC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;IAED,KAAK,SAMJ;IAED,IAAI,SAKH;IAED,UAAU,CAAC,OAAO,EAAE,MAAM,QAGzB;IAED,OAAO,CAAC,aAAa;CAOrB","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\n/**\n * Loader component that updates every 80ms with spinning animation\n */\nexport class Loader extends Text {\n\tprivate frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\n\n\tconstructor(\n\t\tui: TUI,\n\t\tprivate spinnerColorFn: (str: string) => string,\n\t\tprivate messageColorFn: (str: string) => string,\n\t\tprivate message: string = \"Loading...\",\n\t) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart() {\n\t\tthis.updateDisplay();\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, 80);\n\t}\n\n\tstop() {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tsetMessage(message: string) {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay() {\n\t\tconst frame = this.frames[this.currentFrame];\n\t\tthis.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { Text } from "./text.js";
|
|
3
2
|
/**
|
|
4
3
|
* Loader component that updates every 80ms with spinning animation
|
|
5
4
|
*/
|
|
6
5
|
export class Loader extends Text {
|
|
6
|
+
spinnerColorFn;
|
|
7
|
+
messageColorFn;
|
|
7
8
|
message;
|
|
8
9
|
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
9
10
|
currentFrame = 0;
|
|
10
11
|
intervalId = null;
|
|
11
12
|
ui = null;
|
|
12
|
-
constructor(ui, message = "Loading...") {
|
|
13
|
+
constructor(ui, spinnerColorFn, messageColorFn, message = "Loading...") {
|
|
13
14
|
super("", 1, 0);
|
|
15
|
+
this.spinnerColorFn = spinnerColorFn;
|
|
16
|
+
this.messageColorFn = messageColorFn;
|
|
14
17
|
this.message = message;
|
|
15
18
|
this.ui = ui;
|
|
16
19
|
this.start();
|
|
@@ -37,7 +40,7 @@ export class Loader extends Text {
|
|
|
37
40
|
}
|
|
38
41
|
updateDisplay() {
|
|
39
42
|
const frame = this.frames[this.currentFrame];
|
|
40
|
-
this.setText(`${
|
|
43
|
+
this.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);
|
|
41
44
|
if (this.ui) {
|
|
42
45
|
this.ui.requestRender();
|
|
43
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,MAAM,OAAO,MAAO,SAAQ,IAAI;IAQtB,cAAc;IACd,cAAc;IACd,OAAO;IATR,MAAM,GAAG,CAAC,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,EAAE,KAAG,CAAC,CAAC;IAC5D,YAAY,GAAG,CAAC,CAAC;IACjB,UAAU,GAA0B,IAAI,CAAC;IACzC,EAAE,GAAe,IAAI,CAAC;IAE9B,YACC,EAAO,EACC,cAAuC,EACvC,cAAuC,EACvC,OAAO,GAAW,YAAY,EACrC;QACD,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;8BAJR,cAAc;8BACd,cAAc;uBACd,OAAO;QAGf,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;IAAA,CACb;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,OAAO,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAAA,CACpC;IAED,KAAK,GAAG;QACP,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YACnC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;YACjE,IAAI,CAAC,aAAa,EAAE,CAAC;QAAA,CACrB,EAAE,EAAE,CAAC,CAAC;IAAA,CACP;IAED,IAAI,GAAG;QACN,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACxB,CAAC;IAAA,CACD;IAED,UAAU,CAAC,OAAe,EAAE;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,aAAa,GAAG;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnF,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QACzB,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\n/**\n * Loader component that updates every 80ms with spinning animation\n */\nexport class Loader extends Text {\n\tprivate frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\n\n\tconstructor(\n\t\tui: TUI,\n\t\tprivate spinnerColorFn: (str: string) => string,\n\t\tprivate messageColorFn: (str: string) => string,\n\t\tprivate message: string = \"Loading...\",\n\t) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart() {\n\t\tthis.updateDisplay();\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, 80);\n\t}\n\n\tstop() {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tsetMessage(message: string) {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay() {\n\t\tconst frame = this.frames[this.currentFrame];\n\t\tthis.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
|
|
@@ -4,10 +4,10 @@ import type { Component } from "../tui.js";
|
|
|
4
4
|
* Applied to all text unless overridden by markdown formatting.
|
|
5
5
|
*/
|
|
6
6
|
export interface DefaultTextStyle {
|
|
7
|
-
/** Foreground color
|
|
8
|
-
color?: string;
|
|
9
|
-
/** Background color
|
|
10
|
-
bgColor?: string;
|
|
7
|
+
/** Foreground color function */
|
|
8
|
+
color?: (text: string) => string;
|
|
9
|
+
/** Background color function */
|
|
10
|
+
bgColor?: (text: string) => string;
|
|
11
11
|
/** Bold text */
|
|
12
12
|
bold?: boolean;
|
|
13
13
|
/** Italic text */
|
|
@@ -17,21 +17,39 @@ export interface DefaultTextStyle {
|
|
|
17
17
|
/** Underline text */
|
|
18
18
|
underline?: boolean;
|
|
19
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
|
+
}
|
|
20
40
|
export declare class Markdown implements Component {
|
|
21
41
|
private text;
|
|
22
42
|
private paddingX;
|
|
23
43
|
private paddingY;
|
|
24
44
|
private defaultTextStyle?;
|
|
45
|
+
private theme;
|
|
25
46
|
private cachedText?;
|
|
26
47
|
private cachedWidth?;
|
|
27
48
|
private cachedLines?;
|
|
28
|
-
constructor(text
|
|
49
|
+
constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle);
|
|
29
50
|
setText(text: string): void;
|
|
51
|
+
invalidate(): void;
|
|
30
52
|
render(width: number): string[];
|
|
31
|
-
/**
|
|
32
|
-
* Parse background color from defaultTextStyle to RGB values
|
|
33
|
-
*/
|
|
34
|
-
private parseBgColor;
|
|
35
53
|
/**
|
|
36
54
|
* Apply default text style to a string.
|
|
37
55
|
* This is the base styling applied to all text content.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/components/markdown.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAM3C;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAChC,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yBAAyB;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,QAAS,YAAW,SAAS;IACzC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAG5C,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAW;IAE/B,YAAY,IAAI,GAAE,MAAW,EAAE,QAAQ,GAAE,MAAU,EAAE,QAAQ,GAAE,MAAU,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,EAK7G;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM1B;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA6E9B;IAED;;OAEG;IACH,OAAO,CAAC,YAAY;IA8BpB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,WAAW;IAyFnB,OAAO,CAAC,kBAAkB;IAqE1B;;OAEG;IACH,OAAO,CAAC,UAAU;IA8ClB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAsCtB;;OAEG;IACH,OAAO,CAAC,WAAW;CAqDnB","sourcesContent":["import { Chalk } from \"chalk\";\nimport { marked, type Token } from \"marked\";\nimport type { Component } from \"../tui.js\";\nimport { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from \"../utils.js\";\n\n// Use a chalk instance with color level 3 for consistent ANSI output\nconst colorChalk = new Chalk({ level: 3 });\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 - named color or hex string like \"#ff0000\" */\n\tcolor?: string;\n\t/** Background color - named color or hex string like \"#ff0000\" */\n\tbgColor?: 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\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\n\t// Cache for rendered output\n\tprivate cachedText?: string;\n\tprivate cachedWidth?: number;\n\tprivate cachedLines?: string[];\n\n\tconstructor(text: string = \"\", paddingX: number = 1, paddingY: number = 1, defaultTextStyle?: DefaultTextStyle) {\n\t\tthis.text = text;\n\t\tthis.paddingX = paddingX;\n\t\tthis.paddingY = paddingY;\n\t\tthis.defaultTextStyle = defaultTextStyle;\n\t}\n\n\tsetText(text: string): void {\n\t\tthis.text = text;\n\t\t// Invalidate cache when text changes\n\t\tthis.cachedText = undefined;\n\t\tthis.cachedWidth = undefined;\n\t\tthis.cachedLines = undefined;\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;\n\t\t}\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;\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 = marked.lexer(normalizedText);\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\trenderedLines.push(...tokenLines);\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\twrappedLines.push(...wrapTextWithAnsi(line, contentWidth));\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 bgRgb = this.defaultTextStyle?.bgColor ? this.parseBgColor() : undefined;\n\t\tconst contentLines: string[] = [];\n\n\t\tfor (const line of wrappedLines) {\n\t\t\tconst lineWithMargins = leftMargin + line + rightMargin;\n\n\t\t\tif (bgRgb) {\n\t\t\t\tcontentLines.push(applyBackgroundToLine(lineWithMargins, width, bgRgb));\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 = bgRgb ? applyBackgroundToLine(emptyLine, width, bgRgb) : 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, ...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 : [\"\"];\n\t}\n\n\t/**\n\t * Parse background color from defaultTextStyle to RGB values\n\t */\n\tprivate parseBgColor(): { r: number; g: number; b: number } | undefined {\n\t\tif (!this.defaultTextStyle?.bgColor) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (this.defaultTextStyle.bgColor.startsWith(\"#\")) {\n\t\t\t// Hex color\n\t\t\tconst hex = this.defaultTextStyle.bgColor.substring(1);\n\t\t\treturn {\n\t\t\t\tr: Number.parseInt(hex.substring(0, 2), 16),\n\t\t\t\tg: Number.parseInt(hex.substring(2, 4), 16),\n\t\t\t\tb: Number.parseInt(hex.substring(4, 6), 16),\n\t\t\t};\n\t\t}\n\n\t\t// Named colors - map to RGB (common terminal colors)\n\t\tconst colorMap: Record<string, { r: number; g: number; b: number }> = {\n\t\t\tbgBlack: { r: 0, g: 0, b: 0 },\n\t\t\tbgRed: { r: 255, g: 0, b: 0 },\n\t\t\tbgGreen: { r: 0, g: 255, b: 0 },\n\t\t\tbgYellow: { r: 255, g: 255, b: 0 },\n\t\t\tbgBlue: { r: 0, g: 0, b: 255 },\n\t\t\tbgMagenta: { r: 255, g: 0, b: 255 },\n\t\t\tbgCyan: { r: 0, g: 255, b: 255 },\n\t\t\tbgWhite: { r: 255, g: 255, b: 255 },\n\t\t};\n\n\t\treturn colorMap[this.defaultTextStyle.bgColor];\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\tif (this.defaultTextStyle.color.startsWith(\"#\")) {\n\t\t\t\t// Hex color\n\t\t\t\tconst hex = this.defaultTextStyle.color.substring(1);\n\t\t\t\tconst r = Number.parseInt(hex.substring(0, 2), 16);\n\t\t\t\tconst g = Number.parseInt(hex.substring(2, 4), 16);\n\t\t\t\tconst b = Number.parseInt(hex.substring(4, 6), 16);\n\t\t\t\tstyled = colorChalk.rgb(r, g, b)(styled);\n\t\t\t} else {\n\t\t\t\t// Named color\n\t\t\t\tstyled = (colorChalk as any)[this.defaultTextStyle.color](styled);\n\t\t\t}\n\t\t}\n\n\t\t// Apply text decorations\n\t\tif (this.defaultTextStyle.bold) {\n\t\t\tstyled = colorChalk.bold(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.italic) {\n\t\t\tstyled = colorChalk.italic(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.strikethrough) {\n\t\t\tstyled = colorChalk.strikethrough(styled);\n\t\t}\n\t\tif (this.defaultTextStyle.underline) {\n\t\t\tstyled = colorChalk.underline(styled);\n\t\t}\n\n\t\treturn styled;\n\t}\n\n\tprivate renderToken(token: Token, width: number, nextTokenType?: string): 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\t\t\t\tconst headingText = this.renderInlineTokens(token.tokens || []);\n\t\t\t\tif (headingLevel === 1) {\n\t\t\t\t\tlines.push(colorChalk.bold.underline.yellow(headingText));\n\t\t\t\t} else if (headingLevel === 2) {\n\t\t\t\t\tlines.push(colorChalk.bold.yellow(headingText));\n\t\t\t\t} else {\n\t\t\t\t\tlines.push(colorChalk.bold(headingPrefix + headingText));\n\t\t\t\t}\n\t\t\t\tlines.push(\"\"); // Add spacing after headings\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 || []);\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 \"code\": {\n\t\t\t\tlines.push(colorChalk.gray(\"```\" + (token.lang || \"\")));\n\t\t\t\t// Split code by newlines and style each line\n\t\t\t\tconst codeLines = token.text.split(\"\\n\");\n\t\t\t\tfor (const codeLine of codeLines) {\n\t\t\t\t\tlines.push(colorChalk.dim(\" \") + colorChalk.green(codeLine));\n\t\t\t\t}\n\t\t\t\tlines.push(colorChalk.gray(\"```\"));\n\t\t\t\tlines.push(\"\"); // Add spacing after code blocks\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 any, 0);\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 any);\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 quoteText = this.renderInlineTokens(token.tokens || []);\n\t\t\t\tconst quoteLines = quoteText.split(\"\\n\");\n\t\t\t\tfor (const quoteLine of quoteLines) {\n\t\t\t\t\tlines.push(colorChalk.gray(\"│ \") + colorChalk.italic(quoteLine));\n\t\t\t\t}\n\t\t\t\tlines.push(\"\"); // Add spacing after blockquotes\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"hr\":\n\t\t\t\tlines.push(colorChalk.gray(\"─\".repeat(Math.min(width, 80))));\n\t\t\t\tlines.push(\"\"); // Add spacing after horizontal rules\n\t\t\t\tbreak;\n\n\t\t\tcase \"html\":\n\t\t\t\t// Skip HTML for terminal output\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[]): string {\n\t\tlet result = \"\";\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);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Apply default style to plain text\n\t\t\t\t\t\tresult += this.applyDefaultStyle(token.text);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"strong\": {\n\t\t\t\t\t// Apply bold, then reapply default style after\n\t\t\t\t\tconst boldContent = this.renderInlineTokens(token.tokens || []);\n\t\t\t\t\tresult += colorChalk.bold(boldContent) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"em\": {\n\t\t\t\t\t// Apply italic, then reapply default style after\n\t\t\t\t\tconst italicContent = this.renderInlineTokens(token.tokens || []);\n\t\t\t\t\tresult += colorChalk.italic(italicContent) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"codespan\":\n\t\t\t\t\t// Apply code styling without backticks\n\t\t\t\t\tresult += colorChalk.cyan(token.text) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"link\": {\n\t\t\t\t\tconst linkText = this.renderInlineTokens(token.tokens || []);\n\t\t\t\t\t// If link text matches href, only show the link once\n\t\t\t\t\tif (linkText === token.href) {\n\t\t\t\t\t\tresult += colorChalk.underline.blue(linkText) + this.applyDefaultStyle(\"\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult +=\n\t\t\t\t\t\t\tcolorChalk.underline.blue(linkText) +\n\t\t\t\t\t\t\tcolorChalk.gray(` (${token.href})`) +\n\t\t\t\t\t\t\tthis.applyDefaultStyle(\"\");\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 || []);\n\t\t\t\t\tresult += colorChalk.strikethrough(delContent) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\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 += this.applyDefaultStyle(token.text);\n\t\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Render a list with proper nesting support\n\t */\n\tprivate renderList(token: Token & { items: any[]; ordered: boolean }, depth: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst indent = \" \".repeat(depth);\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 ? `${i + 1}. ` : \"- \";\n\n\t\t\t// Process item tokens to handle nested lists\n\t\t\tconst itemLines = this.renderListItem(item.tokens || [], depth);\n\n\t\t\tif (itemLines.length > 0) {\n\t\t\t\t// First line - check if it's a nested list\n\t\t\t\t// A nested list will start with indent (spaces) followed by cyan bullet\n\t\t\t\tconst firstLine = itemLines[0];\n\t\t\t\tconst isNestedList = /^\\s+\\x1b\\[36m[-\\d]/.test(firstLine); // starts with spaces + cyan + bullet char\n\n\t\t\t\tif (isNestedList) {\n\t\t\t\t\t// This is a nested list, just add it as-is (already has full indent)\n\t\t\t\t\tlines.push(firstLine);\n\t\t\t\t} else {\n\t\t\t\t\t// Regular text content - add indent and bullet\n\t\t\t\t\tlines.push(indent + colorChalk.cyan(bullet) + firstLine);\n\t\t\t\t}\n\n\t\t\t\t// Rest of the lines\n\t\t\t\tfor (let j = 1; j < itemLines.length; j++) {\n\t\t\t\t\tconst line = itemLines[j];\n\t\t\t\t\tconst isNestedListLine = /^\\s+\\x1b\\[36m[-\\d]/.test(line); // starts with spaces + cyan + bullet char\n\n\t\t\t\t\tif (isNestedListLine) {\n\t\t\t\t\t\t// Nested list line - already has full indent\n\t\t\t\t\t\tlines.push(line);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Regular content - add parent indent + 2 spaces for continuation\n\t\t\t\t\t\tlines.push(indent + \" \" + line);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlines.push(indent + colorChalk.cyan(bullet));\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\t/**\n\t * Render list item tokens, handling nested lists\n\t * Returns lines WITHOUT the parent indent (renderList will add it)\n\t */\n\tprivate renderListItem(tokens: Token[], parentDepth: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tfor (const token of tokens) {\n\t\t\tif (token.type === \"list\") {\n\t\t\t\t// Nested list - render with one additional indent level\n\t\t\t\t// These lines will have their own indent, so we just add them as-is\n\t\t\t\tconst nestedLines = this.renderList(token as any, parentDepth + 1);\n\t\t\t\tlines.push(...nestedLines);\n\t\t\t} else if (token.type === \"text\") {\n\t\t\t\t// Text content (may have inline tokens)\n\t\t\t\tconst text =\n\t\t\t\t\ttoken.tokens && token.tokens.length > 0 ? this.renderInlineTokens(token.tokens) : token.text || \"\";\n\t\t\t\tlines.push(text);\n\t\t\t} else if (token.type === \"paragraph\") {\n\t\t\t\t// Paragraph in list item\n\t\t\t\tconst text = this.renderInlineTokens(token.tokens || []);\n\t\t\t\tlines.push(text);\n\t\t\t} else if (token.type === \"code\") {\n\t\t\t\t// Code block in list item\n\t\t\t\tlines.push(colorChalk.gray(\"```\" + (token.lang || \"\")));\n\t\t\t\tconst codeLines = token.text.split(\"\\n\");\n\t\t\t\tfor (const codeLine of codeLines) {\n\t\t\t\t\tlines.push(colorChalk.dim(\" \") + colorChalk.green(codeLine));\n\t\t\t\t}\n\t\t\t\tlines.push(colorChalk.gray(\"```\"));\n\t\t\t} else {\n\t\t\t\t// Other token types - try to render as inline\n\t\t\t\tconst text = this.renderInlineTokens([token]);\n\t\t\t\tif (text) {\n\t\t\t\t\tlines.push(text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\t/**\n\t * Render a table\n\t */\n\tprivate renderTable(token: Token & { header: any[]; rows: any[][] }): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Calculate column widths\n\t\tconst columnWidths: number[] = [];\n\n\t\t// Check header\n\t\tfor (let i = 0; i < token.header.length; i++) {\n\t\t\tconst headerText = this.renderInlineTokens(token.header[i].tokens || []);\n\t\t\tconst width = visibleWidth(headerText);\n\t\t\tcolumnWidths[i] = Math.max(columnWidths[i] || 0, width);\n\t\t}\n\n\t\t// Check rows\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 || []);\n\t\t\t\tconst width = visibleWidth(cellText);\n\t\t\t\tcolumnWidths[i] = Math.max(columnWidths[i] || 0, width);\n\t\t\t}\n\t\t}\n\n\t\t// Limit column widths to reasonable max\n\t\tconst maxColWidth = 40;\n\t\tfor (let i = 0; i < columnWidths.length; i++) {\n\t\t\tcolumnWidths[i] = Math.min(columnWidths[i], maxColWidth);\n\t\t}\n\n\t\t// Render header\n\t\tconst headerCells = token.header.map((cell, i) => {\n\t\t\tconst text = this.renderInlineTokens(cell.tokens || []);\n\t\t\treturn colorChalk.bold(text.padEnd(columnWidths[i]));\n\t\t});\n\t\tlines.push(\"│ \" + headerCells.join(\" │ \") + \" │\");\n\n\t\t// Render separator\n\t\tconst separatorCells = columnWidths.map((width) => \"─\".repeat(width));\n\t\tlines.push(\"├─\" + separatorCells.join(\"─┼─\") + \"─┤\");\n\n\t\t// Render rows\n\t\tfor (const row of token.rows) {\n\t\t\tconst rowCells = row.map((cell, i) => {\n\t\t\t\tconst text = this.renderInlineTokens(cell.tokens || []);\n\t\t\t\tconst visWidth = visibleWidth(text);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, columnWidths[i] - visWidth));\n\t\t\t\treturn text + padding;\n\t\t\t});\n\t\t\tlines.push(\"│ \" + rowCells.join(\" │ \") + \" │\");\n\t\t}\n\n\t\tlines.push(\"\"); // Add spacing after table\n\t\treturn lines;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/components/markdown.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,gCAAgC;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,gBAAgB;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yBAAyB;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACpC;AAED,qBAAa,QAAS,YAAW,SAAS;IACzC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAG7B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAW;IAE/B,YACC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,EACpB,gBAAgB,CAAC,EAAE,gBAAgB,EAOnC;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAG1B;IAED,UAAU,IAAI,IAAI,CAIjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA6E9B;IAED;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IA6BzB,OAAO,CAAC,WAAW;IA2FnB,OAAO,CAAC,kBAAkB;IAqE1B;;OAEG;IACH,OAAO,CAAC,UAAU;IA8ClB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAsCtB;;OAEG;IACH,OAAO,CAAC,WAAW;CAqDnB","sourcesContent":["import { marked, type Token } from \"marked\";\nimport type { Component } from \"../tui.js\";\nimport { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from \"../utils.js\";\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}\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\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) {\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}\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\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;\n\t\t}\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;\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 = marked.lexer(normalizedText);\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\trenderedLines.push(...tokenLines);\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\twrappedLines.push(...wrapTextWithAnsi(line, contentWidth));\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\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, ...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 : [\"\"];\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 renderToken(token: Token, width: number, nextTokenType?: string): 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\t\t\t\tconst headingText = this.renderInlineTokens(token.tokens || []);\n\t\t\t\tlet styledHeading: string;\n\t\t\t\tif (headingLevel === 1) {\n\t\t\t\t\tstyledHeading = this.theme.heading(this.theme.bold(this.theme.underline(headingText)));\n\t\t\t\t} else if (headingLevel === 2) {\n\t\t\t\t\tstyledHeading = this.theme.heading(this.theme.bold(headingText));\n\t\t\t\t} else {\n\t\t\t\t\tstyledHeading = this.theme.heading(this.theme.bold(headingPrefix + headingText));\n\t\t\t\t}\n\t\t\t\tlines.push(styledHeading);\n\t\t\t\tlines.push(\"\"); // Add spacing after headings\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 || []);\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 \"code\": {\n\t\t\t\tlines.push(this.theme.codeBlockBorder(\"```\" + (token.lang || \"\")));\n\t\t\t\t// Split code by newlines and style each line\n\t\t\t\tconst codeLines = token.text.split(\"\\n\");\n\t\t\t\tfor (const codeLine of codeLines) {\n\t\t\t\t\tlines.push(\" \" + this.theme.codeBlock(codeLine));\n\t\t\t\t}\n\t\t\t\tlines.push(this.theme.codeBlockBorder(\"```\"));\n\t\t\t\tlines.push(\"\"); // Add spacing after code blocks\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 any, 0);\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 any);\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 quoteText = this.renderInlineTokens(token.tokens || []);\n\t\t\t\tconst quoteLines = quoteText.split(\"\\n\");\n\t\t\t\tfor (const quoteLine of quoteLines) {\n\t\t\t\t\tlines.push(this.theme.quoteBorder(\"│ \") + this.theme.quote(this.theme.italic(quoteLine)));\n\t\t\t\t}\n\t\t\t\tlines.push(\"\"); // Add spacing after blockquotes\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\tlines.push(\"\"); // Add spacing after horizontal rules\n\t\t\t\tbreak;\n\n\t\t\tcase \"html\":\n\t\t\t\t// Skip HTML for terminal output\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[]): string {\n\t\tlet result = \"\";\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);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Apply default style to plain text\n\t\t\t\t\t\tresult += this.applyDefaultStyle(token.text);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"strong\": {\n\t\t\t\t\t// Apply bold, then reapply default style after\n\t\t\t\t\tconst boldContent = this.renderInlineTokens(token.tokens || []);\n\t\t\t\t\tresult += this.theme.bold(boldContent) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"em\": {\n\t\t\t\t\t// Apply italic, then reapply default style after\n\t\t\t\t\tconst italicContent = this.renderInlineTokens(token.tokens || []);\n\t\t\t\t\tresult += this.theme.italic(italicContent) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"codespan\":\n\t\t\t\t\t// Apply code styling without backticks\n\t\t\t\t\tresult += this.theme.code(token.text) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"link\": {\n\t\t\t\t\tconst linkText = this.renderInlineTokens(token.tokens || []);\n\t\t\t\t\t// If link text matches href, only show the link once\n\t\t\t\t\tif (linkText === token.href) {\n\t\t\t\t\t\tresult += this.theme.link(this.theme.underline(linkText)) + this.applyDefaultStyle(\"\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult +=\n\t\t\t\t\t\t\tthis.theme.link(this.theme.underline(linkText)) +\n\t\t\t\t\t\t\tthis.theme.linkUrl(` (${token.href})`) +\n\t\t\t\t\t\t\tthis.applyDefaultStyle(\"\");\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 || []);\n\t\t\t\t\tresult += this.theme.strikethrough(delContent) + this.applyDefaultStyle(\"\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\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 += this.applyDefaultStyle(token.text);\n\t\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Render a list with proper nesting support\n\t */\n\tprivate renderList(token: Token & { items: any[]; ordered: boolean }, depth: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst indent = \" \".repeat(depth);\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 ? `${i + 1}. ` : \"- \";\n\n\t\t\t// Process item tokens to handle nested lists\n\t\t\tconst itemLines = this.renderListItem(item.tokens || [], depth);\n\n\t\t\tif (itemLines.length > 0) {\n\t\t\t\t// First line - check if it's a nested list\n\t\t\t\t// A nested list will start with indent (spaces) followed by cyan bullet\n\t\t\t\tconst firstLine = itemLines[0];\n\t\t\t\tconst isNestedList = /^\\s+\\x1b\\[36m[-\\d]/.test(firstLine); // starts with spaces + cyan + bullet char\n\n\t\t\t\tif (isNestedList) {\n\t\t\t\t\t// This is a nested list, just add it as-is (already has full indent)\n\t\t\t\t\tlines.push(firstLine);\n\t\t\t\t} else {\n\t\t\t\t\t// Regular text content - add indent and bullet\n\t\t\t\t\tlines.push(indent + this.theme.listBullet(bullet) + firstLine);\n\t\t\t\t}\n\n\t\t\t\t// Rest of the lines\n\t\t\t\tfor (let j = 1; j < itemLines.length; j++) {\n\t\t\t\t\tconst line = itemLines[j];\n\t\t\t\t\tconst isNestedListLine = /^\\s+\\x1b\\[36m[-\\d]/.test(line); // starts with spaces + cyan + bullet char\n\n\t\t\t\t\tif (isNestedListLine) {\n\t\t\t\t\t\t// Nested list line - already has full indent\n\t\t\t\t\t\tlines.push(line);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Regular content - add parent indent + 2 spaces for continuation\n\t\t\t\t\t\tlines.push(indent + \" \" + line);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlines.push(indent + this.theme.listBullet(bullet));\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\t/**\n\t * Render list item tokens, handling nested lists\n\t * Returns lines WITHOUT the parent indent (renderList will add it)\n\t */\n\tprivate renderListItem(tokens: Token[], parentDepth: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tfor (const token of tokens) {\n\t\t\tif (token.type === \"list\") {\n\t\t\t\t// Nested list - render with one additional indent level\n\t\t\t\t// These lines will have their own indent, so we just add them as-is\n\t\t\t\tconst nestedLines = this.renderList(token as any, parentDepth + 1);\n\t\t\t\tlines.push(...nestedLines);\n\t\t\t} else if (token.type === \"text\") {\n\t\t\t\t// Text content (may have inline tokens)\n\t\t\t\tconst text =\n\t\t\t\t\ttoken.tokens && token.tokens.length > 0 ? this.renderInlineTokens(token.tokens) : token.text || \"\";\n\t\t\t\tlines.push(text);\n\t\t\t} else if (token.type === \"paragraph\") {\n\t\t\t\t// Paragraph in list item\n\t\t\t\tconst text = this.renderInlineTokens(token.tokens || []);\n\t\t\t\tlines.push(text);\n\t\t\t} else if (token.type === \"code\") {\n\t\t\t\t// Code block in list item\n\t\t\t\tlines.push(this.theme.codeBlockBorder(\"```\" + (token.lang || \"\")));\n\t\t\t\tconst codeLines = token.text.split(\"\\n\");\n\t\t\t\tfor (const codeLine of codeLines) {\n\t\t\t\t\tlines.push(\" \" + this.theme.codeBlock(codeLine));\n\t\t\t\t}\n\t\t\t\tlines.push(this.theme.codeBlockBorder(\"```\"));\n\t\t\t} else {\n\t\t\t\t// Other token types - try to render as inline\n\t\t\t\tconst text = this.renderInlineTokens([token]);\n\t\t\t\tif (text) {\n\t\t\t\t\tlines.push(text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\t/**\n\t * Render a table\n\t */\n\tprivate renderTable(token: Token & { header: any[]; rows: any[][] }): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Calculate column widths\n\t\tconst columnWidths: number[] = [];\n\n\t\t// Check header\n\t\tfor (let i = 0; i < token.header.length; i++) {\n\t\t\tconst headerText = this.renderInlineTokens(token.header[i].tokens || []);\n\t\t\tconst width = visibleWidth(headerText);\n\t\t\tcolumnWidths[i] = Math.max(columnWidths[i] || 0, width);\n\t\t}\n\n\t\t// Check rows\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 || []);\n\t\t\t\tconst width = visibleWidth(cellText);\n\t\t\t\tcolumnWidths[i] = Math.max(columnWidths[i] || 0, width);\n\t\t\t}\n\t\t}\n\n\t\t// Limit column widths to reasonable max\n\t\tconst maxColWidth = 40;\n\t\tfor (let i = 0; i < columnWidths.length; i++) {\n\t\t\tcolumnWidths[i] = Math.min(columnWidths[i], maxColWidth);\n\t\t}\n\n\t\t// Render header\n\t\tconst headerCells = token.header.map((cell, i) => {\n\t\t\tconst text = this.renderInlineTokens(cell.tokens || []);\n\t\t\treturn this.theme.bold(text.padEnd(columnWidths[i]));\n\t\t});\n\t\tlines.push(\"│ \" + headerCells.join(\" │ \") + \" │\");\n\n\t\t// Render separator\n\t\tconst separatorCells = columnWidths.map((width) => \"─\".repeat(width));\n\t\tlines.push(\"├─\" + separatorCells.join(\"─┼─\") + \"─┤\");\n\n\t\t// Render rows\n\t\tfor (const row of token.rows) {\n\t\t\tconst rowCells = row.map((cell, i) => {\n\t\t\t\tconst text = this.renderInlineTokens(cell.tokens || []);\n\t\t\t\tconst visWidth = visibleWidth(text);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, columnWidths[i] - visWidth));\n\t\t\t\treturn text + padding;\n\t\t\t});\n\t\t\tlines.push(\"│ \" + rowCells.join(\" │ \") + \" │\");\n\t\t}\n\n\t\tlines.push(\"\"); // Add spacing after table\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import { Chalk } from "chalk";
|
|
2
1
|
import { marked } from "marked";
|
|
3
2
|
import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils.js";
|
|
4
|
-
// Use a chalk instance with color level 3 for consistent ANSI output
|
|
5
|
-
const colorChalk = new Chalk({ level: 3 });
|
|
6
3
|
export class Markdown {
|
|
7
4
|
text;
|
|
8
5
|
paddingX; // Left/right padding
|
|
9
6
|
paddingY; // Top/bottom padding
|
|
10
7
|
defaultTextStyle;
|
|
8
|
+
theme;
|
|
11
9
|
// Cache for rendered output
|
|
12
10
|
cachedText;
|
|
13
11
|
cachedWidth;
|
|
14
12
|
cachedLines;
|
|
15
|
-
constructor(text
|
|
13
|
+
constructor(text, paddingX, paddingY, theme, defaultTextStyle) {
|
|
16
14
|
this.text = text;
|
|
17
15
|
this.paddingX = paddingX;
|
|
18
16
|
this.paddingY = paddingY;
|
|
17
|
+
this.theme = theme;
|
|
19
18
|
this.defaultTextStyle = defaultTextStyle;
|
|
20
19
|
}
|
|
21
20
|
setText(text) {
|
|
22
21
|
this.text = text;
|
|
23
|
-
|
|
22
|
+
this.invalidate();
|
|
23
|
+
}
|
|
24
|
+
invalidate() {
|
|
24
25
|
this.cachedText = undefined;
|
|
25
26
|
this.cachedWidth = undefined;
|
|
26
27
|
this.cachedLines = undefined;
|
|
@@ -61,12 +62,12 @@ export class Markdown {
|
|
|
61
62
|
// Add margins and background to each wrapped line
|
|
62
63
|
const leftMargin = " ".repeat(this.paddingX);
|
|
63
64
|
const rightMargin = " ".repeat(this.paddingX);
|
|
64
|
-
const
|
|
65
|
+
const bgFn = this.defaultTextStyle?.bgColor;
|
|
65
66
|
const contentLines = [];
|
|
66
67
|
for (const line of wrappedLines) {
|
|
67
68
|
const lineWithMargins = leftMargin + line + rightMargin;
|
|
68
|
-
if (
|
|
69
|
-
contentLines.push(applyBackgroundToLine(lineWithMargins, width,
|
|
69
|
+
if (bgFn) {
|
|
70
|
+
contentLines.push(applyBackgroundToLine(lineWithMargins, width, bgFn));
|
|
70
71
|
}
|
|
71
72
|
else {
|
|
72
73
|
// No background - just pad to width
|
|
@@ -79,7 +80,7 @@ export class Markdown {
|
|
|
79
80
|
const emptyLine = " ".repeat(width);
|
|
80
81
|
const emptyLines = [];
|
|
81
82
|
for (let i = 0; i < this.paddingY; i++) {
|
|
82
|
-
const line =
|
|
83
|
+
const line = bgFn ? applyBackgroundToLine(emptyLine, width, bgFn) : emptyLine;
|
|
83
84
|
emptyLines.push(line);
|
|
84
85
|
}
|
|
85
86
|
// Combine top padding, content, and bottom padding
|
|
@@ -90,35 +91,6 @@ export class Markdown {
|
|
|
90
91
|
this.cachedLines = result;
|
|
91
92
|
return result.length > 0 ? result : [""];
|
|
92
93
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Parse background color from defaultTextStyle to RGB values
|
|
95
|
-
*/
|
|
96
|
-
parseBgColor() {
|
|
97
|
-
if (!this.defaultTextStyle?.bgColor) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
if (this.defaultTextStyle.bgColor.startsWith("#")) {
|
|
101
|
-
// Hex color
|
|
102
|
-
const hex = this.defaultTextStyle.bgColor.substring(1);
|
|
103
|
-
return {
|
|
104
|
-
r: Number.parseInt(hex.substring(0, 2), 16),
|
|
105
|
-
g: Number.parseInt(hex.substring(2, 4), 16),
|
|
106
|
-
b: Number.parseInt(hex.substring(4, 6), 16),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
// Named colors - map to RGB (common terminal colors)
|
|
110
|
-
const colorMap = {
|
|
111
|
-
bgBlack: { r: 0, g: 0, b: 0 },
|
|
112
|
-
bgRed: { r: 255, g: 0, b: 0 },
|
|
113
|
-
bgGreen: { r: 0, g: 255, b: 0 },
|
|
114
|
-
bgYellow: { r: 255, g: 255, b: 0 },
|
|
115
|
-
bgBlue: { r: 0, g: 0, b: 255 },
|
|
116
|
-
bgMagenta: { r: 255, g: 0, b: 255 },
|
|
117
|
-
bgCyan: { r: 0, g: 255, b: 255 },
|
|
118
|
-
bgWhite: { r: 255, g: 255, b: 255 },
|
|
119
|
-
};
|
|
120
|
-
return colorMap[this.defaultTextStyle.bgColor];
|
|
121
|
-
}
|
|
122
94
|
/**
|
|
123
95
|
* Apply default text style to a string.
|
|
124
96
|
* This is the base styling applied to all text content.
|
|
@@ -132,31 +104,20 @@ export class Markdown {
|
|
|
132
104
|
let styled = text;
|
|
133
105
|
// Apply foreground color (NOT background - that's applied at padding stage)
|
|
134
106
|
if (this.defaultTextStyle.color) {
|
|
135
|
-
|
|
136
|
-
// Hex color
|
|
137
|
-
const hex = this.defaultTextStyle.color.substring(1);
|
|
138
|
-
const r = Number.parseInt(hex.substring(0, 2), 16);
|
|
139
|
-
const g = Number.parseInt(hex.substring(2, 4), 16);
|
|
140
|
-
const b = Number.parseInt(hex.substring(4, 6), 16);
|
|
141
|
-
styled = colorChalk.rgb(r, g, b)(styled);
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
// Named color
|
|
145
|
-
styled = colorChalk[this.defaultTextStyle.color](styled);
|
|
146
|
-
}
|
|
107
|
+
styled = this.defaultTextStyle.color(styled);
|
|
147
108
|
}
|
|
148
|
-
// Apply text decorations
|
|
109
|
+
// Apply text decorations using this.theme
|
|
149
110
|
if (this.defaultTextStyle.bold) {
|
|
150
|
-
styled =
|
|
111
|
+
styled = this.theme.bold(styled);
|
|
151
112
|
}
|
|
152
113
|
if (this.defaultTextStyle.italic) {
|
|
153
|
-
styled =
|
|
114
|
+
styled = this.theme.italic(styled);
|
|
154
115
|
}
|
|
155
116
|
if (this.defaultTextStyle.strikethrough) {
|
|
156
|
-
styled =
|
|
117
|
+
styled = this.theme.strikethrough(styled);
|
|
157
118
|
}
|
|
158
119
|
if (this.defaultTextStyle.underline) {
|
|
159
|
-
styled =
|
|
120
|
+
styled = this.theme.underline(styled);
|
|
160
121
|
}
|
|
161
122
|
return styled;
|
|
162
123
|
}
|
|
@@ -167,15 +128,17 @@ export class Markdown {
|
|
|
167
128
|
const headingLevel = token.depth;
|
|
168
129
|
const headingPrefix = "#".repeat(headingLevel) + " ";
|
|
169
130
|
const headingText = this.renderInlineTokens(token.tokens || []);
|
|
131
|
+
let styledHeading;
|
|
170
132
|
if (headingLevel === 1) {
|
|
171
|
-
|
|
133
|
+
styledHeading = this.theme.heading(this.theme.bold(this.theme.underline(headingText)));
|
|
172
134
|
}
|
|
173
135
|
else if (headingLevel === 2) {
|
|
174
|
-
|
|
136
|
+
styledHeading = this.theme.heading(this.theme.bold(headingText));
|
|
175
137
|
}
|
|
176
138
|
else {
|
|
177
|
-
|
|
139
|
+
styledHeading = this.theme.heading(this.theme.bold(headingPrefix + headingText));
|
|
178
140
|
}
|
|
141
|
+
lines.push(styledHeading);
|
|
179
142
|
lines.push(""); // Add spacing after headings
|
|
180
143
|
break;
|
|
181
144
|
}
|
|
@@ -189,13 +152,13 @@ export class Markdown {
|
|
|
189
152
|
break;
|
|
190
153
|
}
|
|
191
154
|
case "code": {
|
|
192
|
-
lines.push(
|
|
155
|
+
lines.push(this.theme.codeBlockBorder("```" + (token.lang || "")));
|
|
193
156
|
// Split code by newlines and style each line
|
|
194
157
|
const codeLines = token.text.split("\n");
|
|
195
158
|
for (const codeLine of codeLines) {
|
|
196
|
-
lines.push(
|
|
159
|
+
lines.push(" " + this.theme.codeBlock(codeLine));
|
|
197
160
|
}
|
|
198
|
-
lines.push(
|
|
161
|
+
lines.push(this.theme.codeBlockBorder("```"));
|
|
199
162
|
lines.push(""); // Add spacing after code blocks
|
|
200
163
|
break;
|
|
201
164
|
}
|
|
@@ -215,13 +178,13 @@ export class Markdown {
|
|
|
215
178
|
const quoteText = this.renderInlineTokens(token.tokens || []);
|
|
216
179
|
const quoteLines = quoteText.split("\n");
|
|
217
180
|
for (const quoteLine of quoteLines) {
|
|
218
|
-
lines.push(
|
|
181
|
+
lines.push(this.theme.quoteBorder("│ ") + this.theme.quote(this.theme.italic(quoteLine)));
|
|
219
182
|
}
|
|
220
183
|
lines.push(""); // Add spacing after blockquotes
|
|
221
184
|
break;
|
|
222
185
|
}
|
|
223
186
|
case "hr":
|
|
224
|
-
lines.push(
|
|
187
|
+
lines.push(this.theme.hr("─".repeat(Math.min(width, 80))));
|
|
225
188
|
lines.push(""); // Add spacing after horizontal rules
|
|
226
189
|
break;
|
|
227
190
|
case "html":
|
|
@@ -256,29 +219,29 @@ export class Markdown {
|
|
|
256
219
|
case "strong": {
|
|
257
220
|
// Apply bold, then reapply default style after
|
|
258
221
|
const boldContent = this.renderInlineTokens(token.tokens || []);
|
|
259
|
-
result +=
|
|
222
|
+
result += this.theme.bold(boldContent) + this.applyDefaultStyle("");
|
|
260
223
|
break;
|
|
261
224
|
}
|
|
262
225
|
case "em": {
|
|
263
226
|
// Apply italic, then reapply default style after
|
|
264
227
|
const italicContent = this.renderInlineTokens(token.tokens || []);
|
|
265
|
-
result +=
|
|
228
|
+
result += this.theme.italic(italicContent) + this.applyDefaultStyle("");
|
|
266
229
|
break;
|
|
267
230
|
}
|
|
268
231
|
case "codespan":
|
|
269
232
|
// Apply code styling without backticks
|
|
270
|
-
result +=
|
|
233
|
+
result += this.theme.code(token.text) + this.applyDefaultStyle("");
|
|
271
234
|
break;
|
|
272
235
|
case "link": {
|
|
273
236
|
const linkText = this.renderInlineTokens(token.tokens || []);
|
|
274
237
|
// If link text matches href, only show the link once
|
|
275
238
|
if (linkText === token.href) {
|
|
276
|
-
result +=
|
|
239
|
+
result += this.theme.link(this.theme.underline(linkText)) + this.applyDefaultStyle("");
|
|
277
240
|
}
|
|
278
241
|
else {
|
|
279
242
|
result +=
|
|
280
|
-
|
|
281
|
-
|
|
243
|
+
this.theme.link(this.theme.underline(linkText)) +
|
|
244
|
+
this.theme.linkUrl(` (${token.href})`) +
|
|
282
245
|
this.applyDefaultStyle("");
|
|
283
246
|
}
|
|
284
247
|
break;
|
|
@@ -288,7 +251,7 @@ export class Markdown {
|
|
|
288
251
|
break;
|
|
289
252
|
case "del": {
|
|
290
253
|
const delContent = this.renderInlineTokens(token.tokens || []);
|
|
291
|
-
result +=
|
|
254
|
+
result += this.theme.strikethrough(delContent) + this.applyDefaultStyle("");
|
|
292
255
|
break;
|
|
293
256
|
}
|
|
294
257
|
default:
|
|
@@ -322,7 +285,7 @@ export class Markdown {
|
|
|
322
285
|
}
|
|
323
286
|
else {
|
|
324
287
|
// Regular text content - add indent and bullet
|
|
325
|
-
lines.push(indent +
|
|
288
|
+
lines.push(indent + this.theme.listBullet(bullet) + firstLine);
|
|
326
289
|
}
|
|
327
290
|
// Rest of the lines
|
|
328
291
|
for (let j = 1; j < itemLines.length; j++) {
|
|
@@ -339,7 +302,7 @@ export class Markdown {
|
|
|
339
302
|
}
|
|
340
303
|
}
|
|
341
304
|
else {
|
|
342
|
-
lines.push(indent +
|
|
305
|
+
lines.push(indent + this.theme.listBullet(bullet));
|
|
343
306
|
}
|
|
344
307
|
}
|
|
345
308
|
return lines;
|
|
@@ -369,12 +332,12 @@ export class Markdown {
|
|
|
369
332
|
}
|
|
370
333
|
else if (token.type === "code") {
|
|
371
334
|
// Code block in list item
|
|
372
|
-
lines.push(
|
|
335
|
+
lines.push(this.theme.codeBlockBorder("```" + (token.lang || "")));
|
|
373
336
|
const codeLines = token.text.split("\n");
|
|
374
337
|
for (const codeLine of codeLines) {
|
|
375
|
-
lines.push(
|
|
338
|
+
lines.push(" " + this.theme.codeBlock(codeLine));
|
|
376
339
|
}
|
|
377
|
-
lines.push(
|
|
340
|
+
lines.push(this.theme.codeBlockBorder("```"));
|
|
378
341
|
}
|
|
379
342
|
else {
|
|
380
343
|
// Other token types - try to render as inline
|
|
@@ -415,7 +378,7 @@ export class Markdown {
|
|
|
415
378
|
// Render header
|
|
416
379
|
const headerCells = token.header.map((cell, i) => {
|
|
417
380
|
const text = this.renderInlineTokens(cell.tokens || []);
|
|
418
|
-
return
|
|
381
|
+
return this.theme.bold(text.padEnd(columnWidths[i]));
|
|
419
382
|
});
|
|
420
383
|
lines.push("│ " + headerCells.join(" │ ") + " │");
|
|
421
384
|
// Render separator
|