Merge pull request #422 from Tyriar/361_circular_list_scrollback

Use a circular list to manage buffer
This commit is contained in:
Daniel Imms 2016-12-28 03:10:31 -08:00 committed by GitHub
commit e6ad8d8b50
5 changed files with 577 additions and 126 deletions

View File

@ -59,7 +59,7 @@ function terminalToString(term) {
for (var line=0; line<term.rows; ++line) { for (var line=0; line<term.rows; ++line) {
line_s = ''; line_s = '';
for (var cell=0; cell<term.cols; ++cell) { for (var cell=0; cell<term.cols; ++cell) {
line_s += term.lines[line][cell][1]; line_s += term.lines.get(line)[cell][1];
} }
// rtrim empty cells as xterm does // rtrim empty cells as xterm does
line_s = line_s.replace(/\s+$/, ''); line_s = line_s.replace(/\s+$/, '');
@ -83,7 +83,6 @@ describe('xterm output comparison', function() {
var files = glob.sync('**/escape_sequence_files/*.in'); var files = glob.sync('**/escape_sequence_files/*.in');
// only successful tests for now // only successful tests for now
var successful = [0, 2, 6, 12, 13, 18, 20, 22, 27, 28]; var successful = [0, 2, 6, 12, 13, 18, 20, 22, 27, 28];
console.log(files);
for (var a in successful) { for (var a in successful) {
var i = successful[a]; var i = successful[a];
(function(filename){ (function(filename){
@ -92,7 +91,7 @@ describe('xterm output comparison', function() {
var in_file = fs.readFileSync(filename, 'utf8'); var in_file = fs.readFileSync(filename, 'utf8');
var from_pty = pty_write_read(in_file); var from_pty = pty_write_read(in_file);
// uncomment this to get log from terminal // uncomment this to get log from terminal
console.log = function(){}; //console.log = function(){};
xterm.write(from_pty); xterm.write(from_pty);
var from_emulator = terminalToString(xterm); var from_emulator = terminalToString(xterm);
console.log = CONSOLE_LOG; console.log = CONSOLE_LOG;

View File

@ -48,15 +48,15 @@ describe('xterm.js', function() {
describe('clear', function() { describe('clear', function() {
it('should clear a buffer equal to rows', function() { it('should clear a buffer equal to rows', function() {
var promptLine = xterm.lines[xterm.ybase + xterm.y]; var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
xterm.clear(); xterm.clear();
assert.equal(xterm.y, 0); assert.equal(xterm.y, 0);
assert.equal(xterm.ybase, 0); assert.equal(xterm.ybase, 0);
assert.equal(xterm.ydisp, 0); assert.equal(xterm.ydisp, 0);
assert.equal(xterm.lines.length, xterm.rows); assert.equal(xterm.lines.length, xterm.rows);
assert.deepEqual(xterm.lines[0], promptLine); assert.deepEqual(xterm.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) { for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.lines[0], xterm.blankLine()); assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
} }
}); });
it('should clear a buffer larger than rows', function() { it('should clear a buffer larger than rows', function() {
@ -65,28 +65,28 @@ describe('xterm.js', function() {
xterm.write('test\n'); xterm.write('test\n');
} }
var promptLine = xterm.lines[xterm.ybase + xterm.y]; var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
xterm.clear(); xterm.clear();
assert.equal(xterm.y, 0); assert.equal(xterm.y, 0);
assert.equal(xterm.ybase, 0); assert.equal(xterm.ybase, 0);
assert.equal(xterm.ydisp, 0); assert.equal(xterm.ydisp, 0);
assert.equal(xterm.lines.length, xterm.rows); assert.equal(xterm.lines.length, xterm.rows);
assert.deepEqual(xterm.lines[0], promptLine); assert.deepEqual(xterm.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) { for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.lines[i], xterm.blankLine()); assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
} }
}); });
it('should not break the prompt when cleared twice', function() { it('should not break the prompt when cleared twice', function() {
var promptLine = xterm.lines[xterm.ybase + xterm.y]; var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
xterm.clear(); xterm.clear();
xterm.clear(); xterm.clear();
assert.equal(xterm.y, 0); assert.equal(xterm.y, 0);
assert.equal(xterm.ybase, 0); assert.equal(xterm.ybase, 0);
assert.equal(xterm.ydisp, 0); assert.equal(xterm.ydisp, 0);
assert.equal(xterm.lines.length, xterm.rows); assert.equal(xterm.lines.length, xterm.rows);
assert.deepEqual(xterm.lines[0], promptLine); assert.deepEqual(xterm.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) { for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.lines[i], xterm.blankLine()); assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
} }
}); });
}); });
@ -543,11 +543,11 @@ describe('xterm.js', function() {
var high = String.fromCharCode(0xD800); var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) { for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.write(high + String.fromCharCode(i)); xterm.write(high + String.fromCharCode(i));
var tchar = xterm.lines[0][0]; var tchar = xterm.lines.get(0)[0];
expect(tchar[1]).eql(high + String.fromCharCode(i)); expect(tchar[1]).eql(high + String.fromCharCode(i));
expect(tchar[1].length).eql(2); expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
expect(xterm.lines[0][1][1]).eql(' '); expect(xterm.lines.get(0)[1][1]).eql(' ');
xterm.reset(); xterm.reset();
} }
}); });
@ -556,9 +556,9 @@ describe('xterm.js', function() {
for (var i=0xDC00; i<=0xDCFF; ++i) { for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.x = xterm.cols - 1; xterm.x = xterm.cols - 1;
xterm.write(high + String.fromCharCode(i)); xterm.write(high + String.fromCharCode(i));
expect(xterm.lines[0][xterm.x-1][1]).eql(high + String.fromCharCode(i)); expect(xterm.lines.get(0)[xterm.x-1][1]).eql(high + String.fromCharCode(i));
expect(xterm.lines[0][xterm.x-1][1].length).eql(2); expect(xterm.lines.get(0)[xterm.x-1][1].length).eql(2);
expect(xterm.lines[1][0][1]).eql(' '); expect(xterm.lines.get(1)[0][1]).eql(' ');
xterm.reset(); xterm.reset();
} }
}); });
@ -568,10 +568,10 @@ describe('xterm.js', function() {
xterm.x = xterm.cols - 1; xterm.x = xterm.cols - 1;
xterm.wraparoundMode = true; xterm.wraparoundMode = true;
xterm.write('a' + high + String.fromCharCode(i)); xterm.write('a' + high + String.fromCharCode(i));
expect(xterm.lines[0][xterm.cols-1][1]).eql('a'); expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
expect(xterm.lines[1][0][1]).eql(high + String.fromCharCode(i)); expect(xterm.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
expect(xterm.lines[1][0][1].length).eql(2); expect(xterm.lines.get(1)[0][1].length).eql(2);
expect(xterm.lines[1][1][1]).eql(' '); expect(xterm.lines.get(1)[1][1]).eql(' ');
xterm.reset(); xterm.reset();
} }
}); });
@ -581,9 +581,9 @@ describe('xterm.js', function() {
xterm.x = xterm.cols - 1; xterm.x = xterm.cols - 1;
xterm.wraparoundMode = false; xterm.wraparoundMode = false;
xterm.write('a' + high + String.fromCharCode(i)); xterm.write('a' + high + String.fromCharCode(i));
expect(xterm.lines[0][xterm.cols-1][1]).eql(high + String.fromCharCode(i)); expect(xterm.lines.get(0)[xterm.cols-1][1]).eql(high + String.fromCharCode(i));
expect(xterm.lines[0][xterm.cols-1][1].length).eql(2); expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(2);
expect(xterm.lines[1][1][1]).eql(' '); expect(xterm.lines.get(1)[1][1]).eql(' ');
xterm.reset(); xterm.reset();
} }
}); });
@ -592,11 +592,11 @@ describe('xterm.js', function() {
for (var i=0xDC00; i<=0xDCFF; ++i) { for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.write(high); xterm.write(high);
xterm.write(String.fromCharCode(i)); xterm.write(String.fromCharCode(i));
var tchar = xterm.lines[0][0]; var tchar = xterm.lines.get(0)[0];
expect(tchar[1]).eql(high + String.fromCharCode(i)); expect(tchar[1]).eql(high + String.fromCharCode(i));
expect(tchar[1].length).eql(2); expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
expect(xterm.lines[0][1][1]).eql(' '); expect(xterm.lines.get(0)[1][1]).eql(' ');
xterm.reset(); xterm.reset();
} }
}); });
@ -605,30 +605,30 @@ describe('xterm.js', function() {
describe('unicode - combining characters', function() { describe('unicode - combining characters', function() {
it('café', function () { it('café', function () {
xterm.write('cafe\u0301'); xterm.write('cafe\u0301');
expect(xterm.lines[0][3][1]).eql('e\u0301'); expect(xterm.lines.get(0)[3][1]).eql('e\u0301');
expect(xterm.lines[0][3][1].length).eql(2); expect(xterm.lines.get(0)[3][1].length).eql(2);
expect(xterm.lines[0][3][2]).eql(1); expect(xterm.lines.get(0)[3][2]).eql(1);
}); });
it('café - end of line', function() { it('café - end of line', function() {
xterm.x = xterm.cols - 1 - 3; xterm.x = xterm.cols - 1 - 3;
xterm.write('cafe\u0301'); xterm.write('cafe\u0301');
expect(xterm.lines[0][xterm.cols-1][1]).eql('e\u0301'); expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
expect(xterm.lines[0][xterm.cols-1][1].length).eql(2); expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(2);
expect(xterm.lines[0][xterm.cols-1][2]).eql(1); expect(xterm.lines.get(0)[xterm.cols-1][2]).eql(1);
expect(xterm.lines[0][1][1]).eql(' '); expect(xterm.lines.get(0)[1][1]).eql(' ');
expect(xterm.lines[0][1][1].length).eql(1); expect(xterm.lines.get(0)[1][1].length).eql(1);
expect(xterm.lines[0][1][2]).eql(1); expect(xterm.lines.get(0)[1][2]).eql(1);
}); });
it('multiple combined é', function() { it('multiple combined é', function() {
xterm.wraparoundMode = true; xterm.wraparoundMode = true;
xterm.write(Array(100).join('e\u0301')); xterm.write(Array(100).join('e\u0301'));
for (var i=0; i<xterm.cols; ++i) { for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
expect(tchar[1]).eql('e\u0301'); expect(tchar[1]).eql('e\u0301');
expect(tchar[1].length).eql(2); expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
} }
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('e\u0301'); expect(tchar[1]).eql('e\u0301');
expect(tchar[1].length).eql(2); expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
@ -637,12 +637,12 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true; xterm.wraparoundMode = true;
xterm.write(Array(100).join('\uD800\uDC00\u0301')); xterm.write(Array(100).join('\uD800\uDC00\u0301'));
for (var i=0; i<xterm.cols; ++i) { for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
expect(tchar[1]).eql('\uD800\uDC00\u0301'); expect(tchar[1]).eql('\uD800\uDC00\u0301');
expect(tchar[1].length).eql(3); expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
} }
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('\uD800\uDC00\u0301'); expect(tchar[1]).eql('\uD800\uDC00\u0301');
expect(tchar[1].length).eql(3); expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
@ -665,7 +665,7 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true; xterm.wraparoundMode = true;
xterm.write(Array(50).join('¥')); xterm.write(Array(50).join('¥'));
for (var i=0; i<xterm.cols; ++i) { for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
if (i % 2) { if (i % 2) {
expect(tchar[1]).eql(''); expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0); expect(tchar[1].length).eql(0);
@ -676,7 +676,7 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
} }
} }
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('¥'); expect(tchar[1]).eql('¥');
expect(tchar[1].length).eql(1); expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
@ -686,7 +686,7 @@ describe('xterm.js', function() {
xterm.x = 1; xterm.x = 1;
xterm.write(Array(50).join('¥')); xterm.write(Array(50).join('¥'));
for (var i=1; i<xterm.cols-1; ++i) { for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
if (!(i % 2)) { if (!(i % 2)) {
expect(tchar[1]).eql(''); expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0); expect(tchar[1].length).eql(0);
@ -697,11 +697,11 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
} }
} }
tchar = xterm.lines[0][xterm.cols-1]; tchar = xterm.lines.get(0)[xterm.cols-1];
expect(tchar[1]).eql(' '); expect(tchar[1]).eql(' ');
expect(tchar[1].length).eql(1); expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('¥'); expect(tchar[1]).eql('¥');
expect(tchar[1].length).eql(1); expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
@ -711,7 +711,7 @@ describe('xterm.js', function() {
xterm.x = 1; xterm.x = 1;
xterm.write(Array(50).join('¥\u0301')); xterm.write(Array(50).join('¥\u0301'));
for (var i=1; i<xterm.cols-1; ++i) { for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
if (!(i % 2)) { if (!(i % 2)) {
expect(tchar[1]).eql(''); expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0); expect(tchar[1].length).eql(0);
@ -722,11 +722,11 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
} }
} }
tchar = xterm.lines[0][xterm.cols-1]; tchar = xterm.lines.get(0)[xterm.cols-1];
expect(tchar[1]).eql(' '); expect(tchar[1]).eql(' ');
expect(tchar[1].length).eql(1); expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('¥\u0301'); expect(tchar[1]).eql('¥\u0301');
expect(tchar[1].length).eql(2); expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
@ -735,7 +735,7 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true; xterm.wraparoundMode = true;
xterm.write(Array(50).join('¥\u0301')); xterm.write(Array(50).join('¥\u0301'));
for (var i=0; i<xterm.cols; ++i) { for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
if (i % 2) { if (i % 2) {
expect(tchar[1]).eql(''); expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0); expect(tchar[1].length).eql(0);
@ -746,7 +746,7 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
} }
} }
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('¥\u0301'); expect(tchar[1]).eql('¥\u0301');
expect(tchar[1].length).eql(2); expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
@ -756,7 +756,7 @@ describe('xterm.js', function() {
xterm.x = 1; xterm.x = 1;
xterm.write(Array(50).join('\ud843\ude6d\u0301')); xterm.write(Array(50).join('\ud843\ude6d\u0301'));
for (var i=1; i<xterm.cols-1; ++i) { for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
if (!(i % 2)) { if (!(i % 2)) {
expect(tchar[1]).eql(''); expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0); expect(tchar[1].length).eql(0);
@ -767,11 +767,11 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
} }
} }
tchar = xterm.lines[0][xterm.cols-1]; tchar = xterm.lines.get(0)[xterm.cols-1];
expect(tchar[1]).eql(' '); expect(tchar[1]).eql(' ');
expect(tchar[1].length).eql(1); expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(1); expect(tchar[2]).eql(1);
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('\ud843\ude6d\u0301'); expect(tchar[1]).eql('\ud843\ude6d\u0301');
expect(tchar[1].length).eql(3); expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
@ -780,7 +780,7 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true; xterm.wraparoundMode = true;
xterm.write(Array(50).join('\ud843\ude6d\u0301')); xterm.write(Array(50).join('\ud843\ude6d\u0301'));
for (var i=0; i<xterm.cols; ++i) { for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines[0][i]; var tchar = xterm.lines.get(0)[i];
if (i % 2) { if (i % 2) {
expect(tchar[1]).eql(''); expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0); expect(tchar[1].length).eql(0);
@ -791,7 +791,7 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
} }
} }
tchar = xterm.lines[1][0]; tchar = xterm.lines.get(1)[0];
expect(tchar[1]).eql('\ud843\ude6d\u0301'); expect(tchar[1]).eql('\ud843\ude6d\u0301');
expect(tchar[1].length).eql(3); expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(2); expect(tchar[2]).eql(2);
@ -805,11 +805,11 @@ describe('xterm.js', function() {
xterm.y = 0; xterm.y = 0;
xterm.insertMode = true; xterm.insertMode = true;
xterm.write('abcde'); xterm.write('abcde');
expect(xterm.lines[0].length).eql(xterm.cols); expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines[0][10][1]).eql('a'); expect(xterm.lines.get(0)[10][1]).eql('a');
expect(xterm.lines[0][14][1]).eql('e'); expect(xterm.lines.get(0)[14][1]).eql('e');
expect(xterm.lines[0][15][1]).eql('0'); expect(xterm.lines.get(0)[15][1]).eql('0');
expect(xterm.lines[0][79][1]).eql('4'); expect(xterm.lines.get(0)[79][1]).eql('4');
}); });
it('fullwidth - insert', function() { it('fullwidth - insert', function() {
xterm.write(Array(9).join('0123456789').slice(-80)); xterm.write(Array(9).join('0123456789').slice(-80));
@ -817,12 +817,12 @@ describe('xterm.js', function() {
xterm.y = 0; xterm.y = 0;
xterm.insertMode = true; xterm.insertMode = true;
xterm.write('¥¥¥'); xterm.write('¥¥¥');
expect(xterm.lines[0].length).eql(xterm.cols); expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines[0][10][1]).eql('¥'); expect(xterm.lines.get(0)[10][1]).eql('¥');
expect(xterm.lines[0][11][1]).eql(''); expect(xterm.lines.get(0)[11][1]).eql('');
expect(xterm.lines[0][14][1]).eql('¥'); expect(xterm.lines.get(0)[14][1]).eql('¥');
expect(xterm.lines[0][15][1]).eql(''); expect(xterm.lines.get(0)[15][1]).eql('');
expect(xterm.lines[0][79][1]).eql('3'); expect(xterm.lines.get(0)[79][1]).eql('3');
}); });
it('fullwidth - right border', function() { it('fullwidth - right border', function() {
xterm.write(Array(41).join('¥')); xterm.write(Array(41).join('¥'));
@ -830,15 +830,15 @@ describe('xterm.js', function() {
xterm.y = 0; xterm.y = 0;
xterm.insertMode = true; xterm.insertMode = true;
xterm.write('a'); xterm.write('a');
expect(xterm.lines[0].length).eql(xterm.cols); expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines[0][10][1]).eql('a'); expect(xterm.lines.get(0)[10][1]).eql('a');
expect(xterm.lines[0][11][1]).eql('¥'); expect(xterm.lines.get(0)[11][1]).eql('¥');
expect(xterm.lines[0][79][1]).eql(' '); // fullwidth char got replaced expect(xterm.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
xterm.write('b'); xterm.write('b');
expect(xterm.lines[0].length).eql(xterm.cols); expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines[0][11][1]).eql('b'); expect(xterm.lines.get(0)[11][1]).eql('b');
expect(xterm.lines[0][12][1]).eql('¥'); expect(xterm.lines.get(0)[12][1]).eql('¥');
expect(xterm.lines[0][79][1]).eql(''); // empty cell after fullwidth expect(xterm.lines.get(0)[79][1]).eql(''); // empty cell after fullwidth
}); });
}); });
}); });

