diff options
author | Ruben Bridgewater <ruben@bridgewater.de> | 2019-12-31 17:07:37 +0300 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2020-01-10 11:11:51 +0300 |
commit | 539df7387dfc0b35840817e0a710a31db49d5a5a (patch) | |
tree | 88372f731184e9cdfbe9ac38fba06951d6f89203 /lib/internal/readline | |
parent | b52bf605187d65be272a0ab8fb7f5623d8934f18 (diff) |
readline: improve getStringWidth()
1. Simplify the getStringWidth function used by Intl builds by removing
dead code (the options were unused) and by refactoring the logic.
2. Improve the getStringWidth unicode handling used by non-Intl builds.
The getStringWidth function returned the wrong width for multiple
inputs. It's now improved by supporting various zero width characters
and more full width characters.
PR-URL: https://github.com/nodejs/node/pull/31112
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'lib/internal/readline')
-rw-r--r-- | lib/internal/readline/utils.js | 74 |
1 files changed, 28 insertions, 46 deletions
diff --git a/lib/internal/readline/utils.js b/lib/internal/readline/utils.js index 6a2d1553655..e4d718d94f4 100644 --- a/lib/internal/readline/utils.js +++ b/lib/internal/readline/utils.js @@ -1,8 +1,6 @@ 'use strict'; const { - Boolean, - NumberIsInteger, RegExp, Symbol, } = primordials; @@ -21,7 +19,6 @@ const kEscape = '\x1b'; const kSubstringSearch = Symbol('kSubstringSearch'); let getStringWidth; -let isFullWidthCodePoint; function CSI(strings, ...args) { let ret = `${kEscape}[`; @@ -41,63 +38,37 @@ CSI.kClearScreenDown = CSI`0J`; if (internalBinding('config').hasIntl) { const icu = internalBinding('icu'); - getStringWidth = function getStringWidth(str, options) { - options = options || {}; - if (NumberIsInteger(str)) { - // Provide information about the character with code point 'str'. - return icu.getStringWidth( - str, - Boolean(options.ambiguousAsFullWidth), - false - ); - } - str = stripVTControlCharacters(String(str)); + // icu.getStringWidth(string, ambiguousAsFullWidth, expandEmojiSequence) + // Defaults: ambiguousAsFullWidth = false; expandEmojiSequence = true; + getStringWidth = function getStringWidth(str) { let width = 0; + str = stripVTControlCharacters(str); for (let i = 0; i < str.length; i++) { // Try to avoid calling into C++ by first handling the ASCII portion of // the string. If it is fully ASCII, we skip the C++ part. const code = str.charCodeAt(i); - if (code < 127) { - width += code >= 32; - continue; + if (code >= 127) { + width += icu.getStringWidth(str.slice(i)); + break; } - width += icu.getStringWidth( - str.slice(i), - Boolean(options.ambiguousAsFullWidth), - Boolean(options.expandEmojiSequence) - ); - break; + width += code >= 32 ? 1 : 0; } return width; }; - isFullWidthCodePoint = - function isFullWidthCodePoint(code, options) { - if (typeof code !== 'number') - return false; - return icu.getStringWidth(code, options) === 2; - }; } else { /** * Returns the number of columns required to display the given string. */ getStringWidth = function getStringWidth(str) { - if (NumberIsInteger(str)) - return isFullWidthCodePoint(str) ? 2 : 1; - let width = 0; - str = stripVTControlCharacters(String(str)); - - for (let i = 0; i < str.length; i++) { - const code = str.codePointAt(i); - - if (code >= kUTF16SurrogateThreshold) { // Surrogates. - i++; - } + str = stripVTControlCharacters(str); + for (const char of str) { + const code = char.codePointAt(0); if (isFullWidthCodePoint(code)) { width += 2; - } else { + } else if (!isZeroWidthCodePoint(code)) { width++; } } @@ -109,10 +80,10 @@ if (internalBinding('config').hasIntl) { * Returns true if the character represented by a given * Unicode code point is full-width. Otherwise returns false. */ - isFullWidthCodePoint = function isFullWidthCodePoint(code) { - // Code points are derived from: + const isFullWidthCodePoint = (code) => { + // Code points are partially derived from: // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt - return NumberIsInteger(code) && code >= 0x1100 && ( + return code >= 0x1100 && ( code <= 0x115f || // Hangul Jamo code === 0x2329 || // LEFT-POINTING ANGLE BRACKET code === 0x232a || // RIGHT-POINTING ANGLE BRACKET @@ -139,10 +110,23 @@ if (internalBinding('config').hasIntl) { (code >= 0x1b000 && code <= 0x1b001) || // Enclosed Ideographic Supplement (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane (code >= 0x20000 && code <= 0x3fffd) ); }; + + const isZeroWidthCodePoint = (code) => { + return code <= 0x1F || // C0 control codes + (code > 0x7F && code <= 0x9F) || // C1 control codes + (code >= 0x0300 && code <= 0x036F) || // Combining Diacritical Marks + (code >= 0x200B && code <= 0x200F) || // Modifying Invisible Characters + (code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors + (code >= 0xFE20 && code <= 0xFE2F) || // Combining Half Marks + (code >= 0xE0100 && code <= 0xE01EF); // Variation Selectors + }; } /** @@ -471,9 +455,7 @@ module.exports = { commonPrefix, emitKeys, getStringWidth, - isFullWidthCodePoint, kSubstringSearch, - kUTF16SurrogateThreshold, stripVTControlCharacters, CSI }; |