Skip to content

Commit

Permalink
fix: cropToWidth crashing under some circumstances, textWidth ret…
Browse files Browse the repository at this point in the history
…urning accumulated width of all lines
  • Loading branch information
Im-Beast committed Jan 21, 2024
1 parent c15c6d9 commit e039596
Showing 1 changed file with 50 additions and 22 deletions.
72 changes: 50 additions & 22 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,40 +80,68 @@ export function textWidth(text: string, start = 0): number {
let width = 0;
let ansi = false;
const len = text.length;
for (let i = start; i < len; ++i) {
loop: for (let i = start; i < len; ++i) {
const char = text[i];
if (char === "\x1b") {
ansi = true;
i += 2; // [ "\x1b" "[" "X" "m" ] <-- shortest ansi sequence
} else if (char === "m" && ansi) {
ansi = false;
} else if (!ansi) {
width += characterWidth(char);

switch (char) {
case "\x1b":
ansi = true;
i += 2;
break;
case "\n":
break loop;
default:
if (!ansi) {
width += characterWidth(char);
} else if (isFinalAnsiByte(char)) {
ansi = false;
}
break;
}
}

return width;
}

/** Crops {text} to given {width} */
export function cropToWidth(text: string, width: number): string {
const stripped = stripStyles(text);
const letter = stripped[width];
let cropped = "";
let croppedWidth = 0;
let ansi = 0;

if (textWidth(text) <= width) return text;
const len = text.length;
for (let i = 0; i < len; ++i) {
const char = text[i];

if (char === "\x1b") {
ansi = 1;
} else if (ansi >= 3 && isFinalAnsiByte(char)) {
ansi = 0;
} else if (ansi > 0) {
ansi += 1;
} else {
const charWidth = characterWidth(char);

if (croppedWidth + charWidth > width) {
if (croppedWidth + 1 === width) {
cropped += " ";
}
break;
} else {
croppedWidth += charWidth;
}
}

text = text.slice(0, text.lastIndexOf(letter));
if (textWidth(text) <= width) return text;
cropped += char;
}

const start = text.indexOf(letter);
const knownPart = text.slice(0, start);
const knownWidth = textWidth(knownPart);
if (knownWidth === width) return knownPart;
return cropped;
}

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

/**
Expand Down

0 comments on commit e039596

Please sign in to comment.