@mariozechner/pi-tui 0.9.4 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autocomplete.d.ts +2 -0
- package/dist/autocomplete.d.ts.map +1 -1
- package/dist/autocomplete.js +179 -109
- package/dist/autocomplete.js.map +1 -1
- package/dist/components/editor.d.ts +16 -0
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +210 -17
- package/dist/components/editor.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +73 -46
- package/dist/utils.js.map +1 -1
- package/package.json +2 -1
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAwGD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CActE;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAwGD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CActE;AAoID;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CASzG","sourcesContent":["import stringWidth from \"string-width\";\n\n/**\n * Calculate the visible width of a string in terminal columns.\n */\nexport function visibleWidth(str: string): number {\n\tconst normalized = str.replace(/\\t/g, \" \");\n\treturn stringWidth(normalized);\n}\n\n/**\n * Extract ANSI escape sequences from a string at the given position.\n */\nfunction extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {\n\tif (pos >= str.length || str[pos] !== \"\\x1b\" || str[pos + 1] !== \"[\") {\n\t\treturn null;\n\t}\n\n\tlet j = pos + 2;\n\twhile (j < str.length && str[j] && !/[mGKHJ]/.test(str[j]!)) {\n\t\tj++;\n\t}\n\n\tif (j < str.length) {\n\t\treturn {\n\t\t\tcode: str.substring(pos, j + 1),\n\t\t\tlength: j + 1 - pos,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Track active ANSI SGR codes to preserve styling across line breaks.\n */\nclass AnsiCodeTracker {\n\tprivate activeAnsiCodes: string[] = [];\n\n\tprocess(ansiCode: string): void {\n\t\tif (!ansiCode.endsWith(\"m\")) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Full reset clears everything\n\t\tif (ansiCode === \"\\x1b[0m\" || ansiCode === \"\\x1b[m\") {\n\t\t\tthis.activeAnsiCodes.length = 0;\n\t\t} else {\n\t\t\tthis.activeAnsiCodes.push(ansiCode);\n\t\t}\n\t}\n\n\tgetActiveCodes(): string {\n\t\treturn this.activeAnsiCodes.join(\"\");\n\t}\n\n\thasActiveCodes(): boolean {\n\t\treturn this.activeAnsiCodes.length > 0;\n\t}\n}\n\nfunction updateTrackerFromText(text: string, tracker: AnsiCodeTracker): void {\n\tlet i = 0;\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\ttracker.process(ansiResult.code);\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\n/**\n * Split text into words while keeping ANSI codes attached.\n */\nfunction splitIntoTokensWithAnsi(text: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet inWhitespace = false;\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\tcurrent += ansiResult.code;\n\t\t\ti += ansiResult.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char = text[i];\n\t\tconst charIsSpace = char === \" \";\n\n\t\tif (charIsSpace !== inWhitespace && current) {\n\t\t\t// Switching between whitespace and non-whitespace, push current token\n\t\t\ttokens.push(current);\n\t\t\tcurrent = \"\";\n\t\t}\n\n\t\tinWhitespace = charIsSpace;\n\t\tcurrent += char;\n\t\ti++;\n\t}\n\n\tif (current) {\n\t\ttokens.push(current);\n\t}\n\n\treturn tokens;\n}\n\n/**\n * Wrap text with ANSI codes preserved.\n *\n * ONLY does word wrapping - NO padding, NO background colors.\n * Returns lines where each line is <= width visible chars.\n * Active ANSI codes are preserved across line breaks.\n *\n * @param text - Text to wrap (may contain ANSI codes and newlines)\n * @param width - Maximum visible width per line\n * @returns Array of wrapped lines (NOT padded to width)\n */\nexport function wrapTextWithAnsi(text: string, width: number): string[] {\n\tif (!text) {\n\t\treturn [\"\"];\n\t}\n\n\t// Handle newlines by processing each line separately\n\tconst inputLines = text.split(\"\\n\");\n\tconst result: string[] = [];\n\n\tfor (const inputLine of inputLines) {\n\t\tresult.push(...wrapSingleLine(inputLine, width));\n\t}\n\n\treturn result.length > 0 ? result : [\"\"];\n}\n\nfunction wrapSingleLine(line: string, width: number): string[] {\n\tif (!line) {\n\t\treturn [\"\"];\n\t}\n\n\tconst visibleLength = visibleWidth(line);\n\tif (visibleLength <= width) {\n\t\treturn [line];\n\t}\n\n\tconst wrapped: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\tconst tokens = splitIntoTokensWithAnsi(line);\n\n\tlet currentLine = \"\";\n\tlet currentVisibleLength = 0;\n\n\tfor (const token of tokens) {\n\t\tconst tokenVisibleLength = visibleWidth(token);\n\t\tconst isWhitespace = token.trim() === \"\";\n\n\t\t// Token itself is too long - break it character by character\n\t\tif (tokenVisibleLength > width && !isWhitespace) {\n\t\t\tif (currentLine) {\n\t\t\t\twrapped.push(currentLine);\n\t\t\t\tcurrentLine = \"\";\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t}\n\n\t\t\t// Break long token\n\t\t\tconst broken = breakLongWord(token, width, tracker);\n\t\t\twrapped.push(...broken.slice(0, -1));\n\t\t\tcurrentLine = broken[broken.length - 1];\n\t\t\tcurrentVisibleLength = visibleWidth(currentLine);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if adding this token would exceed width\n\t\tconst totalNeeded = currentVisibleLength + tokenVisibleLength;\n\n\t\tif (totalNeeded > width && currentVisibleLength > 0) {\n\t\t\t// Wrap to next line - don't carry trailing whitespace\n\t\t\twrapped.push(currentLine);\n\t\t\tif (isWhitespace) {\n\t\t\t\t// Don't start new line with whitespace\n\t\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t} else {\n\t\t\t\tcurrentLine = tracker.getActiveCodes() + token;\n\t\t\t\tcurrentVisibleLength = tokenVisibleLength;\n\t\t\t}\n\t\t} else {\n\t\t\t// Add to current line\n\t\t\tcurrentLine += token;\n\t\t\tcurrentVisibleLength += tokenVisibleLength;\n\t\t}\n\n\t\tupdateTrackerFromText(token, tracker);\n\t}\n\n\tif (currentLine) {\n\t\twrapped.push(currentLine);\n\t}\n\n\treturn wrapped.length > 0 ? wrapped : [\"\"];\n}\n\n// Grapheme segmenter for proper Unicode iteration (handles emojis, etc.)\nconst segmenter = new Intl.Segmenter();\n\nfunction breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {\n\tconst lines: string[] = [];\n\tlet currentLine = tracker.getActiveCodes();\n\tlet currentWidth = 0;\n\n\t// First, separate ANSI codes from visible content\n\t// We need to handle ANSI codes specially since they're not graphemes\n\tlet i = 0;\n\tconst segments: Array<{ type: \"ansi\" | \"grapheme\"; value: string }> = [];\n\n\twhile (i < word.length) {\n\t\tconst ansiResult = extractAnsiCode(word, i);\n\t\tif (ansiResult) {\n\t\t\tsegments.push({ type: \"ansi\", value: ansiResult.code });\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\t// Find the next ANSI code or end of string\n\t\t\tlet end = i;\n\t\t\twhile (end < word.length) {\n\t\t\t\tconst nextAnsi = extractAnsiCode(word, end);\n\t\t\t\tif (nextAnsi) break;\n\t\t\t\tend++;\n\t\t\t}\n\t\t\t// Segment this non-ANSI portion into graphemes\n\t\t\tconst textPortion = word.slice(i, end);\n\t\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\t\tsegments.push({ type: \"grapheme\", value: seg.segment });\n\t\t\t}\n\t\t\ti = end;\n\t\t}\n\t}\n\n\t// Now process segments\n\tfor (const seg of segments) {\n\t\tif (seg.type === \"ansi\") {\n\t\t\tcurrentLine += seg.value;\n\t\t\ttracker.process(seg.value);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grapheme = seg.value;\n\t\tconst graphemeWidth = visibleWidth(grapheme);\n\n\t\tif (currentWidth + graphemeWidth > width) {\n\t\t\tlines.push(currentLine);\n\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\tcurrentWidth = 0;\n\t\t}\n\n\t\tcurrentLine += grapheme;\n\t\tcurrentWidth += graphemeWidth;\n\t}\n\n\tif (currentLine) {\n\t\tlines.push(currentLine);\n\t}\n\n\treturn lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Apply background color to a line, padding to full width.\n *\n * @param line - Line of text (may contain ANSI codes)\n * @param width - Total width to pad to\n * @param bgFn - Background color function\n * @returns Line with background applied and padded to width\n */\nexport function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string {\n\t// Calculate padding needed\n\tconst visibleLen = visibleWidth(line);\n\tconst paddingNeeded = Math.max(0, width - visibleLen);\n\tconst padding = \" \".repeat(paddingNeeded);\n\n\t// Apply background to content + padding\n\tconst withPadding = line + padding;\n\treturn bgFn(withPadding);\n}\n"]}
|
package/dist/utils.js
CHANGED
|
@@ -65,33 +65,33 @@ function updateTrackerFromText(text, tracker) {
|
|
|
65
65
|
/**
|
|
66
66
|
* Split text into words while keeping ANSI codes attached.
|
|
67
67
|
*/
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
let
|
|
68
|
+
function splitIntoTokensWithAnsi(text) {
|
|
69
|
+
const tokens = [];
|
|
70
|
+
let current = "";
|
|
71
|
+
let inWhitespace = false;
|
|
71
72
|
let i = 0;
|
|
72
73
|
while (i < text.length) {
|
|
73
|
-
const char = text[i];
|
|
74
74
|
const ansiResult = extractAnsiCode(text, i);
|
|
75
75
|
if (ansiResult) {
|
|
76
|
-
|
|
76
|
+
current += ansiResult.code;
|
|
77
77
|
i += ansiResult.length;
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
continue;
|
|
80
|
+
const char = text[i];
|
|
81
|
+
const charIsSpace = char === " ";
|
|
82
|
+
if (charIsSpace !== inWhitespace && current) {
|
|
83
|
+
// Switching between whitespace and non-whitespace, push current token
|
|
84
|
+
tokens.push(current);
|
|
85
|
+
current = "";
|
|
87
86
|
}
|
|
88
|
-
|
|
87
|
+
inWhitespace = charIsSpace;
|
|
88
|
+
current += char;
|
|
89
89
|
i++;
|
|
90
90
|
}
|
|
91
|
-
if (
|
|
92
|
-
|
|
91
|
+
if (current) {
|
|
92
|
+
tokens.push(current);
|
|
93
93
|
}
|
|
94
|
-
return
|
|
94
|
+
return tokens;
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Wrap text with ANSI codes preserved.
|
|
@@ -126,75 +126,102 @@ function wrapSingleLine(line, width) {
|
|
|
126
126
|
}
|
|
127
127
|
const wrapped = [];
|
|
128
128
|
const tracker = new AnsiCodeTracker();
|
|
129
|
-
const
|
|
129
|
+
const tokens = splitIntoTokensWithAnsi(line);
|
|
130
130
|
let currentLine = "";
|
|
131
131
|
let currentVisibleLength = 0;
|
|
132
|
-
for (const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
for (const token of tokens) {
|
|
133
|
+
const tokenVisibleLength = visibleWidth(token);
|
|
134
|
+
const isWhitespace = token.trim() === "";
|
|
135
|
+
// Token itself is too long - break it character by character
|
|
136
|
+
if (tokenVisibleLength > width && !isWhitespace) {
|
|
136
137
|
if (currentLine) {
|
|
137
138
|
wrapped.push(currentLine);
|
|
138
139
|
currentLine = "";
|
|
139
140
|
currentVisibleLength = 0;
|
|
140
141
|
}
|
|
141
|
-
// Break long
|
|
142
|
-
const broken = breakLongWord(
|
|
142
|
+
// Break long token
|
|
143
|
+
const broken = breakLongWord(token, width, tracker);
|
|
143
144
|
wrapped.push(...broken.slice(0, -1));
|
|
144
145
|
currentLine = broken[broken.length - 1];
|
|
145
146
|
currentVisibleLength = visibleWidth(currentLine);
|
|
146
147
|
continue;
|
|
147
148
|
}
|
|
148
|
-
// Check if adding this
|
|
149
|
-
const
|
|
150
|
-
const totalNeeded = currentVisibleLength + spaceNeeded + wordVisibleLength;
|
|
149
|
+
// Check if adding this token would exceed width
|
|
150
|
+
const totalNeeded = currentVisibleLength + tokenVisibleLength;
|
|
151
151
|
if (totalNeeded > width && currentVisibleLength > 0) {
|
|
152
|
-
// Wrap to next line
|
|
152
|
+
// Wrap to next line - don't carry trailing whitespace
|
|
153
153
|
wrapped.push(currentLine);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// Add to current line
|
|
159
|
-
if (currentVisibleLength > 0) {
|
|
160
|
-
currentLine += " " + word;
|
|
161
|
-
currentVisibleLength += 1 + wordVisibleLength;
|
|
154
|
+
if (isWhitespace) {
|
|
155
|
+
// Don't start new line with whitespace
|
|
156
|
+
currentLine = tracker.getActiveCodes();
|
|
157
|
+
currentVisibleLength = 0;
|
|
162
158
|
}
|
|
163
159
|
else {
|
|
164
|
-
currentLine
|
|
165
|
-
currentVisibleLength =
|
|
160
|
+
currentLine = tracker.getActiveCodes() + token;
|
|
161
|
+
currentVisibleLength = tokenVisibleLength;
|
|
166
162
|
}
|
|
167
163
|
}
|
|
168
|
-
|
|
164
|
+
else {
|
|
165
|
+
// Add to current line
|
|
166
|
+
currentLine += token;
|
|
167
|
+
currentVisibleLength += tokenVisibleLength;
|
|
168
|
+
}
|
|
169
|
+
updateTrackerFromText(token, tracker);
|
|
169
170
|
}
|
|
170
171
|
if (currentLine) {
|
|
171
172
|
wrapped.push(currentLine);
|
|
172
173
|
}
|
|
173
174
|
return wrapped.length > 0 ? wrapped : [""];
|
|
174
175
|
}
|
|
176
|
+
// Grapheme segmenter for proper Unicode iteration (handles emojis, etc.)
|
|
177
|
+
const segmenter = new Intl.Segmenter();
|
|
175
178
|
function breakLongWord(word, width, tracker) {
|
|
176
179
|
const lines = [];
|
|
177
180
|
let currentLine = tracker.getActiveCodes();
|
|
178
181
|
let currentWidth = 0;
|
|
182
|
+
// First, separate ANSI codes from visible content
|
|
183
|
+
// We need to handle ANSI codes specially since they're not graphemes
|
|
179
184
|
let i = 0;
|
|
185
|
+
const segments = [];
|
|
180
186
|
while (i < word.length) {
|
|
181
187
|
const ansiResult = extractAnsiCode(word, i);
|
|
182
188
|
if (ansiResult) {
|
|
183
|
-
|
|
184
|
-
tracker.process(ansiResult.code);
|
|
189
|
+
segments.push({ type: "ansi", value: ansiResult.code });
|
|
185
190
|
i += ansiResult.length;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// Find the next ANSI code or end of string
|
|
194
|
+
let end = i;
|
|
195
|
+
while (end < word.length) {
|
|
196
|
+
const nextAnsi = extractAnsiCode(word, end);
|
|
197
|
+
if (nextAnsi)
|
|
198
|
+
break;
|
|
199
|
+
end++;
|
|
200
|
+
}
|
|
201
|
+
// Segment this non-ANSI portion into graphemes
|
|
202
|
+
const textPortion = word.slice(i, end);
|
|
203
|
+
for (const seg of segmenter.segment(textPortion)) {
|
|
204
|
+
segments.push({ type: "grapheme", value: seg.segment });
|
|
205
|
+
}
|
|
206
|
+
i = end;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Now process segments
|
|
210
|
+
for (const seg of segments) {
|
|
211
|
+
if (seg.type === "ansi") {
|
|
212
|
+
currentLine += seg.value;
|
|
213
|
+
tracker.process(seg.value);
|
|
186
214
|
continue;
|
|
187
215
|
}
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
if (currentWidth +
|
|
216
|
+
const grapheme = seg.value;
|
|
217
|
+
const graphemeWidth = visibleWidth(grapheme);
|
|
218
|
+
if (currentWidth + graphemeWidth > width) {
|
|
191
219
|
lines.push(currentLine);
|
|
192
220
|
currentLine = tracker.getActiveCodes();
|
|
193
221
|
currentWidth = 0;
|
|
194
222
|
}
|
|
195
|
-
currentLine +=
|
|
196
|
-
currentWidth +=
|
|
197
|
-
i++;
|
|
223
|
+
currentLine += grapheme;
|
|
224
|
+
currentWidth += graphemeWidth;
|
|
198
225
|
}
|
|
199
226
|
if (currentLine) {
|
|
200
227
|
lines.push(currentLine);
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import stringWidth from \"string-width\";\n\n/**\n * Calculate the visible width of a string in terminal columns.\n */\nexport function visibleWidth(str: string): number {\n\tconst normalized = str.replace(/\\t/g, \" \");\n\treturn stringWidth(normalized);\n}\n\n/**\n * Extract ANSI escape sequences from a string at the given position.\n */\nfunction extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {\n\tif (pos >= str.length || str[pos] !== \"\\x1b\" || str[pos + 1] !== \"[\") {\n\t\treturn null;\n\t}\n\n\tlet j = pos + 2;\n\twhile (j < str.length && str[j] && !/[mGKHJ]/.test(str[j]!)) {\n\t\tj++;\n\t}\n\n\tif (j < str.length) {\n\t\treturn {\n\t\t\tcode: str.substring(pos, j + 1),\n\t\t\tlength: j + 1 - pos,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Track active ANSI SGR codes to preserve styling across line breaks.\n */\nclass AnsiCodeTracker {\n\tprivate activeAnsiCodes: string[] = [];\n\n\tprocess(ansiCode: string): void {\n\t\tif (!ansiCode.endsWith(\"m\")) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Full reset clears everything\n\t\tif (ansiCode === \"\\x1b[0m\" || ansiCode === \"\\x1b[m\") {\n\t\t\tthis.activeAnsiCodes.length = 0;\n\t\t} else {\n\t\t\tthis.activeAnsiCodes.push(ansiCode);\n\t\t}\n\t}\n\n\tgetActiveCodes(): string {\n\t\treturn this.activeAnsiCodes.join(\"\");\n\t}\n\n\thasActiveCodes(): boolean {\n\t\treturn this.activeAnsiCodes.length > 0;\n\t}\n}\n\nfunction updateTrackerFromText(text: string, tracker: AnsiCodeTracker): void {\n\tlet i = 0;\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\ttracker.process(ansiResult.code);\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\n/**\n * Split text into words while keeping ANSI codes attached.\n */\nfunction splitIntoWordsWithAnsi(text: string): string[] {\n\tconst words: string[] = [];\n\tlet currentWord = \"\";\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\tconst char = text[i];\n\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\tcurrentWord += ansiResult.code;\n\t\t\ti += ansiResult.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (char === \" \") {\n\t\t\tif (currentWord) {\n\t\t\t\twords.push(currentWord);\n\t\t\t\tcurrentWord = \"\";\n\t\t\t}\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tcurrentWord += char;\n\t\ti++;\n\t}\n\n\tif (currentWord) {\n\t\twords.push(currentWord);\n\t}\n\n\treturn words;\n}\n\n/**\n * Wrap text with ANSI codes preserved.\n *\n * ONLY does word wrapping - NO padding, NO background colors.\n * Returns lines where each line is <= width visible chars.\n * Active ANSI codes are preserved across line breaks.\n *\n * @param text - Text to wrap (may contain ANSI codes and newlines)\n * @param width - Maximum visible width per line\n * @returns Array of wrapped lines (NOT padded to width)\n */\nexport function wrapTextWithAnsi(text: string, width: number): string[] {\n\tif (!text) {\n\t\treturn [\"\"];\n\t}\n\n\t// Handle newlines by processing each line separately\n\tconst inputLines = text.split(\"\\n\");\n\tconst result: string[] = [];\n\n\tfor (const inputLine of inputLines) {\n\t\tresult.push(...wrapSingleLine(inputLine, width));\n\t}\n\n\treturn result.length > 0 ? result : [\"\"];\n}\n\nfunction wrapSingleLine(line: string, width: number): string[] {\n\tif (!line) {\n\t\treturn [\"\"];\n\t}\n\n\tconst visibleLength = visibleWidth(line);\n\tif (visibleLength <= width) {\n\t\treturn [line];\n\t}\n\n\tconst wrapped: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\tconst words = splitIntoWordsWithAnsi(line);\n\n\tlet currentLine = \"\";\n\tlet currentVisibleLength = 0;\n\n\tfor (const word of words) {\n\t\tconst wordVisibleLength = visibleWidth(word);\n\n\t\t// Word itself is too long - break it character by character\n\t\tif (wordVisibleLength > width) {\n\t\t\tif (currentLine) {\n\t\t\t\twrapped.push(currentLine);\n\t\t\t\tcurrentLine = \"\";\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t}\n\n\t\t\t// Break long word\n\t\t\tconst broken = breakLongWord(word, width, tracker);\n\t\t\twrapped.push(...broken.slice(0, -1));\n\t\t\tcurrentLine = broken[broken.length - 1];\n\t\t\tcurrentVisibleLength = visibleWidth(currentLine);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if adding this word would exceed width\n\t\tconst spaceNeeded = currentVisibleLength > 0 ? 1 : 0;\n\t\tconst totalNeeded = currentVisibleLength + spaceNeeded + wordVisibleLength;\n\n\t\tif (totalNeeded > width && currentVisibleLength > 0) {\n\t\t\t// Wrap to next line\n\t\t\twrapped.push(currentLine);\n\t\t\tcurrentLine = tracker.getActiveCodes() + word;\n\t\t\tcurrentVisibleLength = wordVisibleLength;\n\t\t} else {\n\t\t\t// Add to current line\n\t\t\tif (currentVisibleLength > 0) {\n\t\t\t\tcurrentLine += \" \" + word;\n\t\t\t\tcurrentVisibleLength += 1 + wordVisibleLength;\n\t\t\t} else {\n\t\t\t\tcurrentLine += word;\n\t\t\t\tcurrentVisibleLength = wordVisibleLength;\n\t\t\t}\n\t\t}\n\n\t\tupdateTrackerFromText(word, tracker);\n\t}\n\n\tif (currentLine) {\n\t\twrapped.push(currentLine);\n\t}\n\n\treturn wrapped.length > 0 ? wrapped : [\"\"];\n}\n\nfunction breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {\n\tconst lines: string[] = [];\n\tlet currentLine = tracker.getActiveCodes();\n\tlet currentWidth = 0;\n\tlet i = 0;\n\n\twhile (i < word.length) {\n\t\tconst ansiResult = extractAnsiCode(word, i);\n\t\tif (ansiResult) {\n\t\t\tcurrentLine += ansiResult.code;\n\t\t\ttracker.process(ansiResult.code);\n\t\t\ti += ansiResult.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char = word[i];\n\t\tconst charWidth = visibleWidth(char);\n\n\t\tif (currentWidth + charWidth > width) {\n\t\t\tlines.push(currentLine);\n\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\tcurrentWidth = 0;\n\t\t}\n\n\t\tcurrentLine += char;\n\t\tcurrentWidth += charWidth;\n\t\ti++;\n\t}\n\n\tif (currentLine) {\n\t\tlines.push(currentLine);\n\t}\n\n\treturn lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Apply background color to a line, padding to full width.\n *\n * @param line - Line of text (may contain ANSI codes)\n * @param width - Total width to pad to\n * @param bgFn - Background color function\n * @returns Line with background applied and padded to width\n */\nexport function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string {\n\t// Calculate padding needed\n\tconst visibleLen = visibleWidth(line);\n\tconst paddingNeeded = Math.max(0, width - visibleLen);\n\tconst padding = \" \".repeat(paddingNeeded);\n\n\t// Apply background to content + padding\n\tconst withPadding = line + padding;\n\treturn bgFn(withPadding);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import stringWidth from \"string-width\";\n\n/**\n * Calculate the visible width of a string in terminal columns.\n */\nexport function visibleWidth(str: string): number {\n\tconst normalized = str.replace(/\\t/g, \" \");\n\treturn stringWidth(normalized);\n}\n\n/**\n * Extract ANSI escape sequences from a string at the given position.\n */\nfunction extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {\n\tif (pos >= str.length || str[pos] !== \"\\x1b\" || str[pos + 1] !== \"[\") {\n\t\treturn null;\n\t}\n\n\tlet j = pos + 2;\n\twhile (j < str.length && str[j] && !/[mGKHJ]/.test(str[j]!)) {\n\t\tj++;\n\t}\n\n\tif (j < str.length) {\n\t\treturn {\n\t\t\tcode: str.substring(pos, j + 1),\n\t\t\tlength: j + 1 - pos,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Track active ANSI SGR codes to preserve styling across line breaks.\n */\nclass AnsiCodeTracker {\n\tprivate activeAnsiCodes: string[] = [];\n\n\tprocess(ansiCode: string): void {\n\t\tif (!ansiCode.endsWith(\"m\")) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Full reset clears everything\n\t\tif (ansiCode === \"\\x1b[0m\" || ansiCode === \"\\x1b[m\") {\n\t\t\tthis.activeAnsiCodes.length = 0;\n\t\t} else {\n\t\t\tthis.activeAnsiCodes.push(ansiCode);\n\t\t}\n\t}\n\n\tgetActiveCodes(): string {\n\t\treturn this.activeAnsiCodes.join(\"\");\n\t}\n\n\thasActiveCodes(): boolean {\n\t\treturn this.activeAnsiCodes.length > 0;\n\t}\n}\n\nfunction updateTrackerFromText(text: string, tracker: AnsiCodeTracker): void {\n\tlet i = 0;\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\ttracker.process(ansiResult.code);\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\n/**\n * Split text into words while keeping ANSI codes attached.\n */\nfunction splitIntoTokensWithAnsi(text: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet inWhitespace = false;\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\tconst ansiResult = extractAnsiCode(text, i);\n\t\tif (ansiResult) {\n\t\t\tcurrent += ansiResult.code;\n\t\t\ti += ansiResult.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char = text[i];\n\t\tconst charIsSpace = char === \" \";\n\n\t\tif (charIsSpace !== inWhitespace && current) {\n\t\t\t// Switching between whitespace and non-whitespace, push current token\n\t\t\ttokens.push(current);\n\t\t\tcurrent = \"\";\n\t\t}\n\n\t\tinWhitespace = charIsSpace;\n\t\tcurrent += char;\n\t\ti++;\n\t}\n\n\tif (current) {\n\t\ttokens.push(current);\n\t}\n\n\treturn tokens;\n}\n\n/**\n * Wrap text with ANSI codes preserved.\n *\n * ONLY does word wrapping - NO padding, NO background colors.\n * Returns lines where each line is <= width visible chars.\n * Active ANSI codes are preserved across line breaks.\n *\n * @param text - Text to wrap (may contain ANSI codes and newlines)\n * @param width - Maximum visible width per line\n * @returns Array of wrapped lines (NOT padded to width)\n */\nexport function wrapTextWithAnsi(text: string, width: number): string[] {\n\tif (!text) {\n\t\treturn [\"\"];\n\t}\n\n\t// Handle newlines by processing each line separately\n\tconst inputLines = text.split(\"\\n\");\n\tconst result: string[] = [];\n\n\tfor (const inputLine of inputLines) {\n\t\tresult.push(...wrapSingleLine(inputLine, width));\n\t}\n\n\treturn result.length > 0 ? result : [\"\"];\n}\n\nfunction wrapSingleLine(line: string, width: number): string[] {\n\tif (!line) {\n\t\treturn [\"\"];\n\t}\n\n\tconst visibleLength = visibleWidth(line);\n\tif (visibleLength <= width) {\n\t\treturn [line];\n\t}\n\n\tconst wrapped: string[] = [];\n\tconst tracker = new AnsiCodeTracker();\n\tconst tokens = splitIntoTokensWithAnsi(line);\n\n\tlet currentLine = \"\";\n\tlet currentVisibleLength = 0;\n\n\tfor (const token of tokens) {\n\t\tconst tokenVisibleLength = visibleWidth(token);\n\t\tconst isWhitespace = token.trim() === \"\";\n\n\t\t// Token itself is too long - break it character by character\n\t\tif (tokenVisibleLength > width && !isWhitespace) {\n\t\t\tif (currentLine) {\n\t\t\t\twrapped.push(currentLine);\n\t\t\t\tcurrentLine = \"\";\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t}\n\n\t\t\t// Break long token\n\t\t\tconst broken = breakLongWord(token, width, tracker);\n\t\t\twrapped.push(...broken.slice(0, -1));\n\t\t\tcurrentLine = broken[broken.length - 1];\n\t\t\tcurrentVisibleLength = visibleWidth(currentLine);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if adding this token would exceed width\n\t\tconst totalNeeded = currentVisibleLength + tokenVisibleLength;\n\n\t\tif (totalNeeded > width && currentVisibleLength > 0) {\n\t\t\t// Wrap to next line - don't carry trailing whitespace\n\t\t\twrapped.push(currentLine);\n\t\t\tif (isWhitespace) {\n\t\t\t\t// Don't start new line with whitespace\n\t\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t} else {\n\t\t\t\tcurrentLine = tracker.getActiveCodes() + token;\n\t\t\t\tcurrentVisibleLength = tokenVisibleLength;\n\t\t\t}\n\t\t} else {\n\t\t\t// Add to current line\n\t\t\tcurrentLine += token;\n\t\t\tcurrentVisibleLength += tokenVisibleLength;\n\t\t}\n\n\t\tupdateTrackerFromText(token, tracker);\n\t}\n\n\tif (currentLine) {\n\t\twrapped.push(currentLine);\n\t}\n\n\treturn wrapped.length > 0 ? wrapped : [\"\"];\n}\n\n// Grapheme segmenter for proper Unicode iteration (handles emojis, etc.)\nconst segmenter = new Intl.Segmenter();\n\nfunction breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {\n\tconst lines: string[] = [];\n\tlet currentLine = tracker.getActiveCodes();\n\tlet currentWidth = 0;\n\n\t// First, separate ANSI codes from visible content\n\t// We need to handle ANSI codes specially since they're not graphemes\n\tlet i = 0;\n\tconst segments: Array<{ type: \"ansi\" | \"grapheme\"; value: string }> = [];\n\n\twhile (i < word.length) {\n\t\tconst ansiResult = extractAnsiCode(word, i);\n\t\tif (ansiResult) {\n\t\t\tsegments.push({ type: \"ansi\", value: ansiResult.code });\n\t\t\ti += ansiResult.length;\n\t\t} else {\n\t\t\t// Find the next ANSI code or end of string\n\t\t\tlet end = i;\n\t\t\twhile (end < word.length) {\n\t\t\t\tconst nextAnsi = extractAnsiCode(word, end);\n\t\t\t\tif (nextAnsi) break;\n\t\t\t\tend++;\n\t\t\t}\n\t\t\t// Segment this non-ANSI portion into graphemes\n\t\t\tconst textPortion = word.slice(i, end);\n\t\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\t\tsegments.push({ type: \"grapheme\", value: seg.segment });\n\t\t\t}\n\t\t\ti = end;\n\t\t}\n\t}\n\n\t// Now process segments\n\tfor (const seg of segments) {\n\t\tif (seg.type === \"ansi\") {\n\t\t\tcurrentLine += seg.value;\n\t\t\ttracker.process(seg.value);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grapheme = seg.value;\n\t\tconst graphemeWidth = visibleWidth(grapheme);\n\n\t\tif (currentWidth + graphemeWidth > width) {\n\t\t\tlines.push(currentLine);\n\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\tcurrentWidth = 0;\n\t\t}\n\n\t\tcurrentLine += grapheme;\n\t\tcurrentWidth += graphemeWidth;\n\t}\n\n\tif (currentLine) {\n\t\tlines.push(currentLine);\n\t}\n\n\treturn lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Apply background color to a line, padding to full width.\n *\n * @param line - Line of text (may contain ANSI codes)\n * @param width - Total width to pad to\n * @param bgFn - Background color function\n * @returns Line with background applied and padded to width\n */\nexport function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string {\n\t// Calculate padding needed\n\tconst visibleLen = visibleWidth(line);\n\tconst paddingNeeded = Math.max(0, width - visibleLen);\n\tconst padding = \" \".repeat(paddingNeeded);\n\n\t// Apply background to content + padding\n\tconst withPadding = line + padding;\n\treturn bgFn(withPadding);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"chalk": "^5.5.0",
|
|
42
42
|
"marked": "^15.0.12",
|
|
43
43
|
"mime-types": "^3.0.1",
|
|
44
|
+
"minimatch": "^10.1.1",
|
|
44
45
|
"string-width": "^8.1.0"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|