Skip to content

Truncation exceeds maxWidth for East Asian wide characters #28

@RyogaK

Description

@RyogaK

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions