Merge branch 'master' into faster_wcwidth

This commit is contained in:
Daniel Imms
2017-07-17 11:06:38 -07:00
committed by GitHub
22 changed files with 1194 additions and 533 deletions
+31
View File
@@ -0,0 +1,31 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { Buffer } from './Buffer';
import { CircularList } from './utils/CircularList';
describe('Buffer', () => {
let terminal: ITerminal;
let buffer: Buffer;
beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
scrollback: 1000
};
buffer = new Buffer(terminal);
});
describe('constructor', () => {
it('should create a CircularList with max length equal to scrollback, for its lines', () => {
assert.instanceOf(buffer.lines, CircularList);
assert.equal(buffer.lines.maxLength, terminal.scrollback);
});
it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => {
assert.equal(buffer.scrollBottom, terminal.rows - 1);
});
});
});
+39
View File
@@ -0,0 +1,39 @@
/**
* @license MIT
*/
import { ITerminal } from './Interfaces';
import { CircularList } from './utils/CircularList';
/**
* This class represents a terminal buffer (an internal state of the terminal), where the
* following information is stored (in high-level):
* - text content of this particular buffer
* - cursor position
* - scroll position
*/
export class Buffer {
public lines: CircularList<[number, string, number][]>;
/**
* Create a new Buffer.
* @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 = {},
) {
this.lines = new CircularList<[number, string, number][]>(this.terminal.scrollback);
this.scrollBottom = this.terminal.rows - 1;
}
}
+49
View File
@@ -0,0 +1,49 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { BufferSet } from './BufferSet';
import { Buffer } from './Buffer';
describe('BufferSet', () => {
let terminal: ITerminal;
let bufferSet: BufferSet;
beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
scrollback: 1000
};
bufferSet = new BufferSet(terminal);
});
describe('constructor', () => {
it('should create two different buffers: alt and normal', () => {
assert.instanceOf(bufferSet.normal, Buffer);
assert.instanceOf(bufferSet.alt, Buffer);
assert.notEqual(bufferSet.normal, bufferSet.alt);
});
});
describe('activateNormalBuffer', () => {
beforeEach(() => {
bufferSet.activateNormalBuffer();
});
it('should set the normal buffer as the currently active buffer', () => {
assert.equal(bufferSet.active, bufferSet.normal);
});
});
describe('activateAltBuffer', () => {
beforeEach(() => {
bufferSet.activateAltBuffer();
});
it('should set the alt buffer as the currently active buffer', () => {
assert.equal(bufferSet.active, bufferSet.alt);
});
});
});
+68
View File
@@ -0,0 +1,68 @@
/**
* @license MIT
*/
import { ITerminal, IBufferSet } from './Interfaces';
import { Buffer } from './Buffer';
import { EventEmitter } from './EventEmitter';
/**
* The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
* provides also utilities for working with them.
*/
export class BufferSet extends EventEmitter implements IBufferSet {
private _normal: Buffer;
private _alt: Buffer;
private _activeBuffer: Buffer;
/**
* Create a new BufferSet for the given terminal.
* @param {Terminal} terminal - The terminal the BufferSet will belong to
*/
constructor(private _terminal: ITerminal) {
super();
this._normal = new Buffer(this._terminal);
this._alt = new Buffer(this._terminal);
this._activeBuffer = this._normal;
}
/**
* Returns the alt Buffer of the BufferSet
* @returns {Buffer}
*/
public get alt(): Buffer {
return this._alt;
}
/**
* Returns the normal Buffer of the BufferSet
* @returns {Buffer}
*/
public get active(): Buffer {
return this._activeBuffer;
}
/**
* Returns the currently active Buffer of the BufferSet
* @returns {Buffer}
*/
public get normal(): Buffer {
return this._normal;
}
/**
* Sets the normal Buffer of the BufferSet as its currently active Buffer
*/
public activateNormalBuffer(): void {
this._activeBuffer = this._normal;
this.emit('activate', this._normal);
}
/**
* Sets the alt Buffer of the BufferSet as its currently active Buffer
*/
public activateAltBuffer(): void {
this._activeBuffer = this._alt;
this.emit('activate', this._alt);
}
}
+3 -1
View File
@@ -2,12 +2,14 @@
* @license MIT
*/
import { IEventEmitter } from './Interfaces';
interface ListenerType {
(): void;
listener?: () => void;
};
export class EventEmitter {
export class EventEmitter implements IEventEmitter {
private _events: {[type: string]: ListenerType[]};
constructor() {
+169 -197
View File
@@ -27,49 +27,48 @@ export class InputHandler implements IInputHandler {
char = this._terminal.charset[char];
}
let row = this._terminal.y + this._terminal.ybase;
let row = this._terminal.buffer.y + this._terminal.buffer.ybase;
// insert combining char in last cell
// FIXME: needs handling after cursor jumps
if (!ch_width && this._terminal.x) {
if (!ch_width && this._terminal.buffer.x) {
// dont overflow left
if (this._terminal.lines.get(row)[this._terminal.x - 1]) {
if (!this._terminal.lines.get(row)[this._terminal.x - 1][2]) {
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {
// found empty cell after fullwidth, need to go 2 cells back
if (this._terminal.lines.get(row)[this._terminal.x - 2])
this._terminal.lines.get(row)[this._terminal.x - 2][1] += char;
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;
} else {
this._terminal.lines.get(row)[this._terminal.x - 1][1] += char;
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;
}
this._terminal.updateRange(this._terminal.y);
this._terminal.updateRange(this._terminal.buffer.y);
}
return;
}
// goto next line if ch would overflow
// TODO: needs a global min terminal width of 2
if (this._terminal.x + ch_width - 1 >= this._terminal.cols) {
if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {
// autowrap - DECAWM
if (this._terminal.wraparoundMode) {
this._terminal.x = 0;
this._terminal.y++;
if (this._terminal.y > this._terminal.scrollBottom) {
// Insert a new line, scroll and mark as a wrapped line
this._terminal.y--;
this._terminal.buffer.x = 0;
this._terminal.buffer.y++;
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
this._terminal.buffer.y--;
this._terminal.scroll(true);
} else {
// The line already exists (eg. the initial viewport), mark it as a
// wrapped line
this._terminal.lines.get(this._terminal.y).isWrapped = true;
this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;
}
} else {
if (ch_width === 2) // FIXME: check for xterm behavior
return;
}
}
row = this._terminal.y + this._terminal.ybase;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
// insert mode: move characters to right
if (this._terminal.insertMode) {
@@ -77,26 +76,26 @@ export class InputHandler implements IInputHandler {
for (let moves = 0; moves < ch_width; ++moves) {
// remove last cell, if it's width is 0
// we have to adjust the second last cell as well
const removed = this._terminal.lines.get(this._terminal.y + this._terminal.ybase).pop();
const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();
if (removed[2] === 0
&& this._terminal.lines.get(row)[this._terminal.cols - 2]
&& this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) {
this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {
this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
}
// insert empty cell at cursor
this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]);
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]);
}
}
this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, char, ch_width];
this._terminal.x++;
this._terminal.updateRange(this._terminal.y);
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];
this._terminal.buffer.x++;
this._terminal.updateRange(this._terminal.buffer.y);
// fullwidth char - set next cell width to zero and advance cursor
if (ch_width === 2) {
this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0];
this._terminal.x++;
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];
this._terminal.buffer.x++;
}
}
}
@@ -122,16 +121,16 @@ export class InputHandler implements IInputHandler {
*/
public lineFeed(): void {
if (this._terminal.convertEol) {
this._terminal.x = 0;
this._terminal.buffer.x = 0;
}
this._terminal.y++;
if (this._terminal.y > this._terminal.scrollBottom) {
this._terminal.y--;
this._terminal.buffer.y++;
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
this._terminal.buffer.y--;
this._terminal.scroll();
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x--;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
}
@@ -140,7 +139,7 @@ export class InputHandler implements IInputHandler {
* Carriage Return (Ctrl-M).
*/
public carriageReturn(): void {
this._terminal.x = 0;
this._terminal.buffer.x = 0;
}
/**
@@ -148,8 +147,8 @@ export class InputHandler implements IInputHandler {
* Backspace (Ctrl-H).
*/
public backspace(): void {
if (this._terminal.x > 0) {
this._terminal.x--;
if (this._terminal.buffer.x > 0) {
this._terminal.buffer.x--;
}
}
@@ -158,7 +157,7 @@ export class InputHandler implements IInputHandler {
* Horizontal Tab (HT) (Ctrl-I).
*/
public tab(): void {
this._terminal.x = this._terminal.nextStop();
this._terminal.buffer.x = this._terminal.nextStop();
}
/**
@@ -189,13 +188,13 @@ export class InputHandler implements IInputHandler {
param = params[0];
if (param < 1) param = 1;
row = this._terminal.y + this._terminal.ybase;
j = this._terminal.x;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
j = this._terminal.buffer.x;
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
while (param-- && j < this._terminal.cols) {
this._terminal.lines.get(row).splice(j++, 0, ch);
this._terminal.lines.get(row).pop();
this._terminal.buffer.lines.get(row).splice(j++, 0, ch);
this._terminal.buffer.lines.get(row).pop();
}
}
@@ -208,9 +207,9 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.y -= param;
if (this._terminal.y < 0) {
this._terminal.y = 0;
this._terminal.buffer.y -= param;
if (this._terminal.buffer.y < 0) {
this._terminal.buffer.y = 0;
}
}
@@ -223,13 +222,13 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.y += param;
if (this._terminal.y >= this._terminal.rows) {
this._terminal.y = this._terminal.rows - 1;
this._terminal.buffer.y += param;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x--;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
}
@@ -242,9 +241,9 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.x += param;
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x = this._terminal.cols - 1;
this._terminal.buffer.x += param;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
}
@@ -258,12 +257,12 @@ export class InputHandler implements IInputHandler {
param = 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x--;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
this._terminal.x -= param;
if (this._terminal.x < 0) {
this._terminal.x = 0;
this._terminal.buffer.x -= param;
if (this._terminal.buffer.x < 0) {
this._terminal.buffer.x = 0;
}
}
@@ -277,11 +276,11 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.y += param;
if (this._terminal.y >= this._terminal.rows) {
this._terminal.y = this._terminal.rows - 1;
this._terminal.buffer.y += param;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
this._terminal.x = 0;
this._terminal.buffer.x = 0;
};
@@ -295,11 +294,11 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.y -= param;
if (this._terminal.y < 0) {
this._terminal.y = 0;
this._terminal.buffer.y -= param;
if (this._terminal.buffer.y < 0) {
this._terminal.buffer.y = 0;
}
this._terminal.x = 0;
this._terminal.buffer.x = 0;
};
@@ -312,7 +311,7 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.x = param - 1;
this._terminal.buffer.x = param - 1;
}
/**
@@ -342,8 +341,8 @@ export class InputHandler implements IInputHandler {
col = this._terminal.cols - 1;
}
this._terminal.x = col;
this._terminal.y = row;
this._terminal.buffer.x = col;
this._terminal.buffer.y = row;
}
/**
@@ -353,7 +352,7 @@ export class InputHandler implements IInputHandler {
public cursorForwardTab(params: number[]): void {
let param = params[0] || 1;
while (param--) {
this._terminal.x = this._terminal.nextStop();
this._terminal.buffer.x = this._terminal.nextStop();
}
}
@@ -373,15 +372,15 @@ export class InputHandler implements IInputHandler {
let j;
switch (params[0]) {
case 0:
this._terminal.eraseRight(this._terminal.x, this._terminal.y);
j = this._terminal.y + 1;
this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
j = this._terminal.buffer.y + 1;
for (; j < this._terminal.rows; j++) {
this._terminal.eraseLine(j);
}
break;
case 1:
this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
j = this._terminal.y;
this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
j = this._terminal.buffer.y;
while (j--) {
this._terminal.eraseLine(j);
}
@@ -392,11 +391,11 @@ export class InputHandler implements IInputHandler {
break;
case 3:
// Clear scrollback (everything not in viewport)
const scrollBackSize = this._terminal.lines.length - this._terminal.rows;
const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;
if (scrollBackSize > 0) {
this._terminal.lines.trimStart(scrollBackSize);
this._terminal.ybase = Math.max(this._terminal.ybase - scrollBackSize, 0);
this._terminal.ydisp = Math.max(this._terminal.ydisp - scrollBackSize, 0);
this._terminal.buffer.lines.trimStart(scrollBackSize);
this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);
this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);
// Force a scroll event to refresh viewport
this._terminal.emit('scroll', 0);
}
@@ -418,13 +417,13 @@ export class InputHandler implements IInputHandler {
public eraseInLine(params: number[]): void {
switch (params[0]) {
case 0:
this._terminal.eraseRight(this._terminal.x, this._terminal.y);
this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
break;
case 1:
this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
break;
case 2:
this._terminal.eraseLine(this._terminal.y);
this._terminal.eraseLine(this._terminal.buffer.y);
break;
}
}
@@ -440,29 +439,29 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
row = this._terminal.y + this._terminal.ybase;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
j = this._terminal.rows - 1 - this._terminal.scrollBottom;
j = this._terminal.rows - 1 + this._terminal.ybase - j + 1;
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;
while (param--) {
if (this._terminal.lines.length === this._terminal.lines.maxLength) {
if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {
// Trim the start of lines to make room for the new line
this._terminal.lines.trimStart(1);
this._terminal.ybase--;
this._terminal.ydisp--;
this._terminal.buffer.lines.trimStart(1);
this._terminal.buffer.ybase--;
this._terminal.buffer.ydisp--;
row--;
j--;
}
// test: echo -e '\e[44m\e[1L\e[0m'
// blankLine(true) - xterm/linux behavior
this._terminal.lines.splice(row, 0, this._terminal.blankLine(true));
this._terminal.lines.splice(j, 1);
this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));
this._terminal.buffer.lines.splice(j, 1);
}
// this.maxRange();
this._terminal.updateRange(this._terminal.y);
this._terminal.updateRange(this._terminal.scrollBottom);
this._terminal.updateRange(this._terminal.buffer.y);
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
}
/**
@@ -476,27 +475,27 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
row = this._terminal.y + this._terminal.ybase;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
j = this._terminal.rows - 1 - this._terminal.scrollBottom;
j = this._terminal.rows - 1 + this._terminal.ybase - j;
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;
while (param--) {
if (this._terminal.lines.length === this._terminal.lines.maxLength) {
if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {
// Trim the start of lines to make room for the new line
this._terminal.lines.trimStart(1);
this._terminal.ybase -= 1;
this._terminal.ydisp -= 1;
this._terminal.buffer.lines.trimStart(1);
this._terminal.buffer.ybase -= 1;
this._terminal.buffer.ydisp -= 1;
}
// test: echo -e '\e[44m\e[1M\e[0m'
// blankLine(true) - xterm/linux behavior
this._terminal.lines.splice(j + 1, 0, this._terminal.blankLine(true));
this._terminal.lines.splice(row, 1);
this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));
this._terminal.buffer.lines.splice(row, 1);
}
// this.maxRange();
this._terminal.updateRange(this._terminal.y);
this._terminal.updateRange(this._terminal.scrollBottom);
this._terminal.updateRange(this._terminal.buffer.y);
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
}
/**
@@ -511,12 +510,12 @@ export class InputHandler implements IInputHandler {
param = 1;
}
row = this._terminal.y + this._terminal.ybase;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
while (param--) {
this._terminal.lines.get(row).splice(this._terminal.x, 1);
this._terminal.lines.get(row).push(ch);
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);
this._terminal.buffer.lines.get(row).push(ch);
}
}
@@ -526,12 +525,12 @@ export class InputHandler implements IInputHandler {
public scrollUp(params: number[]): void {
let param = params[0] || 1;
while (param--) {
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 1);
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 0, this._terminal.blankLine());
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());
}
// this.maxRange();
this._terminal.updateRange(this._terminal.scrollTop);
this._terminal.updateRange(this._terminal.scrollBottom);
this._terminal.updateRange(this._terminal.buffer.scrollTop);
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
}
/**
@@ -540,12 +539,12 @@ export class InputHandler implements IInputHandler {
public scrollDown(params: number[]): void {
let param = params[0] || 1;
while (param--) {
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 1);
this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 0, this._terminal.blankLine());
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());
}
// this.maxRange();
this._terminal.updateRange(this._terminal.scrollTop);
this._terminal.updateRange(this._terminal.scrollBottom);
this._terminal.updateRange(this._terminal.buffer.scrollTop);
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
}
/**
@@ -560,12 +559,12 @@ export class InputHandler implements IInputHandler {
param = 1;
}
row = this._terminal.y + this._terminal.ybase;
j = this._terminal.x;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
j = this._terminal.buffer.x;
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
while (param-- && j < this._terminal.cols) {
this._terminal.lines.get(row)[j++] = ch;
this._terminal.buffer.lines.get(row)[j++] = ch;
}
}
@@ -575,7 +574,7 @@ export class InputHandler implements IInputHandler {
public cursorBackwardTab(params: number[]): void {
let param = params[0] || 1;
while (param--) {
this._terminal.x = this._terminal.prevStop();
this._terminal.buffer.x = this._terminal.prevStop();
}
}
@@ -588,9 +587,9 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.x = param - 1;
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x = this._terminal.cols - 1;
this._terminal.buffer.x = param - 1;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
}
@@ -604,9 +603,9 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.x += param;
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x = this._terminal.cols - 1;
this._terminal.buffer.x += param;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
}
@@ -615,11 +614,11 @@ export class InputHandler implements IInputHandler {
*/
public repeatPrecedingCharacter(params: number[]): void {
let param = params[0] || 1
, line = this._terminal.lines.get(this._terminal.ybase + this._terminal.y)
, ch = line[this._terminal.x - 1] || [this._terminal.defAttr, ' ', 1];
, line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)
, ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];
while (param--) {
line[this._terminal.x++] = ch;
line[this._terminal.buffer.x++] = ch;
}
}
@@ -698,9 +697,9 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.y = param - 1;
if (this._terminal.y >= this._terminal.rows) {
this._terminal.y = this._terminal.rows - 1;
this._terminal.buffer.y = param - 1;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
}
@@ -714,13 +713,13 @@ export class InputHandler implements IInputHandler {
if (param < 1) {
param = 1;
}
this._terminal.y += param;
if (this._terminal.y >= this._terminal.rows) {
this._terminal.y = this._terminal.rows - 1;
this._terminal.buffer.y += param;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x--;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x--;
}
}
@@ -733,14 +732,14 @@ export class InputHandler implements IInputHandler {
if (params[0] < 1) params[0] = 1;
if (params[1] < 1) params[1] = 1;
this._terminal.y = params[0] - 1;
if (this._terminal.y >= this._terminal.rows) {
this._terminal.y = this._terminal.rows - 1;
this._terminal.buffer.y = params[0] - 1;
if (this._terminal.buffer.y >= this._terminal.rows) {
this._terminal.buffer.y = this._terminal.rows - 1;
}
this._terminal.x = params[1] - 1;
if (this._terminal.x >= this._terminal.cols) {
this._terminal.x = this._terminal.cols - 1;
this._terminal.buffer.x = params[1] - 1;
if (this._terminal.buffer.x >= this._terminal.cols) {
this._terminal.buffer.x = this._terminal.cols - 1;
}
}
@@ -755,9 +754,9 @@ export class InputHandler implements IInputHandler {
public tabClear(params: number[]): void {
let param = params[0];
if (param <= 0) {
delete this._terminal.tabs[this._terminal.x];
delete this._terminal.buffer.tabs[this._terminal.buffer.x];
} else if (param === 3) {
this._terminal.tabs = {};
this._terminal.buffer.tabs = {};
}
}
@@ -943,30 +942,14 @@ export class InputHandler implements IInputHandler {
this._terminal.cursorHidden = false;
break;
case 1049: // alt screen buffer cursor
// this._terminal.saveCursor();
; // FALL-THROUGH
this.saveCursor(params);
// FALL-THROUGH
case 47: // alt screen buffer
case 1047: // alt screen buffer
if (!this._terminal.normal) {
let normal = {
lines: this._terminal.lines,
ybase: this._terminal.ybase,
ydisp: this._terminal.ydisp,
x: this._terminal.x,
y: this._terminal.y,
scrollTop: this._terminal.scrollTop,
scrollBottom: this._terminal.scrollBottom,
tabs: this._terminal.tabs
// XXX save charset(s) here?
// charset: this._terminal.charset,
// glevel: this._terminal.glevel,
// charsets: this._terminal.charsets
};
this._terminal.reset();
this._terminal.viewport.syncScrollArea();
this._terminal.normal = normal;
this._terminal.showCursor();
}
this._terminal.buffers.activateAltBuffer();
this._terminal.reset();
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
}
}
@@ -1127,26 +1110,15 @@ export class InputHandler implements IInputHandler {
; // FALL-THROUGH
case 47: // normal screen buffer
case 1047: // normal screen buffer - clearing it first
if (this._terminal.normal) {
this._terminal.lines = this._terminal.normal.lines;
this._terminal.ybase = this._terminal.normal.ybase;
this._terminal.ydisp = this._terminal.normal.ydisp;
this._terminal.x = this._terminal.normal.x;
this._terminal.y = this._terminal.normal.y;
this._terminal.scrollTop = this._terminal.normal.scrollTop;
this._terminal.scrollBottom = this._terminal.normal.scrollBottom;
this._terminal.tabs = this._terminal.normal.tabs;
this._terminal.normal = null;
// Ensure the selection manager has the correct buffer
this._terminal.selectionManager.setBuffer(this._terminal.lines);
// if (params === 1049) {
// this.x = this.savedX;
// this.y = this.savedY;
// }
this._terminal.refresh(0, this._terminal.rows - 1);
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
// Ensure the selection manager has the correct buffer
this._terminal.buffers.activateNormalBuffer();
if (params[0] === 1049) {
this.restoreCursor(params);
}
this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);
this._terminal.refresh(0, this._terminal.rows - 1);
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
}
}
@@ -1366,9 +1338,9 @@ export class InputHandler implements IInputHandler {
case 6:
// cursor position
this._terminal.send(C0.ESC + '['
+ (this._terminal.y + 1)
+ (this._terminal.buffer.y + 1)
+ ';'
+ (this._terminal.x + 1)
+ (this._terminal.buffer.x + 1)
+ 'R');
break;
}
@@ -1379,9 +1351,9 @@ export class InputHandler implements IInputHandler {
case 6:
// cursor position
this._terminal.send(C0.ESC + '[?'
+ (this._terminal.y + 1)
+ (this._terminal.buffer.y + 1)
+ ';'
+ (this._terminal.x + 1)
+ (this._terminal.buffer.x + 1)
+ 'R');
break;
case 15:
@@ -1416,10 +1388,10 @@ export class InputHandler implements IInputHandler {
this._terminal.applicationKeypad = false; // ?
this._terminal.viewport.syncScrollArea();
this._terminal.applicationCursor = false;
this._terminal.scrollTop = 0;
this._terminal.scrollBottom = this._terminal.rows - 1;
this._terminal.buffer.scrollTop = 0;
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
this._terminal.curAttr = this._terminal.defAttr;
this._terminal.x = this._terminal.y = 0; // ?
this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?
this._terminal.charset = null;
this._terminal.glevel = 0; // ??
this._terminal.charsets = [null]; // ??
@@ -1463,10 +1435,10 @@ export class InputHandler implements IInputHandler {
*/
public setScrollRegion(params: number[]): void {
if (this._terminal.prefix) return;
this._terminal.scrollTop = (params[0] || 1) - 1;
this._terminal.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
this._terminal.x = 0;
this._terminal.y = 0;
this._terminal.buffer.scrollTop = (params[0] || 1) - 1;
this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
this._terminal.buffer.x = 0;
this._terminal.buffer.y = 0;
}
@@ -1475,8 +1447,8 @@ export class InputHandler implements IInputHandler {
* Save cursor (ANSI.SYS).
*/
public saveCursor(params: number[]): void {
this._terminal.savedX = this._terminal.x;
this._terminal.savedY = this._terminal.y;
this._terminal.buffers.active.x = this._terminal.buffer.x;
this._terminal.buffers.active.y = this._terminal.buffer.y;
}
@@ -1485,8 +1457,8 @@ export class InputHandler implements IInputHandler {
* Restore cursor (ANSI.SYS).
*/
public restoreCursor(params: number[]): void {
this._terminal.x = this._terminal.savedX || 0;
this._terminal.y = this._terminal.savedY || 0;
this._terminal.buffer.x = this._terminal.buffers.active.x || 0;
this._terminal.buffer.y = this._terminal.buffers.active.y || 0;
}
}
+29 -6
View File
@@ -24,9 +24,6 @@ export interface ITerminal {
selectionManager: ISelectionManager;
charMeasure: ICharMeasure;
textarea: HTMLTextAreaElement;
ybase: number;
ydisp: number;
lines: ICircularList<string>;
rows: number;
cols: number;
browser: IBrowser;
@@ -34,9 +31,10 @@ export interface ITerminal {
children: HTMLElement[];
cursorHidden: boolean;
cursorState: number;
x: number;
y: number;
defAttr: number;
scrollback: number;
buffers: IBufferSet;
buffer: IBuffer;
/**
* Emit the 'data' event and populate the given data.
@@ -48,6 +46,26 @@ export interface ITerminal {
cancel(ev: Event, force?: boolean);
log(text: string): void;
emit(event: string, data: any);
reset(): void;
showCursor(): void;
}
export interface IBuffer {
lines: ICircularList<[number, string, number][]>;
ydisp: number;
ybase: number;
y: number;
x: number;
tabs: any;
}
export interface IBufferSet {
alt: IBuffer;
normal: IBuffer;
active: IBuffer;
activateNormalBuffer(): void;
activateAltBuffer(): void;
}
export interface ISelectionManager {
@@ -71,7 +89,7 @@ export interface ILinkifier {
deregisterLinkMatcher(matcherId: number): boolean;
}
interface ICircularList<T> {
export interface ICircularList<T> extends IEventEmitter {
length: number;
maxLength: number;
@@ -85,6 +103,11 @@ interface ICircularList<T> {
shiftElements(start: number, count: number, offset: number): void;
}
export interface IEventEmitter {
on(type, listener): void;
off(type, listener): void;
}
export interface LinkMatcherOptions {
/**
* The index of the link from the regex.match(text) call. This defaults to 0
+3 -3
View File
@@ -52,7 +52,7 @@ escapedStateHandler['c'] = (parser, terminal) => {
};
escapedStateHandler['E'] = (parser, terminal) => {
// ESC E Next Line ( NEL is 0x85).
terminal.x = 0;
terminal.buffer.x = 0;
terminal.index();
parser.setState(ParserState.NORMAL);
};
@@ -499,9 +499,9 @@ export class Parser {
// DECSTBM
case 'r':
pt = ''
+ (this._terminal.scrollTop + 1)
+ (this._terminal.buffer.scrollTop + 1)
+ ';'
+ (this._terminal.scrollBottom + 1)
+ (this._terminal.buffer.scrollBottom + 1)
+ 'r';
break;
+6 -6
View File
@@ -139,15 +139,15 @@ export class Renderer {
}
for (; y <= end; y++) {
let row = y + this._terminal.ydisp;
let row = y + this._terminal.buffer.ydisp;
let line = this._terminal.lines.get(row);
let line = this._terminal.buffer.lines.get(row);
let x;
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) &&
if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) &&
this._terminal.cursorState &&
!this._terminal.cursorHidden) {
x = this._terminal.x;
x = this._terminal.buffer.x;
} else {
x = -1;
}
@@ -337,8 +337,8 @@ export class Renderer {
}
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - this._terminal.ydisp;
const viewportEndRow = end[1] - this._terminal.ydisp;
const viewportStartRow = start[1] - this._terminal.buffer.ydisp;
const viewportEndRow = end[1] - this._terminal.buffer.ydisp;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
+20 -16
View File
@@ -3,16 +3,17 @@
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { ITerminal, ICircularList } from './Interfaces';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
class TestSelectionManager extends SelectionManager {
constructor(
terminal: ITerminal,
buffer: CircularList<any>,
buffer: ICircularList<[number, string, number][]>,
rowContainer: HTMLElement,
charMeasure: CharMeasure
) {
@@ -36,7 +37,7 @@ describe('SelectionManager', () => {
let document: Document;
let terminal: ITerminal;
let buffer: CircularList<any>;
let bufferLines: ICircularList<[number, string, number][]>;
let rowContainer: HTMLElement;
let selectionManager: TestSelectionManager;
@@ -44,9 +45,12 @@ describe('SelectionManager', () => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
buffer = new CircularList<any>(100);
terminal = <any>{ cols: 80, rows: 2 };
selectionManager = new TestSelectionManager(terminal, buffer, rowContainer, null);
terminal.scrollback = 100;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
bufferLines = terminal.buffer.lines;
selectionManager = new TestSelectionManager(terminal, bufferLines, rowContainer, null);
});
function stringToRow(text: string): [number, string, number][] {
@@ -59,7 +63,7 @@ describe('SelectionManager', () => {
describe('_selectWordAt', () => {
it('should expand selection for normal width chars', () => {
buffer.push(stringToRow('foo bar'));
bufferLines.push(stringToRow('foo bar'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'foo');
selectionManager.selectWordAt([1, 0]);
@@ -76,7 +80,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'bar');
});
it('should expand selection for whitespace', () => {
buffer.push(stringToRow('a b'));
bufferLines.push(stringToRow('a b'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'a');
selectionManager.selectWordAt([1, 0]);
@@ -90,7 +94,7 @@ describe('SelectionManager', () => {
});
it('should expand selection for wide characters', () => {
// Wide characters use a special format
buffer.push([
bufferLines.push([
[null, '中', 2],
[null, '', 0],
[null, '文', 2],
@@ -142,7 +146,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'foo');
});
it('should select up to non-path characters that are commonly adjacent to paths', () => {
buffer.push(stringToRow('(cd)[ef]{gh}\'ij"'));
bufferLines.push(stringToRow('(cd)[ef]{gh}\'ij"'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, '(cd');
selectionManager.selectWordAt([1, 0]);
@@ -180,7 +184,7 @@ describe('SelectionManager', () => {
describe('_selectLineAt', () => {
it('should select the entire line', () => {
buffer.push(stringToRow('foo bar'));
bufferLines.push(stringToRow('foo bar'));
selectionManager.selectLineAt(0);
assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
@@ -190,13 +194,13 @@ describe('SelectionManager', () => {
describe('selectAll', () => {
it('should select the entire buffer, beyond the viewport', () => {
buffer.push(stringToRow('1'));
buffer.push(stringToRow('2'));
buffer.push(stringToRow('3'));
buffer.push(stringToRow('4'));
buffer.push(stringToRow('5'));
bufferLines.push(stringToRow('1'));
bufferLines.push(stringToRow('2'));
bufferLines.push(stringToRow('3'));
bufferLines.push(stringToRow('4'));
bufferLines.push(stringToRow('5'));
selectionManager.selectAll();
terminal.ybase = buffer.length - terminal.rows;
terminal.buffer.ybase = bufferLines.length - terminal.rows;
assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
});
});
+8 -8
View File
@@ -7,7 +7,7 @@ import * as Browser from './utils/Browser';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
import { ITerminal } from './Interfaces';
import { ITerminal, ICircularList } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import { translateBufferLineToString } from './utils/BufferLine';
@@ -98,7 +98,7 @@ export class SelectionManager extends EventEmitter {
constructor(
private _terminal: ITerminal,
private _buffer: CircularList<any>,
private _buffer: ICircularList<[number, string, number][]>,
private _rowContainer: HTMLElement,
private _charMeasure: CharMeasure
) {
@@ -147,7 +147,7 @@ export class SelectionManager extends EventEmitter {
* switched in or out.
* @param buffer The active buffer.
*/
public setBuffer(buffer: CircularList<any>): void {
public setBuffer(buffer: ICircularList<[number, string, number][]>): void {
this._buffer = buffer;
this.clearSelection();
}
@@ -186,7 +186,7 @@ export class SelectionManager extends EventEmitter {
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
const bufferLine = this._buffer.get(i);
const lineText = translateBufferLineToString(bufferLine, true);
if (bufferLine.isWrapped) {
if ((<any>bufferLine).isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
@@ -197,7 +197,7 @@ export class SelectionManager extends EventEmitter {
if (start[1] !== end[1]) {
const bufferLine = this._buffer.get(end[1]);
const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);
if (bufferLine.isWrapped) {
if ((<any>bufferLine).isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
@@ -281,7 +281,7 @@ export class SelectionManager extends EventEmitter {
coords[0]--;
coords[1]--;
// Convert viewport coords to buffer coords
coords[1] += this._terminal.ydisp;
coords[1] += this._terminal.buffer.ydisp;
return coords;
}
@@ -476,9 +476,9 @@ export class SelectionManager extends EventEmitter {
this._terminal.scrollDisp(this._dragScrollAmount, false);
// Re-evaluate selection
if (this._dragScrollAmount > 0) {
this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.ydisp + this._terminal.rows];
this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];
} else {
this._model.selectionEnd = [0, this._terminal.ydisp];
this._model.selectionEnd = [0, this._terminal.buffer.ydisp];
}
this.refresh();
}
+5
View File
@@ -4,6 +4,7 @@
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import {BufferSet} from './BufferSet';
class TestSelectionModel extends SelectionModel {
constructor(
@@ -22,6 +23,10 @@ describe('SelectionManager', () => {
beforeEach(() => {
terminal = <any>{ cols: 80, rows: 2, ybase: 0 };
terminal.scrollback = 10;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
model = new TestSelectionModel(terminal);
});
+1 -1
View File
@@ -68,7 +68,7 @@ export class SelectionModel {
*/
public get finalSelectionEnd(): [number, number] {
if (this.isSelectAllActive) {
return [this._terminal.cols, this._terminal.ybase + this._terminal.rows - 1];
return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];
}
if (!this.selectionStart) {
+11 -9
View File
@@ -1,10 +1,10 @@
import { assert } from 'chai';
import { Viewport } from './Viewport';
import {BufferSet} from './BufferSet';
describe('Viewport', () => {
let terminal;
let viewportElement;
let selectionContainer;
let charMeasure;
let viewport;
let scrollAreaElement;
@@ -13,7 +13,6 @@ describe('Viewport', () => {
beforeEach(() => {
terminal = {
lines: [],
rows: 0,
ydisp: 0,
on: () => {},
@@ -26,8 +25,11 @@ describe('Viewport', () => {
style: {
height: 0
}
}
},
scrollback: 10
};
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
viewportElement = {
addEventListener: () => {},
style: {
@@ -60,14 +62,14 @@ describe('Viewport', () => {
}, 0);
});
it('should set the height of the viewport when the line-height changed', () => {
terminal.lines.push('');
terminal.lines.push('');
terminal.buffer.lines.push('');
terminal.buffer.lines.push('');
terminal.rows = 1;
viewport.refresh();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
charMeasure.height = 20;
charMeasure.height = 2 * CHARACTER_HEIGHT;
viewport.refresh();
assert.equal(viewportElement.style.height, 20 + 'px');
assert.equal(viewportElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
});
});
@@ -75,13 +77,13 @@ describe('Viewport', () => {
it('should sync the scroll area', done => {
// Allow CharMeasure to be initialized
setTimeout(() => {
terminal.lines.push('');
terminal.buffer.lines.push('');
terminal.rows = 1;
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
viewport.syncScrollArea();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
terminal.lines.push('');
terminal.buffer.lines.push('');
viewport.syncScrollArea();
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
+5 -7
View File
@@ -20,7 +20,7 @@ export class Viewport {
* @param terminal The terminal this viewport belongs to.
* @param viewportElement The DOM element acting as the viewport.
* @param scrollArea The DOM element acting as the scroll area.
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
* @param charMeasure A DOM element used to measure the character size of. the terminal.
*/
constructor(
private terminal: ITerminal,
@@ -43,8 +43,6 @@ export class Viewport {
/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
* be created.
*/
private refresh(): void {
if (this.charMeasure.height > 0) {
@@ -68,9 +66,9 @@ export class Viewport {
* Updates dimensions and synchronizes the scroll area if necessary.
*/
public syncScrollArea(): void {
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {
// If buffer height changed
this.lastRecordedBufferLength = this.terminal.lines.length;
this.lastRecordedBufferLength = this.terminal.buffer.lines.length;
this.refresh();
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
// If viewport height changed
@@ -83,7 +81,7 @@ export class Viewport {
}
// Sync scrollTop
const scrollTop = this.terminal.ydisp * this.currentRowHeight;
const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;
if (this.viewportElement.scrollTop !== scrollTop) {
this.viewportElement.scrollTop = scrollTop;
}
@@ -96,7 +94,7 @@ export class Viewport {
*/
private onScroll(ev: Event) {
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
const diff = newRow - this.terminal.ydisp;
const diff = newRow - this.terminal.buffer.ydisp;
this.terminal.scrollDisp(diff, true);
}
+2 -2
View File
@@ -62,10 +62,10 @@ function formatError(in_, out_, expected) {
function terminalToString(term) {
var result = '';
var line_s = '';
for (var line = term.ybase; line < term.ybase + term.rows; line++) {
for (var line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
line_s = '';
for (var cell=0; cell<term.cols; ++cell) {
line_s += term.lines.get(line)[cell][1];
line_s += term.buffer.lines.get(line)[cell][1];
}
// rtrim empty cells as xterm does
line_s = line_s.replace(/\s+$/, '');
+128 -128
View File
@@ -64,15 +64,15 @@ describe('xterm.js', function() {
describe('clear', function() {
it('should clear a buffer equal to rows', function() {
var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
xterm.clear();
assert.equal(xterm.y, 0);
assert.equal(xterm.ybase, 0);
assert.equal(xterm.ydisp, 0);
assert.equal(xterm.lines.length, xterm.rows);
assert.deepEqual(xterm.lines.get(0), promptLine);
assert.equal(xterm.buffer.y, 0);
assert.equal(xterm.buffer.ybase, 0);
assert.equal(xterm.buffer.ydisp, 0);
assert.equal(xterm.buffer.lines.length, xterm.rows);
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
}
});
it('should clear a buffer larger than rows', function() {
@@ -81,28 +81,28 @@ describe('xterm.js', function() {
xterm.write('test\n');
}
var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
xterm.clear();
assert.equal(xterm.y, 0);
assert.equal(xterm.ybase, 0);
assert.equal(xterm.ydisp, 0);
assert.equal(xterm.lines.length, xterm.rows);
assert.deepEqual(xterm.lines.get(0), promptLine);
assert.equal(xterm.buffer.y, 0);
assert.equal(xterm.buffer.ybase, 0);
assert.equal(xterm.buffer.ydisp, 0);
assert.equal(xterm.buffer.lines.length, xterm.rows);
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
}
});
it('should not break the prompt when cleared twice', function() {
var promptLine = xterm.lines.get(xterm.ybase + xterm.y);
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
xterm.clear();
xterm.clear();
assert.equal(xterm.y, 0);
assert.equal(xterm.ybase, 0);
assert.equal(xterm.ydisp, 0);
assert.equal(xterm.lines.length, xterm.rows);
assert.deepEqual(xterm.lines.get(0), promptLine);
assert.equal(xterm.buffer.y, 0);
assert.equal(xterm.buffer.ybase, 0);
assert.equal(xterm.buffer.ydisp, 0);
assert.equal(xterm.buffer.lines.length, xterm.rows);
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.lines.get(i), xterm.blankLine());
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
}
});
});
@@ -117,29 +117,29 @@ describe('xterm.js', function() {
startYDisp = xterm.rows + 1;
});
it('should scroll a single line', function() {
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(-1);
assert.equal(xterm.ydisp, startYDisp - 1);
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
xterm.scrollDisp(1);
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
it('should scroll multiple lines', function() {
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(-5);
assert.equal(xterm.ydisp, startYDisp - 5);
assert.equal(xterm.buffer.ydisp, startYDisp - 5);
xterm.scrollDisp(5);
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
it('should not scroll beyond the bounds of the buffer', function() {
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(1);
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
for (var i = 0; i < startYDisp; i++) {
xterm.scrollDisp(-1);
}
assert.equal(xterm.ydisp, 0);
assert.equal(xterm.buffer.ydisp, 0);
xterm.scrollDisp(-1);
assert.equal(xterm.ydisp, 0);
assert.equal(xterm.buffer.ydisp, 0);
});
});
@@ -152,18 +152,18 @@ describe('xterm.js', function() {
startYDisp = (xterm.rows * 2) + 1;
});
it('should scroll a single page', function() {
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollPages(-1);
assert.equal(xterm.ydisp, startYDisp - (xterm.rows - 1));
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1));
xterm.scrollPages(1);
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
it('should scroll a multiple pages', function() {
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollPages(-2);
assert.equal(xterm.ydisp, startYDisp - (xterm.rows - 1) * 2);
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1) * 2);
xterm.scrollPages(2);
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
});
@@ -174,9 +174,9 @@ describe('xterm.js', function() {
}
});
it('should scroll to the top', function() {
assert.notEqual(xterm.ydisp, 0);
assert.notEqual(xterm.buffer.ydisp, 0);
xterm.scrollToTop();
assert.equal(xterm.ydisp, 0);
assert.equal(xterm.buffer.ydisp, 0);
});
});
@@ -191,13 +191,13 @@ describe('xterm.js', function() {
it('should scroll to the bottom', function() {
xterm.scrollDisp(-1);
xterm.scrollToBottom();
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollPages(-1);
xterm.scrollToBottom();
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollToTop();
xterm.scrollToBottom();
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
});
@@ -214,13 +214,13 @@ describe('xterm.js', function() {
stopPropagation: function(){}
};
xterm.ydisp = 0;
xterm.ybase = 40;
assert.notEqual(xterm.ydisp, xterm.ybase);
xterm.buffer.ydisp = 0;
xterm.buffer.ybase = 40;
assert.notEqual(xterm.buffer.ydisp, xterm.buffer.ybase);
xterm.keyDown(event);
// Ensure that now the terminal is scrolled to bottom
assert.equal(xterm.ydisp, xterm.ybase);
assert.equal(xterm.buffer.ydisp, xterm.buffer.ybase);
});
it('should not scroll down, when a custom keydown handler prevents the event', function () {
@@ -233,11 +233,11 @@ describe('xterm.js', function() {
return false;
});
assert.equal(xterm.ydisp, startYDisp);
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(-1);
assert.equal(xterm.ydisp, startYDisp - 1);
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
xterm.keyDown({ keyCode: 0 });
assert.equal(xterm.ydisp, startYDisp - 1);
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
});
});
});
@@ -575,48 +575,48 @@ describe('xterm.js', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.write(high + String.fromCharCode(i));
var tchar = xterm.lines.get(0)[0];
var tchar = xterm.buffer.lines.get(0)[0];
expect(tchar[1]).eql(high + String.fromCharCode(i));
expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1);
expect(xterm.lines.get(0)[1][1]).eql(' ');
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
xterm.reset();
}
});
it('2 characters at last cell', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.x = xterm.cols - 1;
xterm.buffer.x = xterm.cols - 1;
xterm.write(high + String.fromCharCode(i));
expect(xterm.lines.get(0)[xterm.x-1][1]).eql(high + String.fromCharCode(i));
expect(xterm.lines.get(0)[xterm.x-1][1].length).eql(2);
expect(xterm.lines.get(1)[0][1]).eql(' ');
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][1]).eql(high + String.fromCharCode(i));
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][1].length).eql(2);
expect(xterm.buffer.lines.get(1)[0][1]).eql(' ');
xterm.reset();
}
});
it('2 characters per cell over line end with autowrap', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.x = xterm.cols - 1;
xterm.buffer.x = xterm.cols - 1;
xterm.wraparoundMode = true;
xterm.write('a' + high + String.fromCharCode(i));
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
expect(xterm.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
expect(xterm.lines.get(1)[0][1].length).eql(2);
expect(xterm.lines.get(1)[1][1]).eql(' ');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('a');
expect(xterm.buffer.lines.get(1)[0][1]).eql(high + String.fromCharCode(i));
expect(xterm.buffer.lines.get(1)[0][1].length).eql(2);
expect(xterm.buffer.lines.get(1)[1][1]).eql(' ');
xterm.reset();
}
});
it('2 characters per cell over line end without autowrap', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.x = xterm.cols - 1;
xterm.buffer.x = xterm.cols - 1;
xterm.wraparoundMode = false;
xterm.write('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('a');
expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(1);
expect(xterm.lines.get(1)[1][1]).eql(' ');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('a');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1].length).eql(1);
expect(xterm.buffer.lines.get(1)[1][1]).eql(' ');
xterm.reset();
}
});
@@ -625,11 +625,11 @@ describe('xterm.js', function() {
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.write(high);
xterm.write(String.fromCharCode(i));
var tchar = xterm.lines.get(0)[0];
var tchar = xterm.buffer.lines.get(0)[0];
expect(tchar[1]).eql(high + String.fromCharCode(i));
expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1);
expect(xterm.lines.get(0)[1][1]).eql(' ');
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
xterm.reset();
}
});
@@ -638,30 +638,30 @@ describe('xterm.js', function() {
describe('unicode - combining characters', function() {
it('café', function () {
xterm.write('cafe\u0301');
expect(xterm.lines.get(0)[3][1]).eql('e\u0301');
expect(xterm.lines.get(0)[3][1].length).eql(2);
expect(xterm.lines.get(0)[3][2]).eql(1);
expect(xterm.buffer.lines.get(0)[3][1]).eql('e\u0301');
expect(xterm.buffer.lines.get(0)[3][1].length).eql(2);
expect(xterm.buffer.lines.get(0)[3][2]).eql(1);
});
it('café - end of line', function() {
xterm.x = xterm.cols - 1 - 3;
xterm.buffer.x = xterm.cols - 1 - 3;
xterm.write('cafe\u0301');
expect(xterm.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
expect(xterm.lines.get(0)[xterm.cols-1][1].length).eql(2);
expect(xterm.lines.get(0)[xterm.cols-1][2]).eql(1);
expect(xterm.lines.get(0)[1][1]).eql(' ');
expect(xterm.lines.get(0)[1][1].length).eql(1);
expect(xterm.lines.get(0)[1][2]).eql(1);
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1]).eql('e\u0301');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][1].length).eql(2);
expect(xterm.buffer.lines.get(0)[xterm.cols-1][2]).eql(1);
expect(xterm.buffer.lines.get(0)[1][1]).eql(' ');
expect(xterm.buffer.lines.get(0)[1][1].length).eql(1);
expect(xterm.buffer.lines.get(0)[1][2]).eql(1);
});
it('multiple combined é', function() {
xterm.wraparoundMode = true;
xterm.write(Array(100).join('e\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
expect(tchar[1]).eql('e\u0301');
expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1);
}
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('e\u0301');
expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(1);
@@ -670,12 +670,12 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true;
xterm.write(Array(100).join('\uD800\uDC00\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
expect(tchar[1]).eql('\uD800\uDC00\u0301');
expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(1);
}
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('\uD800\uDC00\u0301');
expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(1);
@@ -684,21 +684,21 @@ describe('xterm.js', function() {
describe('unicode - fullwidth characters', function() {
it('cursor movement even', function() {
expect(xterm.x).eql(0);
expect(xterm.buffer.x).eql(0);
xterm.write('¥');
expect(xterm.x).eql(2);
expect(xterm.buffer.x).eql(2);
});
it('cursor movement odd', function() {
xterm.x = 1;
expect(xterm.x).eql(1);
xterm.buffer.x = 1;
expect(xterm.buffer.x).eql(1);
xterm.write('¥');
expect(xterm.x).eql(3);
expect(xterm.buffer.x).eql(3);
});
it('line of ¥ even', function() {
xterm.wraparoundMode = true;
xterm.write(Array(50).join('¥'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0);
@@ -709,17 +709,17 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2);
}
}
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('¥');
expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(2);
});
it('line of ¥ odd', function() {
xterm.wraparoundMode = true;
xterm.x = 1;
xterm.buffer.x = 1;
xterm.write(Array(50).join('¥'));
for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0);
@@ -730,21 +730,21 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2);
}
}
tchar = xterm.lines.get(0)[xterm.cols-1];
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
expect(tchar[1]).eql(' ');
expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(1);
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('¥');
expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(2);
});
it('line of ¥ with combining odd', function() {
xterm.wraparoundMode = true;
xterm.x = 1;
xterm.buffer.x = 1;
xterm.write(Array(50).join('¥\u0301'));
for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0);
@@ -755,11 +755,11 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2);
}
}
tchar = xterm.lines.get(0)[xterm.cols-1];
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
expect(tchar[1]).eql(' ');
expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(1);
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('¥\u0301');
expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(2);
@@ -768,7 +768,7 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true;
xterm.write(Array(50).join('¥\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0);
@@ -779,17 +779,17 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2);
}
}
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('¥\u0301');
expect(tchar[1].length).eql(2);
expect(tchar[2]).eql(2);
});
it('line of surrogate fullwidth with combining odd', function() {
xterm.wraparoundMode = true;
xterm.x = 1;
xterm.buffer.x = 1;
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0);
@@ -800,11 +800,11 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2);
}
}
tchar = xterm.lines.get(0)[xterm.cols-1];
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
expect(tchar[1]).eql(' ');
expect(tchar[1].length).eql(1);
expect(tchar[2]).eql(1);
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('\ud843\ude6d\u0301');
expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(2);
@@ -813,7 +813,7 @@ describe('xterm.js', function() {
xterm.wraparoundMode = true;
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.lines.get(0)[i];
var tchar = xterm.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[1]).eql('');
expect(tchar[1].length).eql(0);
@@ -824,7 +824,7 @@ describe('xterm.js', function() {
expect(tchar[2]).eql(2);
}
}
tchar = xterm.lines.get(1)[0];
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[1]).eql('\ud843\ude6d\u0301');
expect(tchar[1].length).eql(3);
expect(tchar[2]).eql(2);
@@ -834,44 +834,44 @@ describe('xterm.js', function() {
describe('insert mode', function() {
it('halfwidth - all', function () {
xterm.write(Array(9).join('0123456789').slice(-80));
xterm.x = 10;
xterm.y = 0;
xterm.buffer.x = 10;
xterm.buffer.y = 0;
xterm.insertMode = true;
xterm.write('abcde');
expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines.get(0)[10][1]).eql('a');
expect(xterm.lines.get(0)[14][1]).eql('e');
expect(xterm.lines.get(0)[15][1]).eql('0');
expect(xterm.lines.get(0)[79][1]).eql('4');
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[10][1]).eql('a');
expect(xterm.buffer.lines.get(0)[14][1]).eql('e');
expect(xterm.buffer.lines.get(0)[15][1]).eql('0');
expect(xterm.buffer.lines.get(0)[79][1]).eql('4');
});
it('fullwidth - insert', function() {
xterm.write(Array(9).join('0123456789').slice(-80));
xterm.x = 10;
xterm.y = 0;
xterm.buffer.x = 10;
xterm.buffer.y = 0;
xterm.insertMode = true;
xterm.write('¥¥¥');
expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines.get(0)[10][1]).eql('¥');
expect(xterm.lines.get(0)[11][1]).eql('');
expect(xterm.lines.get(0)[14][1]).eql('¥');
expect(xterm.lines.get(0)[15][1]).eql('');
expect(xterm.lines.get(0)[79][1]).eql('3');
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[10][1]).eql('¥');
expect(xterm.buffer.lines.get(0)[11][1]).eql('');
expect(xterm.buffer.lines.get(0)[14][1]).eql('¥');
expect(xterm.buffer.lines.get(0)[15][1]).eql('');
expect(xterm.buffer.lines.get(0)[79][1]).eql('3');
});
it('fullwidth - right border', function() {
xterm.write(Array(41).join('¥'));
xterm.x = 10;
xterm.y = 0;
xterm.buffer.x = 10;
xterm.buffer.y = 0;
xterm.insertMode = true;
xterm.write('a');
expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines.get(0)[10][1]).eql('a');
expect(xterm.lines.get(0)[11][1]).eql('¥');
expect(xterm.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[10][1]).eql('a');
expect(xterm.buffer.lines.get(0)[11][1]).eql('¥');
expect(xterm.buffer.lines.get(0)[79][1]).eql(' '); // fullwidth char got replaced
xterm.write('b');
expect(xterm.lines.get(0).length).eql(xterm.cols);
expect(xterm.lines.get(0)[11][1]).eql('b');
expect(xterm.lines.get(0)[12][1]).eql('¥');
expect(xterm.lines.get(0)[79][1]).eql(''); // empty cell after fullwidth
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[11][1]).eql('b');
expect(xterm.buffer.lines.get(0)[12][1]).eql('¥');
expect(xterm.buffer.lines.get(0)[79][1]).eql(''); // empty cell after fullwidth
});
});
});
+111 -128
View File
@@ -10,6 +10,7 @@
* @license MIT
*/
import { BufferSet } from './BufferSet';
import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './EventEmitter';
import { Viewport } from './Viewport';
@@ -141,33 +142,10 @@ function Terminal(options) {
this.on('data', options.handler);
}
/**
* The scroll position of the y cursor, ie. ybase + y = the y position within the entire
* buffer
*/
this.ybase = 0;
/**
* The scroll position of the viewport
*/
this.ydisp = 0;
/**
* The cursor's x position after ybase
*/
this.x = 0;
/**
* The cursor's y position after ybase
*/
this.y = 0;
this.cursorState = 0;
this.cursorHidden = false;
this.convertEol;
this.queue = '';
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;
this.customKeyEventHandler = null;
this.cursorBlinkInterval = null;
@@ -177,7 +155,6 @@ function Terminal(options) {
this.originMode = false;
this.insertMode = false;
this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
this.normal = null;
// charset
this.charset = null;
@@ -243,21 +220,23 @@ function Terminal(options) {
// leftover surrogate high from previous write invocation
this.surrogate_high = '';
/**
* An array of all lines in the entire buffer, including the prompt. The lines are array of
* characters which are 2-length arrays where [0] is an attribute and [1] is the character.
*/
this.lines = new CircularList(this.scrollback);
// Create the terminal's buffers and set the current buffer
this.buffers = new BufferSet(this);
this.buffer = this.buffers.active; // Convenience shortcut;
this.buffers.on('activate', function (buffer) {
this._terminal.buffer = buffer;
});
var i = this.rows;
while (i--) {
this.lines.push(this.blankLine());
this.buffer.lines.push(this.blankLine());
}
// Ensure the selection manager has the correct buffer
if (this.selectionManager) {
this.selectionManager.setBuffer(this.lines);
this.selectionManager.setBuffer(this.buffer.lines);
}
this.tabs;
this.setupStops();
// Store if user went browsing history in scrollback
@@ -430,17 +409,17 @@ Terminal.prototype.setOption = function(key, value) {
}
if (this.options[key] !== value) {
if (this.lines.length > value) {
const amountToTrim = this.lines.length - value;
const needsRefresh = (this.ydisp - amountToTrim < 0);
this.lines.trimStart(amountToTrim);
this.ybase = Math.max(this.ybase - amountToTrim, 0);
this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
if (this.buffer.lines.length > value) {
const amountToTrim = this.buffer.lines.length - value;
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
this.buffer.lines.trimStart(amountToTrim);
this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
if (needsRefresh) {
this.refresh(0, this.rows - 1);
}
}
this.lines.maxLength = value;
this.buffer.lines.maxLength = value;
this.viewport.syncScrollArea();
}
break;
@@ -513,7 +492,7 @@ Terminal.prototype.blur = function() {
*/
Terminal.bindBlur = function (term) {
on(term.textarea, 'blur', function (ev) {
term.refresh(term.y, term.y);
term.refresh(term.buffer.y, term.buffer.y);
if (term.sendFocus) {
term.send(C0.ESC + '[O');
}
@@ -725,7 +704,9 @@ Terminal.prototype.open = function(parent, focus) {
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
this.renderer = new Renderer(this);
this.selectionManager = new SelectionManager(this, this.lines, this.rowContainer, this.charMeasure);
this.selectionManager = new SelectionManager(
this, this.buffer.lines, this.rowContainer, this.charMeasure
);
this.selectionManager.on('refresh', data => {
this.renderer.refreshSelection(data.start, data.end);
});
@@ -1156,7 +1137,7 @@ Terminal.prototype.queueLinkification = function(start, end) {
Terminal.prototype.showCursor = function() {
if (!this.cursorState) {
this.cursorState = 1;
this.refresh(this.y, this.y);
this.refresh(this.buffer.y, this.buffer.y);
}
};
@@ -1169,48 +1150,48 @@ Terminal.prototype.scroll = function(isWrapped) {
var row;
// Make room for the new row in lines
if (this.lines.length === this.lines.maxLength) {
this.lines.trimStart(1);
this.ybase--;
if (this.ydisp !== 0) {
this.ydisp--;
if (this.buffer.lines.length === this.buffer.lines.maxLength) {
this.buffer.lines.trimStart(1);
this.buffer.ybase--;
if (this.buffer.ydisp !== 0) {
this.buffer.ydisp--;
}
}
this.ybase++;
this.buffer.ybase++;
// TODO: Why is this done twice?
if (!this.userScrolling) {
this.ydisp = this.ybase;
this.buffer.ydisp = this.buffer.ybase;
}
// last line
row = this.ybase + this.rows - 1;
row = this.buffer.ybase + this.rows - 1;
// subtract the bottom scroll region
row -= this.rows - 1 - this.scrollBottom;
row -= this.rows - 1 - this.buffer.scrollBottom;
if (row === this.lines.length) {
if (row === this.buffer.lines.length) {
// Optimization: pushing is faster than splicing when they amount to the same behavior
this.lines.push(this.blankLine(undefined, isWrapped));
this.buffer.lines.push(this.blankLine(undefined, isWrapped));
} else {
// add our new line
this.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
}
if (this.scrollTop !== 0) {
if (this.ybase !== 0) {
this.ybase--;
if (this.buffer.scrollTop !== 0) {
if (this.buffer.ybase !== 0) {
this.buffer.ybase--;
if (!this.userScrolling) {
this.ydisp = this.ybase;
this.buffer.ydisp = this.buffer.ybase;
}
}
this.lines.splice(this.ybase + this.scrollTop, 1);
this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);
}
// this.maxRange();
this.updateRange(this.scrollTop);
this.updateRange(this.scrollBottom);
this.updateRange(this.buffer.scrollTop);
this.updateRange(this.buffer.scrollBottom);
/**
* This event is emitted whenever the terminal is scrolled.
@@ -1218,7 +1199,7 @@ Terminal.prototype.scroll = function(isWrapped) {
*
* @event scroll
*/
this.emit('scroll', this.ydisp);
this.emit('scroll', this.buffer.ydisp);
};
/**
@@ -1230,24 +1211,24 @@ Terminal.prototype.scroll = function(isWrapped) {
*/
Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
if (disp < 0) {
if (this.ydisp === 0) {
if (this.buffer.ydisp === 0) {
return;
}
this.userScrolling = true;
} else if (disp + this.ydisp >= this.ybase) {
} else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
this.userScrolling = false;
}
const oldYdisp = this.ydisp;
this.ydisp = Math.max(Math.min(this.ydisp + disp, this.ybase), 0);
const oldYdisp = this.buffer.ydisp;
this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);
// No change occurred, don't trigger scroll/refresh
if (oldYdisp === this.ydisp) {
if (oldYdisp === this.buffer.ydisp) {
return;
}
if (!suppressScrollEvent) {
this.emit('scroll', this.ydisp);
this.emit('scroll', this.buffer.ydisp);
}
this.refresh(0, this.rows - 1);
@@ -1265,14 +1246,14 @@ Terminal.prototype.scrollPages = function(pageCount) {
* Scrolls the display of the terminal to the top.
*/
Terminal.prototype.scrollToTop = function() {
this.scrollDisp(-this.ydisp);
this.scrollDisp(-this.buffer.ydisp);
};
/**
* Scrolls the display of the terminal to the bottom.
*/
Terminal.prototype.scrollToBottom = function() {
this.scrollDisp(this.ybase - this.ydisp);
this.scrollDisp(this.buffer.ybase - this.buffer.ydisp);
};
/**
@@ -1316,8 +1297,8 @@ Terminal.prototype.innerWrite = function() {
this.xoffSentToCatchUp = false;
}
this.refreshStart = this.y;
this.refreshEnd = this.y;
this.refreshStart = this.buffer.y;
this.refreshEnd = this.buffer.y;
// HACK: Set the parser state based on it's state at the time of return.
// This works around the bug #662 which saw the parser state reset in the
@@ -1327,7 +1308,7 @@ Terminal.prototype.innerWrite = function() {
var state = this.parser.parse(data);
this.parser.setState(state);
this.updateRange(this.y);
this.updateRange(this.buffer.y);
this.refresh(this.refreshStart, this.refreshEnd);
}
if (this.writeBuffer.length > 0) {
@@ -1480,7 +1461,7 @@ Terminal.prototype.keyDown = function(ev) {
this.restartCursorBlinking();
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
if (this.ybase !== this.ydisp) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this.scrollToBottom();
}
return false;
@@ -1958,10 +1939,10 @@ Terminal.prototype.resize = function(x, y) {
j = this.cols;
if (j < x) {
ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
i = this.lines.length;
i = this.buffer.lines.length;
while (i--) {
while (this.lines.get(i).length < x) {
this.lines.get(i).push(ch);
while (this.buffer.lines.get(i).length < x) {
this.buffer.lines.get(i).push(ch);
}
}
}
@@ -1975,21 +1956,21 @@ Terminal.prototype.resize = function(x, y) {
if (j < y) {
el = this.element;
while (j++ < y) {
// y is rows, not this.y
if (this.lines.length < y + this.ybase) {
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
// 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.ybase--;
this.buffer.ybase--;
addToY++;
if (this.ydisp > 0) {
if (this.buffer.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.ydisp--;
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.lines.push(this.blankLine());
this.buffer.lines.push(this.blankLine());
}
}
if (this.children.length < y) {
@@ -1998,14 +1979,14 @@ Terminal.prototype.resize = function(x, y) {
}
} else { // (j > y)
while (j-- > y) {
if (this.lines.length > y + this.ybase) {
if (this.lines.length > this.ybase + this.y + 1) {
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.lines.pop();
this.buffer.lines.pop();
} else {
// The line is the cursor, scroll down
this.ybase++;
this.ydisp++;
this.buffer.ybase++;
this.buffer.ydisp++;
}
}
if (this.children.length > y) {
@@ -2018,26 +1999,24 @@ Terminal.prototype.resize = function(x, y) {
this.rows = y;
// Make sure that the cursor stays on screen
if (this.y >= y) {
this.y = y - 1;
if (this.buffer.y >= y) {
this.buffer.y = y - 1;
}
if (addToY) {
this.y += addToY;
this.buffer.y += addToY;
}
if (this.x >= x) {
this.x = x - 1;
if (this.buffer.x >= x) {
this.buffer.x = x - 1;
}
this.scrollTop = 0;
this.scrollBottom = y - 1;
this.buffer.scrollTop = 0;
this.buffer.scrollBottom = y - 1;
this.charMeasure.measure();
this.refresh(0, this.rows - 1);
this.normal = null;
this.geometry = [this.cols, this.rows];
this.emit('resize', {terminal: this, cols: x, rows: y});
};
@@ -2073,16 +2052,16 @@ Terminal.prototype.maxRange = function() {
*/
Terminal.prototype.setupStops = function(i) {
if (i != null) {
if (!this.tabs[i]) {
if (!this.buffer.tabs[i]) {
i = this.prevStop(i);
}
} else {
this.tabs = {};
this.buffer.tabs = {};
i = 0;
}
for (; i < this.cols; i += this.getOption('tabStopWidth')) {
this.tabs[i] = true;
this.buffer.tabs[i] = true;
}
};
@@ -2092,8 +2071,8 @@ Terminal.prototype.setupStops = function(i) {
* @param {number} x The position to move the cursor to the previous tab stop.
*/
Terminal.prototype.prevStop = function(x) {
if (x == null) x = this.x;
while (!this.tabs[--x] && x > 0);
if (x == null) x = this.buffer.x;
while (!this.buffer.tabs[--x] && x > 0);
return x >= this.cols
? this.cols - 1
: x < 0 ? 0 : x;
@@ -2105,8 +2084,8 @@ Terminal.prototype.prevStop = function(x) {
* @param {number} x The position to move the cursor one tab stop forward.
*/
Terminal.prototype.nextStop = function(x) {
if (x == null) x = this.x;
while (!this.tabs[++x] && x < this.cols);
if (x == null) x = this.buffer.x;
while (!this.buffer.tabs[++x] && x < this.cols);
return x >= this.cols
? this.cols - 1
: x < 0 ? 0 : x;
@@ -2119,7 +2098,7 @@ Terminal.prototype.nextStop = function(x) {
* @param {number} y The line in which to operate.
*/
Terminal.prototype.eraseRight = function(x, y) {
var line = this.lines.get(this.ybase + y);
var line = this.buffer.lines.get(this.buffer.ybase + y);
if (!line) {
return;
}
@@ -2138,7 +2117,7 @@ Terminal.prototype.eraseRight = function(x, y) {
* @param {number} y The line in which to operate.
*/
Terminal.prototype.eraseLeft = function(x, y) {
var line = this.lines.get(this.ybase + y);
var line = this.buffer.lines.get(this.buffer.ybase + y);
if (!line) {
return;
}
@@ -2154,20 +2133,20 @@ Terminal.prototype.eraseLeft = function(x, y) {
* Clears the entire buffer, making the prompt line the new first line.
*/
Terminal.prototype.clear = function() {
if (this.ybase === 0 && this.y === 0) {
if (this.buffer.ybase === 0 && this.buffer.y === 0) {
// Don't clear if it's already clear
return;
}
this.lines.set(0, this.lines.get(this.ybase + this.y));
this.lines.length = 1;
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));
this.buffer.lines.length = 1;
this.buffer.ydisp = 0;
this.buffer.ybase = 0;
this.buffer.y = 0;
for (var i = 1; i < this.rows; i++) {
this.lines.push(this.blankLine());
this.buffer.lines.push(this.blankLine());
}
this.refresh(0, this.rows - 1);
this.emit('scroll', this.ydisp);
this.emit('scroll', this.buffer.ydisp);
};
/**
@@ -2244,7 +2223,7 @@ Terminal.prototype.handler = function(data) {
}
// Input is being sent to the terminal, the terminal should focus the prompt.
if (this.ybase !== this.ydisp) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this.scrollToBottom();
}
this.emit('data', data);
@@ -2274,14 +2253,14 @@ Terminal.prototype.handleTitle = function(title) {
* ESC D Index (IND is 0x84).
*/
Terminal.prototype.index = function() {
this.y++;
if (this.y > this.scrollBottom) {
this.y--;
this.buffer.y++;
if (this.buffer.y > this.buffer.scrollBottom) {
this.buffer.y--;
this.scroll();
}
// If the end of the line is hit, prevent this action from wrapping around to the next line.
if (this.x >= this.cols) {
this.x--;
if (this.buffer.x >= this.cols) {
this.buffer.x--;
}
};
@@ -2293,16 +2272,16 @@ Terminal.prototype.index = function() {
*/
Terminal.prototype.reverseIndex = function() {
var j;
if (this.y === this.scrollTop) {
if (this.buffer.y === this.buffer.scrollTop) {
// possibly move the code below to term.reverseScroll();
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
// blankLine(true) is xterm/linux behavior
this.lines.shiftElements(this.y + this.ybase, this.rows - 1, 1);
this.lines.set(this.y + this.ybase, this.blankLine(true));
this.updateRange(this.scrollTop);
this.updateRange(this.scrollBottom);
this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);
this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));
this.updateRange(this.buffer.scrollTop);
this.updateRange(this.buffer.scrollBottom);
} else {
this.y--;
this.buffer.y--;
}
};
@@ -2315,9 +2294,13 @@ Terminal.prototype.reset = function() {
this.options.cols = this.cols;
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();
};
@@ -2327,7 +2310,7 @@ Terminal.prototype.reset = function() {
* ESC H Tab Set (HTS is 0x88).
*/
Terminal.prototype.tabSet = function() {
this.tabs[this.x] = true;
this.buffer.tabs[this.buffer.x] = true;
};
/**