Compare commits

..
13 Commits
Author SHA1 Message Date
Paris Kasidiaris ea07bf8f69 Bump version to 2.9.2
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2017-08-09 06:48:24 +00:00
Daniel Imms 0b1711fb79 Merge pull request #873 from Tyriar/860_resize_exception
Fix exception when resizing both dimensions
2017-08-08 18:03:02 -07:00
Daniel Imms bb526aaa88 Fix exception when resizing both dimensions
This happened because buffers now resize themselves but they were
relying on Terminal to give them a blank line. The blank line was
coming back with the old columns value, causing an NPE.

Fixes #860
2017-08-08 12:59:02 -07:00
Paris Kasidiaris a28d9f1456 Bump version to 2.9.1
Signed-off-by: Paris Kasidiaris <paris@sourcelair.com>
2017-08-06 17:50:53 +00:00
Daniel Imms fb6b173fe7 Merge pull request #843 from Tyriar/842_resize_buffers_bug
Resize both buffers on resize, clean up alt buffer state
2017-08-06 10:34:50 -07:00
Daniel Imms 1361ff8e22 Merge branch 'master' into 842_resize_buffers_bug 2017-08-06 10:25:38 -07:00
Daniel Imms 0731f28615 jsdoc 2017-08-06 10:25:25 -07:00
Paris Kasidiaris f7b58d0718 Merge pull request #834 from blink1073/webpack-fix
Prevent Webpack warning in 2.9.0
2017-08-06 13:25:08 +03:00
Daniel Imms d46003843b Add tests for Buffer.fillViewportRows 2017-08-05 20:15:23 -07:00
Daniel Imms 13619d427a Add a bunch of Buffer tests 🎉 2017-08-05 20:11:26 -07:00
Daniel Imms f03d00a497 Clean up buffer clean up/fill logic
The alt buffer is now cleared immediated after activating the normal buffer and
is filled when switching to it. The tests were failing because the alt buffer
wasn't being cleared properly with the previous solution.
2017-08-05 19:42:54 -07:00
Daniel Imms 3d20c2f266 Resize both buffers on resize
This brings in proper support to resize both buffers (#510) and fixes an
exception that was caused by wrongfully not clearing the normal buffer when
resizing while the alt buffer is active.

There was an obscure bug in this that could have caused some great confusion
later on; When switching to the alt buffer, a hard terminal reset was performed
which tried to retain the buffers. However, because buffers was initialized in
the Terminal constructor to a new BufferSet, the Terminal.buffer convenience
pointer was pointing at a stale alt buffer which was the one actually being
used, not Terminal.buffers.alt.

Fixes #842
Fixes #510
2017-08-05 18:59:44 -07:00
Steven Silvester 6f749b196a Prevent webpack error 2017-08-03 14:43:51 -05:00
16 changed files with 480 additions and 221 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm.js",
"version": "2.9.0",
"version": "2.9.2",
"ignore": ["demo", "test", ".gitignore"],
"main": [
"dist/xterm.js",
+106 -92
View File
@@ -3,25 +3,93 @@
Object.defineProperty(exports, "__esModule", { value: true });
var CircularList_1 = require("./utils/CircularList");
var Buffer = (function () {
function Buffer(terminal, ydisp, ybase, y, x, scrollBottom, scrollTop, tabs) {
if (ydisp === void 0) { ydisp = 0; }
if (ybase === void 0) { ybase = 0; }
if (y === void 0) { y = 0; }
if (x === void 0) { x = 0; }
if (scrollBottom === void 0) { scrollBottom = 0; }
if (scrollTop === void 0) { scrollTop = 0; }
if (tabs === void 0) { tabs = {}; }
this.terminal = terminal;
this.ydisp = ydisp;
this.ybase = ybase;
this.y = y;
this.x = x;
this.scrollBottom = scrollBottom;
this.scrollTop = scrollTop;
this.tabs = tabs;
this.lines = new CircularList_1.CircularList(this.terminal.scrollback);
this.scrollBottom = this.terminal.rows - 1;
function Buffer(_terminal) {
this._terminal = _terminal;
this.clear();
}
Object.defineProperty(Buffer.prototype, "lines", {
get: function () {
return this._lines;
},
enumerable: true,
configurable: true
});
Buffer.prototype.fillViewportRows = function () {
if (this._lines.length === 0) {
var i = this._terminal.rows;
while (i--) {
this.lines.push(this._terminal.blankLine());
}
}
};
Buffer.prototype.clear = function () {
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
this.x = 0;
this.scrollBottom = 0;
this.scrollTop = 0;
this.tabs = {};
this._lines = new CircularList_1.CircularList(this._terminal.scrollback);
this.scrollBottom = this._terminal.rows - 1;
};
Buffer.prototype.resize = function (newCols, newRows) {
if (this._lines.length === 0) {
return;
}
if (this._terminal.cols < newCols) {
var ch = [this._terminal.defAttr, ' ', 1];
for (var i = 0; i < this._lines.length; i++) {
if (this._lines.get(i) === undefined) {
this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));
}
while (this._lines.get(i).length < newCols) {
this._lines.get(i).push(ch);
}
}
}
var addToY = 0;
if (this._terminal.rows < newRows) {
for (var y = this._terminal.rows; y < newRows; y++) {
if (this._lines.length < newRows + this.ybase) {
if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {
this.ybase--;
addToY++;
if (this.ydisp > 0) {
this.ydisp--;
}
}
else {
this._lines.push(this._terminal.blankLine(undefined, undefined, newCols));
}
}
}
}
else {
for (var y = this._terminal.rows; y > newRows; y--) {
if (this._lines.length > newRows + this.ybase) {
if (this._lines.length > this.ybase + this.y + 1) {
this._lines.pop();
}
else {
this.ybase++;
this.ydisp++;
}
}
}
}
if (this.y >= newRows) {
this.y = newRows - 1;
}
if (addToY) {
this.y += addToY;
}
if (this.x >= newCols) {
this.x = newCols - 1;
}
this.scrollTop = 0;
this.scrollBottom = newRows - 1;
};
return Buffer;
}());
exports.Buffer = Buffer;
@@ -49,6 +117,7 @@ var BufferSet = (function (_super) {
var _this = _super.call(this) || this;
_this._terminal = _terminal;
_this._normal = new Buffer_1.Buffer(_this._terminal);
_this._normal.fillViewportRows();
_this._alt = new Buffer_1.Buffer(_this._terminal);
_this._activeBuffer = _this._normal;
return _this;
@@ -75,13 +144,19 @@ var BufferSet = (function (_super) {
configurable: true
});
BufferSet.prototype.activateNormalBuffer = function () {
this._alt.clear();
this._activeBuffer = this._normal;
this.emit('activate', this._normal);
};
BufferSet.prototype.activateAltBuffer = function () {
this._alt.fillViewportRows();
this._activeBuffer = this._alt;
this.emit('activate', this._alt);
};
BufferSet.prototype.resize = function (newCols, newRows) {
this._normal.resize(newCols, newRows);
this._alt.resize(newCols, newRows);
};
return BufferSet;
}(EventEmitter_1.EventEmitter));
exports.BufferSet = BufferSet;
@@ -1024,7 +1099,6 @@ var InputHandler = (function () {
case 47:
case 1047:
this._terminal.buffers.activateAltBuffer();
this._terminal.reset();
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
@@ -3596,10 +3670,6 @@ function Terminal(options) {
this.buffers.on('activate', function (buffer) {
this._terminal.buffer = buffer;
});
var i = this.rows;
while (i--) {
this.buffer.lines.push(this.blankLine());
}
if (this.selectionManager) {
this.selectionManager.setBuffer(this.buffer.lines);
}
@@ -4775,74 +4845,19 @@ Terminal.prototype.resize = function (x, y) {
x = 1;
if (y < 1)
y = 1;
j = this.cols;
if (j < x) {
ch = [this.defAttr, ' ', 1];
i = this.buffer.lines.length;
while (i--) {
if (this.buffer.lines.get(i) === undefined) {
this.buffer.lines.set(i, this.blankLine());
}
while (this.buffer.lines.get(i).length < x) {
this.buffer.lines.get(i).push(ch);
}
}
this.buffers.resize(x, y);
while (this.children.length < y) {
this.insertRow();
}
while (this.children.length > y) {
el = this.children.shift();
if (!el)
continue;
el.parentNode.removeChild(el);
}
this.cols = x;
this.setupStops(this.cols);
j = this.rows;
addToY = 0;
if (j < y) {
el = this.element;
while (j++ < y) {
if (this.buffer.lines.length < y + this.buffer.ybase) {
if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) {
this.buffer.ybase--;
addToY++;
if (this.buffer.ydisp > 0) {
this.buffer.ydisp--;
}
}
else {
this.buffer.lines.push(this.blankLine());
}
}
if (this.children.length < y) {
this.insertRow();
}
}
}
else {
while (j-- > y) {
if (this.buffer.lines.length > y + this.buffer.ybase) {
if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) {
this.buffer.lines.pop();
}
else {
this.buffer.ybase++;
this.buffer.ydisp++;
}
}
if (this.children.length > y) {
el = this.children.shift();
if (!el)
continue;
el.parentNode.removeChild(el);
}
}
}
this.rows = y;
if (this.buffer.y >= y) {
this.buffer.y = y - 1;
}
if (addToY) {
this.buffer.y += addToY;
}
if (this.buffer.x >= x) {
this.buffer.x = x - 1;
}
this.buffer.scrollTop = 0;
this.buffer.scrollBottom = y - 1;
this.setupStops(this.cols);
this.charMeasure.measure();
this.refresh(0, this.rows - 1);
this.geometry = [this.cols, this.rows];
@@ -4931,7 +4946,7 @@ Terminal.prototype.clear = function () {
Terminal.prototype.eraseLine = function (y) {
this.eraseRight(0, y);
};
Terminal.prototype.blankLine = function (cur, isWrapped) {
Terminal.prototype.blankLine = function (cur, isWrapped, cols) {
var attr = cur
? this.eraseAttr()
: this.defAttr;
@@ -4939,7 +4954,8 @@ Terminal.prototype.blankLine = function (cur, isWrapped) {
if (isWrapped) {
line.isWrapped = isWrapped;
}
for (; i < this.cols; i++) {
cols = cols || this.cols;
for (; i < cols; i++) {
line[i] = ch;
}
return line;
@@ -4996,12 +5012,10 @@ Terminal.prototype.reset = function () {
var customKeyEventHandler = this.customKeyEventHandler;
var cursorBlinkInterval = this.cursorBlinkInterval;
var inputHandler = this.inputHandler;
var buffers = this.buffers;
Terminal.call(this, this.options);
this.customKeyEventHandler = customKeyEventHandler;
this.cursorBlinkInterval = cursorBlinkInterval;
this.inputHandler = inputHandler;
this.buffers = buffers;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
};
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "2.9.0",
"version": "2.9.2",
"ignore": [
"demo",
"test",
+132 -5
View File
@@ -5,17 +5,20 @@ import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { Buffer } from './Buffer';
import { CircularList } from './utils/CircularList';
import { MockTerminal } from './utils/TestUtils';
const INIT_COLS = 80;
const INIT_ROWS = 24;
describe('Buffer', () => {
let terminal: ITerminal;
let buffer: Buffer;
beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
scrollback: 1000
};
terminal = new MockTerminal();
terminal.cols = INIT_COLS;
terminal.rows = INIT_ROWS;
terminal.scrollback = 1000;
buffer = new Buffer(terminal);
});
@@ -28,4 +31,128 @@ describe('Buffer', () => {
assert.equal(buffer.scrollBottom, terminal.rows - 1);
});
});
describe('fillViewportRows', () => {
it('should fill the buffer with blank lines based on the size of the viewport', () => {
const blankLineChar = terminal.blankLine()[0];
buffer.fillViewportRows();
assert.equal(buffer.lines.length, INIT_ROWS);
for (let y = 0; y < INIT_ROWS; y++) {
assert.equal(buffer.lines.get(y).length, INIT_COLS);
for (let x = 0; x < INIT_COLS; x++) {
assert.deepEqual(buffer.lines.get(y)[x], blankLineChar);
}
}
});
});
describe('resize', () => {
describe('column size is reduced', () => {
it('should not trim the data in the buffer', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS / 2, INIT_ROWS);
assert.equal(buffer.lines.length, INIT_ROWS);
for (let i = 0; i < INIT_ROWS; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS);
}
});
});
describe('column size is increased', () => {
it('should add pad columns', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS + 10, INIT_ROWS);
assert.equal(buffer.lines.length, INIT_ROWS);
for (let i = 0; i < INIT_ROWS; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS + 10);
}
});
});
describe('row size reduced', () => {
it('should trim blank lines from the end', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS, INIT_ROWS - 10);
assert.equal(buffer.lines.length, INIT_ROWS - 10);
});
it('should move the viewport down when it\'s at the end', () => {
buffer.fillViewportRows();
// Set cursor y to have 5 blank lines below it
buffer.y = INIT_ROWS - 5 - 1;
buffer.resize(INIT_COLS, INIT_ROWS - 10);
// Trim 5 rows
assert.equal(buffer.lines.length, INIT_ROWS - 5);
// Shift the viewport down 5 rows
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 5);
});
});
describe('row size increased', () => {
describe('empty buffer', () => {
it('should add blank lines to end', () => {
buffer.fillViewportRows();
assert.equal(buffer.ydisp, 0);
buffer.resize(INIT_COLS, INIT_ROWS + 10);
assert.equal(buffer.ydisp, 0);
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
});
describe('filled buffer', () => {
it('should show more of the buffer above', () => {
buffer.fillViewportRows();
// Create 10 extra blank lines
for (let i = 0; i < 10; i++) {
buffer.lines.push(terminal.blankLine());
}
// Set cursor to the bottom of the buffer
buffer.y = INIT_ROWS - 1;
// Scroll down 10 lines
buffer.ybase = 10;
buffer.ydisp = 10;
assert.equal(buffer.lines.length, INIT_ROWS + 10);
buffer.resize(INIT_COLS, INIT_ROWS + 5);
// Should be should 5 more lines
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 5);
// Should not trim the buffer
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
it('should show more of the buffer below when the viewport is at the top of the buffer', () => {
buffer.fillViewportRows();
// Create 10 extra blank lines
for (let i = 0; i < 10; i++) {
buffer.lines.push(terminal.blankLine());
}
// Set cursor to the bottom of the buffer
buffer.y = INIT_ROWS - 1;
// Scroll down 10 lines
buffer.ybase = 10;
buffer.ydisp = 0;
assert.equal(buffer.lines.length, INIT_ROWS + 10);
buffer.resize(INIT_COLS, INIT_ROWS + 5);
// The viewport should remain at the top
assert.equal(buffer.ydisp, 0);
// The buffer ybase should move up 5 lines
assert.equal(buffer.ybase, 5);
// Should not trim the buffer
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
});
});
describe('row and column increased', () => {
it('should resize properly', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS + 5, INIT_ROWS + 5);
assert.equal(buffer.lines.length, INIT_ROWS + 5);
for (let i = 0; i < INIT_ROWS + 5; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS + 5);
}
});
});
});
});
+122 -14
View File
@@ -2,7 +2,7 @@
* @license MIT
*/
import { ITerminal } from './Interfaces';
import { ITerminal, IBuffer } from './Interfaces';
import { CircularList } from './utils/CircularList';
/**
@@ -12,31 +12,139 @@ import { CircularList } from './utils/CircularList';
* - cursor position
* - scroll position
*/
export class Buffer {
public lines: CircularList<[number, string, number][]>;
export class Buffer implements IBuffer {
private _lines: CircularList<[number, string, number][]>;
public ydisp: number;
public ybase: number;
public y: number;
public x: number;
public scrollBottom: number;
public scrollTop: number;
public tabs: any;
public savedY: number;
public savedX: number;
/**
* Create a new Buffer.
* @param {Terminal} terminal - The terminal the Buffer will belong to
* @param {Terminal} _terminal - The terminal the Buffer will belong to
* @param {number} ydisp - The scroll position of the Buffer in the viewport
* @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)
* @param {number} y - The cursor's y position after ybase
* @param {number} x - The cursor's x position after ybase
*/
constructor(
private terminal: ITerminal,
public ydisp: number = 0,
public ybase: number = 0,
public y: number = 0,
public x: number = 0,
public scrollBottom: number = 0,
public scrollTop: number = 0,
public tabs: any = {},
private _terminal: ITerminal
) {
this.lines = new CircularList<[number, string, number][]>(this.terminal.scrollback);
this.scrollBottom = this.terminal.rows - 1;
this.clear();
}
public get lines(): CircularList<[number, string, number][]> {
return this._lines;
}
/**
* Fills the buffer's viewport with blank lines.
*/
public fillViewportRows(): void {
if (this._lines.length === 0) {
let i = this._terminal.rows;
while (i--) {
this.lines.push(this._terminal.blankLine());
}
}
}
/**
* Clears the buffer to it's initial state, discarding all previous data.
*/
public clear(): void {
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
this.x = 0;
this.scrollBottom = 0;
this.scrollTop = 0;
this.tabs = {};
this._lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);
this.scrollBottom = this._terminal.rows - 1;
}
/**
* Resizes the buffer, adjusting its data accordingly.
* @param newCols The new number of columns.
* @param newRows The new number of rows.
*/
public resize(newCols: number, newRows: number): void {
// Don't resize the buffer if it's empty and hasn't been used yet.
if (this._lines.length === 0) {
return;
}
// Deal with columns increasing (we don't do anything when columns reduce)
if (this._terminal.cols < newCols) {
const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?
for (let i = 0; i < this._lines.length; i++) {
// TODO: This should be removed, with tests setup for the case that was
// causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824
if (this._lines.get(i) === undefined) {
this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));
}
while (this._lines.get(i).length < newCols) {
this._lines.get(i).push(ch);
}
}
}
// Resize rows in both directions as needed
let addToY = 0;
if (this._terminal.rows < newRows) {
for (let y = this._terminal.rows; y < newRows; y++) {
if (this._lines.length < newRows + this.ybase) {
if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.ybase--;
addToY++;
if (this.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.ydisp--;
}
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this._lines.push(this._terminal.blankLine(undefined, undefined, newCols));
}
}
}
} else { // (this._terminal.rows >= newRows)
for (let y = this._terminal.rows; y > newRows; y--) {
if (this._lines.length > newRows + this.ybase) {
if (this._lines.length > this.ybase + this.y + 1) {
// The line is a blank line below the cursor, remove it
this._lines.pop();
} else {
// The line is the cursor, scroll down
this.ybase++;
this.ydisp++;
}
}
}
}
// Make sure that the cursor stays on screen
if (this.y >= newRows) {
this.y = newRows - 1;
}
if (addToY) {
this.y += addToY;
}
if (this.x >= newCols) {
this.x = newCols - 1;
}
this.scrollTop = 0;
this.scrollBottom = newRows - 1;
}
}
+5 -5
View File
@@ -5,17 +5,17 @@ import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { BufferSet } from './BufferSet';
import { Buffer } from './Buffer';
import { MockTerminal } from './utils/TestUtils';
describe('BufferSet', () => {
let terminal: ITerminal;
let bufferSet: BufferSet;
beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
scrollback: 1000
};
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 24;
terminal.scrollback = 1000;
bufferSet = new BufferSet(terminal);
});
+20
View File
@@ -22,6 +22,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
constructor(private _terminal: ITerminal) {
super();
this._normal = new Buffer(this._terminal);
this._normal.fillViewportRows();
this._alt = new Buffer(this._terminal);
this._activeBuffer = this._normal;
}
@@ -54,6 +55,11 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the normal Buffer of the BufferSet as its currently active Buffer
*/
public activateNormalBuffer(): void {
// The alt buffer should always be cleared when we switch to the normal
// buffer. This frees up memory since the alt buffer should always be new
// when activated.
this._alt.clear();
this._activeBuffer = this._normal;
this.emit('activate', this._normal);
}
@@ -62,7 +68,21 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the alt Buffer of the BufferSet as its currently active Buffer
*/
public activateAltBuffer(): void {
// Since the alt buffer is always cleared when the normal buffer is
// activated, we want to fill it when switching to it.
this._alt.fillViewportRows();
this._activeBuffer = this._alt;
this.emit('activate', this._alt);
}
/**
* Resizes both normal and alt buffers, adjusting their data accordingly.
* @param newCols The new number of columns.
* @param newRows The new number of rows.
*/
public resize(newCols: number, newRows: number): void {
this._normal.resize(newCols, newRows);
this._alt.resize(newCols, newRows);
}
}
-1
View File
@@ -954,7 +954,6 @@ export class InputHandler implements IInputHandler {
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._terminal.buffers.activateAltBuffer();
this._terminal.reset();
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
+2 -1
View File
@@ -48,6 +48,7 @@ export interface ITerminal {
emit(event: string, data: any);
reset(): void;
showCursor(): void;
blankLine(cur?: boolean, isWrapped?: boolean, cols?: number);
}
export interface IBuffer {
@@ -92,8 +93,8 @@ export interface ILinkifier {
export interface ICircularList<T> extends IEventEmitter {
length: number;
maxLength: number;
forEach: (callbackfn: (value: T, index: number) => void) => void;
forEach(callbackfn: (value: T, index: number, array: T[]) => void): void;
get(index: number): T;
set(index: number, value: T): void;
push(value: T): void;
+15 -11
View File
@@ -9,6 +9,7 @@ import { CircularList } from './utils/CircularList';
import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { MockTerminal } from './utils/TestUtils';
class TestSelectionManager extends SelectionManager {
constructor(
@@ -46,7 +47,9 @@ describe('SelectionManager', () => {
window = dom.window;
document = window.document;
rowContainer = document.createElement('div');
terminal = <any>{ cols: 80, rows: 2 };
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 2;
terminal.scrollback = 100;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
@@ -64,7 +67,7 @@ describe('SelectionManager', () => {
describe('_selectWordAt', () => {
it('should expand selection for normal width chars', () => {
bufferLines.push(stringToRow('foo bar'));
bufferLines.set(0, stringToRow('foo bar'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'foo');
selectionManager.selectWordAt([1, 0]);
@@ -81,7 +84,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'bar');
});
it('should expand selection for whitespace', () => {
bufferLines.push(stringToRow('a b'));
bufferLines.set(0, stringToRow('a b'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'a');
selectionManager.selectWordAt([1, 0]);
@@ -95,7 +98,7 @@ describe('SelectionManager', () => {
});
it('should expand selection for wide characters', () => {
// Wide characters use a special format
bufferLines.push([
bufferLines.set(0, [
[null, '中', 2],
[null, '', 0],
[null, '文', 2],
@@ -147,7 +150,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'foo');
});
it('should select up to non-path characters that are commonly adjacent to paths', () => {
bufferLines.push(stringToRow('(cd)[ef]{gh}\'ij"'));
bufferLines.set(0, stringToRow('(cd)[ef]{gh}\'ij"'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, '(cd');
selectionManager.selectWordAt([1, 0]);
@@ -185,7 +188,7 @@ describe('SelectionManager', () => {
describe('_selectLineAt', () => {
it('should select the entire line', () => {
bufferLines.push(stringToRow('foo bar'));
bufferLines.set(0, stringToRow('foo bar'));
selectionManager.selectLineAt(0);
assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
@@ -195,11 +198,12 @@ describe('SelectionManager', () => {
describe('selectAll', () => {
it('should select the entire buffer, beyond the viewport', () => {
bufferLines.push(stringToRow('1'));
bufferLines.push(stringToRow('2'));
bufferLines.push(stringToRow('3'));
bufferLines.push(stringToRow('4'));
bufferLines.push(stringToRow('5'));
bufferLines.length = 5;
bufferLines.set(0, stringToRow('1'));
bufferLines.set(1, stringToRow('2'));
bufferLines.set(2, stringToRow('3'));
bufferLines.set(3, stringToRow('4'));
bufferLines.set(4, stringToRow('5'));
selectionManager.selectAll();
terminal.buffer.ybase = bufferLines.length - terminal.rows;
assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
+4 -1
View File
@@ -5,6 +5,7 @@ import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import {BufferSet} from './BufferSet';
import { MockTerminal } from './utils/TestUtils';
class TestSelectionModel extends SelectionModel {
constructor(
@@ -22,7 +23,9 @@ describe('SelectionManager', () => {
let model: TestSelectionModel;
beforeEach(() => {
terminal = <any>{ cols: 80, rows: 2, ybase: 0 };
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 2;
terminal.scrollback = 10;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
+1 -2
View File
@@ -20,8 +20,7 @@ declare var window: any;
/**
* CommonJS environment
*/
const xterm = '../../xterm';
module.exports = addon(require(xterm));
module.exports = addon(require('../../xterm'));
} else if (typeof define == 'function') {
/**
* Require.js is available
+2 -1
View File
@@ -5,8 +5,9 @@
* @license MIT
*/
import { EventEmitter } from '../EventEmitter';
import { ICircularList } from '../Interfaces';
export class CircularList<T> extends EventEmitter {
export class CircularList<T> extends EventEmitter implements ICircularList<T> {
private _array: T[];
private _startIndex: number;
private _length: number;
+54
View File
@@ -0,0 +1,54 @@
import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager } from '../Interfaces';
export class MockTerminal implements ITerminal {
public element: HTMLElement;
public rowContainer: HTMLElement;
public selectionContainer: HTMLElement;
public selectionManager: ISelectionManager;
public charMeasure: ICharMeasure;
public textarea: HTMLTextAreaElement;
public rows: number;
public cols: number;
public browser: IBrowser;
public writeBuffer: string[];
public children: HTMLElement[];
public cursorHidden: boolean;
public cursorState: number;
public defAttr: number;
public scrollback: number;
public buffers: IBufferSet;
public buffer: IBuffer;
handler(data: string) {
throw new Error('Method not implemented.');
}
on(event: string, callback: () => void) {
throw new Error('Method not implemented.');
}
scrollDisp(disp: number, suppressScrollEvent: boolean) {
throw new Error('Method not implemented.');
}
cancel(ev: Event, force?: boolean) {
throw new Error('Method not implemented.');
}
log(text: string): void {
throw new Error('Method not implemented.');
}
emit(event: string, data: any) {
throw new Error('Method not implemented.');
}
reset(): void {
throw new Error('Method not implemented.');
}
showCursor(): void {
throw new Error('Method not implemented.');
}
blankLine(cur?: boolean, isWrapped?: boolean, cols?: number) {
const line = [];
cols = cols || this.cols;
for (let i = 0; i < cols; i++) {
line.push([0, ' ', 1]);
}
return line;
}
}
+14 -85
View File
@@ -227,11 +227,6 @@ function Terminal(options) {
this._terminal.buffer = buffer;
});
var i = this.rows;
while (i--) {
this.buffer.lines.push(this.blankLine());
}
// Ensure the selection manager has the correct buffer
if (this.selectionManager) {
this.selectionManager.setBuffer(this.buffer.lines);
@@ -1930,86 +1925,21 @@ Terminal.prototype.resize = function(x, y) {
if (x < 1) x = 1;
if (y < 1) y = 1;
// resize cols
j = this.cols;
if (j < x) {
ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
i = this.buffer.lines.length;
while (i--) {
if (this.buffer.lines.get(i) === undefined) {
this.buffer.lines.set(i, this.blankLine());
}
while (this.buffer.lines.get(i).length < x) {
this.buffer.lines.get(i).push(ch);
}
}
this.buffers.resize(x, y);
// Adjust rows in the DOM to accurately reflect the new dimensions
while (this.children.length < y) {
this.insertRow();
}
while (this.children.length > y) {
el = this.children.shift();
if (!el) continue;
el.parentNode.removeChild(el);
}
this.cols = x;
this.setupStops(this.cols);
// resize rows
j = this.rows;
addToY = 0;
if (j < y) {
el = this.element;
while (j++ < y) {
// y is rows, not this.buffer.y
if (this.buffer.lines.length < y + this.buffer.ybase) {
if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.buffer.ybase--;
addToY++;
if (this.buffer.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.buffer.ydisp--;
}
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this.buffer.lines.push(this.blankLine());
}
}
if (this.children.length < y) {
this.insertRow();
}
}
} else { // (j > y)
while (j-- > y) {
if (this.buffer.lines.length > y + this.buffer.ybase) {
if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) {
// The line is a blank line below the cursor, remove it
this.buffer.lines.pop();
} else {
// The line is the cursor, scroll down
this.buffer.ybase++;
this.buffer.ydisp++;
}
}
if (this.children.length > y) {
el = this.children.shift();
if (!el) continue;
el.parentNode.removeChild(el);
}
}
}
this.rows = y;
// Make sure that the cursor stays on screen
if (this.buffer.y >= y) {
this.buffer.y = y - 1;
}
if (addToY) {
this.buffer.y += addToY;
}
if (this.buffer.x >= x) {
this.buffer.x = x - 1;
}
this.buffer.scrollTop = 0;
this.buffer.scrollBottom = y - 1;
this.setupStops(this.cols);
this.charMeasure.measure();
@@ -2161,7 +2091,7 @@ Terminal.prototype.eraseLine = function(y) {
* @param {number} cur First bunch of data for each "blank" character.
* @param {boolean} isWrapped Whether the new line is wrapped from the previous line.
*/
Terminal.prototype.blankLine = function(cur, isWrapped) {
Terminal.prototype.blankLine = function(cur, isWrapped, cols) {
var attr = cur
? this.eraseAttr()
: this.defAttr;
@@ -2176,7 +2106,8 @@ Terminal.prototype.blankLine = function(cur, isWrapped) {
line.isWrapped = isWrapped;
}
for (; i < this.cols; i++) {
cols = cols || this.cols;
for (; i < cols; i++) {
line[i] = ch;
}
@@ -2293,12 +2224,10 @@ Terminal.prototype.reset = function() {
var customKeyEventHandler = this.customKeyEventHandler;
var cursorBlinkInterval = this.cursorBlinkInterval;
var inputHandler = this.inputHandler;
var buffers = this.buffers;
Terminal.call(this, this.options);
this.customKeyEventHandler = customKeyEventHandler;
this.cursorBlinkInterval = cursorBlinkInterval;
this.inputHandler = inputHandler;
this.buffers = buffers;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
};