View File

@ -0,0 +1,255 @@
import { assert } from 'chai';
import { CircularList } from './CircularList';
describe('CircularList', () => {
describe('push', () => {
it('should push values onto the array', () => {
const list = new CircularList<string>(5);
list.push('1');
list.push('2');
list.push('3');
list.push('4');
list.push('5');
assert.equal(list.get(0), '1');
assert.equal(list.get(1), '2');
assert.equal(list.get(2), '3');
assert.equal(list.get(3), '4');
assert.equal(list.get(4), '5');
});
it('should push old values from the start out of the array when max length is reached', () => {
const list = new CircularList<string>(2);
list.push('1');
list.push('2');
assert.equal(list.get(0), '1');
assert.equal(list.get(1), '2');
list.push('3');
assert.equal(list.get(0), '2');
assert.equal(list.get(1), '3');
list.push('4');
assert.equal(list.get(0), '3');
assert.equal(list.get(1), '4');
});
});
describe('maxLength', () => {
it('should increase the size of the list', () => {
const list = new CircularList<string>(2);
list.push('1');
list.push('2');
assert.equal(list.get(0), '1');
assert.equal(list.get(1), '2');
list.maxLength = 4;
list.push('3');
list.push('4');
assert.equal(list.get(0), '1');
assert.equal(list.get(1), '2');
assert.equal(list.get(2), '3');
assert.equal(list.get(3), '4');
list.push('wrapped');
assert.equal(list.get(0), '2');
assert.equal(list.get(1), '3');
assert.equal(list.get(2), '4');
assert.equal(list.get(3), 'wrapped');
});
it('should return the maximum length of the list', () => {
const list = new CircularList<string>(2);
assert.equal(list.maxLength, 2);
list.push('1');
list.push('2');
assert.equal(list.maxLength, 2);
list.push('3');
assert.equal(list.maxLength, 2);
list.maxLength = 4;
assert.equal(list.maxLength, 4);
});
});
describe('length', () => {
it('should return the current length of the list, capped at the maximum length', () => {
const list = new CircularList<string>(2);
assert.equal(list.length, 0);
list.push('1');
assert.equal(list.length, 1);
list.push('2');
assert.equal(list.length, 2);
list.push('3');
assert.equal(list.length, 2);
});
});
describe('splice', () => {
it('should delete items', () => {
const list = new CircularList<string>(2);
list.push('1');
list.push('2');
list.splice(0, 1);
assert.equal(list.length, 1);
assert.equal(list.get(0), '2');
list.push('3');
list.splice(1, 1);
assert.equal(list.length, 1);
assert.equal(list.get(0), '2');
});
it('should insert items', () => {
const list = new CircularList<string>(2);
list.push('1');
list.splice(0, 0, '2');
assert.equal(list.length, 2);
assert.equal(list.get(0), '2');
assert.equal(list.get(1), '1');
list.splice(1, 0, '3');
assert.equal(list.length, 2);
assert.equal(list.get(0), '3');
assert.equal(list.get(1), '1');
});
it('should delete items then insert items', () => {
const list = new CircularList<string>(3);
list.push('1');
list.push('2');
list.splice(0, 1, '3', '4');
assert.equal(list.length, 3);
assert.equal(list.get(0), '3');
assert.equal(list.get(1), '4');
assert.equal(list.get(2), '2');
});
it('should wrap the array correctly when more items are inserted than deleted', () => {
const list = new CircularList<string>(3);
list.push('1');
list.push('2');
list.splice(1, 0, '3', '4');
assert.equal(list.length, 3);
assert.equal(list.get(0), '3');
assert.equal(list.get(1), '4');
assert.equal(list.get(2), '2');
});
});
describe('trimStart', () => {
it('should remove items from the beginning of the list', () => {
const list = new CircularList<string>(5);
list.push('1');
list.push('2');
list.push('3');
list.push('4');
list.push('5');
list.trimStart(1);
assert.equal(list.length, 4);
assert.deepEqual(list.get(0), '2');
assert.deepEqual(list.get(1), '3');
assert.deepEqual(list.get(2), '4');
assert.deepEqual(list.get(3), '5');
list.trimStart(2);
assert.equal(list.length, 2);
assert.deepEqual(list.get(0), '4');
assert.deepEqual(list.get(1), '5');
});
it('should remove all items if the requested trim amount is larger than the list\'s length', () => {
const list = new CircularList<string>(5);
list.push('1');
list.trimStart(2);
assert.equal(list.length, 0);
});
});
describe('shiftElements', () => {
it('should not mutate the list when count is 0', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.shiftElements(0, 0, 1);
assert.equal(list.length, 2);
assert.equal(list.get(0), 1);
assert.equal(list.get(1), 2);
});
it('should throw for invalid args', () => {
const list = new CircularList<number>(5);
list.push(1);
assert.throws(() => list.shiftElements(-1, 1, 1), 'start argument out of range');
assert.throws(() => list.shiftElements(1, 1, 1), 'start argument out of range');
assert.throws(() => list.shiftElements(0, 1, -1), 'Cannot shift elements in list beyond index 0');
});
it('should shift an element forward', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.shiftElements(0, 1, 1);
assert.equal(list.length, 2);
assert.equal(list.get(0), 1);
assert.equal(list.get(1), 1);
});
it('should shift elements forward', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.push(3);
list.push(4);
list.shiftElements(0, 2, 2);
assert.equal(list.length, 4);
assert.equal(list.get(0), 1);
assert.equal(list.get(1), 2);
assert.equal(list.get(2), 1);
assert.equal(list.get(3), 2);
});
it('should shift elements forward, expanding the list if needed', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.shiftElements(0, 2, 2);
assert.equal(list.length, 4);
assert.equal(list.get(0), 1);
assert.equal(list.get(1), 2);
assert.equal(list.get(2), 1);
assert.equal(list.get(3), 2);
});
it('should shift elements forward, wrapping the list if needed', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.push(3);
list.push(4);
list.push(5);
list.shiftElements(2, 2, 3);
assert.equal(list.length, 5);
assert.equal(list.get(0), 3);
assert.equal(list.get(1), 4);
assert.equal(list.get(2), 5);
assert.equal(list.get(3), 3);
assert.equal(list.get(4), 4);
});
it('should shift an element backwards', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.shiftElements(1, 1, -1);
assert.equal(list.length, 2);
assert.equal(list.get(0), 2);
assert.equal(list.get(1), 2);
});
it('should shift elements backwards', () => {
const list = new CircularList<number>(5);
list.push(1);
list.push(2);
list.push(3);
list.push(4);
list.shiftElements(2, 2, -2);
assert.equal(list.length, 4);
assert.equal(list.get(0), 3);
assert.equal(list.get(1), 4);
assert.equal(list.get(2), 3);
assert.equal(list.get(3), 4);
});
});
});

