Merge branch 'master' into text-render-layer
This commit is contained in:
+20
-25
@@ -22,8 +22,7 @@ export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
|
|||||||
* - scroll position
|
* - scroll position
|
||||||
*/
|
*/
|
||||||
export class Buffer implements IBuffer {
|
export class Buffer implements IBuffer {
|
||||||
private _lines: CircularList<LineData>;
|
public lines: CircularList<LineData>;
|
||||||
|
|
||||||
public ydisp: number;
|
public ydisp: number;
|
||||||
public ybase: number;
|
public ybase: number;
|
||||||
public y: number;
|
public y: number;
|
||||||
@@ -48,10 +47,6 @@ export class Buffer implements IBuffer {
|
|||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get lines(): CircularList<LineData> {
|
|
||||||
return this._lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get hasScrollback(): boolean {
|
public get hasScrollback(): boolean {
|
||||||
return this._hasScrollback && this.lines.maxLength > this._terminal.rows;
|
return this._hasScrollback && this.lines.maxLength > this._terminal.rows;
|
||||||
}
|
}
|
||||||
@@ -81,7 +76,7 @@ export class Buffer implements IBuffer {
|
|||||||
* Fills the buffer's viewport with blank lines.
|
* Fills the buffer's viewport with blank lines.
|
||||||
*/
|
*/
|
||||||
public fillViewportRows(): void {
|
public fillViewportRows(): void {
|
||||||
if (this._lines.length === 0) {
|
if (this.lines.length === 0) {
|
||||||
let i = this._terminal.rows;
|
let i = this._terminal.rows;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
this.lines.push(this._terminal.blankLine());
|
this.lines.push(this._terminal.blankLine());
|
||||||
@@ -97,7 +92,7 @@ export class Buffer implements IBuffer {
|
|||||||
this.ybase = 0;
|
this.ybase = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
this.x = 0;
|
this.x = 0;
|
||||||
this._lines = new CircularList<LineData>(this._getCorrectBufferLength(this._terminal.rows));
|
this.lines = new CircularList<LineData>(this._getCorrectBufferLength(this._terminal.rows));
|
||||||
this.scrollTop = 0;
|
this.scrollTop = 0;
|
||||||
this.scrollBottom = this._terminal.rows - 1;
|
this.scrollBottom = this._terminal.rows - 1;
|
||||||
this.setupTabStops();
|
this.setupTabStops();
|
||||||
@@ -112,19 +107,19 @@ export class Buffer implements IBuffer {
|
|||||||
// Increase max length if needed before adjustments to allow space to fill
|
// Increase max length if needed before adjustments to allow space to fill
|
||||||
// as required.
|
// as required.
|
||||||
const newMaxLength = this._getCorrectBufferLength(newRows);
|
const newMaxLength = this._getCorrectBufferLength(newRows);
|
||||||
if (newMaxLength > this._lines.maxLength) {
|
if (newMaxLength > this.lines.maxLength) {
|
||||||
this._lines.maxLength = newMaxLength;
|
this.lines.maxLength = newMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following adjustments should only happen if the buffer has been
|
// The following adjustments should only happen if the buffer has been
|
||||||
// initialized/filled.
|
// initialized/filled.
|
||||||
if (this._lines.length > 0) {
|
if (this.lines.length > 0) {
|
||||||
// Deal with columns increasing (we don't do anything when columns reduce)
|
// Deal with columns increasing (we don't do anything when columns reduce)
|
||||||
if (this._terminal.cols < newCols) {
|
if (this._terminal.cols < newCols) {
|
||||||
const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
|
const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
|
||||||
for (let i = 0; i < this._lines.length; i++) {
|
for (let i = 0; i < this.lines.length; i++) {
|
||||||
while (this._lines.get(i).length < newCols) {
|
while (this.lines.get(i).length < newCols) {
|
||||||
this._lines.get(i).push(ch);
|
this.lines.get(i).push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,8 +128,8 @@ export class Buffer implements IBuffer {
|
|||||||
let addToY = 0;
|
let addToY = 0;
|
||||||
if (this._terminal.rows < newRows) {
|
if (this._terminal.rows < newRows) {
|
||||||
for (let y = this._terminal.rows; y < newRows; y++) {
|
for (let y = this._terminal.rows; y < newRows; y++) {
|
||||||
if (this._lines.length < newRows + this.ybase) {
|
if (this.lines.length < newRows + this.ybase) {
|
||||||
if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {
|
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,
|
// There is room above the buffer and there are no empty elements below the line,
|
||||||
// scroll up
|
// scroll up
|
||||||
this.ybase--;
|
this.ybase--;
|
||||||
@@ -146,16 +141,16 @@ export class Buffer implements IBuffer {
|
|||||||
} else {
|
} else {
|
||||||
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
// 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
|
// are blank lines after the cursor
|
||||||
this._lines.push(this._terminal.blankLine(undefined, undefined, newCols));
|
this.lines.push(this._terminal.blankLine(undefined, undefined, newCols));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // (this._terminal.rows >= newRows)
|
} else { // (this._terminal.rows >= newRows)
|
||||||
for (let y = this._terminal.rows; y > newRows; y--) {
|
for (let y = this._terminal.rows; y > newRows; y--) {
|
||||||
if (this._lines.length > newRows + this.ybase) {
|
if (this.lines.length > newRows + this.ybase) {
|
||||||
if (this._lines.length > this.ybase + this.y + 1) {
|
if (this.lines.length > this.ybase + this.y + 1) {
|
||||||
// The line is a blank line below the cursor, remove it
|
// The line is a blank line below the cursor, remove it
|
||||||
this._lines.pop();
|
this.lines.pop();
|
||||||
} else {
|
} else {
|
||||||
// The line is the cursor, scroll down
|
// The line is the cursor, scroll down
|
||||||
this.ybase++;
|
this.ybase++;
|
||||||
@@ -167,15 +162,15 @@ export class Buffer implements IBuffer {
|
|||||||
|
|
||||||
// Reduce max length if needed after adjustments, this is done after as it
|
// Reduce max length if needed after adjustments, this is done after as it
|
||||||
// would otherwise cut data from the bottom of the buffer.
|
// would otherwise cut data from the bottom of the buffer.
|
||||||
if (newMaxLength < this._lines.maxLength) {
|
if (newMaxLength < this.lines.maxLength) {
|
||||||
// Trim from the top of the buffer and adjust ybase and ydisp.
|
// Trim from the top of the buffer and adjust ybase and ydisp.
|
||||||
const amountToTrim = this._lines.length - newMaxLength;
|
const amountToTrim = this.lines.length - newMaxLength;
|
||||||
if (amountToTrim > 0) {
|
if (amountToTrim > 0) {
|
||||||
this._lines.trimStart(amountToTrim);
|
this.lines.trimStart(amountToTrim);
|
||||||
this.ybase = Math.max(this.ybase - amountToTrim, 0);
|
this.ybase = Math.max(this.ybase - amountToTrim, 0);
|
||||||
this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
|
this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
|
||||||
}
|
}
|
||||||
this._lines.maxLength = newMaxLength;
|
this.lines.maxLength = newMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that the cursor stays on screen
|
// Make sure that the cursor stays on screen
|
||||||
@@ -310,7 +305,7 @@ export class Buffer implements IBuffer {
|
|||||||
public addMarker(y: number): Marker {
|
public addMarker(y: number): Marker {
|
||||||
const marker = new Marker(y);
|
const marker = new Marker(y);
|
||||||
this.markers.push(marker);
|
this.markers.push(marker);
|
||||||
marker.disposables.push(this._lines.addDisposableListener('trim', amount => {
|
marker.disposables.push(this.lines.addDisposableListener('trim', amount => {
|
||||||
marker.line -= amount;
|
marker.line -= amount;
|
||||||
// The marker should be disposed when the line is trimmed from the buffer
|
// The marker should be disposed when the line is trimmed from the buffer
|
||||||
if (marker.line < 0) {
|
if (marker.line < 0) {
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
import { XtermListener } from './Types';
|
import { XtermListener } from './Types';
|
||||||
import { IEventEmitter, IDisposable } from 'xterm';
|
import { IEventEmitter, IDisposable } from 'xterm';
|
||||||
|
|
||||||
export class EventEmitter implements IEventEmitter {
|
export class EventEmitter implements IEventEmitter, IDisposable {
|
||||||
private _events: {[type: string]: XtermListener[]};
|
private _events: {[type: string]: XtermListener[]};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -75,7 +75,7 @@ export class EventEmitter implements IEventEmitter {
|
|||||||
return this._events[type] || [];
|
return this._events[type] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected destroy(): void {
|
public dispose(): void {
|
||||||
this._events = {};
|
this._events = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+110
-70
@@ -23,6 +23,10 @@ export class InputHandler implements IInputHandler {
|
|||||||
|
|
||||||
public addChar(char: string, code: number): void {
|
public addChar(char: string, code: number): void {
|
||||||
if (char >= ' ') {
|
if (char >= ' ') {
|
||||||
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
// calculate print space
|
// calculate print space
|
||||||
// expensive call, therefore we save width in line buffer
|
// expensive call, therefore we save width in line buffer
|
||||||
const chWidth = wcwidth(code);
|
const chWidth = wcwidth(code);
|
||||||
@@ -35,42 +39,42 @@ export class InputHandler implements IInputHandler {
|
|||||||
this._terminal.emit('a11y.char', char);
|
this._terminal.emit('a11y.char', char);
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
let row = buffer.y + buffer.ybase;
|
||||||
|
|
||||||
// insert combining char in last cell
|
// insert combining char in last cell
|
||||||
// FIXME: needs handling after cursor jumps
|
// FIXME: needs handling after cursor jumps
|
||||||
if (!chWidth && this._terminal.buffer.x) {
|
if (!chWidth && buffer.x) {
|
||||||
// dont overflow left
|
// dont overflow left
|
||||||
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {
|
if (buffer.lines.get(row)[buffer.x - 1]) {
|
||||||
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {
|
if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {
|
||||||
// found empty cell after fullwidth, need to go 2 cells back
|
// found empty cell after fullwidth, need to go 2 cells back
|
||||||
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) {
|
if (buffer.lines.get(row)[buffer.x - 2]) {
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char;
|
buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char;
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][3] = char.charCodeAt(0);
|
buffer.lines.get(row)[buffer.x - 2][3] = char.charCodeAt(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char;
|
buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char;
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][3] = char.charCodeAt(0);
|
buffer.lines.get(row)[buffer.x - 1][3] = char.charCodeAt(0);
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(this._terminal.buffer.y);
|
this._terminal.updateRange(buffer.y);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// goto next line if ch would overflow
|
// goto next line if ch would overflow
|
||||||
// TODO: needs a global min terminal width of 2
|
// TODO: needs a global min terminal width of 2
|
||||||
if (this._terminal.buffer.x + chWidth - 1 >= this._terminal.cols) {
|
if (buffer.x + chWidth - 1 >= this._terminal.cols) {
|
||||||
// autowrap - DECAWM
|
// autowrap - DECAWM
|
||||||
if (this._terminal.wraparoundMode) {
|
if (this._terminal.wraparoundMode) {
|
||||||
this._terminal.buffer.x = 0;
|
buffer.x = 0;
|
||||||
this._terminal.buffer.y++;
|
buffer.y++;
|
||||||
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
if (buffer.y > buffer.scrollBottom) {
|
||||||
this._terminal.buffer.y--;
|
buffer.y--;
|
||||||
this._terminal.scroll(true);
|
this._terminal.scroll(true);
|
||||||
} else {
|
} else {
|
||||||
// The line already exists (eg. the initial viewport), mark it as a
|
// The line already exists (eg. the initial viewport), mark it as a
|
||||||
// wrapped line
|
// wrapped line
|
||||||
(<any>this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true;
|
(<any>buffer.lines.get(buffer.y)).isWrapped = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (chWidth === 2) { // FIXME: check for xterm behavior
|
if (chWidth === 2) { // FIXME: check for xterm behavior
|
||||||
@@ -78,7 +82,7 @@ export class InputHandler implements IInputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
row = buffer.y + buffer.ybase;
|
||||||
|
|
||||||
// insert mode: move characters to right
|
// insert mode: move characters to right
|
||||||
if (this._terminal.insertMode) {
|
if (this._terminal.insertMode) {
|
||||||
@@ -86,26 +90,26 @@ export class InputHandler implements IInputHandler {
|
|||||||
for (let moves = 0; moves < chWidth; ++moves) {
|
for (let moves = 0; moves < chWidth; ++moves) {
|
||||||
// remove last cell, if it's width is 0
|
// remove last cell, if it's width is 0
|
||||||
// we have to adjust the second last cell as well
|
// we have to adjust the second last cell as well
|
||||||
const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();
|
const removed = buffer.lines.get(buffer.y + buffer.ybase).pop();
|
||||||
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
|
if (removed[CHAR_DATA_WIDTH_INDEX] === 0
|
||||||
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]
|
&& buffer.lines.get(row)[this._terminal.cols - 2]
|
||||||
&& this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
|
&& buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)];
|
buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert empty cell at cursor
|
// insert empty cell at cursor
|
||||||
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]);
|
buffer.lines.get(row).splice(buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)];
|
buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)];
|
||||||
this._terminal.buffer.x++;
|
buffer.x++;
|
||||||
this._terminal.updateRange(this._terminal.buffer.y);
|
this._terminal.updateRange(buffer.y);
|
||||||
|
|
||||||
// fullwidth char - set next cell width to zero and advance cursor
|
// fullwidth char - set next cell width to zero and advance cursor
|
||||||
if (chWidth === 2) {
|
if (chWidth === 2) {
|
||||||
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined];
|
buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined];
|
||||||
this._terminal.buffer.x++;
|
buffer.x++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,17 +127,20 @@ export class InputHandler implements IInputHandler {
|
|||||||
* Line Feed or New Line (NL). (LF is Ctrl-J).
|
* Line Feed or New Line (NL). (LF is Ctrl-J).
|
||||||
*/
|
*/
|
||||||
public lineFeed(): void {
|
public lineFeed(): void {
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
if (this._terminal.convertEol) {
|
if (this._terminal.convertEol) {
|
||||||
this._terminal.buffer.x = 0;
|
buffer.x = 0;
|
||||||
}
|
}
|
||||||
this._terminal.buffer.y++;
|
buffer.y++;
|
||||||
if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {
|
if (buffer.y > buffer.scrollBottom) {
|
||||||
this._terminal.buffer.y--;
|
buffer.y--;
|
||||||
this._terminal.scroll();
|
this._terminal.scroll();
|
||||||
}
|
}
|
||||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||||
if (this._terminal.buffer.x >= this._terminal.cols) {
|
if (buffer.x >= this._terminal.cols) {
|
||||||
this._terminal.buffer.x--;
|
buffer.x--;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This event is emitted whenever the terminal outputs a LF or NL.
|
* This event is emitted whenever the terminal outputs a LF or NL.
|
||||||
@@ -199,13 +206,16 @@ export class InputHandler implements IInputHandler {
|
|||||||
let param = params[0];
|
let param = params[0];
|
||||||
if (param < 1) param = 1;
|
if (param < 1) param = 1;
|
||||||
|
|
||||||
const row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
// make buffer local for faster access
|
||||||
let j = this._terminal.buffer.x;
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
|
const row = buffer.y + buffer.ybase;
|
||||||
|
let j = buffer.x;
|
||||||
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
||||||
|
|
||||||
while (param-- && j < this._terminal.cols) {
|
while (param-- && j < this._terminal.cols) {
|
||||||
this._terminal.buffer.lines.get(row).splice(j++, 0, ch);
|
buffer.lines.get(row).splice(j++, 0, ch);
|
||||||
this._terminal.buffer.lines.get(row).pop();
|
buffer.lines.get(row).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,20 +457,24 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
let row: number = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
|
||||||
|
|
||||||
let scrollBottomRowsOffset = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
|
// make buffer local for faster access
|
||||||
let scrollBottomAbsolute = this._terminal.rows - 1 + this._terminal.buffer.ybase - scrollBottomRowsOffset + 1;
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
|
let row: number = buffer.y + buffer.ybase;
|
||||||
|
|
||||||
|
let scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom;
|
||||||
|
let scrollBottomAbsolute = this._terminal.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1;
|
||||||
while (param--) {
|
while (param--) {
|
||||||
// test: echo -e '\e[44m\e[1L\e[0m'
|
// test: echo -e '\e[44m\e[1L\e[0m'
|
||||||
// blankLine(true) - xterm/linux behavior
|
// blankLine(true) - xterm/linux behavior
|
||||||
this._terminal.buffer.lines.splice(scrollBottomAbsolute - 1, 1);
|
buffer.lines.splice(scrollBottomAbsolute - 1, 1);
|
||||||
this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));
|
buffer.lines.splice(row, 0, this._terminal.blankLine(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.buffer.y);
|
this._terminal.updateRange(buffer.y);
|
||||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
this._terminal.updateRange(buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -472,21 +486,25 @@ export class InputHandler implements IInputHandler {
|
|||||||
if (param < 1) {
|
if (param < 1) {
|
||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
const row: number = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
|
const row: number = buffer.y + buffer.ybase;
|
||||||
|
|
||||||
let j: number;
|
let j: number;
|
||||||
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
|
j = this._terminal.rows - 1 - buffer.scrollBottom;
|
||||||
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;
|
j = this._terminal.rows - 1 + buffer.ybase - j;
|
||||||
while (param--) {
|
while (param--) {
|
||||||
// test: echo -e '\e[44m\e[1M\e[0m'
|
// test: echo -e '\e[44m\e[1M\e[0m'
|
||||||
// blankLine(true) - xterm/linux behavior
|
// blankLine(true) - xterm/linux behavior
|
||||||
this._terminal.buffer.lines.splice(row, 1);
|
buffer.lines.splice(row, 1);
|
||||||
this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true));
|
buffer.lines.splice(j, 0, this._terminal.blankLine(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.buffer.y);
|
this._terminal.updateRange(buffer.y);
|
||||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
this._terminal.updateRange(buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -499,14 +517,17 @@ export class InputHandler implements IInputHandler {
|
|||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
|
const row = buffer.y + buffer.ybase;
|
||||||
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);
|
buffer.lines.get(row).splice(buffer.x, 1);
|
||||||
this._terminal.buffer.lines.get(row).push(ch);
|
buffer.lines.get(row).push(ch);
|
||||||
}
|
}
|
||||||
this._terminal.updateRange(this._terminal.buffer.y);
|
this._terminal.updateRange(buffer.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -514,13 +535,17 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public scrollUp(params: number[]): void {
|
public scrollUp(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);
|
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);
|
||||||
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());
|
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, this._terminal.blankLine());
|
||||||
}
|
}
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.buffer.scrollTop);
|
this._terminal.updateRange(buffer.scrollTop);
|
||||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
this._terminal.updateRange(buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -528,13 +553,17 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public scrollDown(params: number[]): void {
|
public scrollDown(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);
|
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
|
||||||
this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());
|
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, this._terminal.blankLine());
|
||||||
}
|
}
|
||||||
// this.maxRange();
|
// this.maxRange();
|
||||||
this._terminal.updateRange(this._terminal.buffer.scrollTop);
|
this._terminal.updateRange(buffer.scrollTop);
|
||||||
this._terminal.updateRange(this._terminal.buffer.scrollBottom);
|
this._terminal.updateRange(buffer.scrollBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -547,12 +576,15 @@ export class InputHandler implements IInputHandler {
|
|||||||
param = 1;
|
param = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = this._terminal.buffer.y + this._terminal.buffer.ybase;
|
// make buffer local for faster access
|
||||||
let j = this._terminal.buffer.x;
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
|
const row = buffer.y + buffer.ybase;
|
||||||
|
let j = buffer.x;
|
||||||
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm
|
||||||
|
|
||||||
while (param-- && j < this._terminal.cols) {
|
while (param-- && j < this._terminal.cols) {
|
||||||
this._terminal.buffer.lines.get(row)[j++] = ch;
|
buffer.lines.get(row)[j++] = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,8 +593,12 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public cursorBackwardTab(params: number[]): void {
|
public cursorBackwardTab(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
|
|
||||||
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
this._terminal.buffer.x = this._terminal.buffer.prevStop();
|
buffer.x = buffer.prevStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,11 +638,15 @@ export class InputHandler implements IInputHandler {
|
|||||||
*/
|
*/
|
||||||
public repeatPrecedingCharacter(params: number[]): void {
|
public repeatPrecedingCharacter(params: number[]): void {
|
||||||
let param = params[0] || 1;
|
let param = params[0] || 1;
|
||||||
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y);
|
|
||||||
const ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32];
|
// make buffer local for faster access
|
||||||
|
const buffer = this._terminal.buffer;
|
||||||
|
|
||||||
|
const line = buffer.lines.get(buffer.ybase + buffer.y);
|
||||||
|
const ch = line[buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32];
|
||||||
|
|
||||||
while (param--) {
|
while (param--) {
|
||||||
line[this._terminal.buffer.x++] = ch;
|
line[buffer.x++] = ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe('Linkifier', () => {
|
|||||||
terminal = new MockTerminal();
|
terminal = new MockTerminal();
|
||||||
terminal.cols = 100;
|
terminal.cols = 100;
|
||||||
terminal.buffer = new MockBuffer();
|
terminal.buffer = new MockBuffer();
|
||||||
terminal.buffer.lines = new CircularList<LineData>(20);
|
(<MockBuffer>terminal.buffer).setLines(new CircularList<LineData>(20));
|
||||||
terminal.buffer.ydisp = 0;
|
terminal.buffer.ydisp = 0;
|
||||||
linkifier = new TestLinkifier(terminal);
|
linkifier = new TestLinkifier(terminal);
|
||||||
mouseZoneManager = new TestMouseZoneManager();
|
mouseZoneManager = new TestMouseZoneManager();
|
||||||
|
|||||||
+28
-16
@@ -38,6 +38,7 @@ import { Linkifier } from './Linkifier';
|
|||||||
import { SelectionManager } from './SelectionManager';
|
import { SelectionManager } from './SelectionManager';
|
||||||
import { CharMeasure } from './utils/CharMeasure';
|
import { CharMeasure } from './utils/CharMeasure';
|
||||||
import * as Browser from './shared/utils/Browser';
|
import * as Browser from './shared/utils/Browser';
|
||||||
|
import * as Dom from './utils/Dom';
|
||||||
import * as Strings from './Strings';
|
import * as Strings from './Strings';
|
||||||
import { MouseHelper } from './utils/MouseHelper';
|
import { MouseHelper } from './utils/MouseHelper';
|
||||||
import { clone } from './utils/Clone';
|
import { clone } from './utils/Clone';
|
||||||
@@ -46,7 +47,8 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
|
|||||||
import { MouseZoneManager } from './input/MouseZoneManager';
|
import { MouseZoneManager } from './input/MouseZoneManager';
|
||||||
import { AccessibilityManager } from './AccessibilityManager';
|
import { AccessibilityManager } from './AccessibilityManager';
|
||||||
import { ScreenDprMonitor } from './utils/ScreenDprMonitor';
|
import { ScreenDprMonitor } from './utils/ScreenDprMonitor';
|
||||||
import { ITheme, ILocalizableStrings, IMarker } from 'xterm';
|
import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm';
|
||||||
|
import { removeTerminalFromCache } from './renderer/atlas/CharAtlas';
|
||||||
|
|
||||||
// reg + shift key mappings for digits and special chars
|
// reg + shift key mappings for digits and special chars
|
||||||
const KEYCODE_KEY_MAPPINGS = {
|
const KEYCODE_KEY_MAPPINGS = {
|
||||||
@@ -122,11 +124,13 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
|||||||
rightClickSelectsWord: Browser.isMac
|
rightClickSelectsWord: Browser.isMac
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal {
|
export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
|
||||||
public textarea: HTMLTextAreaElement;
|
public textarea: HTMLTextAreaElement;
|
||||||
public element: HTMLElement;
|
public element: HTMLElement;
|
||||||
public screenElement: HTMLElement;
|
public screenElement: HTMLElement;
|
||||||
|
|
||||||
|
private _disposables: IDisposable[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTMLElement that the terminal is created in, set by Terminal.open.
|
* The HTMLElement that the terminal is created in, set by Terminal.open.
|
||||||
*/
|
*/
|
||||||
@@ -248,7 +252,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
|||||||
this._setup();
|
this._setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
this._disposables.forEach(d => d.dispose());
|
||||||
|
this._disposables.length = 0;
|
||||||
|
removeTerminalFromCache(this);
|
||||||
|
this.handler = () => {};
|
||||||
|
this.write = () => {};
|
||||||
|
if (this.element && this.element.parentNode) {
|
||||||
|
this.element.parentNode.removeChild(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use dispose instead.
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private _setup(): void {
|
private _setup(): void {
|
||||||
|
this._disposables = [];
|
||||||
|
|
||||||
Object.keys(DEFAULT_OPTIONS).forEach((key) => {
|
Object.keys(DEFAULT_OPTIONS).forEach((key) => {
|
||||||
if (this.options[key] == null) {
|
if (this.options[key] == null) {
|
||||||
this.options[key] = DEFAULT_OPTIONS[key];
|
this.options[key] = DEFAULT_OPTIONS[key];
|
||||||
@@ -690,7 +715,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
|||||||
this.on('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio));
|
this.on('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio));
|
||||||
// dprchange should handle this case, we need this as well for browsers that don't support the
|
// dprchange should handle this case, we need this as well for browsers that don't support the
|
||||||
// matchMedia query.
|
// matchMedia query.
|
||||||
window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio));
|
this._disposables.push(Dom.addDisposableListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio)));
|
||||||
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows));
|
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows));
|
||||||
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
|
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
|
||||||
|
|
||||||
@@ -1083,19 +1108,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the terminal.
|
|
||||||
*/
|
|
||||||
public destroy(): void {
|
|
||||||
super.destroy();
|
|
||||||
this.handler = () => {};
|
|
||||||
this.write = () => {};
|
|
||||||
if (this.element && this.element.parentNode) {
|
|
||||||
this.element.parentNode.removeChild(this.element);
|
|
||||||
}
|
|
||||||
// this.emit('close');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the renderer to refresh terminal content between two rows (inclusive) at the next
|
* Tells the renderer to refresh terminal content between two rows (inclusive) at the next
|
||||||
* opportunity.
|
* opportunity.
|
||||||
|
|||||||
+1
-1
@@ -248,7 +248,7 @@ export interface ITerminalOptions extends IPublicTerminalOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IBuffer {
|
export interface IBuffer {
|
||||||
lines: ICircularList<LineData>;
|
readonly lines: ICircularList<LineData>;
|
||||||
ydisp: number;
|
ydisp: number;
|
||||||
ybase: number;
|
ybase: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ let charAtlasCache: ICharAtlasCacheEntry[] = [];
|
|||||||
export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise<ImageBitmap> {
|
export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise<ImageBitmap> {
|
||||||
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
||||||
|
|
||||||
|
// TODO: Currently if a terminal changes configs it will not free the entry reference (until it's disposed)
|
||||||
|
|
||||||
// Check to see if the terminal already owns this config
|
// Check to see if the terminal already owns this config
|
||||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||||
const entry = charAtlasCache[i];
|
const entry = charAtlasCache[i];
|
||||||
@@ -70,3 +72,23 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC
|
|||||||
charAtlasCache.push(newEntry);
|
charAtlasCache.push(newEntry);
|
||||||
return newEntry.bitmap;
|
return newEntry.bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a terminal reference from the cache, allowing its memory to be freed.
|
||||||
|
* @param terminal The terminal to remove.
|
||||||
|
*/
|
||||||
|
export function removeTerminalFromCache(terminal: ITerminal): void {
|
||||||
|
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||||
|
const index = charAtlasCache[i].ownedBy.indexOf(terminal);
|
||||||
|
if (index !== -1) {
|
||||||
|
if (charAtlasCache[i].ownedBy.length === 1) {
|
||||||
|
// Remove the cache entry if it's the only terminal
|
||||||
|
charAtlasCache.splice(i, 1);
|
||||||
|
} else {
|
||||||
|
// Remove the reference from the cache entry
|
||||||
|
charAtlasCache[i].ownedBy.splice(index, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ export class MockTerminal implements ITerminal {
|
|||||||
selectAll(): void {
|
selectAll(): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
dispose(): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
@@ -306,6 +309,9 @@ export class MockBuffer implements IBuffer {
|
|||||||
prevStop(x?: number): number {
|
prevStop(x?: number): number {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
setLines(lines: ICircularList<[number, string, number, number][]>): void {
|
||||||
|
this.lines = lines;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockRenderer implements IRenderer {
|
export class MockRenderer implements IRenderer {
|
||||||
|
|||||||
Vendored
+9
-1
@@ -251,7 +251,7 @@ declare module 'xterm' {
|
|||||||
/**
|
/**
|
||||||
* The class that represents an xterm.js terminal.
|
* The class that represents an xterm.js terminal.
|
||||||
*/
|
*/
|
||||||
export class Terminal implements IEventEmitter {
|
export class Terminal implements IEventEmitter, IDisposable {
|
||||||
/**
|
/**
|
||||||
* The element containing the terminal.
|
* The element containing the terminal.
|
||||||
*/
|
*/
|
||||||
@@ -451,8 +451,16 @@ declare module 'xterm' {
|
|||||||
*/
|
*/
|
||||||
selectLines(start: number, end: number): void;
|
selectLines(start: number, end: number): void;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disposes of the terminal, detaching it from the DOM and removing any
|
||||||
|
* active listeners.
|
||||||
|
*/
|
||||||
|
dispose(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroys the terminal and detaches it from the DOM.
|
* Destroys the terminal and detaches it from the DOM.
|
||||||
|
*
|
||||||
|
* @deprecated Use dispose() instead.
|
||||||
*/
|
*/
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user