Skip to content
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

Merged
merged 4 commits into from
Jun 27, 2016
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 164 additions & 28 deletions src/xterm.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,122 @@

var window = this, document = this.document;

/**
* wcwidth
*/
var wcswidth = (function() {
Copy link
Member

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.

var _WIDTH_COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where was this list sourced from?

Copy link
Member Author

@jerch jerch Jun 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1368,7 +1490,6 @@
if (ch <= ' ') {
out += '&nbsp;';
} else {
if (isWide(ch)) i++;
out += ch;
}
break;
Expand Down Expand Up @@ -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;
Expand All @@ -1468,8 +1589,30 @@
this.maxRange();
}

// apply leftover surrogate left from last write
if (this.surrogate_left) {
data = this.surrogate_left + data;
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low will always equal low?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

charCodeAt with an illegal index returns NaN, (NaN !== NaN) evaluates always to true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use isNaN() for clarity?

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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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++;
}
}
Expand Down Expand Up @@ -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++) {
Expand All @@ -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;
Expand All @@ -2937,7 +3084,7 @@
? this.eraseAttr()
: this.defAttr;

var ch = [attr, ' ']
var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character
, line = []
, i = 0;

Expand All @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down