183
src/utils/CircularList.ts Normal file
View File

@ -0,0 +1,183 @@
/**
* Represents a circular list; a list with a maximum size that wraps around when push is called,
* overriding values at the start of the list.
* @module xterm/utils/CircularList
* @license MIT
*/
export class CircularList<T> {
private _array: T[];
private _startIndex: number;
private _length: number;
constructor(maxLength: number) {
this._array = new Array<T>(maxLength);
this._startIndex = 0;
this._length = 0;
}
public get maxLength(): number {
return this._array.length;
}
public set maxLength(newMaxLength: number) {
// Reconstruct array, starting at index 0. Only transfer values from the
// indexes 0 to length.
let newArray = new Array<T>(newMaxLength);
for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
newArray[i] = this._array[this._getCyclicIndex(i)];
}
this._array = newArray;
this._startIndex = 0;
}
public get length(): number {
return this._length;
}
public set length(newLength: number) {
if (newLength > this._length) {
for (let i = this._length; i < newLength; i++) {
this._array[i] = undefined;
}
}
this._length = newLength;
}
public get forEach(): (callbackfn: (value: T, index: number, array: T[]) => void) => void {
return this._array.forEach;
}
/**
* Gets the value at an index.
*
* Note that for performance reasons there is no bounds checking here, the index reference is
* circular so this should always return a value and never throw.
* @param index The index of the value to get.
* @return The value corresponding to the index.
*/
public get(index: number): T {
return this._array[this._getCyclicIndex(index)];
}
/**
* Sets the value at an index.
*
* Note that for performance reasons there is no bounds checking here, the index reference is
* circular so this should always return a value and never throw.
* @param index The index to set.
* @param value The value to set.
*/
public set(index: number, value: T): void {
this._array[this._getCyclicIndex(index)] = value;
}
/**
* Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
* if the maximum length is reached.
* @param value The value to push onto the list.
*/
public push(value: T): void {
this._array[this._getCyclicIndex(this._length)] = value;
if (this._length === this.maxLength) {
this._startIndex++;
if (this._startIndex === this.maxLength) {
this._startIndex = 0;
}
} else {
this._length++;
}
}
/**
* Removes and returns the last value on the list.
* @return The popped value.
*/
public pop(): T {
return this._array[this._getCyclicIndex(this._length-- - 1)];
}
/**
* Deletes and/or inserts items at a particular index (in that order). Unlike
* Array.prototype.splice, this operation does not return the deleted items as a new array in
* order to save creating a new array. Note that this operation may shift all values in the list
* in the worst case.
* @param start The index to delete and/or insert.
* @param deleteCount The number of elements to delete.
* @param items The items to insert.
*/
public splice(start: number, deleteCount: number, ...items: T[]): void {
if (deleteCount) {
for (let i = start; i < this._length - deleteCount; i++) {
this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
}
this._length -= deleteCount;
}
if (items && items.length) {
for (let i = this._length - 1; i >= start; i--) {
this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
}
for (let i = 0; i < items.length; i++) {
this._array[this._getCyclicIndex(start + i)] = items[i];
}
if (this._length + items.length > this.maxLength) {
this._startIndex += (this._length + items.length) - this.maxLength;
this._length = this.maxLength;
} else {
this._length += items.length;
}
}
}
/**
* Trims a number of items from the start of the list.
* @param count The number of items to remove.
*/
public trimStart(count: number): void {
if (count > this._length) {
count = this._length;
}
this._startIndex += count;
this._length -= count;
}
public shiftElements(start: number, count: number, offset: number): void {
if (count <= 0) {
return;
}
if (start < 0 || start >= this._length) {
throw new Error('start argument out of range');
}
if (start + offset < 0) {
throw new Error('Cannot shift elements in list beyond index 0');
}
if (offset > 0) {
for (let i = count - 1; i >= 0; i--) {
this.set(start + i + offset, this.get(start + i));
}
const expandListBy = (start + count + offset) - this._length;
if (expandListBy > 0) {
this._length += expandListBy;
while (this._length > this.maxLength) {
this._length--;
this._startIndex++;
}
}
} else {
for (let i = 0; i < count; i++) {
this.set(start + i + offset, this.get(start + i));
}
}
}
/**
* Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
* backing array to get the element associated with the regular index.
* @param index The regular index.
* @returns The cyclic index.
*/
private _getCyclicIndex(index: number): number {
return (this._startIndex + index) % this.maxLength;
}
}

