Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea07bf8f69 | ||
|
|
0b1711fb79 | ||
|
|
bb526aaa88 | ||
|
|
a28d9f1456 | ||
|
|
fb6b173fe7 | ||
|
|
1361ff8e22 | ||
|
|
0731f28615 | ||
|
|
f7b58d0718 | ||
|
|
d46003843b | ||
|
|
13619d427a | ||
|
|
f03d00a497 | ||
|
|
3d20c2f266 | ||
|
|
6f749b196a |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm.js",
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.2",
|
||||
"ignore": ["demo", "test", ".gitignore"],
|
||||
"main": [
|
||||
"dist/xterm.js",
|
||||
|
||||
Vendored
+106
-92
@@ -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();
|
||||
};
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -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
@@ -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
@@ -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,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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user