@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-tui",
4
- "version": "23.6.0",
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.0",
41
- "@f5-sales-demo/pi-utils": "23.6.0",
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 & { items: Array<{ tokens?: Token[] }>; ordered: boolean; start?: number };
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
- // Replace tabs with 3 spaces for consistent rendering
465
- const normalizedText = normalizeOsc8Terminators(replaceTabs(this.#text));
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 codeIndent = padding(this.#codeBlockIndent);
698
- if (token.lang) lines.push(this.#theme.codeBlockBorder(token.lang));
699
- if (this.#theme.highlightCode) {
700
- const highlightedLines = this.#theme.highlightCode(token.text, token.lang);
701
- for (const hlLine of highlightedLines) {
702
- lines.push(`${codeIndent}${hlLine}`);
703
- }
704
- } else {
705
- // Split code by newlines and style each line
706
- const codeLines = token.text.split("\n");
707
- for (const codeLine of codeLines) {
708
- lines.push(`${codeIndent}${this.#theme.codeBlock(codeLine)}`);
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, 0, styleContext);
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
- * Render a list with proper nesting support
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 indent = " ".repeat(depth);
980
- // Use the list's start property (defaults to 1 for ordered lists)
981
- const startNumber = token.start ?? 1;
982
-
983
- for (let i = 0; i < token.items.length; i++) {
984
- const item = token.items[i];
985
- const bullet = token.ordered ? `${startNumber + i}. ` : "- ";
986
-
987
- // Process item tokens to handle nested lists
988
- const itemLines = this.#renderListItem(item.tokens || [], depth, styleContext);
989
-
990
- if (itemLines.length > 0) {
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