View File

@ -14,6 +14,7 @@ import { CompositionHelper } from './CompositionHelper.js';
import { EventEmitter } from './EventEmitter.js'; import { EventEmitter } from './EventEmitter.js';
import { Viewport } from './Viewport.js'; import { Viewport } from './Viewport.js';
import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js'; import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js';
import { CircularList } from './utils/CircularList.js';
import * as Browser from './utils/Browser'; import * as Browser from './utils/Browser';
import * as Keyboard from './utils/Keyboard'; import * as Keyboard from './utils/Keyboard';
@ -208,7 +209,7 @@ function Terminal(options) {
* An array of all lines in the entire buffer, including the prompt. The lines are array of * 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. * characters which are 2-length arrays where [0] is an attribute and [1] is the character.
*/ */
this.lines = []; this.lines = new CircularList(this.scrollback);
var i = this.rows; var i = this.rows;
while (i--) { while (i--) {
this.lines.push(this.blankLine()); this.lines.push(this.blankLine());
@ -1078,7 +1079,7 @@ Terminal.prototype.refresh = function(start, end, queue) {
for (; y <= end; y++) { for (; y <= end; y++) {
row = y + this.ydisp; row = y + this.ydisp;
line = this.lines[row]; line = this.lines.get(row);
out = ''; out = '';
if (this.y === y - (this.ybase - this.ydisp) if (this.y === y - (this.ybase - this.ydisp)
@ -1228,16 +1229,14 @@ Terminal.prototype.showCursor = function() {
}; };
/** /**
* Scroll the terminal * Scroll the terminal down 1 row, creating a blank line.
*/ */
Terminal.prototype.scroll = function() { Terminal.prototype.scroll = function() {
var row; var row;
if (++this.ybase === this.scrollback) { this.ybase++;
this.ybase = this.ybase / 2 | 0;
this.lines = this.lines.slice(-(this.ybase + this.rows) + 1);
}
// TODO: Why is this done twice?
if (!this.userScrolling) { if (!this.userScrolling) {
this.ydisp = this.ybase; this.ydisp = this.ybase;
} }
@ -1249,10 +1248,12 @@ Terminal.prototype.scroll = function() {
row -= this.rows - 1 - this.scrollBottom; row -= this.rows - 1 - this.scrollBottom;
if (row === this.lines.length) { if (row === this.lines.length) {
// potential optimization: // Compensate ybase and ydisp if lines has hit the maximum buffer size
// pushing is faster than splicing if (this.lines.length === this.lines.maxLength) {
// when they amount to the same this.ybase--;
// behavior. this.ydisp--;
}
// Optimization: pushing is faster than splicing when they amount to the same behavior
this.lines.push(this.blankLine()); this.lines.push(this.blankLine());
} else { } else {
// add our new line // add our new line
@ -1370,7 +1371,6 @@ Terminal.prototype.write = function(data) {
// surrogate low - already handled above // surrogate low - already handled above
if (0xDC00 <= code && code <= 0xDFFF) if (0xDC00 <= code && code <= 0xDFFF)
continue; continue;
switch (this.state) { switch (this.state) {
case normal: case normal:
switch (ch) { switch (ch) {
@ -1440,17 +1440,16 @@ Terminal.prototype.write = function(data) {
// insert combining char in last cell // insert combining char in last cell
// FIXME: needs handling after cursor jumps // FIXME: needs handling after cursor jumps
if (!ch_width && this.x) { if (!ch_width && this.x) {
// dont overflow left // dont overflow left
if (this.lines[row][this.x-1]) { if (this.lines.get(row)[this.x-1]) {
if (!this.lines[row][this.x-1][2]) { if (!this.lines.get(row)[this.x-1][2]) {
// found empty cell after fullwidth, need to go 2 cells back // found empty cell after fullwidth, need to go 2 cells back
if (this.lines[row][this.x-2]) if (this.lines.get(row)[this.x-2])
this.lines[row][this.x-2][1] += ch; this.lines.get(row)[this.x-2][1] += ch;
} else { } else {
this.lines[row][this.x-1][1] += ch; this.lines.get(row)[this.x-1][1] += ch;
} }
this.updateRange(this.y); this.updateRange(this.y);
} }
@ -1482,24 +1481,24 @@ Terminal.prototype.write = function(data) {
for (var moves=0; moves<ch_width; ++moves) { for (var moves=0; moves<ch_width; ++moves) {
// remove last cell, if it's width is 0 // remove last cell, if it's width is 0
// we have to adjust the second last cell as well // we have to adjust the second last cell as well
var removed = this.lines[this.y + this.ybase].pop(); var removed = this.lines.get(this.y + this.ybase).pop();
if (removed[2]===0 if (removed[2]===0
&& this.lines[row][this.cols-2] && this.lines.get(row)[this.cols-2]
&& this.lines[row][this.cols-2][2]===2) && this.lines.get(row)[this.cols-2][2]===2)
this.lines[row][this.cols-2] = [this.curAttr, ' ', 1]; this.lines.get(row)[this.cols-2] = [this.curAttr, ' ', 1];
// insert empty cell at cursor // insert empty cell at cursor
this.lines[row].splice(this.x, 0, [this.curAttr, ' ', 1]); this.lines.get(row).splice(this.x, 0, [this.curAttr, ' ', 1]);
} }
} }
this.lines[row][this.x] = [this.curAttr, ch, ch_width]; this.lines.get(row)[this.x] = [this.curAttr, ch, ch_width];
this.x++; this.x++;
this.updateRange(this.y); this.updateRange(this.y);
// fullwidth char - set next cell width to zero and advance cursor // fullwidth char - set next cell width to zero and advance cursor
if (ch_width===2) { if (ch_width===2) {
this.lines[row][this.x] = [this.curAttr, '', 0]; this.lines.get(row)[this.x] = [this.curAttr, '', 0];
this.x++; this.x++;
} }
} }
@ -2866,15 +2865,15 @@ Terminal.prototype.resize = function(x, y) {
ch = [this.defAttr, ' ', 1]; // does xterm use the default attr? ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
i = this.lines.length; i = this.lines.length;
while (i--) { while (i--) {
while (this.lines[i].length < x) { while (this.lines.get(i).length < x) {
this.lines[i].push(ch); this.lines.get(i).push(ch);
} }
} }
} else { // (j > x) } else { // (j > x)
i = this.lines.length; i = this.lines.length;
while (i--) { while (i--) {
while (this.lines[i].length > x) { while (this.lines.get(i).length > x) {
this.lines[i].pop(); this.lines.get(i).pop();
} }
} }
} }
@ -3029,7 +3028,7 @@ Terminal.prototype.nextStop = function(x) {
* @param {number} y The line in which to operate. * @param {number} y The line in which to operate.
*/ */
Terminal.prototype.eraseRight = function(x, y) { Terminal.prototype.eraseRight = function(x, y) {
var line = this.lines[this.ybase + y] var line = this.lines.get(this.ybase + y)
, ch = [this.eraseAttr(), ' ', 1]; // xterm , ch = [this.eraseAttr(), ' ', 1]; // xterm
@ -3048,7 +3047,7 @@ Terminal.prototype.eraseRight = function(x, y) {
* @param {number} y The line in which to operate. * @param {number} y The line in which to operate.
*/ */
Terminal.prototype.eraseLeft = function(x, y) { Terminal.prototype.eraseLeft = function(x, y) {
var line = this.lines[this.ybase + y] var line = this.lines.get(this.ybase + y)
, ch = [this.eraseAttr(), ' ', 1]; // xterm , ch = [this.eraseAttr(), ' ', 1]; // xterm
x++; x++;
@ -3065,7 +3064,8 @@ Terminal.prototype.clear = function() {
// Don't clear if it's already clear // Don't clear if it's already clear
return; return;
} }
this.lines = [this.lines[this.ybase + this.y]]; this.lines.set(0, this.lines.get(this.ybase + this.y));
this.lines.length = 1;
this.ydisp = 0; this.ydisp = 0;
this.ybase = 0; this.ybase = 0;
this.y = 0; this.y = 0;
@ -3086,7 +3086,7 @@ Terminal.prototype.eraseLine = function(y) {
/** /**
* Return the data array of a blank line/ * Return the data array of a blank line
* @param {number} cur First bunch of data for each "blank" character. * @param {number} cur First bunch of data for each "blank" character.
*/ */
Terminal.prototype.blankLine = function(cur) { Terminal.prototype.blankLine = function(cur) {
@ -3174,21 +3174,21 @@ Terminal.prototype.index = function() {
/** /**
* ESC M Reverse Index (RI is 0x8d). * ESC M Reverse Index (RI is 0x8d).
*
* Move the cursor up one row, inserting a new blank line if necessary.
*/ */
Terminal.prototype.reverseIndex = function() { Terminal.prototype.reverseIndex = function() {
var j; var j;
this.y--; if (this.y === this.scrollTop) {
if (this.y < this.scrollTop) {
this.y++;
// possibly move the code below to term.reverseScroll(); // possibly move the code below to term.reverseScroll();
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m' // test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
// blankLine(true) is xterm/linux behavior // blankLine(true) is xterm/linux behavior
this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); this.lines.shiftElements(this.y + this.ybase, this.rows - 1, 1);
j = this.rows - 1 - this.scrollBottom; this.lines.set(this.y + this.ybase, this.blankLine(true));
this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1);
// this.maxRange();
this.updateRange(this.scrollTop); this.updateRange(this.scrollTop);
this.updateRange(this.scrollBottom); this.updateRange(this.scrollBottom);
} else {
this.y--;
} }
this.state = normal; this.state = normal;
}; };
@ -3644,8 +3644,8 @@ Terminal.prototype.insertChars = function(params) {
ch = [this.eraseAttr(), ' ', 1]; // xterm ch = [this.eraseAttr(), ' ', 1]; // xterm
while (param-- && j < this.cols) { while (param-- && j < this.cols) {
this.lines[row].splice(j++, 0, ch); this.lines.get(row).splice(j++, 0, ch);
this.lines[row].pop(); this.lines.get(row).pop();
} }
}; };
@ -3705,6 +3705,14 @@ Terminal.prototype.insertLines = function(params) {
j = this.rows - 1 + this.ybase - j + 1; j = this.rows - 1 + this.ybase - j + 1;
while (param--) { while (param--) {
if (this.lines.length === this.lines.maxLength) {
// Trim the start of lines to make room for the new line
this.lines.trimStart(1);
this.ybase--;
this.ydisp--;
row--;
j--;
}
// test: echo -e '\e[44m\e[1L\e[0m' // test: echo -e '\e[44m\e[1L\e[0m'
// blankLine(true) - xterm/linux behavior // blankLine(true) - xterm/linux behavior
this.lines.splice(row, 0, this.blankLine(true)); this.lines.splice(row, 0, this.blankLine(true));
@ -3732,6 +3740,12 @@ Terminal.prototype.deleteLines = function(params) {
j = this.rows - 1 + this.ybase - j; j = this.rows - 1 + this.ybase - j;
while (param--) { while (param--) {
if (this.lines.length === this.lines.maxLength) {
// Trim the start of lines to make room for the new line
this.lines.trimStart(1);
this.ybase -= 1;
this.ydisp -= 1;
}
// test: echo -e '\e[44m\e[1M\e[0m' // test: echo -e '\e[44m\e[1M\e[0m'
// blankLine(true) - xterm/linux behavior // blankLine(true) - xterm/linux behavior
this.lines.splice(j + 1, 0, this.blankLine(true)); this.lines.splice(j + 1, 0, this.blankLine(true));
@ -3758,8 +3772,8 @@ Terminal.prototype.deleteChars = function(params) {
ch = [this.eraseAttr(), ' ', 1]; // xterm ch = [this.eraseAttr(), ' ', 1]; // xterm
while (param--) { while (param--) {
this.lines[row].splice(this.x, 1); this.lines.get(row).splice(this.x, 1);
this.lines[row].push(ch); this.lines.get(row).push(ch);
} }
}; };
@ -3778,7 +3792,7 @@ Terminal.prototype.eraseChars = function(params) {
ch = [this.eraseAttr(), ' ', 1]; // xterm ch = [this.eraseAttr(), ' ', 1]; // xterm
while (param-- && j < this.cols) { while (param-- && j < this.cols) {
this.lines[row][j++] = ch; this.lines.get(row)[j++] = ch;
} }
}; };
@ -4442,7 +4456,7 @@ Terminal.prototype.cursorBackwardTab = function(params) {
*/ */
Terminal.prototype.repeatPrecedingCharacter = function(params) { Terminal.prototype.repeatPrecedingCharacter = function(params) {
var param = params[0] || 1 var param = params[0] || 1
, line = this.lines[this.ybase + this.y] , line = this.lines.get(this.ybase + this.y)
, ch = line[this.x - 1] || [this.defAttr, ' ', 1]; , ch = line[this.x - 1] || [this.defAttr, ' ', 1];
while (param--) line[this.x++] = ch; while (param--) line[this.x++] = ch;
@ -4677,7 +4691,7 @@ Terminal.prototype.setAttrInRectangle = function(params) {
, i; , i;
for (; t < b + 1; t++) { for (; t < b + 1; t++) {
line = this.lines[this.ybase + t]; line = this.lines.get(this.ybase + t);
for (i = l; i < r; i++) { for (i = l; i < r; i++) {
line[i] = [attr, line[i][1]]; line[i] = [attr, line[i][1]];
} }
@ -4707,7 +4721,7 @@ Terminal.prototype.fillRectangle = function(params) {
, i; , i;
for (; t < b + 1; t++) { for (; t < b + 1; t++) {
line = this.lines[this.ybase + t]; line = this.lines.get(this.ybase + t);
for (i = l; i < r; i++) { for (i = l; i < r; i++) {
line[i] = [line[i][0], String.fromCharCode(ch)]; line[i] = [line[i][0], String.fromCharCode(ch)];
} }
@ -4759,7 +4773,7 @@ Terminal.prototype.eraseRectangle = function(params) {
ch = [this.eraseAttr(), ' ', 1]; // xterm? ch = [this.eraseAttr(), ' ', 1]; // xterm?
for (; t < b + 1; t++) { for (; t < b + 1; t++) {
line = this.lines[this.ybase + t]; line = this.lines.get(this.ybase + t);
for (i = l; i < r; i++) { for (i = l; i < r; i++) {
line[i] = ch; line[i] = ch;
} }
@ -4784,8 +4798,8 @@ Terminal.prototype.insertColumns = function() {
while (param--) { while (param--) {
for (i = this.ybase; i < l; i++) { for (i = this.ybase; i < l; i++) {
this.lines[i].splice(this.x + 1, 0, ch); this.lines.get(i).splice(this.x + 1, 0, ch);
this.lines[i].pop(); this.lines.get(i).pop();
} }
} }
@ -4806,8 +4820,8 @@ Terminal.prototype.deleteColumns = function() {
while (param--) { while (param--) {
for (i = this.ybase; i < l; i++) { for (i = this.ybase; i < l; i++) {
this.lines[i].splice(this.x, 1); this.lines.get(i).splice(this.x, 1);
this.lines[i].push(ch); this.lines.get(i).push(ch);
} }
} }