@mariozechner/pi-tui 0.7.20 → 0.7.22
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/markdown.d.ts +4 -3
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +46 -59
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/text.d.ts.map +1 -1
- package/dist/components/text.js +28 -41
- package/dist/components/text.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +12 -2
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +24 -14
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +118 -167
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/utils.js
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
|
+
import { Chalk } from "chalk";
|
|
1
2
|
import stringWidth from "string-width";
|
|
3
|
+
const colorChalk = new Chalk({ level: 3 });
|
|
2
4
|
/**
|
|
3
5
|
* Calculate the visible width of a string in terminal columns.
|
|
4
|
-
* This correctly handles:
|
|
5
|
-
* - ANSI escape codes (ignored)
|
|
6
|
-
* - Emojis and wide characters (counted as 2 columns)
|
|
7
|
-
* - Combining characters (counted correctly)
|
|
8
|
-
* - Tabs (replaced with 3 spaces for consistent width)
|
|
9
6
|
*/
|
|
10
7
|
export function visibleWidth(str) {
|
|
11
|
-
// Replace tabs with 3 spaces before measuring
|
|
12
8
|
const normalized = str.replace(/\t/g, " ");
|
|
13
9
|
return stringWidth(normalized);
|
|
14
10
|
}
|
|
15
11
|
/**
|
|
16
12
|
* Extract ANSI escape sequences from a string at the given position.
|
|
17
|
-
* Returns the ANSI code and the length consumed, or null if no ANSI code found.
|
|
18
13
|
*/
|
|
19
14
|
function extractAnsiCode(str, pos) {
|
|
20
15
|
if (pos >= str.length || str[pos] !== "\x1b" || str[pos + 1] !== "[") {
|
|
@@ -33,57 +28,83 @@ function extractAnsiCode(str, pos) {
|
|
|
33
28
|
return null;
|
|
34
29
|
}
|
|
35
30
|
/**
|
|
36
|
-
* Track
|
|
31
|
+
* Track active ANSI SGR codes to preserve styling across line breaks.
|
|
37
32
|
*/
|
|
38
33
|
class AnsiCodeTracker {
|
|
39
34
|
activeAnsiCodes = [];
|
|
40
|
-
/**
|
|
41
|
-
* Process an ANSI code and update the active codes.
|
|
42
|
-
*/
|
|
43
35
|
process(ansiCode) {
|
|
44
|
-
// Check if it's a styling code (ends with 'm')
|
|
45
36
|
if (!ansiCode.endsWith("m")) {
|
|
46
37
|
return;
|
|
47
38
|
}
|
|
48
|
-
//
|
|
39
|
+
// Full reset clears everything
|
|
49
40
|
if (ansiCode === "\x1b[0m" || ansiCode === "\x1b[m") {
|
|
50
41
|
this.activeAnsiCodes.length = 0;
|
|
51
42
|
}
|
|
52
43
|
else {
|
|
53
|
-
// Add to active codes
|
|
54
44
|
this.activeAnsiCodes.push(ansiCode);
|
|
55
45
|
}
|
|
56
46
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Get all active ANSI codes as a single string.
|
|
59
|
-
*/
|
|
60
47
|
getActiveCodes() {
|
|
61
48
|
return this.activeAnsiCodes.join("");
|
|
62
49
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Check if there are any active codes.
|
|
65
|
-
*/
|
|
66
50
|
hasActiveCodes() {
|
|
67
51
|
return this.activeAnsiCodes.length > 0;
|
|
68
52
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
}
|
|
54
|
+
function updateTrackerFromText(text, tracker) {
|
|
55
|
+
let i = 0;
|
|
56
|
+
while (i < text.length) {
|
|
57
|
+
const ansiResult = extractAnsiCode(text, i);
|
|
58
|
+
if (ansiResult) {
|
|
59
|
+
tracker.process(ansiResult.code);
|
|
60
|
+
i += ansiResult.length;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Split text into words while keeping ANSI codes attached.
|
|
69
|
+
*/
|
|
70
|
+
function splitIntoWordsWithAnsi(text) {
|
|
71
|
+
const words = [];
|
|
72
|
+
let currentWord = "";
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < text.length) {
|
|
75
|
+
const char = text[i];
|
|
76
|
+
const ansiResult = extractAnsiCode(text, i);
|
|
77
|
+
if (ansiResult) {
|
|
78
|
+
currentWord += ansiResult.code;
|
|
79
|
+
i += ansiResult.length;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (char === " ") {
|
|
83
|
+
if (currentWord) {
|
|
84
|
+
words.push(currentWord);
|
|
85
|
+
currentWord = "";
|
|
86
|
+
}
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
currentWord += char;
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
if (currentWord) {
|
|
94
|
+
words.push(currentWord);
|
|
74
95
|
}
|
|
96
|
+
return words;
|
|
75
97
|
}
|
|
76
98
|
/**
|
|
77
|
-
* Wrap text
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* - Newlines within text
|
|
99
|
+
* Wrap text with ANSI codes preserved.
|
|
100
|
+
*
|
|
101
|
+
* ONLY does word wrapping - NO padding, NO background colors.
|
|
102
|
+
* Returns lines where each line is <= width visible chars.
|
|
103
|
+
* Active ANSI codes are preserved across line breaks.
|
|
83
104
|
*
|
|
84
|
-
* @param text -
|
|
85
|
-
* @param width -
|
|
86
|
-
* @returns Array of wrapped lines
|
|
105
|
+
* @param text - Text to wrap (may contain ANSI codes and newlines)
|
|
106
|
+
* @param width - Maximum visible width per line
|
|
107
|
+
* @returns Array of wrapped lines (NOT padded to width)
|
|
87
108
|
*/
|
|
88
109
|
export function wrapTextWithAnsi(text, width) {
|
|
89
110
|
if (!text) {
|
|
@@ -93,14 +114,11 @@ export function wrapTextWithAnsi(text, width) {
|
|
|
93
114
|
const inputLines = text.split("\n");
|
|
94
115
|
const result = [];
|
|
95
116
|
for (const inputLine of inputLines) {
|
|
96
|
-
result.push(...
|
|
117
|
+
result.push(...wrapSingleLine(inputLine, width));
|
|
97
118
|
}
|
|
98
119
|
return result.length > 0 ? result : [""];
|
|
99
120
|
}
|
|
100
|
-
|
|
101
|
-
* Wrap a single line (no newlines) with word-based wrapping while preserving ANSI codes.
|
|
102
|
-
*/
|
|
103
|
-
function wrapSingleLineWithAnsi(line, width) {
|
|
121
|
+
function wrapSingleLine(line, width) {
|
|
104
122
|
if (!line) {
|
|
105
123
|
return [""];
|
|
106
124
|
}
|
|
@@ -110,129 +128,58 @@ function wrapSingleLineWithAnsi(line, width) {
|
|
|
110
128
|
}
|
|
111
129
|
const wrapped = [];
|
|
112
130
|
const tracker = new AnsiCodeTracker();
|
|
113
|
-
// First, split the line into words while preserving ANSI codes with their words
|
|
114
131
|
const words = splitIntoWordsWithAnsi(line);
|
|
115
132
|
let currentLine = "";
|
|
116
133
|
let currentVisibleLength = 0;
|
|
117
134
|
for (const word of words) {
|
|
118
135
|
const wordVisibleLength = visibleWidth(word);
|
|
119
|
-
//
|
|
136
|
+
// Word itself is too long - break it character by character
|
|
120
137
|
if (wordVisibleLength > width) {
|
|
121
|
-
// Flush current line if any
|
|
122
138
|
if (currentLine) {
|
|
123
|
-
wrapped.push(
|
|
124
|
-
currentLine =
|
|
139
|
+
wrapped.push(currentLine);
|
|
140
|
+
currentLine = "";
|
|
125
141
|
currentVisibleLength = 0;
|
|
126
142
|
}
|
|
127
|
-
// Break
|
|
128
|
-
const
|
|
129
|
-
wrapped.push(...
|
|
130
|
-
currentLine =
|
|
143
|
+
// Break long word
|
|
144
|
+
const broken = breakLongWord(word, width, tracker);
|
|
145
|
+
wrapped.push(...broken.slice(0, -1));
|
|
146
|
+
currentLine = broken[broken.length - 1];
|
|
131
147
|
currentVisibleLength = visibleWidth(currentLine);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// Check if adding this word would exceed width
|
|
151
|
+
const spaceNeeded = currentVisibleLength > 0 ? 1 : 0;
|
|
152
|
+
const totalNeeded = currentVisibleLength + spaceNeeded + wordVisibleLength;
|
|
153
|
+
if (totalNeeded > width && currentVisibleLength > 0) {
|
|
154
|
+
// Wrap to next line
|
|
155
|
+
wrapped.push(currentLine);
|
|
156
|
+
currentLine = tracker.getActiveCodes() + word;
|
|
157
|
+
currentVisibleLength = wordVisibleLength;
|
|
132
158
|
}
|
|
133
159
|
else {
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// Word doesn't fit, wrap to next line
|
|
139
|
-
if (currentLine) {
|
|
140
|
-
wrapped.push(closeLineAndPrepareNext(currentLine, tracker));
|
|
141
|
-
}
|
|
142
|
-
currentLine = tracker.getActiveCodes() + word;
|
|
143
|
-
currentVisibleLength = wordVisibleLength;
|
|
160
|
+
// Add to current line
|
|
161
|
+
if (currentVisibleLength > 0) {
|
|
162
|
+
currentLine += " " + word;
|
|
163
|
+
currentVisibleLength += 1 + wordVisibleLength;
|
|
144
164
|
}
|
|
145
165
|
else {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
currentLine += " " + word;
|
|
149
|
-
currentVisibleLength += 1 + wordVisibleLength;
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
currentLine += word;
|
|
153
|
-
currentVisibleLength = wordVisibleLength;
|
|
154
|
-
}
|
|
166
|
+
currentLine += word;
|
|
167
|
+
currentVisibleLength = wordVisibleLength;
|
|
155
168
|
}
|
|
156
|
-
// Update tracker with ANSI codes from this word
|
|
157
|
-
updateTrackerFromText(word, tracker);
|
|
158
169
|
}
|
|
170
|
+
updateTrackerFromText(word, tracker);
|
|
159
171
|
}
|
|
160
|
-
// Add final line
|
|
161
172
|
if (currentLine) {
|
|
162
173
|
wrapped.push(currentLine);
|
|
163
174
|
}
|
|
164
175
|
return wrapped.length > 0 ? wrapped : [""];
|
|
165
176
|
}
|
|
166
|
-
|
|
167
|
-
* Close current line with reset code if needed, and prepare the next line with active codes.
|
|
168
|
-
*/
|
|
169
|
-
function closeLineAndPrepareNext(line, tracker) {
|
|
170
|
-
if (tracker.hasActiveCodes()) {
|
|
171
|
-
return line + tracker.getResetCode();
|
|
172
|
-
}
|
|
173
|
-
return line;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Update the ANSI code tracker by scanning through text.
|
|
177
|
-
*/
|
|
178
|
-
function updateTrackerFromText(text, tracker) {
|
|
179
|
-
let i = 0;
|
|
180
|
-
while (i < text.length) {
|
|
181
|
-
const ansiResult = extractAnsiCode(text, i);
|
|
182
|
-
if (ansiResult) {
|
|
183
|
-
tracker.process(ansiResult.code);
|
|
184
|
-
i += ansiResult.length;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
i++;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Split text into words while keeping ANSI codes attached to their words.
|
|
193
|
-
*/
|
|
194
|
-
function splitIntoWordsWithAnsi(text) {
|
|
195
|
-
const words = [];
|
|
196
|
-
let currentWord = "";
|
|
197
|
-
let i = 0;
|
|
198
|
-
while (i < text.length) {
|
|
199
|
-
const char = text[i];
|
|
200
|
-
// Check for ANSI code
|
|
201
|
-
const ansiResult = extractAnsiCode(text, i);
|
|
202
|
-
if (ansiResult) {
|
|
203
|
-
currentWord += ansiResult.code;
|
|
204
|
-
i += ansiResult.length;
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
// Check for space (word boundary)
|
|
208
|
-
if (char === " ") {
|
|
209
|
-
if (currentWord) {
|
|
210
|
-
words.push(currentWord);
|
|
211
|
-
currentWord = "";
|
|
212
|
-
}
|
|
213
|
-
i++;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
// Regular character
|
|
217
|
-
currentWord += char;
|
|
218
|
-
i++;
|
|
219
|
-
}
|
|
220
|
-
// Add final word
|
|
221
|
-
if (currentWord) {
|
|
222
|
-
words.push(currentWord);
|
|
223
|
-
}
|
|
224
|
-
return words;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Break a long word that doesn't fit on a single line, character by character.
|
|
228
|
-
*/
|
|
229
|
-
function breakLongWordWithAnsi(word, width, tracker) {
|
|
177
|
+
function breakLongWord(word, width, tracker) {
|
|
230
178
|
const lines = [];
|
|
231
179
|
let currentLine = tracker.getActiveCodes();
|
|
232
|
-
let
|
|
180
|
+
let currentWidth = 0;
|
|
233
181
|
let i = 0;
|
|
234
182
|
while (i < word.length) {
|
|
235
|
-
// Check for ANSI code
|
|
236
183
|
const ansiResult = extractAnsiCode(word, i);
|
|
237
184
|
if (ansiResult) {
|
|
238
185
|
currentLine += ansiResult.code;
|
|
@@ -240,42 +187,46 @@ function breakLongWordWithAnsi(word, width, tracker) {
|
|
|
240
187
|
i += ansiResult.length;
|
|
241
188
|
continue;
|
|
242
189
|
}
|
|
243
|
-
|
|
244
|
-
const codePoint = word.charCodeAt(i);
|
|
245
|
-
let char;
|
|
246
|
-
let charByteLength;
|
|
247
|
-
if (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < word.length) {
|
|
248
|
-
// High surrogate - get the pair
|
|
249
|
-
char = word.substring(i, i + 2);
|
|
250
|
-
charByteLength = 2;
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
// Regular character
|
|
254
|
-
char = word[i];
|
|
255
|
-
charByteLength = 1;
|
|
256
|
-
}
|
|
190
|
+
const char = word[i];
|
|
257
191
|
const charWidth = visibleWidth(char);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
lines.push(currentLine + tracker.getResetCode());
|
|
263
|
-
currentLine = tracker.getActiveCodes();
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
lines.push(currentLine);
|
|
267
|
-
currentLine = "";
|
|
268
|
-
}
|
|
269
|
-
currentVisibleLength = 0;
|
|
192
|
+
if (currentWidth + charWidth > width) {
|
|
193
|
+
lines.push(currentLine);
|
|
194
|
+
currentLine = tracker.getActiveCodes();
|
|
195
|
+
currentWidth = 0;
|
|
270
196
|
}
|
|
271
197
|
currentLine += char;
|
|
272
|
-
|
|
273
|
-
i
|
|
198
|
+
currentWidth += charWidth;
|
|
199
|
+
i++;
|
|
274
200
|
}
|
|
275
|
-
|
|
276
|
-
if (currentLine || lines.length === 0) {
|
|
201
|
+
if (currentLine) {
|
|
277
202
|
lines.push(currentLine);
|
|
278
203
|
}
|
|
279
|
-
return lines;
|
|
204
|
+
return lines.length > 0 ? lines : [""];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Apply background color to a line, padding to full width.
|
|
208
|
+
*
|
|
209
|
+
* Handles the tricky case where content contains \x1b[0m resets that would
|
|
210
|
+
* kill the background color. We reapply the background after any reset.
|
|
211
|
+
*
|
|
212
|
+
* @param line - Line of text (may contain ANSI codes)
|
|
213
|
+
* @param width - Total width to pad to
|
|
214
|
+
* @param bgRgb - Background RGB color
|
|
215
|
+
* @returns Line with background applied and padded to width
|
|
216
|
+
*/
|
|
217
|
+
export function applyBackgroundToLine(line, width, bgRgb) {
|
|
218
|
+
const bgStart = `\x1b[48;2;${bgRgb.r};${bgRgb.g};${bgRgb.b}m`;
|
|
219
|
+
const bgEnd = "\x1b[49m";
|
|
220
|
+
// Calculate padding needed
|
|
221
|
+
const visibleLen = visibleWidth(line);
|
|
222
|
+
const paddingNeeded = Math.max(0, width - visibleLen);
|
|
223
|
+
const padding = " ".repeat(paddingNeeded);
|
|
224
|
+
// Strategy: wrap content + padding in background, then fix any 0m resets
|
|
225
|
+
const withPadding = line + padding;
|
|
226
|
+
const withBg = bgStart + withPadding + bgEnd;
|
|
227
|
+
// Find all \x1b[0m or \x1b[49m that would kill background
|
|
228
|
+
// Replace with reset + background reapplication
|
|
229
|
+
const fixedBg = withBg.replace(/\x1b\[0m/g, `\x1b[0m${bgStart}`);
|
|
230
|
+
return fixedBg;
|
|
280
231
|
}
|
|
281
232
|
//# sourceMappingURL=utils.js.map
|
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 * This correctly handles:\n * - ANSI escape codes (ignored)\n * - Emojis and wide characters (counted as 2 columns)\n * - Combining characters (counted correctly)\n * - Tabs (replaced with 3 spaces for consistent width)\n */\nexport function visibleWidth(str: string): number {\n\t// Replace tabs with 3 spaces before measuring\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 * Returns the ANSI code and the length consumed, or null if no ANSI code found.\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 and manage active ANSI codes for preserving styling across wrapped lines.\n */\nclass AnsiCodeTracker {\n\tprivate activeAnsiCodes: string[] = [];\n\n\t/**\n\t * Process an ANSI code and update the active codes.\n\t */\n\tprocess(ansiCode: string): void {\n\t\t// Check if it's a styling code (ends with 'm')\n\t\tif (!ansiCode.endsWith(\"m\")) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset code clears all active codes\n\t\tif (ansiCode === \"\\x1b[0m\" || ansiCode === \"\\x1b[m\") {\n\t\t\tthis.activeAnsiCodes.length = 0;\n\t\t} else {\n\t\t\t// Add to active codes\n\t\t\tthis.activeAnsiCodes.push(ansiCode);\n\t\t}\n\t}\n\n\t/**\n\t * Get all active ANSI codes as a single string.\n\t */\n\tgetActiveCodes(): string {\n\t\treturn this.activeAnsiCodes.join(\"\");\n\t}\n\n\t/**\n\t * Check if there are any active codes.\n\t */\n\thasActiveCodes(): boolean {\n\t\treturn this.activeAnsiCodes.length > 0;\n\t}\n\n\t/**\n\t * Get the reset code.\n\t */\n\tgetResetCode(): string {\n\t\treturn \"\\x1b[0m\";\n\t}\n}\n\n/**\n * Wrap text lines with word-based wrapping while preserving ANSI escape codes.\n * This function properly handles:\n * - ANSI escape codes (preserved and tracked across lines)\n * - Word-based wrapping (breaks at spaces when possible)\n * - Multi-byte characters (emoji, surrogate pairs)\n * - Newlines within text\n *\n * @param text - The text to wrap (can contain ANSI codes and newlines)\n * @param width - The maximum width in terminal columns\n * @returns Array of wrapped lines with ANSI codes preserved\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(...wrapSingleLineWithAnsi(inputLine, width));\n\t}\n\n\treturn result.length > 0 ? result : [\"\"];\n}\n\n/**\n * Wrap a single line (no newlines) with word-based wrapping while preserving ANSI codes.\n */\nfunction wrapSingleLineWithAnsi(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\n\t// First, split the line into words while preserving ANSI codes with their words\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// If the word itself is longer than the width, we need to break it character by character\n\t\tif (wordVisibleLength > width) {\n\t\t\t// Flush current line if any\n\t\t\tif (currentLine) {\n\t\t\t\twrapped.push(closeLineAndPrepareNext(currentLine, tracker));\n\t\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\t\tcurrentVisibleLength = 0;\n\t\t\t}\n\n\t\t\t// Break the long word\n\t\t\tconst brokenLines = breakLongWordWithAnsi(word, width, tracker);\n\t\t\twrapped.push(...brokenLines.slice(0, -1));\n\t\t\tcurrentLine = brokenLines[brokenLines.length - 1];\n\t\t\tcurrentVisibleLength = visibleWidth(currentLine);\n\t\t} else {\n\t\t\t// Check if adding this word would exceed the width\n\t\t\tconst spaceNeeded = currentVisibleLength > 0 ? 1 : 0; // Space before word if not at line start\n\t\t\tconst totalNeeded = currentVisibleLength + spaceNeeded + wordVisibleLength;\n\n\t\t\tif (totalNeeded > width) {\n\t\t\t\t// Word doesn't fit, wrap to next line\n\t\t\t\tif (currentLine) {\n\t\t\t\t\twrapped.push(closeLineAndPrepareNext(currentLine, tracker));\n\t\t\t\t}\n\t\t\t\tcurrentLine = tracker.getActiveCodes() + word;\n\t\t\t\tcurrentVisibleLength = wordVisibleLength;\n\t\t\t} else {\n\t\t\t\t// Word fits, add it\n\t\t\t\tif (currentVisibleLength > 0) {\n\t\t\t\t\tcurrentLine += \" \" + word;\n\t\t\t\t\tcurrentVisibleLength += 1 + wordVisibleLength;\n\t\t\t\t} else {\n\t\t\t\t\tcurrentLine += word;\n\t\t\t\t\tcurrentVisibleLength = wordVisibleLength;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update tracker with ANSI codes from this word\n\t\t\tupdateTrackerFromText(word, tracker);\n\t\t}\n\t}\n\n\t// Add final line\n\tif (currentLine) {\n\t\twrapped.push(currentLine);\n\t}\n\n\treturn wrapped.length > 0 ? wrapped : [\"\"];\n}\n\n/**\n * Close current line with reset code if needed, and prepare the next line with active codes.\n */\nfunction closeLineAndPrepareNext(line: string, tracker: AnsiCodeTracker): string {\n\tif (tracker.hasActiveCodes()) {\n\t\treturn line + tracker.getResetCode();\n\t}\n\treturn line;\n}\n\n/**\n * Update the ANSI code tracker by scanning through text.\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 to their words.\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\t// Check for ANSI code\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\t// Check for space (word boundary)\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\t// Regular character\n\t\tcurrentWord += char;\n\t\ti++;\n\t}\n\n\t// Add final word\n\tif (currentWord) {\n\t\twords.push(currentWord);\n\t}\n\n\treturn words;\n}\n\n/**\n * Break a long word that doesn't fit on a single line, character by character.\n */\nfunction breakLongWordWithAnsi(word: string, width: number, tracker: AnsiCodeTracker): string[] {\n\tconst lines: string[] = [];\n\tlet currentLine = tracker.getActiveCodes();\n\tlet currentVisibleLength = 0;\n\tlet i = 0;\n\n\twhile (i < word.length) {\n\t\t// Check for ANSI code\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\t// Get character (handle surrogate pairs)\n\t\tconst codePoint = word.charCodeAt(i);\n\t\tlet char: string;\n\t\tlet charByteLength: number;\n\n\t\tif (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < word.length) {\n\t\t\t// High surrogate - get the pair\n\t\t\tchar = word.substring(i, i + 2);\n\t\t\tcharByteLength = 2;\n\t\t} else {\n\t\t\t// Regular character\n\t\t\tchar = word[i];\n\t\t\tcharByteLength = 1;\n\t\t}\n\n\t\tconst charWidth = visibleWidth(char);\n\n\t\t// Check if adding this character would exceed width\n\t\tif (currentVisibleLength + charWidth > width) {\n\t\t\t// Need to wrap\n\t\t\tif (tracker.hasActiveCodes()) {\n\t\t\t\tlines.push(currentLine + tracker.getResetCode());\n\t\t\t\tcurrentLine = tracker.getActiveCodes();\n\t\t\t} else {\n\t\t\t\tlines.push(currentLine);\n\t\t\t\tcurrentLine = \"\";\n\t\t\t}\n\t\t\tcurrentVisibleLength = 0;\n\t\t}\n\n\t\tcurrentLine += char;\n\t\tcurrentVisibleLength += charWidth;\n\t\ti += charByteLength;\n\t}\n\n\t// Add final line (don't close it, let the caller handle that)\n\tif (currentLine || lines.length === 0) {\n\t\tlines.push(currentLine);\n\t}\n\n\treturn lines;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { Chalk } from \"chalk\";\nimport stringWidth from \"string-width\";\n\nconst colorChalk = new Chalk({ level: 3 });\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 * Handles the tricky case where content contains \\x1b[0m resets that would\n * kill the background color. We reapply the background after any reset.\n *\n * @param line - Line of text (may contain ANSI codes)\n * @param width - Total width to pad to\n * @param bgRgb - Background RGB color\n * @returns Line with background applied and padded to width\n */\nexport function applyBackgroundToLine(line: string, width: number, bgRgb: { r: number; g: number; b: number }): string {\n\tconst bgStart = `\\x1b[48;2;${bgRgb.r};${bgRgb.g};${bgRgb.b}m`;\n\tconst bgEnd = \"\\x1b[49m\";\n\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// Strategy: wrap content + padding in background, then fix any 0m resets\n\tconst withPadding = line + padding;\n\tconst withBg = bgStart + withPadding + bgEnd;\n\n\t// Find all \\x1b[0m or \\x1b[49m that would kill background\n\t// Replace with reset + background reapplication\n\tconst fixedBg = withBg.replace(/\\x1b\\[0m/g, `\\x1b[0m${bgStart}`);\n\n\treturn fixedBg;\n}\n"]}
|
package/package.json
CHANGED