Description
cliTruncate produces output wider than the specified columns when the truncation boundary falls on an East Asian wide (CJK) character. This is because slice-ansi rounds up to include the full wide character, and the ellipsis is appended without rechecking the total display width.
Reproduction
import cliTruncate from 'cli-truncate';
import stringWidth from 'string-width';
const result = cliTruncate('あいうえおかきくけこ|end', 20);
console.log(result); // あいうえおかきくけこ…
console.log(stringWidth(result)); // 21 — exceeds maxWidth of 20
Expected
Result should fit within 20 display columns, e.g. あいうえおかきくけ… (19 columns).
Actual
Result is あいうえおかきくけこ… (21 columns). The 10th CJK character spans columns 18–19; sliceAnsi(text, 0, 19) includes it fully (returning 20 display columns), then … is appended for a total of 21.
Pattern across widths
maxWidth=18 → "あいうえおかきくけ…" displayWidth=19 ✗
maxWidth=19 → "あいうえおかきくけ…" displayWidth=19 ✓
maxWidth=20 → "あいうえおかきくけこ…" displayWidth=21 ✗
maxWidth=21 → "あいうえおかきくけこ…" displayWidth=21 ✓
maxWidth=22 → "あいうえおかきくけこさ…" displayWidth=23 ✗
The bug occurs at every even maxWidth for CJK-only strings — whenever maxWidth - 1 (the slice target) falls in the middle of a 2-column character.
Root cause
In index.js line 163:
const left = sliceAnsi(text, 0, columns - stringWidth(truncationCharacter));
sliceAnsi rounds up when the end index falls inside a wide character, so left can be wider than columns - 1. The result left + truncationCharacter then exceeds columns.
Suggested fix
After slicing, verify the total width and re-slice if it exceeds columns:
let left = sliceAnsi(text, 0, columns - stringWidth(truncationCharacter));
while (stringWidth(left) + stringWidth(truncationCharacter) > columns) {
left = sliceAnsi(text, 0, stringWidth(left) - 1);
}
The same issue likely affects position: 'start' and position: 'middle'.
Environment
- cli-truncate 5.2.0
- slice-ansi 7.1.0
- string-width 7.2.0
- Node.js v25.8.2
Description
cliTruncateproduces output wider than the specifiedcolumnswhen the truncation boundary falls on an East Asian wide (CJK) character. This is becauseslice-ansirounds up to include the full wide character, and the ellipsis is appended without rechecking the total display width.Reproduction
Expected
Result should fit within 20 display columns, e.g.
あいうえおかきくけ…(19 columns).Actual
Result is
あいうえおかきくけこ…(21 columns). The 10th CJK character spans columns 18–19;sliceAnsi(text, 0, 19)includes it fully (returning 20 display columns), then…is appended for a total of 21.Pattern across widths
The bug occurs at every even
maxWidthfor CJK-only strings — whenevermaxWidth - 1(the slice target) falls in the middle of a 2-column character.Root cause
In
index.jsline 163:sliceAnsirounds up when the end index falls inside a wide character, soleftcan be wider thancolumns - 1. The resultleft + truncationCharacterthen exceedscolumns.Suggested fix
After slicing, verify the total width and re-slice if it exceeds
columns:The same issue likely affects
position: 'start'andposition: 'middle'.Environment