From b20b83a2df0f1bf9026324c57e73f5824bd2516e Mon Sep 17 00:00:00 2001 From: RyogaK <14046676+RyogaK@users.noreply.github.com> Date: Sat, 4 Apr 2026 02:52:21 +0900 Subject: [PATCH] fix: exclude wide characters that would exceed endColumn When endColumn falls in the middle of a wide character (CJK or emoji), the character is now excluded so that the result never exceeds the requested column range. Fixes #43 --- index.js | 5 ++++- test.js | 23 ++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index f031913..e1a2f4f 100755 --- a/index.js +++ b/index.js @@ -216,7 +216,10 @@ export default function sliceAnsi(string, start, end) { let include = false; for (const [tokenIndex, token] of tokens.entries()) { - let isPastEnd = end !== undefined && position >= end; + let isPastEnd = end !== undefined && ( + position >= end + || (token.type === 'character' && !token.isGraphemeContinuation && position + token.visibleWidth > end) + ); if ( isPastEnd && token.type !== 'character' diff --git a/test.js b/test.js index 6c1e315..980a3ac 100755 --- a/test.js +++ b/test.js @@ -201,7 +201,8 @@ test('supports fullwidth characters', t => { }); test('supports unicode surrogate pairs', t => { - t.is(sliceAnsi('a\uD83C\uDE00BC', 0, 2), 'a\uD83C\uDE00'); + t.is(sliceAnsi('a\uD83C\uDE00BC', 0, 2), 'a'); + t.is(sliceAnsi('a\uD83C\uDE00BC', 0, 3), 'a\uD83C\uDE00'); }); test('does not split grapheme clusters with combining marks', t => { @@ -308,15 +309,31 @@ test('does not lose fullwidth characters', t => { t.is(sliceAnsi('古古test', 0), '古古test'); }); +// See https://github.com/chalk/slice-ansi/issues/43 +test('does not exceed endColumn for wide characters', t => { + const input = 'あいう'; + t.is(sliceAnsi(input, 0, 0), ''); + t.is(sliceAnsi(input, 0, 1), ''); + t.is(sliceAnsi(input, 0, 2), 'あ'); + t.is(sliceAnsi(input, 0, 3), 'あ'); + t.is(sliceAnsi(input, 0, 4), 'あい'); + t.is(sliceAnsi(input, 0, 5), 'あい'); + t.is(sliceAnsi(input, 0, 6), 'あいう'); +}); + test('does not split regional-indicator flag graphemes', t => { const input = 'A🇮🇱B'; - t.is(sliceAnsi(input, 1, 2), '🇮🇱'); + t.is(sliceAnsi(input, 0, 1), 'A'); + t.is(sliceAnsi(input, 1, 2), ''); + t.is(sliceAnsi(input, 1, 3), '🇮🇱'); t.is(sliceAnsi(input, 2, 3), ''); + t.is(sliceAnsi(input, 3, 4), 'B'); }); test('does not split styled regional-indicator flag graphemes', t => { const input = '\u001B[31m🇮🇱\u001B[39m'; - t.is(sliceAnsi(input, 0, 1), input); + t.is(sliceAnsi(input, 0, 1), ''); + t.is(sliceAnsi(input, 0, 2), input); t.is(sliceAnsi(input, 1, 2), ''); });