@f5-sales-demo/pi-tui 23.6.0 → 23.6.9
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/package.json +3 -3
- package/src/components/markdown.ts +38 -114
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-tui",
|
|
4
|
-
"version": "23.6.
|
|
4
|
+
"version": "23.6.9",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@f5-sales-demo/pi-natives": "23.6.
|
|
41
|
-
"@f5-sales-demo/pi-utils": "23.6.
|
|
40
|
+
"@f5-sales-demo/pi-natives": "23.6.9",
|
|
41
|
+
"@f5-sales-demo/pi-utils": "23.6.9",
|
|
42
42
|
"marked": "18.0.11"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Ellipsis,
|
|
11
11
|
padding,
|
|
12
12
|
replaceTabs,
|
|
13
|
+
sliceWithWidth,
|
|
13
14
|
truncateToWidth,
|
|
14
15
|
visibleWidth,
|
|
15
16
|
wrapTextWithAnsi,
|
|
@@ -304,7 +305,11 @@ interface InlineStyleContext {
|
|
|
304
305
|
stylePrefix: string;
|
|
305
306
|
}
|
|
306
307
|
|
|
307
|
-
type ListToken = Token & {
|
|
308
|
+
type ListToken = Token & {
|
|
309
|
+
items: Array<{ tokens?: Token[]; task?: boolean; checked?: boolean }>;
|
|
310
|
+
ordered: boolean;
|
|
311
|
+
start?: number;
|
|
312
|
+
};
|
|
308
313
|
type TableCellToken = { tokens?: Token[] };
|
|
309
314
|
type TableToken = Token & {
|
|
310
315
|
header: TableCellToken[];
|
|
@@ -461,8 +466,8 @@ export class Markdown implements Component {
|
|
|
461
466
|
return result;
|
|
462
467
|
}
|
|
463
468
|
|
|
464
|
-
//
|
|
465
|
-
const normalizedText = normalizeOsc8Terminators(
|
|
469
|
+
// Parse source whitespace unchanged; expand tabs only for display.
|
|
470
|
+
const normalizedText = normalizeOsc8Terminators(this.#text);
|
|
466
471
|
|
|
467
472
|
// Parse markdown to HTML-like tokens
|
|
468
473
|
const tokens = markdownParser.lexer(normalizedText);
|
|
@@ -635,6 +640,7 @@ export class Markdown implements Component {
|
|
|
635
640
|
break;
|
|
636
641
|
}
|
|
637
642
|
|
|
643
|
+
case "text":
|
|
638
644
|
case "paragraph": {
|
|
639
645
|
const paragraphTokens = token.tokens || [];
|
|
640
646
|
if (this.#mediaOptions && paragraphTokens.some(child => child.type === "image")) {
|
|
@@ -694,18 +700,19 @@ export class Markdown implements Component {
|
|
|
694
700
|
}
|
|
695
701
|
}
|
|
696
702
|
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
if (this.#theme.
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
703
|
+
const indentWidth = Math.min(this.#codeBlockIndent, Math.max(0, width - 1));
|
|
704
|
+
const codeIndent = padding(indentWidth);
|
|
705
|
+
if (token.lang) lines.push(...wrapTextWithAnsi(this.#theme.codeBlockBorder(token.lang), width));
|
|
706
|
+
const codeLines = this.#theme.highlightCode
|
|
707
|
+
? this.#theme.highlightCode(token.text, token.lang)
|
|
708
|
+
: token.text.split("\n").map((line: string) => this.#theme.codeBlock(line));
|
|
709
|
+
for (const codeLine of codeLines) {
|
|
710
|
+
const displayed = replaceTabs(codeLine);
|
|
711
|
+
const leadingSpaces = /^ */.exec(Bun.stripANSI(displayed))?.[0].length ?? 0;
|
|
712
|
+
const sourceIndentWidth = Math.min(leadingSpaces, Math.max(0, width - indentWidth - 1));
|
|
713
|
+
const body = sliceWithWidth(displayed, sourceIndentWidth, visibleWidth(displayed), true).text;
|
|
714
|
+
for (const visualLine of wrapTextWithAnsi(body, width - indentWidth - sourceIndentWidth)) {
|
|
715
|
+
lines.push(codeIndent + padding(sourceIndentWidth) + visualLine);
|
|
709
716
|
}
|
|
710
717
|
}
|
|
711
718
|
if (nextTokenType && nextTokenType !== "space") {
|
|
@@ -715,7 +722,7 @@ export class Markdown implements Component {
|
|
|
715
722
|
}
|
|
716
723
|
|
|
717
724
|
case "list": {
|
|
718
|
-
const listLines = this.#renderList(token as ListToken,
|
|
725
|
+
const listLines = this.#renderList(token as ListToken, width, styleContext);
|
|
719
726
|
lines.push(...listLines);
|
|
720
727
|
// Don't add spacing after lists if a space token follows
|
|
721
728
|
// (the space token will handle it)
|
|
@@ -801,7 +808,7 @@ export class Markdown implements Component {
|
|
|
801
808
|
}
|
|
802
809
|
}
|
|
803
810
|
|
|
804
|
-
return lines;
|
|
811
|
+
return lines.flatMap(line => (TERMINAL.isImageLine(line) ? [line] : wrapTextWithAnsi(replaceTabs(line), width)));
|
|
805
812
|
}
|
|
806
813
|
|
|
807
814
|
#renderParagraphWithMedia(tokens: Token[], width: number, styleContext?: InlineStyleContext): string[] {
|
|
@@ -971,105 +978,22 @@ export class Markdown implements Component {
|
|
|
971
978
|
return result;
|
|
972
979
|
}
|
|
973
980
|
|
|
974
|
-
/**
|
|
975
|
-
|
|
976
|
-
*/
|
|
977
|
-
#renderList(token: ListToken, depth: number, styleContext?: InlineStyleContext): string[] {
|
|
981
|
+
/** Render children in their own width, then add the item's structural prefix. */
|
|
982
|
+
#renderList(token: ListToken, width: number, styleContext?: InlineStyleContext): string[] {
|
|
978
983
|
const lines: string[] = [];
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
// First line - check if it's a nested list
|
|
992
|
-
// A nested list will start with indent (spaces) followed by cyan bullet
|
|
993
|
-
const firstLine = itemLines[0];
|
|
994
|
-
const isNestedList = /^\s+\x1b\[36m[-\d]/.test(firstLine); // starts with spaces + cyan + bullet char
|
|
995
|
-
|
|
996
|
-
if (isNestedList) {
|
|
997
|
-
// This is a nested list, just add it as-is (already has full indent)
|
|
998
|
-
lines.push(firstLine);
|
|
999
|
-
} else {
|
|
1000
|
-
// Regular text content - add indent and bullet
|
|
1001
|
-
lines.push(indent + this.#theme.listBullet(bullet) + firstLine);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Rest of the lines
|
|
1005
|
-
for (let j = 1; j < itemLines.length; j++) {
|
|
1006
|
-
const line = itemLines[j];
|
|
1007
|
-
const isNestedListLine = /^\s+\x1b\[36m[-\d]/.test(line); // starts with spaces + cyan + bullet char
|
|
1008
|
-
|
|
1009
|
-
if (isNestedListLine) {
|
|
1010
|
-
// Nested list line - already has full indent
|
|
1011
|
-
lines.push(line);
|
|
1012
|
-
} else {
|
|
1013
|
-
// Regular content - add parent indent + 2 spaces for continuation
|
|
1014
|
-
lines.push(`${indent} ${line}`);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
} else {
|
|
1018
|
-
lines.push(indent + this.#theme.listBullet(bullet));
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
return lines;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
/**
|
|
1026
|
-
* Render list item tokens, handling nested lists
|
|
1027
|
-
* Returns lines WITHOUT the parent indent (renderList will add it)
|
|
1028
|
-
*/
|
|
1029
|
-
#renderListItem(tokens: Token[], parentDepth: number, styleContext?: InlineStyleContext): string[] {
|
|
1030
|
-
const lines: string[] = [];
|
|
1031
|
-
|
|
1032
|
-
for (const token of tokens) {
|
|
1033
|
-
if (token.type === "list") {
|
|
1034
|
-
// Nested list - render with one additional indent level
|
|
1035
|
-
// These lines will have their own indent, so we just add them as-is
|
|
1036
|
-
const nestedLines = this.#renderList(token as ListToken, parentDepth + 1, styleContext);
|
|
1037
|
-
lines.push(...nestedLines);
|
|
1038
|
-
} else if (token.type === "text") {
|
|
1039
|
-
// Text content (may have inline tokens)
|
|
1040
|
-
const text =
|
|
1041
|
-
token.tokens && token.tokens.length > 0
|
|
1042
|
-
? this.#renderInlineTokens(token.tokens, styleContext)
|
|
1043
|
-
: token.text || "";
|
|
1044
|
-
lines.push(text);
|
|
1045
|
-
} else if (token.type === "paragraph") {
|
|
1046
|
-
// Paragraph in list item
|
|
1047
|
-
const text = this.#renderInlineTokens(token.tokens || [], styleContext);
|
|
1048
|
-
lines.push(text);
|
|
1049
|
-
} else if (token.type === "code") {
|
|
1050
|
-
// Code block in list item
|
|
1051
|
-
const codeIndent = padding(this.#codeBlockIndent);
|
|
1052
|
-
if (token.lang) lines.push(this.#theme.codeBlockBorder(token.lang));
|
|
1053
|
-
if (this.#theme.highlightCode) {
|
|
1054
|
-
const highlightedLines = this.#theme.highlightCode(token.text, token.lang);
|
|
1055
|
-
for (const hlLine of highlightedLines) {
|
|
1056
|
-
lines.push(`${codeIndent}${hlLine}`);
|
|
1057
|
-
}
|
|
1058
|
-
} else {
|
|
1059
|
-
const codeLines = token.text.split("\n");
|
|
1060
|
-
for (const codeLine of codeLines) {
|
|
1061
|
-
lines.push(`${codeIndent}${this.#theme.codeBlock(codeLine)}`);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
} else {
|
|
1065
|
-
// Other token types - try to render as inline
|
|
1066
|
-
const text = this.#renderInlineTokens([token], styleContext);
|
|
1067
|
-
if (text) {
|
|
1068
|
-
lines.push(text);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
984
|
+
for (const [index, item] of token.items.entries()) {
|
|
985
|
+
const bullet = token.ordered ? `${(token.start ?? 1) + index}. ` : "- ";
|
|
986
|
+
const task = item.task ? (item.checked ? "[x] " : "[ ] ") : "";
|
|
987
|
+
const prefix = bullet + task;
|
|
988
|
+
const indentWidth = Math.min(visibleWidth(prefix), Math.max(0, width - 1));
|
|
989
|
+
const children = item.tokens ?? [];
|
|
990
|
+
const itemLines = children.flatMap((child, i) =>
|
|
991
|
+
this.#renderToken(child, Math.max(1, width - indentWidth), children[i + 1]?.type, styleContext),
|
|
992
|
+
);
|
|
993
|
+
if (itemLines.length === 0) itemLines.push("");
|
|
994
|
+
lines.push(this.#theme.listBullet(prefix) + itemLines[0]);
|
|
995
|
+
for (const line of itemLines.slice(1)) lines.push(padding(indentWidth) + line);
|
|
1071
996
|
}
|
|
1072
|
-
|
|
1073
997
|
return lines;
|
|
1074
998
|
}
|
|
1075
999
|
|