-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wcwidth calculation #144
wcwidth calculation #144
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,122 @@ | |
|
||
var window = this, document = this.document; | ||
|
||
/** | ||
* wcwidth | ||
*/ | ||
var wcswidth = (function() { | ||
var _WIDTH_COMBINING = [ | ||
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where was this list sourced from? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extracted from here https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c |
||
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], | ||
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], | ||
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], | ||
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], | ||
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], | ||
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], | ||
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], | ||
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], | ||
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], | ||
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], | ||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], | ||
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], | ||
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], | ||
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], | ||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], | ||
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], | ||
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], | ||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], | ||
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], | ||
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], | ||
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], | ||
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], | ||
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], | ||
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], | ||
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], | ||
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], | ||
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], | ||
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], | ||
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], | ||
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], | ||
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], | ||
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], | ||
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], | ||
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], | ||
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], | ||
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], | ||
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], | ||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], | ||
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], | ||
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], | ||
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], | ||
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], | ||
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], | ||
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], | ||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], | ||
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], | ||
[0xE0100, 0xE01EF] | ||
]; | ||
function _width_bisearch(ucs) { | ||
var min = 0; | ||
var max = _WIDTH_COMBINING.length - 1; | ||
var mid; | ||
if (ucs < _WIDTH_COMBINING[0][0] || ucs > _WIDTH_COMBINING[max][1]) | ||
return false; | ||
while (max >= min) { | ||
mid = Math.floor((min + max) / 2); | ||
if (ucs > _WIDTH_COMBINING[mid][1]) | ||
min = mid + 1; | ||
else if (ucs < _WIDTH_COMBINING[mid][0]) | ||
max = mid - 1; | ||
else | ||
return true; | ||
} | ||
return false; | ||
} | ||
function _width_wcwidth(ucs, opts) { | ||
// test for 8-bit control characters | ||
if (ucs === 0) | ||
return opts.nul; | ||
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) | ||
return opts.control; | ||
// binary search in table of non-spacing characters | ||
if (_width_bisearch(ucs)) | ||
return 0; | ||
// if we arrive here, ucs is not a combining or C0/C1 control character | ||
return 1 + | ||
(ucs >= 0x1100 && | ||
(ucs <= 0x115f || // Hangul Jamo init. consonants | ||
ucs == 0x2329 || ucs == 0x232a || | ||
(ucs >= 0x2e80 && ucs <= 0xa4cf && | ||
ucs != 0x303f) || // CJK ... Yi | ||
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables | ||
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs | ||
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms | ||
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms | ||
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms | ||
(ucs >= 0xffe0 && ucs <= 0xffe6) || | ||
(ucs >= 0x20000 && ucs <= 0x2fffd) || | ||
(ucs >= 0x30000 && ucs <= 0x3fffd))); | ||
} | ||
|
||
var _WIDTH_DEFAULTS = {nul: 0, control: 0}; | ||
|
||
function wcswidth(str, opts) { | ||
opts = _WIDTH_DEFAULTS; // FIXME | ||
if (typeof str !== 'string') | ||
return _width_wcwidth(str, opts); | ||
var s = 0; | ||
for (var i = 0; i < str.length; i++) { | ||
var n = _width_wcwidth(str.charCodeAt(i), opts); | ||
if (n < 0) | ||
return -1; | ||
s += n; | ||
} | ||
return s | ||
} | ||
|
||
return wcswidth; | ||
})(); | ||
|
||
/** | ||
* EventEmitter | ||
*/ | ||
|
@@ -294,6 +410,9 @@ | |
this.prefix = ''; | ||
this.postfix = ''; | ||
|
||
// left over left part of surrogate from previous write invocation | ||
this.surrogate_left = ''; | ||
|
||
/** | ||
* An array of all lines in the entire buffer, including the prompt. The lines are array of | ||
* characters which are 2-length arrays where [0] is an attribute and [1] is the character. | ||
|
@@ -1231,7 +1350,7 @@ | |
this._fullRefreshNext = false // reset lock | ||
} | ||
|
||
var x, y, i, line, out, ch, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement; | ||
var x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement; | ||
|
||
// If this is a big refresh, remove the terminal rows from the DOM for faster calculations | ||
if (end - start >= this.rows / 2) { | ||
|
@@ -1269,6 +1388,9 @@ | |
for (; i < width; i++) { | ||
data = line[i][0]; | ||
ch = line[i][1]; | ||
ch_width = line[i][2]; | ||
if (!ch_width) | ||
continue; | ||
|
||
if (i === x) data = -1; | ||
|
||
|
@@ -1368,7 +1490,6 @@ | |
if (ch <= ' ') { | ||
out += ' '; | ||
} else { | ||
if (isWide(ch)) i++; | ||
out += ch; | ||
} | ||
break; | ||
|
@@ -1458,7 +1579,7 @@ | |
* @public | ||
*/ | ||
Terminal.prototype.write = function(data) { | ||
var l = data.length, i = 0, j, cs, ch; | ||
var l = data.length, i = 0, j, cs, ch, code, low, ch_width; | ||
|
||
this.refreshStart = this.y; | ||
this.refreshEnd = this.y; | ||
|
@@ -1468,8 +1589,30 @@ | |
this.maxRange(); | ||
} | ||
|
||
// apply leftover surrogate left from last write | ||
if (this.surrogate_left) { | ||
data = this.surrogate_left + data; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use 2 space indents |
||
this.surrogate_left = ''; | ||
} | ||
|
||
for (; i < l; i++) { | ||
ch = data[i]; | ||
|
||
code = data.charCodeAt(i); | ||
// surrogate high | ||
if (0xD800 <= code && code <= 0xDBFF) { | ||
low = data.charCodeAt(i+1); | ||
if (low !== low) { // end of data stream, we need to save to first part of surrogate | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use |
||
this.surrogate_left = ch; | ||
return; | ||
} | ||
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; | ||
ch += data.charAt(i+1); | ||
} | ||
// surrogate low - already handled above | ||
if (0xDC00 <= code && code <= 0xDFFF) | ||
continue; | ||
|
||
switch (this.state) { | ||
case normal: | ||
switch (ch) { | ||
|
@@ -1525,12 +1668,21 @@ | |
|
||
default: | ||
// ' ' | ||
ch_width = wcswidth(code); // expensive, therefore we save the result in line buffer | ||
|
||
if (ch >= ' ') { | ||
if (this.charset && this.charset[ch]) { | ||
ch = this.charset[ch]; | ||
} | ||
|
||
if (this.x >= this.cols) { | ||
// combining into last cell - fixing #72 and 'café' | ||
if (!ch_width && this.x) { | ||
this.lines[this.y + this.ybase][this.x-1][1] += ch; | ||
this.updateRange(this.y); | ||
break; | ||
} | ||
|
||
if (this.x+ch_width-1 >= this.cols) { // just goto next line if ch would overflow | ||
this.x = 0; | ||
this.y++; | ||
if (this.y > this.scrollBottom) { | ||
|
@@ -1539,17 +1691,12 @@ | |
} | ||
} | ||
|
||
this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch]; | ||
this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch, ch_width]; | ||
this.x++; | ||
this.updateRange(this.y); | ||
|
||
if (isWide(ch)) { | ||
j = this.y + this.ybase; | ||
if (this.cols < 2 || this.x >= this.cols) { | ||
this.lines[j][this.x - 1] = [this.curAttr, ' ']; | ||
break; | ||
} | ||
this.lines[j][this.x] = [this.curAttr, ' ']; | ||
if (ch_width==2) { // set next cell to zero | ||
this.lines[this.y + this.ybase][this.x] = [this.curAttr, '', 0]; | ||
this.x++; | ||
} | ||
} | ||
|
@@ -2908,7 +3055,7 @@ | |
|
||
Terminal.prototype.eraseRight = function(x, y) { | ||
var line = this.lines[this.ybase + y] | ||
, ch = [this.eraseAttr(), ' ']; // xterm | ||
, ch = [this.eraseAttr(), ' ', 1]; // xterm | ||
|
||
|
||
for (; x < this.cols; x++) { | ||
|
@@ -2920,7 +3067,7 @@ | |
|
||
Terminal.prototype.eraseLeft = function(x, y) { | ||
var line = this.lines[this.ybase + y] | ||
, ch = [this.eraseAttr(), ' ']; // xterm | ||
, ch = [this.eraseAttr(), ' ', 1]; // xterm | ||
|
||
x++; | ||
while (x--) line[x] = ch; | ||
|
@@ -2937,7 +3084,7 @@ | |
? this.eraseAttr() | ||
: this.defAttr; | ||
|
||
var ch = [attr, ' '] | ||
var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character | ||
, line = [] | ||
, i = 0; | ||
|
||
|
@@ -2950,8 +3097,8 @@ | |
|
||
Terminal.prototype.ch = function(cur) { | ||
return cur | ||
? [this.eraseAttr(), ' '] | ||
: [this.defAttr, ' ']; | ||
? [this.eraseAttr(), ' ', 1] | ||
: [this.defAttr, ' ', 1]; | ||
}; | ||
|
||
Terminal.prototype.is = function(term) { | ||
|
@@ -4594,17 +4741,6 @@ | |
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); | ||
} | ||
|
||
function isWide(ch) { | ||
if (ch <= '\uff00') return false; | ||
return (ch >= '\uff01' && ch <= '\uffbe') | ||
|| (ch >= '\uffc2' && ch <= '\uffc7') | ||
|| (ch >= '\uffca' && ch <= '\uffcf') | ||
|| (ch >= '\uffd2' && ch <= '\uffd7') | ||
|| (ch >= '\uffda' && ch <= '\uffdc') | ||
|| (ch >= '\uffe0' && ch <= '\uffe6') | ||
|| (ch >= '\uffe8' && ch <= '\uffee'); | ||
} | ||
|
||
function matchColor(r1, g1, b1) { | ||
var hash = (r1 << 16) | (g1 << 8) | b1; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General helper functions should go at the end of the file.