Merge branch 'master' into webgl2

This commit is contained in:
Daniel Imms
2019-01-31 14:10:35 -08:00
committed by GitHub
19 changed files with 596 additions and 332 deletions
-1
View File
@@ -5,7 +5,6 @@
* This file is the entry point for browserify.
*/
const cp = require('child_process');
const path = require('path');
const webpack = require('webpack');
const startServer = require('./server.js');
+51 -32
View File
@@ -19,8 +19,8 @@ describe('Buffer', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = INIT_COLS;
terminal.rows = INIT_ROWS;
(terminal as any).cols = INIT_COLS;
(terminal as any).rows = INIT_ROWS;
terminal.options.scrollback = 1000;
buffer = new Buffer(terminal, true);
});
@@ -265,6 +265,7 @@ describe('Buffer', () => {
const char = String.fromCharCode(code);
firstLine.set(i, [null, char, 1, code]);
}
buffer.y = 1;
assert.equal(buffer.lines.get(0).length, 5);
assert.equal(buffer.lines.get(0).translateToString(), 'abcde');
buffer.resize(1, 10);
@@ -296,7 +297,7 @@ describe('Buffer', () => {
buffer.fillViewportRows();
terminal.options.scrollback = 1;
buffer.resize(10, 5);
const lastLine = buffer.lines.get(4);
const lastLine = buffer.lines.get(3);
for (let i = 0; i < 10; i++) {
const code = 'a'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
@@ -308,27 +309,27 @@ describe('Buffer', () => {
assert.equal(buffer.y, 4);
assert.equal(buffer.ybase, 1);
assert.equal(buffer.lines.length, 6);
assert.equal(buffer.lines.get(0).translateToString(), ' ');
assert.equal(buffer.lines.get(1).translateToString(), 'ab');
assert.equal(buffer.lines.get(2).translateToString(), 'cd');
assert.equal(buffer.lines.get(3).translateToString(), 'ef');
assert.equal(buffer.lines.get(4).translateToString(), 'gh');
assert.equal(buffer.lines.get(5).translateToString(), 'ij');
assert.equal(buffer.lines.get(0).translateToString(), 'ab');
assert.equal(buffer.lines.get(1).translateToString(), 'cd');
assert.equal(buffer.lines.get(2).translateToString(), 'ef');
assert.equal(buffer.lines.get(3).translateToString(), 'gh');
assert.equal(buffer.lines.get(4).translateToString(), 'ij');
assert.equal(buffer.lines.get(5).translateToString(), ' ');
buffer.resize(1, 5);
assert.equal(buffer.y, 4);
assert.equal(buffer.ybase, 1);
assert.equal(buffer.lines.length, 6);
assert.equal(buffer.lines.get(0).translateToString(), 'e');
assert.equal(buffer.lines.get(1).translateToString(), 'f');
assert.equal(buffer.lines.get(2).translateToString(), 'g');
assert.equal(buffer.lines.get(3).translateToString(), 'h');
assert.equal(buffer.lines.get(4).translateToString(), 'i');
assert.equal(buffer.lines.get(5).translateToString(), 'j');
assert.equal(buffer.lines.get(0).translateToString(), 'f');
assert.equal(buffer.lines.get(1).translateToString(), 'g');
assert.equal(buffer.lines.get(2).translateToString(), 'h');
assert.equal(buffer.lines.get(3).translateToString(), 'i');
assert.equal(buffer.lines.get(4).translateToString(), 'j');
assert.equal(buffer.lines.get(5).translateToString(), ' ');
buffer.resize(10, 5);
assert.equal(buffer.y, 0);
assert.equal(buffer.y, 1);
assert.equal(buffer.ybase, 0);
assert.equal(buffer.lines.length, 5);
assert.equal(buffer.lines.get(0).translateToString(), 'efghij ');
assert.equal(buffer.lines.get(0).translateToString(), 'fghij ');
assert.equal(buffer.lines.get(1).translateToString(), ' ');
assert.equal(buffer.lines.get(2).translateToString(), ' ');
assert.equal(buffer.lines.get(3).translateToString(), ' ');
@@ -339,6 +340,7 @@ describe('Buffer', () => {
// 3+ lines removed on a reflow actually remove the right lines
buffer.fillViewportRows();
buffer.resize(10, 10);
buffer.y = 2;
const firstLine = buffer.lines.get(0);
const secondLine = buffer.lines.get(1);
for (let i = 0; i < 10; i++) {
@@ -358,8 +360,8 @@ describe('Buffer', () => {
assert.equal(buffer.lines.get(i).translateToString(), ' ');
}
buffer.resize(2, 10);
assert.equal(buffer.ybase, 0);
assert.equal(buffer.lines.length, 10);
assert.equal(buffer.ybase, 1);
assert.equal(buffer.lines.length, 11);
assert.equal(buffer.lines.get(0).translateToString(), 'ab');
assert.equal(buffer.lines.get(1).translateToString(), 'cd');
assert.equal(buffer.lines.get(2).translateToString(), 'ef');
@@ -370,7 +372,10 @@ describe('Buffer', () => {
assert.equal(buffer.lines.get(7).translateToString(), '45');
assert.equal(buffer.lines.get(8).translateToString(), '67');
assert.equal(buffer.lines.get(9).translateToString(), '89');
assert.equal(buffer.lines.get(10).translateToString(), ' ');
buffer.resize(10, 10);
assert.equal(buffer.ybase, 0);
assert.equal(buffer.lines.length, 10);
assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
for (let i = 2; i < 10; i++) {
@@ -379,22 +384,23 @@ describe('Buffer', () => {
});
it('should transfer combined char data over to reflowed lines', () => {
buffer.fillViewportRows();
buffer.resize(4, 2);
buffer.resize(4, 3);
buffer.y = 2;
const firstLine = buffer.lines.get(0);
firstLine.set(0, [ null, 'a', 1, 'a'.charCodeAt(0) ]);
firstLine.set(1, [ null, 'b', 1, 'b'.charCodeAt(0) ]);
firstLine.set(2, [ null, 'c', 1, 'c'.charCodeAt(0) ]);
firstLine.set(3, [ null, '😁', 1, '😁'.charCodeAt(0) ]);
assert.equal(buffer.lines.length, 2);
assert.equal(buffer.lines.length, 3);
assert.equal(buffer.lines.get(0).translateToString(), 'abc😁');
assert.equal(buffer.lines.get(1).translateToString(), ' ');
buffer.resize(2, 2);
buffer.resize(2, 3);
assert.equal(buffer.lines.get(0).translateToString(), 'ab');
assert.equal(buffer.lines.get(1).translateToString(), 'c😁');
});
it('should adjust markers when reflowing', () => {
buffer.fillViewportRows();
buffer.resize(10, 15);
buffer.resize(10, 16);
for (let i = 0; i < 10; i++) {
const code = 'a'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
@@ -410,6 +416,7 @@ describe('Buffer', () => {
const char = String.fromCharCode(code);
buffer.lines.get(2).set(i, [null, char, 1, code]);
}
buffer.y = 3;
// Buffer:
// abcdefghij
// 0123456789
@@ -423,7 +430,7 @@ describe('Buffer', () => {
assert.equal(firstMarker.line, 0);
assert.equal(secondMarker.line, 1);
assert.equal(thirdMarker.line, 2);
buffer.resize(2, 15);
buffer.resize(2, 16);
assert.equal(buffer.lines.get(0).translateToString(), 'ab');
assert.equal(buffer.lines.get(1).translateToString(), 'cd');
assert.equal(buffer.lines.get(2).translateToString(), 'ef');
@@ -442,7 +449,7 @@ describe('Buffer', () => {
assert.equal(firstMarker.line, 0, 'first marker should remain unchanged');
assert.equal(secondMarker.line, 5, 'second marker should be shifted since the first line wrapped');
assert.equal(thirdMarker.line, 10, 'third marker should be shifted since the first and second lines wrapped');
buffer.resize(10, 15);
buffer.resize(10, 16);
assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
@@ -456,7 +463,7 @@ describe('Buffer', () => {
it('should dispose markers whose rows are trimmed during a reflow', () => {
buffer.fillViewportRows();
terminal.options.scrollback = 1;
buffer.resize(10, 10);
buffer.resize(10, 11);
for (let i = 0; i < 10; i++) {
const code = 'a'.charCodeAt(0) + i;
const char = String.fromCharCode(code);
@@ -472,6 +479,7 @@ describe('Buffer', () => {
const char = String.fromCharCode(code);
buffer.lines.get(2).set(i, [null, char, 1, code]);
}
buffer.y = 10;
// Buffer:
// abcdefghij
// 0123456789
@@ -479,14 +487,14 @@ describe('Buffer', () => {
const firstMarker = buffer.addMarker(0);
const secondMarker = buffer.addMarker(1);
const thirdMarker = buffer.addMarker(2);
buffer.y = 2;
buffer.y = 3;
assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
assert.equal(firstMarker.line, 0);
assert.equal(secondMarker.line, 1);
assert.equal(thirdMarker.line, 2);
buffer.resize(2, 10);
buffer.resize(2, 11);
assert.equal(buffer.lines.get(0).translateToString(), 'ij');
assert.equal(buffer.lines.get(1).translateToString(), '01');
assert.equal(buffer.lines.get(2).translateToString(), '23');
@@ -503,7 +511,7 @@ describe('Buffer', () => {
assert.equal(firstMarker.isDisposed, true, 'first marker was trimmed');
assert.equal(secondMarker.isDisposed, false);
assert.equal(thirdMarker.isDisposed, false);
buffer.resize(10, 10);
buffer.resize(10, 11);
assert.equal(buffer.lines.get(0).translateToString(), 'ij ');
assert.equal(buffer.lines.get(1).translateToString(), '0123456789');
assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst');
@@ -513,6 +521,7 @@ describe('Buffer', () => {
it('should wrap wide characters correctly when reflowing larger', () => {
buffer.fillViewportRows();
buffer.resize(12, 10);
buffer.y = 2;
for (let i = 0; i < 12; i += 4) {
buffer.lines.get(0).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
buffer.lines.get(1).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
@@ -547,6 +556,7 @@ describe('Buffer', () => {
it('should wrap wide characters correctly when reflowing smaller', () => {
buffer.fillViewportRows();
buffer.resize(12, 10);
buffer.y = 2;
for (let i = 0; i < 12; i += 4) {
buffer.lines.get(0).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
buffer.lines.get(1).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
@@ -937,6 +947,7 @@ describe('Buffer', () => {
describe('&& ydisp === ybase', () => {
it('should trim lines and keep ydisp = ybase', () => {
buffer.ydisp = 10;
buffer.y = 13;
buffer.resize(2, 10);
assert.equal(buffer.ydisp, 10);
assert.equal(buffer.ybase, 10);
@@ -962,6 +973,7 @@ describe('Buffer', () => {
describe('&& ydisp !== ybase', () => {
it('should trim lines and not change ydisp', () => {
buffer.ydisp = 5;
buffer.y = 13;
buffer.resize(2, 10);
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 10);
@@ -1266,7 +1278,7 @@ describe('Buffer', () => {
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 10; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
const j = (i - 0) << 1;
assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex);
}
@@ -1278,7 +1290,7 @@ describe('Buffer', () => {
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]);
}
});
@@ -1290,7 +1302,7 @@ describe('Buffer', () => {
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
assert.equal(
(!(i % 3))
? input[i]
@@ -1300,6 +1312,13 @@ describe('Buffer', () => {
terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX]);
}
});
it('should handle \t in lines correctly', () => {
const input = '\thttps://google.de';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(s, Array(terminal.getOption('tabStopWidth') + 1).join(' ') + 'https://google.de');
});
});
describe('BufferStringIterator', function(): void {
it('iterator does not overflow buffer limits', function(): void {
+37 -18
View File
@@ -3,13 +3,13 @@
* @license MIT
*/
import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList';
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types';
import { EventEmitter } from './common/EventEmitter';
import { IMarker } from 'xterm';
import { BufferLine } from './BufferLine';
import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow';
import { CircularList, IDeleteEvent, IInsertEvent } from './common/CircularList';
import { EventEmitter } from './common/EventEmitter';
import { DEFAULT_COLOR } from './renderer/atlas/Types';
import { reflowSmallerGetNewLineLengths, reflowLargerGetLinesToRemove, reflowLargerCreateNewLayout, reflowLargerApplyNewLayout } from './BufferReflow';
import { BufferIndex, CharData, IBuffer, IBufferLine, IBufferStringIterator, IBufferStringIteratorResult, ITerminal } from './Types';
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
@@ -212,7 +212,7 @@ export class Buffer implements IBuffer {
this.scrollBottom = newRows - 1;
if (this._hasScrollback) {
this._reflow(newCols);
this._reflow(newCols, newRows);
// Trim the end of the line off if cols shrunk
if (this._cols > newCols) {
@@ -226,7 +226,7 @@ export class Buffer implements IBuffer {
this._rows = newRows;
}
private _reflow(newCols: number): void {
private _reflow(newCols: number, newRows: number): void {
if (this._cols === newCols) {
return;
}
@@ -235,12 +235,12 @@ export class Buffer implements IBuffer {
if (newCols > this._cols) {
this._reflowLarger(newCols);
} else {
this._reflowSmaller(newCols);
this._reflowSmaller(newCols, newRows);
}
}
private _reflowLarger(newCols: number): void {
const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, newCols);
const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, newCols, this.ybase + this.y);
if (toRemove.length > 0) {
const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);
reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);
@@ -253,9 +253,13 @@ export class Buffer implements IBuffer {
let viewportAdjustments = countRemoved;
while (viewportAdjustments-- > 0) {
if (this.ybase === 0) {
this.y--;
// Add an extra row at the bottom of the viewport
this.lines.push(new BufferLine(newCols, FILL_CHAR_DATA));
if (this.y > 0) {
this.y--;
}
if (this.lines.length < this._rows) {
// Add an extra row at the bottom of the viewport
this.lines.push(new BufferLine(newCols, FILL_CHAR_DATA));
}
} else {
if (this.ydisp === this.ybase) {
this.ydisp--;
@@ -265,7 +269,7 @@ export class Buffer implements IBuffer {
}
}
private _reflowSmaller(newCols: number): void {
private _reflowSmaller(newCols: number, newRows: number): void {
// Gather all BufferLines that need to be inserted into the Buffer here so that they can be
// batched up and only committed once
const toInsert = [];
@@ -285,6 +289,13 @@ export class Buffer implements IBuffer {
wrappedLines.unshift(nextLine);
}
// If these lines contain the cursor don't touch them, the program will handle fixing up
// wrapped lines with the cursor
const absoluteY = this.ybase + this.y;
if (absoluteY >= y && absoluteY < y + wrappedLines.length) {
continue;
}
const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);
const linesToAdd = destLineLengths.length - wrappedLines.length;
@@ -357,10 +368,13 @@ export class Buffer implements IBuffer {
this.ydisp++;
}
} else {
if (this.ybase === this.ydisp) {
this.ydisp++;
// Ensure ybase does not exceed its maximum value
if (this.ybase < Math.min(this.lines.maxLength, this.lines.length + countToInsert) - newRows) {
if (this.ybase === this.ydisp) {
this.ydisp++;
}
this.ybase++;
}
this.ybase++;
}
}
}
@@ -435,14 +449,19 @@ export class Buffer implements IBuffer {
* @param stringIndex index within the string
* @param startCol column offset the string was retrieved from
*/
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): BufferIndex {
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight: boolean = false): BufferIndex {
while (stringIndex) {
const line = this.lines.get(lineIndex);
if (!line) {
return [-1, -1];
}
for (let i = 0; i < line.length; ++i) {
stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length;
const length = (trimRight) ? line.getTrimmedLength() : line.length;
for (let i = 0; i < length; ++i) {
if (line.get(i)[CHAR_DATA_WIDTH_INDEX]) {
// empty cells report a string length of 0, but get replaced
// with a whitespace in translateToString, thus replace with 1
stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length || 1;
}
if (stringIndex < 0) {
return [lineIndex, i];
}
+10 -4
View File
@@ -3,10 +3,10 @@
* @license MIT
*/
import { FILL_CHAR_DATA } from './Buffer';
import { BufferLine } from './BufferLine';
import { CircularList, IDeleteEvent } from './common/CircularList';
import { IBufferLine } from './Types';
import { FILL_CHAR_DATA } from './Buffer';
export interface INewLayoutResult {
layout: number[];
@@ -19,7 +19,7 @@ export interface INewLayoutResult {
* @param lines The buffer lines.
* @param newCols The columns after resize.
*/
export function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, newCols: number): number[] {
export function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, newCols: number, bufferAbsoluteY: number): number[] {
// Gather all BufferLines that need to be removed from the Buffer here so that they can be
// batched up and only committed once
const toRemove: number[] = [];
@@ -39,6 +39,13 @@ export function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, n
nextLine = lines.get(++i) as BufferLine;
}
// If these lines contain the cursor don't touch them, the program will handle fixing up wrapped
// lines with the cursor
if (bufferAbsoluteY >= y && bufferAbsoluteY < i) {
y += wrappedLines.length - 1;
continue;
}
// Copy buffer data to new locations
let destLineIndex = 0;
let destCol = wrappedLines[destLineIndex].getTrimmedLength();
@@ -64,7 +71,7 @@ export function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, n
}
// Make sure the last cell isn't wide, if it is copy it to the current dest
if (destCol === 0) {
if (destCol === 0 && destLineIndex !== 0) {
if (wrappedLines[destLineIndex - 1].getWidth(newCols - 1) === 2) {
wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1, false);
// Null out the end of the last row
@@ -166,7 +173,6 @@ export function reflowLargerApplyNewLayout(lines: CircularList<IBufferLine>, new
*/
export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {
const newLineLengths: number[] = [];
const cellsNeeded = wrappedLines.map(l => l.getTrimmedLength()).reduce((p, c) => p + c);
// Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
+2 -2
View File
@@ -15,8 +15,8 @@ describe('BufferSet', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 24;
(terminal as any).cols = 80;
(terminal as any).rows = 24;
terminal.options.scrollback = 1000;
bufferSet = new BufferSet(terminal);
});
+171 -150
View File
@@ -6,6 +6,7 @@
import { ParserState, IDcsHandler, IParsingState } from './Types';
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from './EscapeSequenceParser';
import * as chai from 'chai';
import { StringToUtf32, stringFromCodePoint } from './core/input/TextDecoder';
function r(a: number, b: number): string[] {
let c = b - a;
@@ -50,8 +51,12 @@ const testTerminal: any = {
compare: function (value: any): void {
chai.expect(this.calls.slice()).eql(value); // weird bug w'o slicing here
},
print: function (data: string, start: number, end: number): void {
this.calls.push(['print', data.substring(start, end)]);
print: function (data: Uint32Array, start: number, end: number): void {
let s = '';
for (let i = start; i < end; ++i) {
s += stringFromCodePoint(data[i]);
}
this.calls.push(['print', s]);
},
actionOSC: function (s: string): void {
this.calls.push(['osc', s]);
@@ -68,8 +73,12 @@ const testTerminal: any = {
actionDCSHook: function (collect: string, params: number[], flag: string): void {
this.calls.push(['dcs hook', collect, params, flag]);
},
actionDCSPrint: function (data: string, start: number, end: number): void {
this.calls.push(['dcs put', data.substring(start, end)]);
actionDCSPrint: function (data: Uint32Array, start: number, end: number): void {
let s = '';
for (let i = start; i < end; ++i) {
s += stringFromCodePoint(data[i]);
}
this.calls.push(['dcs put', s]);
},
actionDCSUnhook: function (): void {
this.calls.push(['dcs unhook']);
@@ -81,7 +90,7 @@ class DcsTest implements IDcsHandler {
hook(collect: string, params: number[], flag: number): void {
testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag));
}
put(data: string, start: number, end: number): void {
put(data: Uint32Array, start: number, end: number): void {
testTerminal.actionDCSPrint(data, start, end);
}
unhook(): void {
@@ -155,6 +164,12 @@ interface IRun {
parser: TestEscapeSequenceParser;
}
// translate string based parse calls into typed array based
function parse(parser: TestEscapeSequenceParser, data: string): void {
const container = new Uint32Array(data.length);
const decoder = new StringToUtf32();
parser.parse(container, decoder.decode(data, container));
}
describe('EscapeSequenceParser', function (): void {
let parser: TestEscapeSequenceParser | null = null;
@@ -205,12 +220,12 @@ describe('EscapeSequenceParser', function (): void {
it('state GROUND execute action', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.GROUND;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -223,7 +238,7 @@ describe('EscapeSequenceParser', function (): void {
const printables = r(0x20, 0x7f); // NOTE: DEL excluded
for (let i = 0; i < printables.length; ++i) {
parser.currentState = ParserState.GROUND;
parser.parse(printables[i]);
parse(parser, printables[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([['print', printables[i]]]);
parser.reset();
@@ -245,13 +260,13 @@ describe('EscapeSequenceParser', function (): void {
for (state in states) {
for (let i = 0; i < exes.length; ++i) {
parser.currentState = state;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare((state in exceptions ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]);
parser.reset();
testTerminal.clear();
}
parser.parse('\x9c');
parse(parser, '\x9c');
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([]);
parser.reset();
@@ -265,7 +280,7 @@ describe('EscapeSequenceParser', function (): void {
parser.osc = '#';
parser.params = [23];
parser.collect = '#';
parser.parse('\x1b');
parse(parser, '\x1b');
chai.expect(parser.currentState).equal(ParserState.ESCAPE);
chai.expect(parser.osc).equal('');
chai.expect(parser.params).eql([0]);
@@ -276,12 +291,12 @@ describe('EscapeSequenceParser', function (): void {
it('state ESCAPE execute rules', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.ESCAPE;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.ESCAPE);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -292,7 +307,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.ESCAPE;
parser.parse('\x7f');
parse(parser, '\x7f');
chai.expect(parser.currentState).equal(ParserState.ESCAPE);
testTerminal.compare([]);
parser.reset();
@@ -301,13 +316,13 @@ describe('EscapeSequenceParser', function (): void {
it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void {
parser.reset();
testTerminal.clear();
const dispatches = r(0x30, 0x50);
dispatches.concat(r(0x51, 0x58));
dispatches.concat(['\x59', '\x5a', '\x5c']);
dispatches.concat(r(0x60, 0x7f));
let dispatches = r(0x30, 0x50);
dispatches = dispatches.concat(r(0x51, 0x58));
dispatches = dispatches.concat(['\x59', '\x5a']); // excluded \x5c
dispatches = dispatches.concat(r(0x60, 0x7f));
for (let i = 0; i < dispatches.length; ++i) {
parser.currentState = ParserState.ESCAPE;
parser.parse(dispatches[i]);
parse(parser, dispatches[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([['esc', '', dispatches[i]]]);
parser.reset();
@@ -319,7 +334,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.ESCAPE;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -328,12 +343,12 @@ describe('EscapeSequenceParser', function (): void {
it('state ESCAPE_INTERMEDIATE execute rules', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -344,7 +359,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
parser.parse('\x7f');
parse(parser, '\x7f');
chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE);
testTerminal.compare([]);
parser.reset();
@@ -355,7 +370,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -367,7 +382,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x30, 0x7f);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
// '\x5c' --> ESC + \ (7bit ST) parser does not expose this as it already got handled
testTerminal.compare((collect[i] === '\x5c') ? [] : [['esc', '', collect[i]]]);
@@ -382,7 +397,7 @@ describe('EscapeSequenceParser', function (): void {
parser.osc = '#';
parser.params = [123];
parser.collect = '#';
parser.parse('[');
parse(parser, '[');
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
chai.expect(parser.osc).equal('');
chai.expect(parser.params).eql([0]);
@@ -394,7 +409,7 @@ describe('EscapeSequenceParser', function (): void {
parser.osc = '#';
parser.params = [123];
parser.collect = '#';
parser.parse('\x9b');
parse(parser, '\x9b');
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
chai.expect(parser.osc).equal('');
chai.expect(parser.params).eql([0]);
@@ -405,12 +420,12 @@ describe('EscapeSequenceParser', function (): void {
it('state CSI_ENTRY execute rules', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.CSI_ENTRY;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -421,7 +436,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.CSI_ENTRY;
parser.parse('\x7f');
parse(parser, '\x7f');
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
testTerminal.compare([]);
parser.reset();
@@ -432,7 +447,7 @@ describe('EscapeSequenceParser', function (): void {
const dispatches = r(0x40, 0x7f);
for (let i = 0; i < dispatches.length; ++i) {
parser.currentState = ParserState.CSI_ENTRY;
parser.parse(dispatches[i]);
parse(parser, dispatches[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([['csi', '', [0], dispatches[i]]]);
parser.reset();
@@ -445,19 +460,19 @@ describe('EscapeSequenceParser', function (): void {
const collect = ['\x3c', '\x3d', '\x3e', '\x3f'];
for (let i = 0; i < params.length; ++i) {
parser.currentState = ParserState.CSI_ENTRY;
parser.parse(params[i]);
parse(parser, params[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]);
parser.reset();
}
parser.currentState = ParserState.CSI_ENTRY;
parser.parse('\x3b');
parse(parser, '\x3b');
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
chai.expect(parser.params).eql([0, 0]);
parser.reset();
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.CSI_ENTRY;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -466,12 +481,12 @@ describe('EscapeSequenceParser', function (): void {
it('state CSI_PARAM execute rules', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.CSI_PARAM;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -483,13 +498,13 @@ describe('EscapeSequenceParser', function (): void {
const params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39'];
for (let i = 0; i < params.length; ++i) {
parser.currentState = ParserState.CSI_PARAM;
parser.parse(params[i]);
parse(parser, params[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]);
parser.reset();
}
parser.currentState = ParserState.CSI_PARAM;
parser.parse('\x3b');
parse(parser, '\x3b');
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
chai.expect(parser.params).eql([0, 0]);
parser.reset();
@@ -498,7 +513,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.CSI_PARAM;
parser.parse('\x7f');
parse(parser, '\x7f');
chai.expect(parser.currentState).equal(ParserState.CSI_PARAM);
testTerminal.compare([]);
parser.reset();
@@ -510,7 +525,7 @@ describe('EscapeSequenceParser', function (): void {
for (let i = 0; i < dispatches.length; ++i) {
parser.currentState = ParserState.CSI_PARAM;
parser.params = [0, 1];
parser.parse(dispatches[i]);
parse(parser, dispatches[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]);
parser.reset();
@@ -522,7 +537,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.CSI_ENTRY;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -533,7 +548,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.CSI_PARAM;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -542,12 +557,12 @@ describe('EscapeSequenceParser', function (): void {
it('state CSI_INTERMEDIATE execute rules', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.CSI_INTERMEDIATE;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -559,7 +574,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.CSI_INTERMEDIATE;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -569,7 +584,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.CSI_INTERMEDIATE;
parser.parse('\x7f');
parse(parser, '\x7f');
chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE);
testTerminal.compare([]);
parser.reset();
@@ -581,7 +596,7 @@ describe('EscapeSequenceParser', function (): void {
for (let i = 0; i < dispatches.length; ++i) {
parser.currentState = ParserState.CSI_INTERMEDIATE;
parser.params = [0, 1];
parser.parse(dispatches[i]);
parse(parser, dispatches[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]);
parser.reset();
@@ -591,7 +606,7 @@ describe('EscapeSequenceParser', function (): void {
it('trans CSI_ENTRY --> CSI_IGNORE', function (): void {
parser.reset();
parser.currentState = ParserState.CSI_ENTRY;
parser.parse('\x3a');
parse(parser, '\x3a');
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
parser.reset();
});
@@ -600,7 +615,7 @@ describe('EscapeSequenceParser', function (): void {
const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f'];
for (let i = 0; i < chars.length; ++i) {
parser.currentState = ParserState.CSI_PARAM;
parser.parse('\x3b' + chars[i]);
parse(parser, '\x3b' + chars[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
chai.expect(parser.params).eql([0, 0]);
parser.reset();
@@ -611,7 +626,7 @@ describe('EscapeSequenceParser', function (): void {
const chars = r(0x30, 0x40);
for (let i = 0; i < chars.length; ++i) {
parser.currentState = ParserState.CSI_INTERMEDIATE;
parser.parse(chars[i]);
parse(parser, chars[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
chai.expect(parser.params).eql([0]);
parser.reset();
@@ -620,12 +635,12 @@ describe('EscapeSequenceParser', function (): void {
it('state CSI_IGNORE execute rules', function (): void {
parser.reset();
testTerminal.clear();
const exes = r(0x00, 0x18);
exes.concat(['\x19']);
exes.concat(r(0x1c, 0x20));
let exes = r(0x00, 0x18);
exes = exes.concat(['\x19']);
exes = exes.concat(r(0x1c, 0x20));
for (let i = 0; i < exes.length; ++i) {
parser.currentState = ParserState.CSI_IGNORE;
parser.parse(exes[i]);
parse(parser, exes[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
testTerminal.compare([['exe', exes[i]]]);
parser.reset();
@@ -635,11 +650,11 @@ describe('EscapeSequenceParser', function (): void {
it('state CSI_IGNORE ignore', function (): void {
parser.reset();
testTerminal.clear();
const ignored = r(0x20, 0x40);
ignored.concat(['\x7f']);
let ignored = r(0x20, 0x40);
ignored = ignored.concat(['\x7f']);
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.CSI_IGNORE;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
testTerminal.compare([]);
parser.reset();
@@ -652,7 +667,7 @@ describe('EscapeSequenceParser', function (): void {
for (let i = 0; i < dispatches.length; ++i) {
parser.currentState = ParserState.CSI_IGNORE;
parser.params = [0, 1];
parser.parse(dispatches[i]);
parse(parser, dispatches[i]);
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([]);
parser.reset();
@@ -664,7 +679,7 @@ describe('EscapeSequenceParser', function (): void {
// C0
let initializers = ['\x58', '\x5e', '\x5f'];
for (let i = 0; i < initializers.length; ++i) {
parser.parse('\x1b' + initializers[i]);
parse(parser, '\x1b' + initializers[i]);
chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING);
parser.reset();
}
@@ -673,7 +688,7 @@ describe('EscapeSequenceParser', function (): void {
parser.currentState = state;
initializers = ['\x98', '\x9e', '\x9f'];
for (let i = 0; i < initializers.length; ++i) {
parser.parse(initializers[i]);
parse(parser, initializers[i]);
chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING);
parser.reset();
}
@@ -681,13 +696,13 @@ describe('EscapeSequenceParser', function (): void {
});
it('state SOS_PM_APC_STRING ignore rules', function (): void {
parser.reset();
const ignored = r(0x00, 0x18);
ignored.concat(['\x19']);
ignored.concat(r(0x1c, 0x20));
ignored.concat(r(0x20, 0x80));
let ignored = r(0x00, 0x18);
ignored = ignored.concat(['\x19']);
ignored = ignored.concat(r(0x1c, 0x20));
ignored = ignored.concat(r(0x20, 0x80));
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.SOS_PM_APC_STRING;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING);
parser.reset();
}
@@ -695,13 +710,13 @@ describe('EscapeSequenceParser', function (): void {
it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void {
parser.reset();
// C0
parser.parse('\x1b]');
parse(parser, '\x1b]');
chai.expect(parser.currentState).equal(ParserState.OSC_STRING);
parser.reset();
// C1
for (state in states) {
parser.currentState = state;
parser.parse('\x9d');
parse(parser, '\x9d');
chai.expect(parser.currentState).equal(ParserState.OSC_STRING);
parser.reset();
}
@@ -714,7 +729,7 @@ describe('EscapeSequenceParser', function (): void {
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f'];
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.OSC_STRING;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.OSC_STRING);
chai.expect(parser.osc).equal('');
parser.reset();
@@ -725,7 +740,7 @@ describe('EscapeSequenceParser', function (): void {
const puts = r(0x20, 0x80);
for (let i = 0; i < puts.length; ++i) {
parser.currentState = ParserState.OSC_STRING;
parser.parse(puts[i]);
parse(parser, puts[i]);
chai.expect(parser.currentState).equal(ParserState.OSC_STRING);
chai.expect(parser.osc).equal(puts[i]);
parser.reset();
@@ -734,13 +749,13 @@ describe('EscapeSequenceParser', function (): void {
it('state DCS_ENTRY', function (): void {
parser.reset();
// C0
parser.parse('\x1bP');
parse(parser, '\x1bP');
chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY);
parser.reset();
// C1
for (state in states) {
parser.currentState = state;
parser.parse('\x90');
parse(parser, '\x90');
chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY);
parser.reset();
}
@@ -753,7 +768,7 @@ describe('EscapeSequenceParser', function (): void {
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f'];
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.DCS_ENTRY;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY);
parser.reset();
}
@@ -764,19 +779,19 @@ describe('EscapeSequenceParser', function (): void {
const collect = ['\x3c', '\x3d', '\x3e', '\x3f'];
for (let i = 0; i < params.length; ++i) {
parser.currentState = ParserState.DCS_ENTRY;
parser.parse(params[i]);
parse(parser, params[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]);
parser.reset();
}
parser.currentState = ParserState.DCS_ENTRY;
parser.parse('\x3b');
parse(parser, '\x3b');
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
chai.expect(parser.params).eql([0, 0]);
parser.reset();
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_ENTRY;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -790,7 +805,7 @@ describe('EscapeSequenceParser', function (): void {
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f'];
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.DCS_PARAM;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
parser.reset();
}
@@ -800,13 +815,13 @@ describe('EscapeSequenceParser', function (): void {
const params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39'];
for (let i = 0; i < params.length; ++i) {
parser.currentState = ParserState.DCS_PARAM;
parser.parse(params[i]);
parse(parser, params[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]);
parser.reset();
}
parser.currentState = ParserState.DCS_PARAM;
parser.parse('\x3b');
parse(parser, '\x3b');
chai.expect(parser.currentState).equal(ParserState.DCS_PARAM);
chai.expect(parser.params).eql([0, 0]);
parser.reset();
@@ -814,7 +829,7 @@ describe('EscapeSequenceParser', function (): void {
it('trans DCS_ENTRY --> DCS_IGNORE', function (): void {
parser.reset();
parser.currentState = ParserState.DCS_ENTRY;
parser.parse('\x3a');
parse(parser, '\x3a');
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
parser.reset();
});
@@ -823,7 +838,7 @@ describe('EscapeSequenceParser', function (): void {
const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f'];
for (let i = 0; i < chars.length; ++i) {
parser.currentState = ParserState.DCS_PARAM;
parser.parse('\x3b' + chars[i]);
parse(parser, '\x3b' + chars[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
chai.expect(parser.params).eql([0, 0]);
parser.reset();
@@ -834,21 +849,21 @@ describe('EscapeSequenceParser', function (): void {
const chars = r(0x30, 0x40);
for (let i = 0; i < chars.length; ++i) {
parser.currentState = ParserState.DCS_INTERMEDIATE;
parser.parse(chars[i]);
parse(parser, chars[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
parser.reset();
}
});
it('state DCS_IGNORE ignore rules', function (): void {
parser.reset();
const ignored = [
let ignored = [
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08',
'\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11',
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f'];
ignored.concat(r(0x20, 0x80));
ignored = ignored.concat(r(0x20, 0x80));
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.DCS_IGNORE;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
parser.reset();
}
@@ -858,7 +873,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_ENTRY;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -869,7 +884,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_PARAM;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -883,7 +898,7 @@ describe('EscapeSequenceParser', function (): void {
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f'];
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.DCS_INTERMEDIATE;
parser.parse(ignored[i]);
parse(parser, ignored[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE);
parser.reset();
}
@@ -893,7 +908,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_INTERMEDIATE;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE);
chai.expect(parser.collect).equal(collect[i]);
parser.reset();
@@ -904,7 +919,7 @@ describe('EscapeSequenceParser', function (): void {
const chars = r(0x30, 0x40);
for (let i = 0; i < chars.length; ++i) {
parser.currentState = ParserState.DCS_INTERMEDIATE;
parser.parse('\x20' + chars[i]);
parse(parser, '\x20' + chars[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
chai.expect(parser.collect).equal('\x20');
parser.reset();
@@ -916,7 +931,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x40, 0x7f);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_ENTRY;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
parser.reset();
@@ -929,7 +944,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x40, 0x7f);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_PARAM;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
parser.reset();
@@ -942,7 +957,7 @@ describe('EscapeSequenceParser', function (): void {
const collect = r(0x40, 0x7f);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.DCS_INTERMEDIATE;
parser.parse(collect[i]);
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
parser.reset();
@@ -952,14 +967,14 @@ describe('EscapeSequenceParser', function (): void {
it('state DCS_PASSTHROUGH put action', function (): void {
parser.reset();
testTerminal.clear();
const puts = r(0x00, 0x18);
puts.concat(['\x19']);
puts.concat(r(0x1c, 0x20));
puts.concat(r(0x20, 0x7f));
let puts = r(0x00, 0x18);
puts = puts.concat(['\x19']);
puts = puts.concat(r(0x1c, 0x20));
puts = puts.concat(r(0x20, 0x7f));
for (let i = 0; i < puts.length; ++i) {
parser.currentState = ParserState.DCS_PASSTHROUGH;
parser.mockActiveDcsHandler();
parser.parse(puts[i]);
parse(parser, puts[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs put', puts[i]]]);
parser.reset();
@@ -970,7 +985,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.DCS_PASSTHROUGH;
parser.parse('\x7f');
parse(parser, '\x7f');
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([]);
parser.reset();
@@ -989,7 +1004,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
}
parser.parse(s);
parse(parser, s);
testTerminal.compare(value);
};
});
@@ -1067,7 +1082,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.CSI_IGNORE;
parser.parse('€öäü');
parse(parser, '€öäü');
chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE);
testTerminal.compare([]);
parser.reset();
@@ -1077,7 +1092,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.DCS_IGNORE;
parser.parse('€öäü');
parse(parser, '€öäü');
chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE);
testTerminal.compare([]);
parser.reset();
@@ -1087,7 +1102,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.DCS_PASSTHROUGH;
parser.parse('\x901;2;3+$a€öäü');
parse(parser, '\x901;2;3+$a€öäü');
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['dcs put', '€öäü']]);
parser.reset();
@@ -1097,7 +1112,7 @@ describe('EscapeSequenceParser', function (): void {
parser.reset();
testTerminal.clear();
parser.currentState = ParserState.GROUND;
parser.parse('\x9c');
parse(parser, '\x9c');
chai.expect(parser.currentState).equal(ParserState.GROUND);
testTerminal.compare([]);
parser.reset();
@@ -1127,15 +1142,17 @@ describe('EscapeSequenceParser', function (): void {
clearAccu();
});
it('print handler', function (): void {
parser2.setPrintHandler(function (data: string, start: number, end: number): void {
print += data.substring(start, end);
parser2.setPrintHandler(function (data: Uint32Array, start: number, end: number): void {
for (let i = start; i < end; ++i) {
print += stringFromCodePoint(data[i]);
}
});
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(print).equal('hello world!$>');
parser2.clearPrintHandler();
parser2.clearPrintHandler(); // should not throw
clearAccu();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(print).equal('');
});
it('ESC handler', function (): void {
@@ -1145,28 +1162,28 @@ describe('EscapeSequenceParser', function (): void {
parser2.setEscHandler('E', function (): void {
esc.push('E');
});
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(esc).eql(['%G', 'E']);
parser2.clearEscHandler('%G');
parser2.clearEscHandler('%G'); // should not throw
clearAccu();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(esc).eql(['E']);
parser2.clearEscHandler('E');
clearAccu();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(esc).eql([]);
});
it('CSI handler', function (): void {
parser2.setCsiHandler('m', function (params: number[], collect: string): void {
csi.push(['m', params, collect]);
});
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
parser2.clearCsiHandler('m');
parser2.clearCsiHandler('m'); // should not throw
clearAccu();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([]);
});
describe('CSI custom handlers', () => {
@@ -1174,7 +1191,7 @@ describe('EscapeSequenceParser', function (): void {
const csiCustom: [string, number[], string][] = [];
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([], 'Should not fallback to original handler');
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
});
@@ -1182,7 +1199,7 @@ describe('EscapeSequenceParser', function (): void {
const csiCustom: [string, number[], string][] = [];
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return false; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
});
@@ -1192,7 +1209,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return false; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([], 'Should not fallback to original handler');
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
@@ -1203,7 +1220,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return true; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([], 'Should not fallback to original handler');
chai.expect(csiCustom).eql([], 'Should not fallback once');
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
@@ -1213,7 +1230,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setCsiHandler('m', () => order.push(1));
parser2.addCsiHandler('m', () => { order.push(2); return false; });
parser2.addCsiHandler('m', () => { order.push(3); return false; });
parser2.parse('\x1b[0m');
parse(parser2, '\x1b[0m');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
@@ -1221,7 +1238,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
customHandler.dispose();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
});
@@ -1231,7 +1248,7 @@ describe('EscapeSequenceParser', function (): void {
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
customHandler.dispose();
customHandler.dispose();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
});
@@ -1243,24 +1260,24 @@ describe('EscapeSequenceParser', function (): void {
parser2.setExecuteHandler('\r', function (): void {
exe.push('\r');
});
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(exe).eql(['\r', '\n']);
parser2.clearExecuteHandler('\r');
parser2.clearExecuteHandler('\r'); // should not throw
clearAccu();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(exe).eql(['\n']);
});
it('OSC handler', function (): void {
parser2.setOscHandler(1, function (data: string): void {
osc.push([1, data]);
});
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([[1, 'foo=bar']]);
parser2.clearOscHandler(1);
parser2.clearOscHandler(1); // should not throw
clearAccu();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([]);
});
describe('OSC custom handlers', () => {
@@ -1268,7 +1285,7 @@ describe('EscapeSequenceParser', function (): void {
const oscCustom: [number, string][] = [];
parser2.setOscHandler(1, data => osc.push([1, data]));
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([], 'Should not fallback to original handler');
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
});
@@ -1276,7 +1293,7 @@ describe('EscapeSequenceParser', function (): void {
const oscCustom: [number, string][] = [];
parser2.setOscHandler(1, data => osc.push([1, data]));
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return false; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
});
@@ -1286,7 +1303,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setOscHandler(1, data => osc.push([1, data]));
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return false; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([], 'Should not fallback to original handler');
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
@@ -1297,7 +1314,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setOscHandler(1, data => osc.push([1, data]));
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return true; });
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([], 'Should not fallback to original handler');
chai.expect(oscCustom).eql([], 'Should not fallback once');
chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
@@ -1307,7 +1324,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setOscHandler(1, () => order.push(1));
parser2.addOscHandler(1, () => { order.push(2); return false; });
parser2.addOscHandler(1, () => { order.push(3); return false; });
parser2.parse('\x1b]1;foo=bar\x1b\\');
parse(parser2, '\x1b]1;foo=bar\x1b\\');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
@@ -1315,7 +1332,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.setOscHandler(1, data => osc.push([1, data]));
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
customHandler.dispose();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([[1, 'foo=bar']]);
chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
});
@@ -1325,7 +1342,7 @@ describe('EscapeSequenceParser', function (): void {
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
customHandler.dispose();
customHandler.dispose();
parser2.parse(INPUT);
parse(parser2, INPUT);
chai.expect(osc).eql([[1, 'foo=bar']]);
chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
});
@@ -1335,15 +1352,19 @@ describe('EscapeSequenceParser', function (): void {
hook: function (collect: string, params: number[], flag: number): void {
dcs.push(['hook', collect, params, flag]);
},
put: function (data: string, start: number, end: number): void {
dcs.push(['put', data.substring(start, end)]);
put: function (data: Uint32Array, start: number, end: number): void {
let s = '';
for (let i = start; i < end; ++i) {
s += stringFromCodePoint(data[i]);
}
dcs.push(['put', s]);
},
unhook: function (): void {
dcs.push(['unhook']);
}
});
parser2.parse('\x1bP1;2;3+pabc');
parser2.parse(';de\x9c');
parse(parser2, '\x1bP1;2;3+pabc');
parse(parser2, ';de\x9c');
chai.expect(dcs).eql([
['hook', '+', [1, 2, 3], 'p'.charCodeAt(0)],
['put', 'abc'], ['put', ';de'],
@@ -1352,8 +1373,8 @@ describe('EscapeSequenceParser', function (): void {
parser2.clearDcsHandler('+p');
parser2.clearDcsHandler('+p'); // should not throw
clearAccu();
parser2.parse('\x1bP1;2;3+pabc');
parser2.parse(';de\x9c');
parse(parser2, '\x1bP1;2;3+pabc');
parse(parser2, ';de\x9c');
chai.expect(dcs).eql([]);
});
it('ERROR handler', function (): void {
@@ -1362,7 +1383,7 @@ describe('EscapeSequenceParser', function (): void {
errorState = state;
return state;
});
parser2.parse('\x1b[1;2;€;3m'); // faulty escape sequence
parse(parser2, '\x1b[1;2;€;3m'); // faulty escape sequence
chai.expect(errorState).eql({
position: 6,
code: '€'.charCodeAt(0),
@@ -1377,7 +1398,7 @@ describe('EscapeSequenceParser', function (): void {
parser2.clearErrorHandler();
parser2.clearErrorHandler(); // should not throw
errorState = null;
parser2.parse('\x1b[1;2;a;3m');
parse(parser2, '\x1b[1;2;a;3m');
chai.expect(errorState).eql(null);
});
});
+15 -14
View File
@@ -6,6 +6,7 @@
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { utf32ToString } from './core/input/TextDecoder';
interface IHandlerCollection<T> {
[key: string]: T[];
@@ -134,6 +135,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
// csi entries
table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
@@ -202,7 +204,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
*/
class DcsDummy implements IDcsHandler {
hook(collect: string, params: number[], flag: number): void { }
put(data: string, start: number, end: number): void { }
put(data: Uint32Array, start: number, end: number): void { }
unhook(): void { }
}
@@ -228,7 +230,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _collect: string;
// handler lookup containers
protected _printHandler: (data: string, start: number, end: number) => void;
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlers: any;
protected _csiHandlers: IHandlerCollection<CsiHandler>;
protected _escHandlers: any;
@@ -238,7 +240,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
protected _printHandlerFb: (data: string, start: number, end: number) => void;
protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlerFb: (code: number) => void;
protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void;
protected _escHandlerFb: (collect: string, flag: number) => void;
@@ -294,7 +296,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._errorHandler = null;
}
setPrintHandler(callback: (data: string, start: number, end: number) => void): void {
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
this._printHandler = callback;
}
clearPrintHandler(): void {
@@ -397,7 +399,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._activeDcsHandler = null;
}
parse(data: string): void {
parse(data: Uint32Array, length: number): void {
let code = 0;
let transition = 0;
let error = false;
@@ -412,15 +414,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
let callback: Function | null = null;
// process input string
const l = data.length;
for (let i = 0; i < l; ++i) {
code = data.charCodeAt(i);
for (let i = 0; i < length; ++i) {
code = data[i];
// shortcut for most chars (print action)
if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {
print = (~print) ? print : i;
do i++;
while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80);
while (i < length && data[i] > 0x1f && data[i] < 0x80);
i--;
continue;
}
@@ -563,10 +564,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
break;
case ParserAction.OSC_PUT:
for (let j = i + 1; ; j++) {
if (j >= l
|| (code = data.charCodeAt(j)) < 0x20
if (j >= length
|| (code = data[j]) < 0x20
|| (code > 0x7f && code <= 0x9f)) {
osc += data.substring(i, j);
osc += utf32ToString(data, i, j);
i = j - 1;
break;
}
@@ -610,9 +611,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// push leftover pushable buffers to terminal
if (currentState === ParserState.GROUND && ~print) {
this._printHandler(data, print, data.length);
this._printHandler(data, print, length);
} else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) {
dcsHandler.put(data, dcs, data.length);
dcsHandler.put(data, dcs, length);
}
// save non pushable buffers
+3 -1
View File
@@ -337,7 +337,9 @@ describe('InputHandler', () => {
it('should not cause an infinite loop (regression test)', () => {
const term = new Terminal();
const inputHandler = new InputHandler(term);
inputHandler.print(String.fromCharCode(0x200B), 0, 1);
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
});
});
+42 -52
View File
@@ -14,6 +14,8 @@ import { EscapeSequenceParser } from './EscapeSequenceParser';
import { ICharset } from './core/Types';
import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { concat } from './common/TypedArrayUtils';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -32,21 +34,22 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1,
* Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)
*/
class DECRQSS implements IDcsHandler {
private _data: string;
private _data: Uint32Array = new Uint32Array(0);
constructor(private _terminal: any) { }
hook(collect: string, params: number[], flag: number): void {
// reset data
this._data = '';
this._data = new Uint32Array(0);
}
put(data: string, start: number, end: number): void {
this._data += data.substring(start, end);
put(data: Uint32Array, start: number, end: number): void {
this._data = concat(this._data, data.subarray(start, end));
}
unhook(): void {
switch (this._data) {
const data = utf32ToString(this._data);
this._data = new Uint32Array(0);
switch (data) {
// valid: DCS 1 $ r Pt ST (xterm)
case '"q': // DECSCA
return this._terminal.handler(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
@@ -66,7 +69,7 @@ class DECRQSS implements IDcsHandler {
return this._terminal.handler(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
default:
// invalid: DCS 0 $ r Pt ST (xterm)
this._terminal.error('Unknown DCS $q %s', this._data);
this._terminal.error('Unknown DCS $q %s', data);
this._terminal.handler(`${C0.ESC}P0$r${C0.ESC}\\`);
}
}
@@ -78,11 +81,17 @@ class DECRQSS implements IDcsHandler {
* not supported
*/
/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/
/**
* DCS + q Pt ST (xterm)
* Request Terminfo String
* not implemented
*/
/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/
@@ -94,7 +103,8 @@ class DECRQSS implements IDcsHandler {
* each function's header comment.
*/
export class InputHandler extends Disposable implements IInputHandler {
private _surrogateFirst: string;
private _parseBuffer: Uint32Array = new Uint32Array(4096);
private _stringDecoder: StringToUtf32 = new StringToUtf32();
constructor(
protected _terminal: IInputHandlingTerminal,
@@ -104,8 +114,6 @@ export class InputHandler extends Disposable implements IInputHandler {
this.register(this._parser);
this._surrogateFirst = '';
/**
* custom fallback handlers
*/
@@ -290,13 +298,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.log('data: ' + data);
}
// apply leftover surrogate high from last write
if (this._surrogateFirst) {
data = this._surrogateFirst + data;
this._surrogateFirst = '';
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
}
this._parser.parse(data);
for (let i = 0; i < data.length; ++i) {
this._parseBuffer[i] = data.charCodeAt(i);
}
this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));
buffer = this._terminal.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
@@ -304,9 +312,9 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
public print(data: string, start: number, end: number): void {
let char: string;
public print(data: Uint32Array, start: number, end: number): void {
let code: number;
let char: string;
let chWidth: number;
const buffer: IBuffer = this._terminal.buffer;
const charset: ICharset = this._terminal.charset;
@@ -318,41 +326,23 @@ export class InputHandler extends Disposable implements IInputHandler {
let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
this._terminal.updateRange(buffer.y);
for (let stringPosition = start; stringPosition < end; ++stringPosition) {
char = data.charAt(stringPosition);
code = data.charCodeAt(stringPosition);
// surrogate pair handling
if (0xD800 <= code && code <= 0xDBFF) {
if (++stringPosition >= end) {
// end of input:
// handle pairs as true UTF-16 and wait for the second part
// since we expect the input comming from a stream there is
// a small chance that the surrogate pair got split
// therefore we dont process the first char here, instead
// it gets added as first char to the next processed chunk
this._surrogateFirst = char;
continue;
}
const second = data.charCodeAt(stringPosition);
// if the second part is in surrogate pair range create the high codepoint
// otherwise fall back to UCS-2 behavior (handle codepoints independently)
if (0xDC00 <= second && second <= 0xDFFF) {
code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
char += data.charAt(stringPosition);
} else {
stringPosition--;
}
}
for (let pos = start; pos < end; ++pos) {
code = data[pos];
char = stringFromCodePoint(code);
// calculate print space
// expensive call, therefore we save width in line buffer
chWidth = wcwidth(code);
// get charset replacement character
if (charset) {
char = charset[char] || char;
code = char.charCodeAt(0);
// charset are only defined for ASCII, therefore we only
// search for an replacement char if code < 127
if (code < 127 && charset) {
const ch = charset[char];
if (ch) {
code = ch.charCodeAt(0);
char = ch;
}
}
if (screenReaderMode) {
+7 -7
View File
@@ -41,8 +41,8 @@ describe('Linkifier', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = 100;
terminal.rows = 10;
(terminal as any).cols = 100;
(terminal as any).rows = 10;
terminal.buffer = new MockBuffer();
(<MockBuffer>terminal.buffer).setLines(new CircularList<IBufferLine>(20));
terminal.buffer.ydisp = 0;
@@ -65,7 +65,7 @@ describe('Linkifier', () => {
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
addRow(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
terminal.rows = terminal.buffer.lines.length - 1;
(terminal as any).rows = terminal.buffer.lines.length - 1;
linkifier.linkifyRows();
// Allow linkify to happen
setTimeout(() => {
@@ -142,19 +142,19 @@ describe('Linkifier', () => {
});
describe('multi-line links', () => {
it('should match links that start on line 1/2 of a wrapped line and end on the last character of line 1/2', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('12345', /1234/, [{x1: 0, x2: 4, y1: 0, y2: 0}], done);
});
it('should match links that start on line 1/2 of a wrapped line and wrap to line 2/2', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('12345', /12345/, [{x1: 0, x2: 1, y1: 0, y2: 1}], done);
});
it('should match links that start and end on line 2/2 of a wrapped line', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('12345678', /5678/, [{x1: 0, x2: 4, y1: 1, y2: 1}], done);
});
it('should match links that start on line 2/3 of a wrapped line and wrap to line 3/3', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('123456789', /56789/, [{x1: 0, x2: 1, y1: 1, y2: 2}], done);
});
});
+2 -2
View File
@@ -45,8 +45,8 @@ describe('SelectionManager', () => {
beforeEach(() => {
terminal = new TestMockTerminal();
terminal.cols = 80;
terminal.rows = 2;
(terminal as any).cols = 80;
(terminal as any).rows = 2;
terminal.options.scrollback = 100;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
+2 -2
View File
@@ -23,8 +23,8 @@ describe('SelectionManager', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 2;
(terminal as any).cols = 80;
(terminal as any).rows = 2;
terminal.options.scrollback = 10;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
+17 -9
View File
@@ -111,7 +111,7 @@ export interface ICompositionHelper {
*/
export interface IInputHandler {
parse(data: string): void;
print(data: string, start: number, end: number): void;
print(data: Uint32Array, start: number, end: number): void;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
@@ -237,7 +237,7 @@ export interface IBufferAccessor {
}
export interface IElementAccessor {
element: HTMLElement;
readonly element: HTMLElement;
}
export interface ILinkifierAccessor {
@@ -453,18 +453,26 @@ export interface IParsingState {
* DCS handler signature for EscapeSequenceParser.
* EscapeSequenceParser handles DCS commands via separate
* subparsers that get hook/unhooked and can handle
* arbitrary amount of print data.
* arbitrary amount of data.
*
* On entering a DSC sequence `hook` is called by
* `EscapeSequenceParser`. Use it to initialize or reset
* states needed to handle the current DCS sequence.
* Note: A DCS parser is only instantiated once, therefore
* you cannot rely on the ctor to reinitialize state.
*
* EscapeSequenceParser will call `put` several times if the
* parsed string got splitted, therefore you might have to collect
* `data` until `unhook` is called. `unhook` marks the end
* of the current DCS sequence.
* parsed data got split, therefore you might have to collect
* `data` until `unhook` is called.
* Note: `data` is borrowed, if you cannot process the data
* in chunks you have to copy it, doing otherwise will lead to
* data losses or corruption.
*
* `unhook` marks the end of the current DCS sequence.
*/
export interface IDcsHandler {
hook(collect: string, params: number[], flag: number): void;
put(data: string, start: number, end: number): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(): void;
}
@@ -481,9 +489,9 @@ export interface IEscapeSequenceParser extends IDisposable {
* Parse string `data`.
* @param data The data to parse.
*/
parse(data: string): void;
parse(data: Uint32Array, length: number): void;
setPrintHandler(callback: (data: string, start: number, end: number) => void): void;
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void;
clearPrintHandler(): void;
setExecuteHandler(flag: string, callback: () => void): void;
+15 -17
View File
@@ -154,24 +154,22 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
this._length -= deleteCount;
}
if (items && items.length) {
// Add items
for (let i = this._length - 1; i >= start; i--) {
this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
}
for (let i = 0; i < items.length; i++) {
this._array[this._getCyclicIndex(start + i)] = items[i];
}
// Add items
for (let i = this._length - 1; i >= start; i--) {
this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
}
for (let i = 0; i < items.length; i++) {
this._array[this._getCyclicIndex(start + i)] = items[i];
}
// Adjust length as needed
if (this._length + items.length > this._maxLength) {
const countToTrim = (this._length + items.length) - this._maxLength;
this._startIndex += countToTrim;
this._length = this._maxLength;
this.emitMayRemoveListeners('trim', countToTrim);
} else {
this._length += items.length;
}
// Adjust length as needed
if (this._length + items.length > this._maxLength) {
const countToTrim = (this._length + items.length) - this._maxLength;
this._startIndex += countToTrim;
this._length = this._maxLength;
this.emitMayRemoveListeners('trim', countToTrim);
} else {
this._length += items.length;
}
}
+16 -8
View File
@@ -3,21 +3,20 @@
* @license MIT
*/
import { assert } from 'chai';
import { fillFallback, sliceFallback } from './TypedArrayUtils';
import { fillFallback, sliceFallback, concat } from './TypedArrayUtils';
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
describe('polyfill conformance tests', function(): void {
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
for (let i = 0; i < a.length; ++i) {
assert.equal(a[i], b[i]);
}
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
for (let i = 0; i < a.length; ++i) {
assert.equal(a[i], b[i]);
}
}
describe('polyfill conformance tests', function(): void {
describe('TypedArray.fill', function(): void {
it('should work with all typed array types', function(): void {
const u81 = new Uint8Array(5);
@@ -190,3 +189,12 @@ describe('polyfill conformance tests', function(): void {
});
});
});
describe('typed array convenience functions', () => {
it('concat', () => {
const a = new Uint8Array([1, 2, 3, 4, 5]);
const b = new Uint8Array([6, 7, 8, 9, 0]);
const merged = concat(a, b);
deepEquals(merged, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]));
});
});
+16 -5
View File
@@ -3,15 +3,15 @@
* @license MIT
*/
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
/**
* polyfill for TypedArray.fill
* This is needed to support .fill in all safari versions and IE 11.
*/
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
export function fill<T extends TypedArray>(array: T, value: number, start?: number, end?: number): T {
// all modern engines that support .fill
if (array.fill) {
@@ -65,3 +65,14 @@ export function sliceFallback<T extends TypedArray>(array: T, start: number = 0,
}
return result;
}
/**
* Concat two typed arrays `a` and `b`.
* Returns a new typed array.
*/
export function concat<T extends TypedArray>(a: T, b: T): T {
const result = new (a.constructor as any)(a.length + b.length);
result.set(a);
result.set(b, a.length);
return result;
}
+67
View File
@@ -0,0 +1,67 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from './TextDecoder';
describe('text encodings', () => {
it('stringFromCodePoint/utf32ToString', () => {
const s = 'abcdefg';
const data = new Uint32Array(s.length);
for (let i = 0; i < s.length; ++i) {
data[i] = s.charCodeAt(i);
assert.equal(stringFromCodePoint(data[i]), s[i]);
}
assert.equal(utf32ToString(data), s);
});
describe('StringToUtf32 Decoder', () => {
describe('full codepoint test', () => {
it('0..65535', () => {
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
for (let i = 0; i < 65536; ++i) {
// skip surrogate pairs
if (i >= 0xD800 && i <= 0xDFFF) {
continue;
}
const length = decoder.decode(String.fromCharCode(i), target);
assert.equal(length, 1);
assert.equal(target[0], i);
assert.equal(utf32ToString(target, 0, length), String.fromCharCode(i));
decoder.clear();
}
});
it('65536..0x10FFFF (surrogates)', function(): void {
this.timeout(20000);
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
for (let i = 65536; i < 0x10FFFF; ++i) {
const codePoint = i - 0x10000;
const s = String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);
const length = decoder.decode(s, target);
assert.equal(length, 1);
assert.equal(target[0], i);
assert.equal(utf32ToString(target, 0, length), s);
decoder.clear();
}
});
});
describe('stream handling', () => {
it('surrogates mixed advance by 1', () => {
const decoder = new StringToUtf32();
const target = new Uint32Array(5);
const input = 'Ä€𝄞Ö𝄞€Ü𝄞€';
let decoded = '';
for (let i = 0; i < input.length; ++i) {
const written = decoder.decode(input[i], target);
decoded += utf32ToString(target, written);
}
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
});
});
});
});
+109
View File
@@ -0,0 +1,109 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
/**
* StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints.
* To keep the decoder in line with JS strings it handles single surrogates as UCS2.
*/
export class StringToUtf32 {
private _interim: number = 0;
/**
* Clears interim and resets decoder to clean state.
*/
public clear(): void {
this._interim = 0;
}
/**
* Decode JS string to UTF32 codepoints.
* The methods assumes stream input and will store partly transmitted
* surrogate pairs and decode them with the next data chunk.
* Note: The method does no bound checks for target, therefore make sure
* the provided input data does not exceed the size of `target`.
* Returns the number of written codepoints in `target`.
*/
decode(input: string, target: Uint32Array): number {
const length = input.length;
if (!length) {
return 0;
}
let size = 0;
let startPos = 0;
// handle leftover surrogate high
if (this._interim) {
const second = input.charCodeAt(startPos++);
if (0xDC00 <= second && second <= 0xDFFF) {
target[size++] = (this._interim - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
} else {
// illegal codepoint (USC2 handling)
target[size++] = this._interim;
target[size++] = second;
}
this._interim = 0;
}
for (let i = startPos; i < length; ++i) {
const code = input.charCodeAt(i);
// surrogate pair first
if (0xD800 <= code && code <= 0xDBFF) {
if (++i >= length) {
this._interim = code;
return size;
}
const second = input.charCodeAt(i);
if (0xDC00 <= second && second <= 0xDFFF) {
target[size++] = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
} else {
// illegal codepoint (USC2 handling)
target[size++] = code;
target[size++] = second;
}
continue;
}
target[size++] = code;
}
return size;
}
}
/**
* Convert UTF32 codepoint into JS string.
*/
export function stringFromCodePoint(codePoint: number): string {
if (codePoint > 0xFFFF) {
// UTF32 to UTF16 conversion (see comments in utf32ToString)
codePoint -= 0x10000;
return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);
}
return String.fromCharCode(codePoint);
}
/**
* Convert UTF32 char codes into JS string.
* Basically the same as `stringFromCodePoint` but for multiple codepoints
* in a loop (which is a lot faster).
*/
export function utf32ToString(data: Uint32Array, start: number = 0, end: number = data.length): string {
let result = '';
for (let i = start; i < end; ++i) {
let codepoint = data[i];
if (codepoint > 0xFFFF) {
// JS string are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair
// conversion rules:
// - subtract 0x10000 from code point, leaving a 20 bit number
// - add high 10 bits to 0xD800 --> first surrogate
// - add low 10 bits to 0xDC00 --> second surrogate
codepoint -= 0x10000;
result += String.fromCharCode((codepoint >> 10) + 0xD800) + String.fromCharCode((codepoint % 0x400) + 0xDC00);
} else {
result += String.fromCharCode(codepoint);
}
}
return result;
}
+14 -8
View File
@@ -314,28 +314,32 @@ declare module 'xterm' {
/**
* The element containing the terminal.
*/
element: HTMLElement;
readonly element: HTMLElement;
/**
* The textarea that accepts input for the terminal.
*/
textarea: HTMLTextAreaElement;
readonly textarea: HTMLTextAreaElement;
/**
* The number of rows in the terminal's viewport.
* The number of rows in the terminal's viewport. Use
* `ITerminalOptions.rows` to set this in the constructor and
* `Terminal.resize` for when the terminal exists.
*/
rows: number;
readonly rows: number;
/**
* The number of columns in the terminal's viewport.
* The number of columns in the terminal's viewport. Use
* `ITerminalOptions.cols` to set this in the constructor and
* `Terminal.resize` for when the terminal exists.
*/
cols: number;
readonly cols: number;
/**
* (EXPERIMENTAL) Get all markers registered against the buffer. If the alt
* buffer is active this will always return [].
*/
markers: IMarker[];
readonly markers: IMarker[];
/**
* Natural language strings that can be localized.
@@ -439,7 +443,9 @@ declare module 'xterm' {
addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable;
/**
* Resizes the terminal.
* Resizes the terminal. It's best practice to debounce calls to resize,
* this will help ensure that the pty can respond to the resize event
* before another one occurs.
* @param x The number of columns to resize to.
* @param y The number of rows to resize to.
*/