From fc4430c638c4775cf8bdc9d41a75b0b9dbdb1829 Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Fri, 21 Jul 2017 18:03:32 +0200 Subject: [PATCH 1/7] =?UTF-8?q?Use=20`for=E2=80=A6of`=20instead=20of=20`fo?= =?UTF-8?q?r`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index fc1919b..749149f 100755 --- a/index.js +++ b/index.js @@ -45,12 +45,13 @@ const wordLengths = str => str.split(' ').map(s => stringWidth(s)); // Wrap a long word across multiple rows // Ansi escape codes do not count towards length -function wrapWord(rows, word, cols) { +const wrapWord = (rows, word, cols) => { let insideEscape = false; let visible = stripAnsi(rows[rows.length - 1]).length; - for (let i = 0; i < word.length; i++) { - const x = word[i]; + for (const item of Array.from(word).entries()) { + const i = item[0]; + const x = item[1]; rows[rows.length - 1] += x; @@ -78,7 +79,7 @@ function wrapWord(rows, word, cols) { if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) { rows[rows.length - 2] += rows.pop(); } -} +}; // The wrap-ansi module can be invoked // in either 'hard' or 'soft' wrap mode @@ -87,7 +88,7 @@ function wrapWord(rows, word, cols) { // than cols characters // // 'soft' allows long words to expand past the column length -function exec(str, cols, opts) { +const exec = (str, cols, opts) => { const options = opts || {}; let pre = ''; @@ -98,7 +99,10 @@ function exec(str, cols, opts) { const words = str.split(' '); const rows = ['']; - for (let i = 0, word; (word = words[i]) !== undefined; i++) { + for (const item of Array.from(words).entries()) { + const i = item[0]; + const x = item[1]; + let rowLength = stringWidth(rows[rows.length - 1]); if (rowLength) { @@ -112,13 +116,13 @@ function exec(str, cols, opts) { if (rowLength) { rows.push(''); } - wrapWord(rows, word, cols); + wrapWord(rows, x, cols); continue; } if (rowLength + lengths[i] > cols && rowLength > 0) { if (options.wordWrap === false && rowLength < cols) { - wrapWord(rows, word, cols); + wrapWord(rows, x, cols); continue; } @@ -126,38 +130,39 @@ function exec(str, cols, opts) { } if (rowLength + lengths[i] > cols && options.wordWrap === false) { - wrapWord(rows, word, cols); + wrapWord(rows, x, cols); continue; } - rows[rows.length - 1] += word; + rows[rows.length - 1] += x; } pre = rows.map(x => x.trim()).join('\n'); - for (let j = 0; j < pre.length; j++) { - const y = pre[j]; + for (const item of Array.from(pre).entries()) { + const i = item[0]; + const x = item[1]; - ret += y; + ret += x; - if (ESCAPES.indexOf(y) !== -1) { - const code = parseFloat(/\d[^m]*/.exec(pre.slice(j, j + 4))); + if (ESCAPES.indexOf(x) !== -1) { + const code = parseFloat(/\d[^m]*/.exec(pre.slice(i, i + 4))); escapeCode = code === END_CODE ? null : code; } - const code = ESCAPE_CODES.get(parseInt(escapeCode, 10)); + const code = ESCAPE_CODES.get(Number(escapeCode)); if (escapeCode && code) { - if (pre[j + 1] === '\n') { + if (pre[i + 1] === '\n') { ret += wrapAnsi(code); - } else if (y === '\n') { + } else if (x === '\n') { ret += wrapAnsi(escapeCode); } } } return ret; -} +}; // For each newline, invoke the method separately module.exports = (str, cols, opts) => { From 0feb955954114043437adedac888ad12f12cb51a Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Fri, 21 Jul 2017 20:48:07 +0200 Subject: [PATCH 2/7] Add support for surrogate pairs and full width characters Fixes #10 and #11. --- index.js | 18 +++++++++++++----- test.js | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 749149f..49ec158 100755 --- a/index.js +++ b/index.js @@ -46,14 +46,21 @@ const wordLengths = str => str.split(' ').map(s => stringWidth(s)); // Wrap a long word across multiple rows // Ansi escape codes do not count towards length const wrapWord = (rows, word, cols) => { + const arr = Array.from(word); + let insideEscape = false; - let visible = stripAnsi(rows[rows.length - 1]).length; + let visible = stringWidth(stripAnsi(rows[rows.length - 1])); - for (const item of Array.from(word).entries()) { + for (const item of arr.entries()) { const i = item[0]; const x = item[1]; + const charLength = stringWidth(x); - rows[rows.length - 1] += x; + if (visible + charLength <= cols) { + rows[rows.length - 1] += x; + } else { + rows.push(x); + } if (ESCAPES.indexOf(x) !== -1) { insideEscape = true; @@ -66,9 +73,9 @@ const wrapWord = (rows, word, cols) => { continue; } - visible++; + visible += charLength; - if (visible >= cols && i < word.length - 1) { + if (visible >= cols && i < arr.length - 1) { rows.push(''); visible = 0; } @@ -167,6 +174,7 @@ const exec = (str, cols, opts) => { // For each newline, invoke the method separately module.exports = (str, cols, opts) => { return String(str) + .normalize() .split('\n') .map(line => exec(line, cols, opts)) .join('\n'); diff --git a/test.js b/test.js index a87b4e5..d1f43ca 100755 --- a/test.js +++ b/test.js @@ -87,11 +87,11 @@ test('no word-wrapping', t => { }); // https://github.com/chalk/wrap-ansi/issues/10 -test.failing('supports fullwidth characters', t => { +test('supports fullwidth characters', t => { t.is(m('안녕하세', 4, {hard: true}), '안녕\n하세'); }); // https://github.com/chalk/wrap-ansi/issues/11 -test.failing('supports unicode surrogate pairs', t => { +test('supports unicode surrogate pairs', t => { t.is(m('a\ud83c\ude00bc', 2, {hard: true}), 'a\n\ud83c\ude00\nbc'); }); From 08851585038fcf6bb2a336e45c95f96a8c5ced29 Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Fri, 21 Jul 2017 21:22:28 +0200 Subject: [PATCH 3/7] Remove moot comments --- test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test.js b/test.js index d1f43ca..0fca577 100755 --- a/test.js +++ b/test.js @@ -86,12 +86,10 @@ test('no word-wrapping', t => { t.is(res3, 'The q\nuick\nbrown\n\u001B[31mfox j\u001B[39m\n\u001B[31mumped\u001B[39m\n\u001B[31mover\u001B[39m\n\u001B[31m\u001B[39mthe l\nazy \u001B[32md\u001B[39m\n\u001B[32mog an\u001B[39m\n\u001B[32md the\u001B[39m\n\u001B[32mn ran\u001B[39m\n\u001B[32maway\u001B[39m\n\u001B[32mwith\u001B[39m\n\u001B[32mthe u\u001B[39m\n\u001B[32mnicor\u001B[39m\n\u001B[32mn.\u001B[39m'); }); -// https://github.com/chalk/wrap-ansi/issues/10 test('supports fullwidth characters', t => { t.is(m('안녕하세', 4, {hard: true}), '안녕\n하세'); }); -// https://github.com/chalk/wrap-ansi/issues/11 test('supports unicode surrogate pairs', t => { t.is(m('a\ud83c\ude00bc', 2, {hard: true}), 'a\n\ud83c\ude00\nbc'); }); From 0f7c2ca489c4eea2eb6b7ee331c37c99cc37bb95 Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Fri, 21 Jul 2017 21:41:15 +0200 Subject: [PATCH 4/7] Small fixes --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 49ec158..987d6c4 100755 --- a/index.js +++ b/index.js @@ -60,6 +60,7 @@ const wrapWord = (rows, word, cols) => { rows[rows.length - 1] += x; } else { rows.push(x); + visible = 0; } if (ESCAPES.indexOf(x) !== -1) { @@ -75,7 +76,7 @@ const wrapWord = (rows, word, cols) => { visible += charLength; - if (visible >= cols && i < arr.length - 1) { + if (visible === cols && i < arr.length - 1) { rows.push(''); visible = 0; } From aedde44ab8e3e8f844744d5049cffe284040ee2c Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Fri, 21 Jul 2017 21:47:36 +0200 Subject: [PATCH 5/7] Add more tests --- test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test.js b/test.js index 0fca577..741394c 100755 --- a/test.js +++ b/test.js @@ -92,4 +92,5 @@ test('supports fullwidth characters', t => { test('supports unicode surrogate pairs', t => { t.is(m('a\ud83c\ude00bc', 2, {hard: true}), 'a\n\ud83c\ude00\nbc'); + t.is(m('a\ud83c\ude00bc\ud83c\ude00d\ud83c\ude00', 2, {hard: true}), 'a\n\ud83c\ude00\nbc\n\ud83c\ude00\nd\n\ud83c\ude00'); }); From e61b63c145f1b1fe50107023eed59a81967b7909 Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Fri, 21 Jul 2017 23:34:40 +0200 Subject: [PATCH 6/7] Tweaks --- index.js | 22 +++++++++++----------- test.js | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 987d6c4..48da2f4 100755 --- a/index.js +++ b/index.js @@ -53,19 +53,19 @@ const wrapWord = (rows, word, cols) => { for (const item of arr.entries()) { const i = item[0]; - const x = item[1]; - const charLength = stringWidth(x); + const char = item[1]; + const charLength = stringWidth(char); if (visible + charLength <= cols) { - rows[rows.length - 1] += x; + rows[rows.length - 1] += char; } else { - rows.push(x); + rows.push(char); visible = 0; } - if (ESCAPES.indexOf(x) !== -1) { + if (ESCAPES.indexOf(char) !== -1) { insideEscape = true; - } else if (insideEscape && x === 'm') { + } else if (insideEscape && char === 'm') { insideEscape = false; continue; } @@ -109,7 +109,7 @@ const exec = (str, cols, opts) => { for (const item of Array.from(words).entries()) { const i = item[0]; - const x = item[1]; + const word = item[1]; let rowLength = stringWidth(rows[rows.length - 1]); @@ -124,13 +124,13 @@ const exec = (str, cols, opts) => { if (rowLength) { rows.push(''); } - wrapWord(rows, x, cols); + wrapWord(rows, word, cols); continue; } if (rowLength + lengths[i] > cols && rowLength > 0) { if (options.wordWrap === false && rowLength < cols) { - wrapWord(rows, x, cols); + wrapWord(rows, word, cols); continue; } @@ -138,11 +138,11 @@ const exec = (str, cols, opts) => { } if (rowLength + lengths[i] > cols && options.wordWrap === false) { - wrapWord(rows, x, cols); + wrapWord(rows, word, cols); continue; } - rows[rows.length - 1] += x; + rows[rows.length - 1] += word; } pre = rows.map(x => x.trim()).join('\n'); diff --git a/test.js b/test.js index 741394c..40af1e6 100755 --- a/test.js +++ b/test.js @@ -91,6 +91,6 @@ test('supports fullwidth characters', t => { }); test('supports unicode surrogate pairs', t => { - t.is(m('a\ud83c\ude00bc', 2, {hard: true}), 'a\n\ud83c\ude00\nbc'); - t.is(m('a\ud83c\ude00bc\ud83c\ude00d\ud83c\ude00', 2, {hard: true}), 'a\n\ud83c\ude00\nbc\n\ud83c\ude00\nd\n\ud83c\ude00'); + t.is(m('a\uD83C\uDE00bc', 2, {hard: true}), 'a\n\uD83C\uDE00\nbc'); + t.is(m('a\uD83C\uDE00bc\uD83C\uDE00d\uD83C\uDE00', 2, {hard: true}), 'a\n\uD83C\uDE00\nbc\n\uD83C\uDE00\nd\n\uD83C\uDE00'); }); From ef6e6c626be078fc3ea83ca088d7e209ddda3ef2 Mon Sep 17 00:00:00 2001 From: Kevin Martensson Date: Sat, 22 Jul 2017 21:19:50 +0200 Subject: [PATCH 7/7] Use a `Set` in `ESCAPES` --- index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 48da2f4..3587c5d 100755 --- a/index.js +++ b/index.js @@ -2,10 +2,10 @@ const stringWidth = require('string-width'); const stripAnsi = require('strip-ansi'); -const ESCAPES = [ +const ESCAPES = new Set([ '\u001B', '\u009B' -]; +]); const END_CODE = 39; @@ -37,7 +37,7 @@ const ESCAPE_CODES = new Map([ [47, 49] ]); -const wrapAnsi = code => `${ESCAPES[0]}[${code}m`; +const wrapAnsi = code => `${ESCAPES.values().next().value}[${code}m`; // Calculate the length of words split on ' ', ignoring // the extra characters added by ansi escape codes @@ -63,7 +63,7 @@ const wrapWord = (rows, word, cols) => { visible = 0; } - if (ESCAPES.indexOf(char) !== -1) { + if (ESCAPES.has(char)) { insideEscape = true; } else if (insideEscape && char === 'm') { insideEscape = false; @@ -149,11 +149,11 @@ const exec = (str, cols, opts) => { for (const item of Array.from(pre).entries()) { const i = item[0]; - const x = item[1]; + const char = item[1]; - ret += x; + ret += char; - if (ESCAPES.indexOf(x) !== -1) { + if (ESCAPES.has(char)) { const code = parseFloat(/\d[^m]*/.exec(pre.slice(i, i + 4))); escapeCode = code === END_CODE ? null : code; } @@ -163,7 +163,7 @@ const exec = (str, cols, opts) => { if (escapeCode && code) { if (pre[i + 1] === '\n') { ret += wrapAnsi(code); - } else if (x === '\n') { + } else if (char === '\n') { ret += wrapAnsi(escapeCode); } }