Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5ccce80

Browse files
committedJan 21, 2024
fix: \cropToWidth\ crashing under some circumstances, \textWidth\ returning accumulated width of all lines
1 parent c15c6d9 commit 5ccce80

File tree

1 file changed

+50
-22
lines changed

1 file changed

+50
-22
lines changed
 

‎src/utils/strings.ts

+50-22
Original file line numberDiff line numberDiff line change
@@ -80,40 +80,68 @@ export function textWidth(text: string, start = 0): number {
8080
let width = 0;
8181
let ansi = false;
8282
const len = text.length;
83-
for (let i = start; i < len; ++i) {
83+
loop: for (let i = start; i < len; ++i) {
8484
const char = text[i];
85-
if (char === "\x1b") {
86-
ansi = true;
87-
i += 2; // [ "\x1b" "[" "X" "m" ] <-- shortest ansi sequence
88-
} else if (char === "m" && ansi) {
89-
ansi = false;
90-
} else if (!ansi) {
91-
width += characterWidth(char);
85+
86+
switch (char) {
87+
case "\x1b":
88+
ansi = true;
89+
i += 2;
90+
break;
91+
case "\n":
92+
break loop;
93+
default:
94+
if (!ansi) {
95+
width += characterWidth(char);
96+
} else if (isFinalAnsiByte(char)) {
97+
ansi = false;
98+
}
99+
break;
92100
}
93101
}
94102

95103
return width;
96104
}
105+
97106
/** Crops {text} to given {width} */
98107
export function cropToWidth(text: string, width: number): string {
99-
const stripped = stripStyles(text);
100-
const letter = stripped[width];
108+
let cropped = "";
109+
let croppedWidth = 0;
110+
let ansi = 0;
101111

102-
if (textWidth(text) <= width) return text;
112+
const len = text.length;
113+
for (let i = 0; i < len; ++i) {
114+
const char = text[i];
115+
116+
if (char === "\x1b") {
117+
ansi = 1;
118+
} else if (ansi >= 3 && isFinalAnsiByte(char)) {
119+
ansi = 0;
120+
} else if (ansi > 0) {
121+
ansi += 1;
122+
} else {
123+
const charWidth = characterWidth(char);
124+
125+
if (croppedWidth + charWidth > width) {
126+
if (croppedWidth + 1 === width) {
127+
cropped += " ";
128+
}
129+
break;
130+
} else {
131+
croppedWidth += charWidth;
132+
}
133+
}
103134

104-
text = text.slice(0, text.lastIndexOf(letter));
105-
if (textWidth(text) <= width) return text;
135+
cropped += char;
136+
}
106137

107-
const start = text.indexOf(letter);
108-
const knownPart = text.slice(0, start);
109-
const knownWidth = textWidth(knownPart);
110-
if (knownWidth === width) return knownPart;
138+
return cropped;
139+
}
111140

112-
do {
113-
const index = text.lastIndexOf(letter);
114-
text = text.slice(0, index);
115-
} while ((knownWidth + textWidth(text, start)) > width);
116-
return text;
141+
export function isFinalAnsiByte(character: string): boolean {
142+
const codePoint = character.charCodeAt(0);
143+
// don't include 0x70–0x7E range because its considered "private"
144+
return codePoint >= 0x40 && codePoint < 0x70;
117145
}
118146

119147
/**

0 commit comments

Comments
 (0)
Please sign in to comment.