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
Show file tree
Hide file tree
Changes from all 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
201 changes: 173 additions & 28 deletions src/xterm.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@
this.prefix = '';
this.postfix = '';

// leftover surrogate high from previous write invocation
this.surrogate_high = '';

/**
* 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 +1234,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 +1272,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 +1374,6 @@
if (ch <= ' ') {
out += '&nbsp;';
} else {
if (isWide(ch)) i++;
out += ch;
}
break;
Expand Down Expand Up @@ -1458,7 +1463,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 +1473,34 @@
this.maxRange();
}

// apply leftover surrogate high from last write
if (this.surrogate_high) {
data = this.surrogate_high + data;
this.surrogate_high = '';
}

for (; i < l; i++) {
ch = data[i];

// FIXME: higher chars than 0xa0 are not allowed in escape sequences
// --> maybe move to default
code = data.charCodeAt(i);
if (0xD800 <= code && code <= 0xDBFF) {
// we got a surrogate high
// get surrogate low (next 2 bytes)
low = data.charCodeAt(i+1);
if (isNaN(low)) {
// end of data stream, save surrogate high
this.surrogate_high = ch;
continue;
}
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 +1556,38 @@

default:
// ' '
// calculate print space
// expensive call, therefore we save width in line buffer
ch_width = wcwidth(code);

if (ch >= ' ') {
if (this.charset && this.charset[ch]) {
ch = this.charset[ch];
}

if (this.x >= this.cols) {
// insert combining char in last cell
// FIXME: needs handling after cursor jumps
if (!ch_width && this.x) {

// dont overflow left
if (this.lines[this.y + this.ybase][this.x-1]) {
if (!this.lines[this.y + this.ybase][this.x-1][2]) {

// found empty cell after fullwidth, need to go 2 cells back
if (this.lines[this.y + this.ybase][this.x-2])
this.lines[this.y + this.ybase][this.x-2][1] += ch;

} else {
this.lines[this.y + this.ybase][this.x-1][1] += ch;
}
this.updateRange(this.y);
}
break;
}

// goto next line if ch would overflow
// TODO: needs a global min terminal width of 2
if (this.x+ch_width-1 >= this.cols) {
this.x = 0;
this.y++;
if (this.y > this.scrollBottom) {
Expand All @@ -1539,17 +1596,13 @@
}
}

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, ' '];
// fullwidth char - set next cell width to zero and advance cursor
if (ch_width==2) {
this.lines[this.y + this.ybase][this.x] = [this.curAttr, '', 0];
this.x++;
}
}
Expand Down Expand Up @@ -2908,7 +2961,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 +2973,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 +2990,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 +3003,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 +4647,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 Expand Up @@ -4670,6 +4712,109 @@
return keys;
}

var wcwidth = (function(opts) {
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
// combining characters
var COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
[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]
];
// binary search
function bisearch(ucs) {
var min = 0;
var max = COMBINING.length - 1;
var mid;
if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1])
return false;
while (max >= min) {
mid = Math.floor((min + max) / 2);
if (ucs > COMBINING[mid][1])
min = mid + 1;
else if (ucs < COMBINING[mid][0])
max = mid - 1;
else
return true;
}
return false;
}
function wcwidth(ucs) {
// 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 (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 Compat Ideographs
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x20000 && ucs <= 0x2fffd) ||
(ucs >= 0x30000 && ucs <= 0x3fffd)
)
);
}
return wcwidth;
})({nul: 0, control: 0}); // configurable options

/**
* Expose
*/
Expand Down
Loading