Files
xterm.js/src/common/InputHandler.test.ts
T
Ayman Bagabas db5bfc7525 Add support to ANSI OSC52
Add support to ANSI OSC52 sequence to manipulate selection and clipboard
data. The sequence specs supports multiple clipboard selections but we
only support the common ones, system and primary clipboard selections.

This adds a new event listener to the common terminal module
`onClipboard` to allow external implementations to hook into it.

The addon uses the browser Clipboard API to read/write from and to the
clipboard. The default `ClipboardProvider` uses the browser Clipboard
API. This means it only supports read/write to and from the system
clipboard.

Reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
Fixes: xtermjs#3260
Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
2023-09-20 17:46:52 -04:00

2431 lines
114 KiB
TypeScript

/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { InputHandler } from 'common/InputHandler';
import { IBufferLine, IAttributeData, IColorEvent, ColorIndex, ColorRequestType, SpecialColorIndex, IClipboardEvent, ClipboardEventType } from 'common/Types';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { Attributes, BgFlags, UnderlineStyle } from 'common/buffer/Constants';
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
import { Params } from 'common/parser/Params';
import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test';
import { IBufferService, ICoreService } from 'common/services/Services';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { clone } from 'common/Clone';
import { BufferService } from 'common/services/BufferService';
import { CoreService } from 'common/services/CoreService';
import { ClipboardSelection } from 'xterm';
function getCursor(bufferService: IBufferService): number[] {
return [
bufferService.buffer.x,
bufferService.buffer.y
];
}
function getLines(bufferService: IBufferService, limit: number = bufferService.rows): string[] {
const res: string[] = [];
for (let i = 0; i < limit; ++i) {
const line = bufferService.buffer.lines.get(i);
if (line) {
res.push(line.translateToString(true));
}
}
return res;
}
class TestInputHandler extends InputHandler {
public get curAttrData(): IAttributeData { return (this as any)._curAttrData; }
public get windowTitleStack(): string[] { return this._windowTitleStack; }
public get iconNameStack(): string[] { return this._iconNameStack; }
/**
* Promise based parse call to await the full resolve of given input data.
* This is useful to test async handlers in inputhandler directly.
*/
public async parseP(data: string | Uint8Array): Promise<void> {
let result: Promise<boolean> | void;
let prev: boolean | undefined;
while (result = this.parse(data, prev)) {
prev = await result;
}
}
}
describe('InputHandler', () => {
let bufferService: IBufferService;
let coreService: ICoreService;
let optionsService: MockOptionsService;
let inputHandler: TestInputHandler;
beforeEach(() => {
optionsService = new MockOptionsService();
bufferService = new BufferService(optionsService);
bufferService.resize(80, 30);
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
describe('SL/SR/DECIC/DECDC', () => {
beforeEach(() => {
bufferService.resize(5, 5);
optionsService.options.scrollback = 1;
bufferService.reset();
});
it('SL (scrollLeft)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[ @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']);
await inputHandler.parseP('\x1b[0 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '345', '345', '345', '345', '345']);
await inputHandler.parseP('\x1b[2 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[ A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
await inputHandler.parseP('\x1b[0 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
await inputHandler.parseP('\x1b[2 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
bufferService.reset();
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[1\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
bufferService.reset();
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[2\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);
bufferService.reset();
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[1\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);
bufferService.reset();
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[2\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '125', '125', '125', '125', '125']);
});
});
describe('BS with reverseWraparound set/unset', () => {
const ttyBS = '\x08 \x08'; // tty ICANON sends <BS SP BS> on pressing BS
beforeEach(() => {
bufferService.resize(5, 5);
optionsService.options.scrollback = 1;
bufferService.reset();
});
describe('reverseWraparound set', () => {
it('should not reverse outside of scroll margins', async () => {
// prepare buffer content
await inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
assert.equal(bufferService.buffers.active.ydisp, 1);
assert.equal(bufferService.buffers.active.x, 5);
assert.equal(bufferService.buffers.active.y, 4);
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']);
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('uvwxy');
// set top/bottom to 1/3 (0-based)
await inputHandler.parseP('\x1b[2;4r');
// place cursor below scroll bottom
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 4;
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']);
await inputHandler.parseP('uvwxy');
// place cursor within scroll margins
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 3;
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']);
assert.equal(bufferService.buffers.active.x, 0);
assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop
await inputHandler.parseP('fghijklmnopqrst');
// place cursor above scroll top
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 0;
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
});
});
});
it('save and restore cursor', () => {
bufferService.buffer.x = 1;
bufferService.buffer.y = 2;
bufferService.buffer.ybase = 0;
inputHandler.curAttrData.fg = 3;
// Save cursor position
inputHandler.saveCursor();
assert.equal(bufferService.buffer.x, 1);
assert.equal(bufferService.buffer.y, 2);
assert.equal(inputHandler.curAttrData.fg, 3);
// Change cursor position
bufferService.buffer.x = 10;
bufferService.buffer.y = 20;
inputHandler.curAttrData.fg = 30;
// Restore cursor position
inputHandler.restoreCursor();
assert.equal(bufferService.buffer.x, 1);
assert.equal(bufferService.buffer.y, 2);
assert.equal(inputHandler.curAttrData.fg, 3);
});
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
inputHandler.setCursorStyle(Params.fromArray([0]));
assert.equal(optionsService.options['cursorStyle'], 'block');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([1]));
assert.equal(optionsService.options['cursorStyle'], 'block');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([2]));
assert.equal(optionsService.options['cursorStyle'], 'block');
assert.equal(optionsService.options['cursorBlink'], false);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([3]));
assert.equal(optionsService.options['cursorStyle'], 'underline');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([4]));
assert.equal(optionsService.options['cursorStyle'], 'underline');
assert.equal(optionsService.options['cursorBlink'], false);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([5]));
assert.equal(optionsService.options['cursorStyle'], 'bar');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([6]));
assert.equal(optionsService.options['cursorStyle'], 'bar');
assert.equal(optionsService.options['cursorBlink'], false);
});
});
describe('setMode', () => {
it('should toggle bracketedPasteMode', () => {
const coreService = new MockCoreService();
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
// Set bracketed paste mode
inputHandler.setModePrivate(Params.fromArray([2004]));
assert.equal(coreService.decPrivateModes.bracketedPasteMode, true);
// Reset bracketed paste mode
inputHandler.resetModePrivate(Params.fromArray([2004]));
assert.equal(coreService.decPrivateModes.bracketedPasteMode, false);
});
});
describe('regression tests', function (): void {
function termContent(bufferService: IBufferService, trim: boolean): string[] {
const result = [];
for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i)!.translateToString(trim));
return result;
}
it('insertChars', async () => {
const bufferService = new MockBufferService(80, 30);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
// insert some data in first and second line
await inputHandler.parseP(Array(bufferService.cols - 9).join('a'));
await inputHandler.parseP('1234567890');
await inputHandler.parseP(Array(bufferService.cols - 9).join('a'));
await inputHandler.parseP('1234567890');
const line1: IBufferLine = bufferService.buffer.lines.get(0)!;
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '1234567890');
// insert one char from params = [0]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.insertChars(Params.fromArray([0]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' 123456789');
// insert one char from params = [1]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.insertChars(Params.fromArray([1]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' 12345678');
// insert two chars from params = [2]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.insertChars(Params.fromArray([2]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' 123456');
// insert 10 chars from params = [10]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.insertChars(Params.fromArray([10]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' ');
assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a'));
});
it('deleteChars', async () => {
const bufferService = new MockBufferService(80, 30);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
// insert some data in first and second line
await inputHandler.parseP(Array(bufferService.cols - 9).join('a'));
await inputHandler.parseP('1234567890');
await inputHandler.parseP(Array(bufferService.cols - 9).join('a'));
await inputHandler.parseP('1234567890');
const line1: IBufferLine = bufferService.buffer.lines.get(0)!;
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '1234567890');
// delete one char from params = [0]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.deleteChars(Params.fromArray([0]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '234567890 ');
assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a') + '234567890');
// insert one char from params = [1]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.deleteChars(Params.fromArray([1]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '34567890 ');
assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a') + '34567890');
// insert two chars from params = [2]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.deleteChars(Params.fromArray([2]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + '567890 ');
assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a') + '567890');
// insert 10 chars from params = [10]
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.deleteChars(Params.fromArray([10]));
assert.equal(line1.translateToString(false), Array(bufferService.cols - 9).join('a') + ' ');
assert.equal(line1.translateToString(true), Array(bufferService.cols - 9).join('a'));
});
it('eraseInLine', async () => {
const bufferService = new MockBufferService(80, 30);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
// fill 6 lines to test 3 different states
await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// params[0] - right erase
bufferService.buffer.y = 0;
bufferService.buffer.x = 70;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), Array(71).join('a') + ' ');
// params[1] - left erase
bufferService.buffer.y = 1;
bufferService.buffer.x = 70;
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(1)!.translateToString(false), Array(71).join(' ') + ' aaaaaaaaa');
// params[1] - left erase
bufferService.buffer.y = 2;
bufferService.buffer.x = 70;
inputHandler.eraseInLine(Params.fromArray([2]));
assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), Array(bufferService.cols + 1).join(' '));
});
it('eraseInLine reflow', async () => {
const bufferService = new MockBufferService(80, 30);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
const resetToBaseState = async (): Promise<void> => {
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0
await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// confirm precondition that line 2 is wrapped
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
};
// params[0] - erase from the cursor through the end of the row.
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
bufferService.buffer.y = 2;
bufferService.buffer.x = 0;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
// params[1] - erase from the beginning of the line through the cursor
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
// params[2] - erase complete line
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([2]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
});
it('eraseInDisplay', async () => {
const bufferService = new MockBufferService(80, 7);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
// fill display with a's
for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// params [0] - right and below erase
bufferService.buffer.y = 5;
bufferService.buffer.x = 40;
inputHandler.eraseInDisplay(Params.fromArray([0]));
assert.deepEqual(termContent(bufferService, false), [
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(40 + 1).join('a') + Array(bufferService.cols - 40 + 1).join(' '),
Array(bufferService.cols + 1).join(' ')
]);
assert.deepEqual(termContent(bufferService, true), [
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(bufferService.cols + 1).join('a'),
Array(40 + 1).join('a'),
''
]);
// reset
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// params [1] - left and above
bufferService.buffer.y = 5;
bufferService.buffer.x = 40;
inputHandler.eraseInDisplay(Params.fromArray([1]));
assert.deepEqual(termContent(bufferService, false), [
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'),
Array(bufferService.cols + 1).join('a')
]);
assert.deepEqual(termContent(bufferService, true), [
'',
'',
'',
'',
'',
Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'),
Array(bufferService.cols + 1).join('a')
]);
// reset
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
for (let i = 0; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// params [2] - whole screen
bufferService.buffer.y = 5;
bufferService.buffer.x = 40;
inputHandler.eraseInDisplay(Params.fromArray([2]));
assert.deepEqual(termContent(bufferService, false), [
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' '),
Array(bufferService.cols + 1).join(' ')
]);
assert.deepEqual(termContent(bufferService, true), [
'',
'',
'',
'',
'',
'',
''
]);
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0
await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// params[1] left and above with wrap
// confirm precondition that line 2 is wrapped
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInDisplay(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0
await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// params[1] left and above with wrap
// confirm precondition that line 2 is wrapped
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
bufferService.buffer.y = 1;
bufferService.buffer.x = 90; // Cursor is beyond last column
inputHandler.eraseInDisplay(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
});
});
describe('print', () => {
it('should not cause an infinite loop (regression test)', () => {
const inputHandler = new TestInputHandler(
new MockBufferService(80, 30),
new MockCharsetService(),
new MockCoreService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
});
it('should clear cells to the right on early wrap-around', async () => {
bufferService.resize(5, 5);
optionsService.options.scrollback = 1;
await inputHandler.parseP('12345');
bufferService.buffer.x = 0;
await inputHandler.parseP('¥¥¥');
assert.deepEqual(getLines(bufferService, 2), ['¥¥', '¥']);
});
});
describe('alt screen', () => {
let bufferService: IBufferService;
let handler: TestInputHandler;
beforeEach(() => {
bufferService = new MockBufferService(80, 30);
handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => {
await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
assert.equal(bufferService.buffer.translateBufferLineToString(0, true), '');
assert.equal(bufferService.buffer.translateBufferLineToString(1, true), ' TEST');
// Text color of 'TEST' should be red
assert.equal((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor()), 1);
});
it('should handle DECSET/DECRST 1047 (alt screen buffer)', async () => {
await handler.parseP('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
assert.equal(bufferService.buffer.translateBufferLineToString(0, true), '');
assert.equal(bufferService.buffer.translateBufferLineToString(1, true), ' TEST');
// Text color of 'TEST' should be red
assert.equal((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor()), 1);
});
it('should handle DECSET/DECRST 1048 (alt screen cursor)', async () => {
await handler.parseP('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST');
assert.equal(bufferService.buffer.translateBufferLineToString(1, true), 'JUNK');
// Text color of 'TEST' should be default
assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg);
// Text color of 'JUNK' should be red
assert.equal((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor()), 1);
});
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', async () => {
await handler.parseP('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST');
assert.equal(bufferService.buffer.translateBufferLineToString(1, true), '');
// Text color of 'TEST' should be default
assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg);
});
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', async () => {
await handler.parseP('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
assert.equal(bufferService.buffer.translateBufferLineToString(0, true), 'TEST');
// Text color of 'TEST' should be default
assert.equal(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg, DEFAULT_ATTR_DATA.fg);
await handler.parseP('\x1b[?1049h\x1b[uTEST');
assert.equal(bufferService.buffer.translateBufferLineToString(1, true), 'TEST');
// Text color of 'TEST' should be red
assert.equal((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor()), 1);
});
it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', async () => {
await handler.parseP('\x1b[42m\x1b[?1049h');
// Buffer should be filled with green background
assert.equal(bufferService.buffer.lines.get(20)!.loadCell(10, new CellData()).getBgColor(), 2);
});
});
describe('text attributes', () => {
it('bold', async () => {
await inputHandler.parseP('\x1b[1m');
assert.equal(!!inputHandler.curAttrData.isBold(), true);
await inputHandler.parseP('\x1b[22m');
assert.equal(!!inputHandler.curAttrData.isBold(), false);
});
it('dim', async () => {
await inputHandler.parseP('\x1b[2m');
assert.equal(!!inputHandler.curAttrData.isDim(), true);
await inputHandler.parseP('\x1b[22m');
assert.equal(!!inputHandler.curAttrData.isDim(), false);
});
it('italic', async () => {
await inputHandler.parseP('\x1b[3m');
assert.equal(!!inputHandler.curAttrData.isItalic(), true);
await inputHandler.parseP('\x1b[23m');
assert.equal(!!inputHandler.curAttrData.isItalic(), false);
});
it('underline', async () => {
await inputHandler.parseP('\x1b[4m');
assert.equal(!!inputHandler.curAttrData.isUnderline(), true);
await inputHandler.parseP('\x1b[24m');
assert.equal(!!inputHandler.curAttrData.isUnderline(), false);
});
it('blink', async () => {
await inputHandler.parseP('\x1b[5m');
assert.equal(!!inputHandler.curAttrData.isBlink(), true);
await inputHandler.parseP('\x1b[25m');
assert.equal(!!inputHandler.curAttrData.isBlink(), false);
});
it('inverse', async () => {
await inputHandler.parseP('\x1b[7m');
assert.equal(!!inputHandler.curAttrData.isInverse(), true);
await inputHandler.parseP('\x1b[27m');
assert.equal(!!inputHandler.curAttrData.isInverse(), false);
});
it('invisible', async () => {
await inputHandler.parseP('\x1b[8m');
assert.equal(!!inputHandler.curAttrData.isInvisible(), true);
await inputHandler.parseP('\x1b[28m');
assert.equal(!!inputHandler.curAttrData.isInvisible(), false);
});
it('strikethrough', async () => {
await inputHandler.parseP('\x1b[9m');
assert.equal(!!inputHandler.curAttrData.isStrikethrough(), true);
await inputHandler.parseP('\x1b[29m');
assert.equal(!!inputHandler.curAttrData.isStrikethrough(), false);
});
it('colormode palette 16', async () => {
assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT
assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT
// lower 8 colors
for (let i = 0; i < 8; ++i) {
await inputHandler.parseP(`\x1b[${i + 30};${i + 40}m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16);
assert.equal(inputHandler.curAttrData.getFgColor(), i);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16);
assert.equal(inputHandler.curAttrData.getBgColor(), i);
}
// reset to DEFAULT
await inputHandler.parseP(`\x1b[39;49m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), 0);
assert.equal(inputHandler.curAttrData.getBgColorMode(), 0);
});
it('colormode palette 256', async () => {
assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT
assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT
// lower 8 colors
for (let i = 0; i < 256; ++i) {
await inputHandler.parseP(`\x1b[38;5;${i};48;5;${i}m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.getFgColor(), i);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.getBgColor(), i);
}
// reset to DEFAULT
await inputHandler.parseP(`\x1b[39;49m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), 0);
assert.equal(inputHandler.curAttrData.getFgColor(), -1);
assert.equal(inputHandler.curAttrData.getBgColorMode(), 0);
assert.equal(inputHandler.curAttrData.getBgColor(), -1);
});
it('colormode RGB', async () => {
assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT
assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT
await inputHandler.parseP(`\x1b[38;2;1;2;3;48;2;4;5;6m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_RGB);
assert.equal(inputHandler.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3);
assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [1, 2, 3]);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_RGB);
assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getBgColor()), [4, 5, 6]);
// reset to DEFAULT
await inputHandler.parseP(`\x1b[39;49m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), 0);
assert.equal(inputHandler.curAttrData.getFgColor(), -1);
assert.equal(inputHandler.curAttrData.getBgColorMode(), 0);
assert.equal(inputHandler.curAttrData.getBgColor(), -1);
});
it('colormode transition RGB to 256', async () => {
// enter RGB for FG and BG
await inputHandler.parseP(`\x1b[38;2;1;2;3;48;2;4;5;6m`);
// enter 256 for FG and BG
await inputHandler.parseP(`\x1b[38;5;255;48;5;255m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.getFgColor(), 255);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.getBgColor(), 255);
});
it('colormode transition RGB to 16', async () => {
// enter RGB for FG and BG
await inputHandler.parseP(`\x1b[38;2;1;2;3;48;2;4;5;6m`);
// enter 16 for FG and BG
await inputHandler.parseP(`\x1b[37;47m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16);
assert.equal(inputHandler.curAttrData.getFgColor(), 7);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16);
assert.equal(inputHandler.curAttrData.getBgColor(), 7);
});
it('colormode transition 16 to 256', async () => {
// enter 16 for FG and BG
await inputHandler.parseP(`\x1b[37;47m`);
// enter 256 for FG and BG
await inputHandler.parseP(`\x1b[38;5;255;48;5;255m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.getFgColor(), 255);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.getBgColor(), 255);
});
it('colormode transition 256 to 16', async () => {
// enter 256 for FG and BG
await inputHandler.parseP(`\x1b[38;5;255;48;5;255m`);
// enter 16 for FG and BG
await inputHandler.parseP(`\x1b[37;47m`);
assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16);
assert.equal(inputHandler.curAttrData.getFgColor(), 7);
assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16);
assert.equal(inputHandler.curAttrData.getBgColor(), 7);
});
it('should zero missing RGB values', async () => {
await inputHandler.parseP(`\x1b[38;2;1;2;3m`);
await inputHandler.parseP(`\x1b[38;2;5m`);
assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [5, 0, 0]);
});
});
describe('colon notation', () => {
let inputHandler2: TestInputHandler;
beforeEach(() => {
inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
describe('should equal to semicolon', () => {
it('CSI 38:2::50:100:150 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;50;100;150m');
await inputHandler.parseP('\x1b[38:2::50:100:150m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38:2::50:100: m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;50;100;m');
await inputHandler.parseP('\x1b[38:2::50:100:m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38:2::50:: m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;50;;m');
await inputHandler.parseP('\x1b[38:2::50::m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38:2:::: m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;;;m');
await inputHandler.parseP('\x1b[38:2::::m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38;2::50:100:150 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;50;100;150m');
await inputHandler.parseP('\x1b[38;2::50:100:150m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38;2;50:100:150 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;50;100;150m');
await inputHandler.parseP('\x1b[38;2;50:100:150m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38;2;50;100:150 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2;50;100;150m');
await inputHandler.parseP('\x1b[38;2;50;100:150m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38:5:50 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;5;50m');
await inputHandler.parseP('\x1b[38:5:50m');
assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38:5: m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;5;m');
await inputHandler.parseP('\x1b[38:5:m');
assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38;5:50 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;5;50m');
await inputHandler.parseP('\x1b[38;5:50m');
assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
});
describe('should fill early sequence end with default of 0', () => {
it('CSI 38:2 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;2m');
await inputHandler.parseP('\x1b[38:2m');
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 38:5 m', async () => {
inputHandler.curAttrData.fg = 0xFFFFFFFF;
inputHandler2.curAttrData.fg = 0xFFFFFFFF;
await inputHandler2.parseP('\x1b[38;5m');
await inputHandler.parseP('\x1b[38:5m');
assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
});
describe('should not interfere with leading/following SGR attrs', () => {
it('CSI 1 ; 38:2::50:100:150 ; 4 m', async () => {
await inputHandler2.parseP('\x1b[1;38;2;50;100;150;4m');
await inputHandler.parseP('\x1b[1;38:2::50:100:150;4m');
assert.equal(!!inputHandler2.curAttrData.isBold(), true);
assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 1 ; 38:2::50:100: ; 4 m', async () => {
await inputHandler2.parseP('\x1b[1;38;2;50;100;;4m');
await inputHandler.parseP('\x1b[1;38:2::50:100:;4m');
assert.equal(!!inputHandler2.curAttrData.isBold(), true);
assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 1 ; 38:2::50:100 ; 4 m', async () => {
await inputHandler2.parseP('\x1b[1;38;2;50;100;;4m');
await inputHandler.parseP('\x1b[1;38:2::50:100;4m');
assert.equal(!!inputHandler2.curAttrData.isBold(), true);
assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 1 ; 38:2:: ; 4 m', async () => {
await inputHandler2.parseP('\x1b[1;38;2;;;;4m');
await inputHandler.parseP('\x1b[1;38:2::;4m');
assert.equal(!!inputHandler2.curAttrData.isBold(), true);
assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
it('CSI 1 ; 38;2:: ; 4 m', async () => {
await inputHandler2.parseP('\x1b[1;38;2;;;;4m');
await inputHandler.parseP('\x1b[1;38;2::;4m');
assert.equal(!!inputHandler2.curAttrData.isBold(), true);
assert.equal(!!inputHandler2.curAttrData.isUnderline(), true);
assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0);
assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg);
});
});
});
describe('cursor positioning', () => {
beforeEach(() => {
bufferService.resize(10, 10);
});
it('cursor forward (CUF)', async () => {
await inputHandler.parseP('\x1b[C');
assert.deepEqual(getCursor(bufferService), [1, 0]);
await inputHandler.parseP('\x1b[1C');
assert.deepEqual(getCursor(bufferService), [2, 0]);
await inputHandler.parseP('\x1b[4C');
assert.deepEqual(getCursor(bufferService), [6, 0]);
await inputHandler.parseP('\x1b[100C');
assert.deepEqual(getCursor(bufferService), [9, 0]);
// should not change y
bufferService.buffer.x = 8;
bufferService.buffer.y = 4;
await inputHandler.parseP('\x1b[C');
assert.deepEqual(getCursor(bufferService), [9, 4]);
});
it('cursor backward (CUB)', async () => {
await inputHandler.parseP('\x1b[D');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[1D');
assert.deepEqual(getCursor(bufferService), [0, 0]);
// place cursor at end of first line
await inputHandler.parseP('\x1b[100C');
await inputHandler.parseP('\x1b[D');
assert.deepEqual(getCursor(bufferService), [8, 0]);
await inputHandler.parseP('\x1b[1D');
assert.deepEqual(getCursor(bufferService), [7, 0]);
await inputHandler.parseP('\x1b[4D');
assert.deepEqual(getCursor(bufferService), [3, 0]);
await inputHandler.parseP('\x1b[100D');
assert.deepEqual(getCursor(bufferService), [0, 0]);
// should not change y
bufferService.buffer.x = 4;
bufferService.buffer.y = 4;
await inputHandler.parseP('\x1b[D');
assert.deepEqual(getCursor(bufferService), [3, 4]);
});
it('cursor down (CUD)', async () => {
await inputHandler.parseP('\x1b[B');
assert.deepEqual(getCursor(bufferService), [0, 1]);
await inputHandler.parseP('\x1b[1B');
assert.deepEqual(getCursor(bufferService), [0, 2]);
await inputHandler.parseP('\x1b[4B');
assert.deepEqual(getCursor(bufferService), [0, 6]);
await inputHandler.parseP('\x1b[100B');
assert.deepEqual(getCursor(bufferService), [0, 9]);
// should not change x
bufferService.buffer.x = 8;
bufferService.buffer.y = 0;
await inputHandler.parseP('\x1b[B');
assert.deepEqual(getCursor(bufferService), [8, 1]);
});
it('cursor up (CUU)', async () => {
await inputHandler.parseP('\x1b[A');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[1A');
assert.deepEqual(getCursor(bufferService), [0, 0]);
// place cursor at beginning of last row
await inputHandler.parseP('\x1b[100B');
await inputHandler.parseP('\x1b[A');
assert.deepEqual(getCursor(bufferService), [0, 8]);
await inputHandler.parseP('\x1b[1A');
assert.deepEqual(getCursor(bufferService), [0, 7]);
await inputHandler.parseP('\x1b[4A');
assert.deepEqual(getCursor(bufferService), [0, 3]);
await inputHandler.parseP('\x1b[100A');
assert.deepEqual(getCursor(bufferService), [0, 0]);
// should not change x
bufferService.buffer.x = 8;
bufferService.buffer.y = 9;
await inputHandler.parseP('\x1b[A');
assert.deepEqual(getCursor(bufferService), [8, 8]);
});
it('cursor next line (CNL)', async () => {
await inputHandler.parseP('\x1b[E');
assert.deepEqual(getCursor(bufferService), [0, 1]);
await inputHandler.parseP('\x1b[1E');
assert.deepEqual(getCursor(bufferService), [0, 2]);
await inputHandler.parseP('\x1b[4E');
assert.deepEqual(getCursor(bufferService), [0, 6]);
await inputHandler.parseP('\x1b[100E');
assert.deepEqual(getCursor(bufferService), [0, 9]);
// should reset x to zero
bufferService.buffer.x = 8;
bufferService.buffer.y = 0;
await inputHandler.parseP('\x1b[E');
assert.deepEqual(getCursor(bufferService), [0, 1]);
});
it('cursor previous line (CPL)', async () => {
await inputHandler.parseP('\x1b[F');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[1F');
assert.deepEqual(getCursor(bufferService), [0, 0]);
// place cursor at beginning of last row
await inputHandler.parseP('\x1b[100E');
await inputHandler.parseP('\x1b[F');
assert.deepEqual(getCursor(bufferService), [0, 8]);
await inputHandler.parseP('\x1b[1F');
assert.deepEqual(getCursor(bufferService), [0, 7]);
await inputHandler.parseP('\x1b[4F');
assert.deepEqual(getCursor(bufferService), [0, 3]);
await inputHandler.parseP('\x1b[100F');
assert.deepEqual(getCursor(bufferService), [0, 0]);
// should reset x to zero
bufferService.buffer.x = 8;
bufferService.buffer.y = 9;
await inputHandler.parseP('\x1b[F');
assert.deepEqual(getCursor(bufferService), [0, 8]);
});
it('cursor character absolute (CHA)', async () => {
await inputHandler.parseP('\x1b[G');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[1G');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[2G');
assert.deepEqual(getCursor(bufferService), [1, 0]);
await inputHandler.parseP('\x1b[5G');
assert.deepEqual(getCursor(bufferService), [4, 0]);
await inputHandler.parseP('\x1b[100G');
assert.deepEqual(getCursor(bufferService), [9, 0]);
});
it('cursor position (CUP)', async () => {
bufferService.buffer.x = 5;
bufferService.buffer.y = 5;
await inputHandler.parseP('\x1b[H');
assert.deepEqual(getCursor(bufferService), [0, 0]);
bufferService.buffer.x = 5;
bufferService.buffer.y = 5;
await inputHandler.parseP('\x1b[1H');
assert.deepEqual(getCursor(bufferService), [0, 0]);
bufferService.buffer.x = 5;
bufferService.buffer.y = 5;
await inputHandler.parseP('\x1b[1;1H');
assert.deepEqual(getCursor(bufferService), [0, 0]);
bufferService.buffer.x = 5;
bufferService.buffer.y = 5;
await inputHandler.parseP('\x1b[8H');
assert.deepEqual(getCursor(bufferService), [0, 7]);
bufferService.buffer.x = 5;
bufferService.buffer.y = 5;
await inputHandler.parseP('\x1b[;8H');
assert.deepEqual(getCursor(bufferService), [7, 0]);
bufferService.buffer.x = 5;
bufferService.buffer.y = 5;
await inputHandler.parseP('\x1b[100;100H');
assert.deepEqual(getCursor(bufferService), [9, 9]);
});
it('horizontal position absolute (HPA)', async () => {
await inputHandler.parseP('\x1b[`');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[1`');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[2`');
assert.deepEqual(getCursor(bufferService), [1, 0]);
await inputHandler.parseP('\x1b[5`');
assert.deepEqual(getCursor(bufferService), [4, 0]);
await inputHandler.parseP('\x1b[100`');
assert.deepEqual(getCursor(bufferService), [9, 0]);
});
it('horizontal position relative (HPR)', async () => {
await inputHandler.parseP('\x1b[a');
assert.deepEqual(getCursor(bufferService), [1, 0]);
await inputHandler.parseP('\x1b[1a');
assert.deepEqual(getCursor(bufferService), [2, 0]);
await inputHandler.parseP('\x1b[4a');
assert.deepEqual(getCursor(bufferService), [6, 0]);
await inputHandler.parseP('\x1b[100a');
assert.deepEqual(getCursor(bufferService), [9, 0]);
// should not change y
bufferService.buffer.x = 8;
bufferService.buffer.y = 4;
await inputHandler.parseP('\x1b[a');
assert.deepEqual(getCursor(bufferService), [9, 4]);
});
it('vertical position absolute (VPA)', async () => {
await inputHandler.parseP('\x1b[d');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[1d');
assert.deepEqual(getCursor(bufferService), [0, 0]);
await inputHandler.parseP('\x1b[2d');
assert.deepEqual(getCursor(bufferService), [0, 1]);
await inputHandler.parseP('\x1b[5d');
assert.deepEqual(getCursor(bufferService), [0, 4]);
await inputHandler.parseP('\x1b[100d');
assert.deepEqual(getCursor(bufferService), [0, 9]);
// should not change x
bufferService.buffer.x = 8;
bufferService.buffer.y = 4;
await inputHandler.parseP('\x1b[d');
assert.deepEqual(getCursor(bufferService), [8, 0]);
});
it('vertical position relative (VPR)', async () => {
await inputHandler.parseP('\x1b[e');
assert.deepEqual(getCursor(bufferService), [0, 1]);
await inputHandler.parseP('\x1b[1e');
assert.deepEqual(getCursor(bufferService), [0, 2]);
await inputHandler.parseP('\x1b[4e');
assert.deepEqual(getCursor(bufferService), [0, 6]);
await inputHandler.parseP('\x1b[100e');
assert.deepEqual(getCursor(bufferService), [0, 9]);
// should not change x
bufferService.buffer.x = 8;
bufferService.buffer.y = 4;
await inputHandler.parseP('\x1b[e');
assert.deepEqual(getCursor(bufferService), [8, 5]);
});
describe('should clamp cursor into addressible range', () => {
it('CUF', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[C');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[C');
assert.deepEqual(getCursor(bufferService), [1, 0]);
});
it('CUB', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[D');
assert.deepEqual(getCursor(bufferService), [8, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[D');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
it('CUD', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[B');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[B');
assert.deepEqual(getCursor(bufferService), [0, 1]);
});
it('CUU', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[A');
assert.deepEqual(getCursor(bufferService), [9, 8]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[A');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
it('CNL', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[E');
assert.deepEqual(getCursor(bufferService), [0, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[E');
assert.deepEqual(getCursor(bufferService), [0, 1]);
});
it('CPL', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[F');
assert.deepEqual(getCursor(bufferService), [0, 8]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[F');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
it('CHA', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[5G');
assert.deepEqual(getCursor(bufferService), [4, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[5G');
assert.deepEqual(getCursor(bufferService), [4, 0]);
});
it('CUP', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[5;5H');
assert.deepEqual(getCursor(bufferService), [4, 4]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[5;5H');
assert.deepEqual(getCursor(bufferService), [4, 4]);
});
it('HPA', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[5`');
assert.deepEqual(getCursor(bufferService), [4, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[5`');
assert.deepEqual(getCursor(bufferService), [4, 0]);
});
it('HPR', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[a');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[a');
assert.deepEqual(getCursor(bufferService), [1, 0]);
});
it('VPA', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[5d');
assert.deepEqual(getCursor(bufferService), [9, 4]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[5d');
assert.deepEqual(getCursor(bufferService), [0, 4]);
});
it('VPR', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[e');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[e');
assert.deepEqual(getCursor(bufferService), [0, 1]);
});
it('DCH', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[P');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[P');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
it('DCH - should delete last cell', async () => {
await inputHandler.parseP('0123456789\x1b[P');
assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 ');
});
it('ECH', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[X');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[X');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
it('ECH - should delete last cell', async () => {
await inputHandler.parseP('0123456789\x1b[X');
assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 ');
});
it('ICH', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[@');
assert.deepEqual(getCursor(bufferService), [9, 9]);
bufferService.buffer.x = -10000;
bufferService.buffer.y = -10000;
await inputHandler.parseP('\x1b[@');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
it('ICH - should delete last cell', async () => {
await inputHandler.parseP('0123456789\x1b[@');
assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 ');
});
});
});
describe('DECSTBM - scroll margins', () => {
beforeEach(() => {
bufferService.resize(10, 10);
});
it('should default to whole viewport', async () => {
await inputHandler.parseP('\x1b[r');
assert.equal(bufferService.buffer.scrollTop, 0);
assert.equal(bufferService.buffer.scrollBottom, 9);
await inputHandler.parseP('\x1b[3;7r');
assert.equal(bufferService.buffer.scrollTop, 2);
assert.equal(bufferService.buffer.scrollBottom, 6);
await inputHandler.parseP('\x1b[0;0r');
assert.equal(bufferService.buffer.scrollTop, 0);
assert.equal(bufferService.buffer.scrollBottom, 9);
});
it('should clamp bottom', async () => {
await inputHandler.parseP('\x1b[3;1000r');
assert.equal(bufferService.buffer.scrollTop, 2);
assert.equal(bufferService.buffer.scrollBottom, 9);
});
it('should only apply for top < bottom', async () => {
await inputHandler.parseP('\x1b[7;2r');
assert.equal(bufferService.buffer.scrollTop, 0);
assert.equal(bufferService.buffer.scrollBottom, 9);
});
it('should home cursor', async () => {
bufferService.buffer.x = 10000;
bufferService.buffer.y = 10000;
await inputHandler.parseP('\x1b[2;7r');
assert.deepEqual(getCursor(bufferService), [0, 0]);
});
});
describe('scroll margins', () => {
beforeEach(() => {
bufferService.resize(10, 10);
});
it('scrollUp', async () => {
await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm');
assert.deepEqual(getLines(bufferService), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']);
});
it('scrollDown', async () => {
await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm');
assert.deepEqual(getLines(bufferService), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']);
});
it('insertLines - out of margins', async () => {
await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
assert.equal(bufferService.buffer.scrollTop, 2);
assert.equal(bufferService.buffer.scrollBottom, 5);
await inputHandler.parseP('\x1b[2Lm');
assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
await inputHandler.parseP('\x1b[2H\x1b[2Ln');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']);
// skip below scrollbottom
await inputHandler.parseP('\x1b[7H\x1b[2Lo');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']);
await inputHandler.parseP('\x1b[8H\x1b[2Lp');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']);
await inputHandler.parseP('\x1b[100H\x1b[2Lq');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']);
});
it('insertLines - within margins', async () => {
await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
assert.equal(bufferService.buffer.scrollTop, 2);
assert.equal(bufferService.buffer.scrollBottom, 5);
await inputHandler.parseP('\x1b[3H\x1b[2Lm');
assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']);
await inputHandler.parseP('\x1b[6H\x1b[2Ln');
assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']);
});
it('deleteLines - out of margins', async () => {
await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
assert.equal(bufferService.buffer.scrollTop, 2);
assert.equal(bufferService.buffer.scrollBottom, 5);
await inputHandler.parseP('\x1b[2Mm');
assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
await inputHandler.parseP('\x1b[2H\x1b[2Mn');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']);
// skip below scrollbottom
await inputHandler.parseP('\x1b[7H\x1b[2Mo');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']);
await inputHandler.parseP('\x1b[8H\x1b[2Mp');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']);
await inputHandler.parseP('\x1b[100H\x1b[2Mq');
assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']);
});
it('deleteLines - within margins', async () => {
await inputHandler.parseP('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r');
assert.equal(bufferService.buffer.scrollTop, 2);
assert.equal(bufferService.buffer.scrollBottom, 5);
await inputHandler.parseP('\x1b[6H\x1b[2Mm');
assert.deepEqual(getLines(bufferService), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']);
await inputHandler.parseP('\x1b[3H\x1b[2Mn');
assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
});
});
it('should parse big chunks in smaller subchunks', async () => {
// max single chunk size is hardcoded as 131072
const calls: any[] = [];
bufferService.resize(10, 10);
(inputHandler as any)._parser.parse = (data: Uint32Array, length: number) => {
calls.push([data.length, length]);
};
await inputHandler.parseP('12345');
await inputHandler.parseP('a'.repeat(10000));
await inputHandler.parseP('a'.repeat(200000));
await inputHandler.parseP('a'.repeat(300000));
assert.deepEqual(calls, [
[4096, 5],
[10000, 10000],
[131072, 131072], [131072, 200000 - 131072],
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
]);
});
describe('windowOptions', () => {
it('all should be disabled by default and not report', async () => {
bufferService.resize(10, 10);
const stack: string[] = [];
coreService.onData(data => stack.push(data));
await inputHandler.parseP('\x1b[14t');
await inputHandler.parseP('\x1b[16t');
await inputHandler.parseP('\x1b[18t');
await inputHandler.parseP('\x1b[20t');
await inputHandler.parseP('\x1b[21t');
assert.deepEqual(stack, []);
});
it('14 - GetWinSizePixels', async () => {
bufferService.resize(10, 10);
optionsService.options.windowOptions.getWinSizePixels = true;
const stack: string[] = [];
coreService.onData(data => stack.push(data));
await inputHandler.parseP('\x1b[14t');
// does not report in test terminal due to missing renderer
assert.deepEqual(stack, []);
});
it('16 - GetCellSizePixels', async () => {
bufferService.resize(10, 10);
optionsService.options.windowOptions.getCellSizePixels = true;
const stack: string[] = [];
coreService.onData(data => stack.push(data));
await inputHandler.parseP('\x1b[16t');
// does not report in test terminal due to missing renderer
assert.deepEqual(stack, []);
});
it('18 - GetWinSizeChars', async () => {
bufferService.resize(10, 10);
optionsService.options.windowOptions.getWinSizeChars = true;
const stack: string[] = [];
coreService.onData(data => stack.push(data));
await inputHandler.parseP('\x1b[18t');
assert.deepEqual(stack, ['\x1b[8;10;10t']);
bufferService.resize(50, 20);
await inputHandler.parseP('\x1b[18t');
assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']);
});
it('22/23 - PushTitle/PopTitle', async () => {
bufferService.resize(10, 10);
optionsService.options.windowOptions.pushTitle = true;
optionsService.options.windowOptions.popTitle = true;
const stack: string[] = [];
inputHandler.onTitleChange(data => stack.push(data));
await inputHandler.parseP('\x1b]0;1\x07');
await inputHandler.parseP('\x1b[22t');
await inputHandler.parseP('\x1b]0;2\x07');
await inputHandler.parseP('\x1b[22t');
await inputHandler.parseP('\x1b]0;3\x07');
await inputHandler.parseP('\x1b[22t');
assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']);
assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']);
assert.deepEqual(stack, ['1', '2', '3']);
await inputHandler.parseP('\x1b[23t');
await inputHandler.parseP('\x1b[23t');
await inputHandler.parseP('\x1b[23t');
await inputHandler.parseP('\x1b[23t'); // one more to test "overflow"
assert.deepEqual(inputHandler.windowTitleStack, []);
assert.deepEqual(inputHandler.iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']);
});
it('22/23 - PushTitle/PopTitle with ;1', async () => {
bufferService.resize(10, 10);
optionsService.options.windowOptions.pushTitle = true;
optionsService.options.windowOptions.popTitle = true;
const stack: string[] = [];
inputHandler.onTitleChange(data => stack.push(data));
await inputHandler.parseP('\x1b]0;1\x07');
await inputHandler.parseP('\x1b[22;1t');
await inputHandler.parseP('\x1b]0;2\x07');
await inputHandler.parseP('\x1b[22;1t');
await inputHandler.parseP('\x1b]0;3\x07');
await inputHandler.parseP('\x1b[22;1t');
assert.deepEqual(inputHandler.windowTitleStack, []);
assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']);
assert.deepEqual(stack, ['1', '2', '3']);
await inputHandler.parseP('\x1b[23;1t');
await inputHandler.parseP('\x1b[23;1t');
await inputHandler.parseP('\x1b[23;1t');
await inputHandler.parseP('\x1b[23;1t'); // one more to test "overflow"
assert.deepEqual(inputHandler.windowTitleStack, []);
assert.deepEqual(inputHandler.iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3']);
});
it('22/23 - PushTitle/PopTitle with ;2', async () => {
bufferService.resize(10, 10);
optionsService.options.windowOptions.pushTitle = true;
optionsService.options.windowOptions.popTitle = true;
const stack: string[] = [];
inputHandler.onTitleChange(data => stack.push(data));
await inputHandler.parseP('\x1b]0;1\x07');
await inputHandler.parseP('\x1b[22;2t');
await inputHandler.parseP('\x1b]0;2\x07');
await inputHandler.parseP('\x1b[22;2t');
await inputHandler.parseP('\x1b]0;3\x07');
await inputHandler.parseP('\x1b[22;2t');
assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']);
assert.deepEqual(inputHandler.iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3']);
await inputHandler.parseP('\x1b[23;2t');
await inputHandler.parseP('\x1b[23;2t');
await inputHandler.parseP('\x1b[23;2t');
await inputHandler.parseP('\x1b[23;2t'); // one more to test "overflow"
assert.deepEqual(inputHandler.windowTitleStack, []);
assert.deepEqual(inputHandler.iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']);
});
it('DECCOLM - should only work with "SetWinLines" (24) enabled', async () => {
// disabled
bufferService.resize(10, 10);
await inputHandler.parseP('\x1b[?3l');
assert.equal(bufferService.cols, 10);
await inputHandler.parseP('\x1b[?3h');
assert.equal(bufferService.cols, 10);
// enabled
inputHandler.reset();
optionsService.options.windowOptions.setWinLines = true;
await inputHandler.parseP('\x1b[?3l');
assert.equal(bufferService.cols, 80);
await inputHandler.parseP('\x1b[?3h');
assert.equal(bufferService.cols, 132);
});
});
describe('should correctly reset cells taken by wide chars', () => {
beforeEach(async () => {
bufferService.resize(10, 5);
optionsService.options.scrollback = 1;
await inputHandler.parseP('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥');
});
it('print', async () => {
await inputHandler.parseP('\x1b[H#');
assert.deepEqual(getLines(bufferService), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[1;6H######');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('#');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('#');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[3;9H#');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']);
await inputHandler.parseP('#');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']);
await inputHandler.parseP('#');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']);
await inputHandler.parseP('\x1b[4;10H#');
assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']);
});
it('EL', async () => {
await inputHandler.parseP('\x1b[1;6H\x1b[K#');
assert.deepEqual(getLines(bufferService), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[2;5H\x1b[1K');
assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[3;6H\x1b[1K');
assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']);
});
it('ICH', async () => {
await inputHandler.parseP('\x1b[1;6H\x1b[@');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[2;4H\x1b[2@');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[3;4H\x1b[3@');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[4;4H\x1b[4@');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']);
});
it('DCH', async () => {
await inputHandler.parseP('\x1b[1;6H\x1b[P');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[2;6H\x1b[2P');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[3;6H\x1b[3P');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
it('ECH', async () => {
await inputHandler.parseP('\x1b[1;6H\x1b[X');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[2;6H\x1b[2X');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
await inputHandler.parseP('\x1b[3;6H\x1b[3X');
assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
});
describe('BS with reverseWraparound set/unset', () => {
const ttyBS = '\x08 \x08'; // tty ICANON sends <BS SP BS> on pressing BS
beforeEach(() => {
bufferService.resize(5, 5);
optionsService.options.scrollback = 1;
});
describe('reverseWraparound unset (default)', () => {
it('cannot delete last cell', async () => {
await inputHandler.parseP('12345');
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 1), ['123 5']);
await inputHandler.parseP(ttyBS.repeat(10));
assert.deepEqual(getLines(bufferService, 1), [' 5']);
});
it('cannot access prev line', async () => {
await inputHandler.parseP('12345'.repeat(2));
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), ['12345', '123 5']);
await inputHandler.parseP(ttyBS.repeat(10));
assert.deepEqual(getLines(bufferService, 2), ['12345', ' 5']);
});
});
describe('reverseWraparound set', () => {
it('can delete last cell', async () => {
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('12345');
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 1), ['1234 ']);
await inputHandler.parseP(ttyBS.repeat(7));
assert.deepEqual(getLines(bufferService, 1), [' ']);
});
it('can access prev line if wrapped', async () => {
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('12345'.repeat(2));
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), ['12345', '1234 ']);
await inputHandler.parseP(ttyBS.repeat(7));
assert.deepEqual(getLines(bufferService, 2), ['12 ', ' ']);
});
it('should lift isWrapped', async () => {
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('12345'.repeat(2));
assert.equal(bufferService.buffer.lines.get(1)?.isWrapped, true);
await inputHandler.parseP(ttyBS.repeat(7));
assert.equal(bufferService.buffer.lines.get(1)?.isWrapped, false);
});
it('stops at hard NLs', async () => {
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('12345\r\n');
await inputHandler.parseP('12345'.repeat(2));
await inputHandler.parseP(ttyBS.repeat(50));
assert.deepEqual(getLines(bufferService, 3), ['12345', ' ', ' ']);
assert.equal(bufferService.buffer.x, 0);
assert.equal(bufferService.buffer.y, 1);
});
it('handles wide chars correctly', async () => {
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('¥¥¥');
assert.deepEqual(getLines(bufferService, 2), ['¥¥', '¥']);
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), ['¥¥', ' ']);
assert.equal(bufferService.buffer.x, 1);
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), ['¥¥', ' ']);
assert.equal(bufferService.buffer.x, 0);
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), ['¥ ', ' ']);
assert.equal(bufferService.buffer.x, 3); // x=4 skipped due to early wrap-around
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), ['¥ ', ' ']);
assert.equal(bufferService.buffer.x, 2);
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), [' ', ' ']);
assert.equal(bufferService.buffer.x, 1);
await inputHandler.parseP(ttyBS);
assert.deepEqual(getLines(bufferService, 2), [' ', ' ']);
assert.equal(bufferService.buffer.x, 0);
});
});
});
describe('reset text attributes (SGR 0)', () => {
it('resets all attributes if there is no url', async () => {
await inputHandler.parseP('\x1b[30m\x1b[40m\x1b[4m');
assert.notEqual(inputHandler.curAttrData.fg, 0);
assert.notEqual(inputHandler.curAttrData.bg, 0);
assert.isFalse(inputHandler.curAttrData.extended.isEmpty());
await inputHandler.parseP('\x1b[m');
assert.equal(inputHandler.curAttrData.fg, 0);
assert.equal(inputHandler.curAttrData.bg, 0);
assert.isTrue(inputHandler.curAttrData.extended.isEmpty());
});
it('resets all attributes except for the url', async () => {
await inputHandler.parseP('\x1b[30m\x1b[40m\x1b[4m');
await inputHandler.parseP('\x1b]8;;http://example.com\x1b\\');
assert.notEqual(inputHandler.curAttrData.fg, 0);
assert.notEqual(inputHandler.curAttrData.bg, 0);
assert.notEqual(inputHandler.curAttrData.extended.ext, 0);
const urlId = inputHandler.curAttrData.extended.urlId;
assert.notEqual(urlId, 0);
await inputHandler.parseP('\x1b[m');
assert.equal(inputHandler.curAttrData.fg, 0);
assert.equal(inputHandler.curAttrData.bg, BgFlags.HAS_EXTENDED);
const expectedExtended = new ExtendedAttrs();
expectedExtended.urlId = urlId;
assert.deepEqual(inputHandler.curAttrData.extended, expectedExtended);
});
});
describe('extended underline style support (SGR 4)', () => {
beforeEach(() => {
bufferService.resize(10, 5);
});
it('4 | 24', async () => {
await inputHandler.parseP('\x1b[4m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('21 | 24', async () => {
await inputHandler.parseP('\x1b[21m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('4:1 | 4:0', async () => {
await inputHandler.parseP('\x1b[4:1m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);
await inputHandler.parseP('\x1b[4:0m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
await inputHandler.parseP('\x1b[4:1m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('4:2 | 4:0', async () => {
await inputHandler.parseP('\x1b[4:2m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE);
await inputHandler.parseP('\x1b[4:0m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
await inputHandler.parseP('\x1b[4:2m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOUBLE);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('4:3 | 4:0', async () => {
await inputHandler.parseP('\x1b[4:3m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.CURLY);
await inputHandler.parseP('\x1b[4:0m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
await inputHandler.parseP('\x1b[4:3m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.CURLY);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('4:4 | 4:0', async () => {
await inputHandler.parseP('\x1b[4:4m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOTTED);
await inputHandler.parseP('\x1b[4:0m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
await inputHandler.parseP('\x1b[4:4m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DOTTED);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('4:5 | 4:0', async () => {
await inputHandler.parseP('\x1b[4:5m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED);
await inputHandler.parseP('\x1b[4:0m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
await inputHandler.parseP('\x1b[4:5m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED);
await inputHandler.parseP('\x1b[24m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.NONE);
});
it('4:x --> 4 should revert to single underline', async () => {
await inputHandler.parseP('\x1b[4:5m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.DASHED);
await inputHandler.parseP('\x1b[4m');
assert.equal(inputHandler.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE);
});
});
describe('underline colors (SGR 58 & SGR 59)', () => {
beforeEach(() => {
bufferService.resize(10, 5);
});
it('defaults to FG color', async () => {
for (const s of ['', '\x1b[30m', '\x1b[38;510m', '\x1b[38;2;1;2;3m']) {
await inputHandler.parseP(s);
assert.equal(inputHandler.curAttrData.getUnderlineColor(), inputHandler.curAttrData.getFgColor());
assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode());
assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB());
assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette());
assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault());
}
});
it('correctly sets P256/RGB colors', async () => {
await inputHandler.parseP('\x1b[4m');
await inputHandler.parseP('\x1b[58;5;123m');
assert.equal(inputHandler.curAttrData.getUnderlineColor(), 123);
assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), false);
assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), true);
assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);
await inputHandler.parseP('\x1b[58;2::1:2:3m');
assert.equal(inputHandler.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);
assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB);
assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), true);
assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), false);
assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);
});
it('P256/RGB persistence', async () => {
const cell = new CellData();
await inputHandler.parseP('\x1b[4m');
await inputHandler.parseP('\x1b[58;5;123m');
assert.equal(inputHandler.curAttrData.getUnderlineColor(), 123);
assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_P256);
assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), false);
assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), true);
assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);
await inputHandler.parseP('ab');
bufferService.buffer!.lines.get(0)!.loadCell(1, cell);
assert.equal(cell.getUnderlineColor(), 123);
assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256);
assert.equal(cell.isUnderlineColorRGB(), false);
assert.equal(cell.isUnderlineColorPalette(), true);
assert.equal(cell.isUnderlineColorDefault(), false);
await inputHandler.parseP('\x1b[4:0m');
assert.equal(inputHandler.curAttrData.getUnderlineColor(), inputHandler.curAttrData.getFgColor());
assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode());
assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB());
assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette());
assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault());
await inputHandler.parseP('a');
bufferService.buffer!.lines.get(0)!.loadCell(1, cell);
assert.equal(cell.getUnderlineColor(), 123);
assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256);
assert.equal(cell.isUnderlineColorRGB(), false);
assert.equal(cell.isUnderlineColorPalette(), true);
assert.equal(cell.isUnderlineColorDefault(), false);
bufferService.buffer!.lines.get(0)!.loadCell(2, cell);
assert.equal(cell.getUnderlineColor(), inputHandler.curAttrData.getFgColor());
assert.equal(cell.getUnderlineColorMode(), inputHandler.curAttrData.getFgColorMode());
assert.equal(cell.isUnderlineColorRGB(), inputHandler.curAttrData.isFgRGB());
assert.equal(cell.isUnderlineColorPalette(), inputHandler.curAttrData.isFgPalette());
assert.equal(cell.isUnderlineColorDefault(), inputHandler.curAttrData.isFgDefault());
await inputHandler.parseP('\x1b[4m');
await inputHandler.parseP('\x1b[58;2::1:2:3m');
assert.equal(inputHandler.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);
assert.equal(inputHandler.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB);
assert.equal(inputHandler.curAttrData.isUnderlineColorRGB(), true);
assert.equal(inputHandler.curAttrData.isUnderlineColorPalette(), false);
assert.equal(inputHandler.curAttrData.isUnderlineColorDefault(), false);
await inputHandler.parseP('a');
await inputHandler.parseP('\x1b[24m');
bufferService.buffer!.lines.get(0)!.loadCell(1, cell);
assert.equal(cell.getUnderlineColor(), 123);
assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256);
assert.equal(cell.isUnderlineColorRGB(), false);
assert.equal(cell.isUnderlineColorPalette(), true);
assert.equal(cell.isUnderlineColorDefault(), false);
bufferService.buffer!.lines.get(0)!.loadCell(3, cell);
assert.equal(cell.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);
assert.equal(cell.getUnderlineColorMode(), Attributes.CM_RGB);
assert.equal(cell.isUnderlineColorRGB(), true);
assert.equal(cell.isUnderlineColorPalette(), false);
assert.equal(cell.isUnderlineColorDefault(), false);
// eAttrs in buffer pos 0 and 1 should be the same object
assert.equal(
(bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[0],
(bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[1]
);
// should not have written eAttr for pos 2 in the buffer
assert.equal((bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[2], undefined);
// eAttrs in buffer pos 1 and pos 3 must be different objs
assert.notEqual(
(bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[1],
(bufferService.buffer!.lines.get(0)! as any)._extendedAttrs[3]
);
});
});
describe('DECSTR', () => {
beforeEach(async () => {
bufferService.resize(10, 5);
optionsService.options.scrollback = 1;
await inputHandler.parseP('01234567890123');
});
it('should reset IRM', async () => {
await inputHandler.parseP('\x1b[4h');
assert.equal(coreService.modes.insertMode, true);
await inputHandler.parseP('\x1b[!p');
assert.equal(coreService.modes.insertMode, false);
});
it('should reset cursor visibility', async () => {
await inputHandler.parseP('\x1b[?25l');
assert.equal(coreService.isCursorHidden, true);
await inputHandler.parseP('\x1b[!p');
assert.equal(coreService.isCursorHidden, false);
});
it('should reset scroll margins', async () => {
await inputHandler.parseP('\x1b[2;4r');
assert.equal(bufferService.buffer.scrollTop, 1);
assert.equal(bufferService.buffer.scrollBottom, 3);
await inputHandler.parseP('\x1b[!p');
assert.equal(bufferService.buffer.scrollTop, 0);
assert.equal(bufferService.buffer.scrollBottom, bufferService.rows - 1);
});
it('should reset text attributes', async () => {
await inputHandler.parseP('\x1b[1;2;32;43m');
assert.equal(!!inputHandler.curAttrData.isBold(), true);
await inputHandler.parseP('\x1b[!p');
assert.equal(!!inputHandler.curAttrData.isBold(), false);
assert.equal(inputHandler.curAttrData.fg, 0);
assert.equal(inputHandler.curAttrData.bg, 0);
});
it('should reset DECSC data', async () => {
await inputHandler.parseP('\x1b7');
assert.equal(bufferService.buffer.savedX, 4);
assert.equal(bufferService.buffer.savedY, 1);
await inputHandler.parseP('\x1b[!p');
assert.equal(bufferService.buffer.savedX, 0);
assert.equal(bufferService.buffer.savedY, 0);
});
it('should reset DECOM', async () => {
await inputHandler.parseP('\x1b[?6h');
assert.equal(coreService.decPrivateModes.origin, true);
await inputHandler.parseP('\x1b[!p');
assert.equal(coreService.decPrivateModes.origin, false);
});
});
describe('OSC', () => {
it('4: query color events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single color query
await inputHandler.parseP('\x1b]4;0;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]4;123;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 123 }]]);
stack.length = 0;
// multiple queries
await inputHandler.parseP('\x1b]4;0;?;123;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }, { type: ColorRequestType.REPORT, index: 123 }]]);
stack.length = 0;
});
it('4: set color events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single color query
await inputHandler.parseP('\x1b]4;0;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]4;123;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 123, color: [170, 187, 204] }]]);
stack.length = 0;
// multiple queries
await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;123;#001122\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);
stack.length = 0;
});
it('4: should ignore invalid values', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;45;rgb:1/22/333;123;#001122\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);
stack.length = 0;
});
describe('52: manipulate selection data', async () => {
const testDataRaw = 'hello world';
const testDataB64 = 'aGVsbG8gd29ybGQ=';
optionsService.options.allowClipboardAccess = true;
const stack: IClipboardEvent[] = [];
inputHandler.onClipboard(ev => stack.push(ev));
await inputHandler.parseP(`\x1b]52;c;\x07`);
await inputHandler.parseP(`\x1b]52;c;${testDataRaw}\x07`);
await inputHandler.parseP(`\x1b]52;c;${testDataB64}\x07`);
await inputHandler.parseP(`\x1b]52;c;${testDataB64}invalid\x07`);
await inputHandler.parseP(`\x1b]52;c;!\x07`);
await inputHandler.parseP(`\x1b]52;c;?\x07`);
await inputHandler.parseP(`\x1b]52;p;\x07`);
await inputHandler.parseP(`\x1b]52;p;${testDataRaw}\x07`);
await inputHandler.parseP(`\x1b]52;p;${testDataB64}\x07`);
await inputHandler.parseP(`\x1b]52;p;${testDataB64}invalid\x07`);
await inputHandler.parseP(`\x1b]52;p;!\x07`);
await inputHandler.parseP(`\x1b]52;p;?\x07`);
assert.deepEqual(stack, [
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.SYSTEM,
data: ''
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.SYSTEM,
data: testDataRaw
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.SYSTEM,
data: testDataB64
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.SYSTEM,
data: testDataB64+'invalid'
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.SYSTEM,
data: '!'
},
{
type: ClipboardEventType.REPORT,
selection: ClipboardSelection.SYSTEM,
data: '?'
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.PRIMARY,
data: ''
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.PRIMARY,
data: testDataRaw
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.PRIMARY,
data: testDataB64
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.PRIMARY,
data: testDataB64+'invalid'
},
{
type: ClipboardEventType.SET,
selection: ClipboardSelection.PRIMARY,
data: '!'
},
{
type: ClipboardEventType.REPORT,
selection: ClipboardSelection.PRIMARY,
data: '?'
}
]);
stack.length = 0;
});
it('104: restore events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]104;0\x07\x1b]104;43\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }], [{ type: ColorRequestType.RESTORE, index: 43 }]]);
stack.length = 0;
// multiple in one command
await inputHandler.parseP('\x1b]104;0;43\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }, { type: ColorRequestType.RESTORE, index: 43 }]]);
stack.length = 0;
// full ANSI table restore
await inputHandler.parseP('\x1b]104\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE }]]);
});
it('10: FG set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single foreground query --> color undefined
await inputHandler.parseP('\x1b]10;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.FOREGROUND }]]);
stack.length = 0;
// OSC with multiple values maps to OSC 10 & OSC 11 & OSC 12
await inputHandler.parseP('\x1b]10;?;?;?;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.FOREGROUND }], [{ type: ColorRequestType.REPORT, index: SpecialColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);
stack.length = 0;
// set foreground color events
await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.FOREGROUND, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]10;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.FOREGROUND, color: [170, 187, 204] }]]);
stack.length = 0;
// set FG, BG and cursor color at once
await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122;rgb:12/34/56\x07');
assert.deepEqual(stack, [
[{ type: ColorRequestType.SET, index: SpecialColorIndex.FOREGROUND, color: [170, 187, 204] }],
[{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [0, 17, 34] }],
[{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [18, 52, 86] }]
]);
});
it('110: restore FG color', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]110\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.FOREGROUND }]]);
});
it('11: BG set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single background query --> color undefined
await inputHandler.parseP('\x1b]11;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.BACKGROUND }]]);
stack.length = 0;
// OSC 11 with multiple values creates only BG and cursor event
await inputHandler.parseP('\x1b]11;?;?;?;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);
stack.length = 0;
// set background color events
await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]11;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [170, 187, 204] }]]);
stack.length = 0;
// set BG and cursor color at once
await inputHandler.parseP('\x1b]11;#001122;rgb:12/34/56\x07');
assert.deepEqual(stack, [
[{ type: ColorRequestType.SET, index: SpecialColorIndex.BACKGROUND, color: [0, 17, 34] }],
[{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [18, 52, 86] }]
]);
});
it('111: restore BG color', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]111\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.BACKGROUND }]]);
});
it('12: cursor color set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single cursor query --> color undefined
await inputHandler.parseP('\x1b]12;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);
stack.length = 0;
// OSC 12 with multiple values creates only cursor event
await inputHandler.parseP('\x1b]12;?;?;?;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: SpecialColorIndex.CURSOR }]]);
stack.length = 0;
// set cursor color events
await inputHandler.parseP('\x1b]12;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]12;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: SpecialColorIndex.CURSOR, color: [170, 187, 204] }]]);
});
it('112: restore cursor color', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]112\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: SpecialColorIndex.CURSOR }]]);
});
});
// issue #3362 and #2979
describe('EL/ED cursor at buffer.cols', () => {
beforeEach(() => {
bufferService.resize(10, 5);
});
describe('cursor should stay at cols / does not overflow', () => {
it('EL0', async () => {
await inputHandler.parseP('##########\x1b[0K');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);
});
it('EL1', async () => {
await inputHandler.parseP('##########\x1b[1K');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('EL2', async () => {
await inputHandler.parseP('##########\x1b[2K');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('ED0', async () => {
await inputHandler.parseP('##########\x1b[0J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);
});
it('ED1', async () => {
await inputHandler.parseP('##########\x1b[1J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('ED2', async () => {
await inputHandler.parseP('##########\x1b[2J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('ED3', async () => {
await inputHandler.parseP('##########\x1b[3J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);
});
});
describe('following sequence keeps working', () => {
// sequences to test (cursor related ones)
const SEQ = [
/* ICH */ '\x1b[10@',
/* SL */ '\x1b[10 @',
/* CUU */ '\x1b[10A',
/* SR */ '\x1b[10 A',
/* CUD */ '\x1b[10B',
/* CUF */ '\x1b[10C',
/* CUB */ '\x1b[10D',
/* CNL */ '\x1b[10E',
/* CPL */ '\x1b[10F',
/* CHA */ '\x1b[10G',
/* CUP */ '\x1b[10;10H',
/* CHT */ '\x1b[10I',
/* IL */ '\x1b[10L',
/* DL */ '\x1b[10M',
/* DCH */ '\x1b[10P',
/* SU */ '\x1b[10S',
/* SD */ '\x1b[10T',
/* ECH */ '\x1b[10X',
/* CBT */ '\x1b[10Z',
/* HPA */ '\x1b[10`',
/* HPR */ '\x1b[10a',
/* REP */ '\x1b[10b',
/* VPA */ '\x1b[10d',
/* VPR */ '\x1b[10e',
/* HVP */ '\x1b[10;10f',
/* TBC */ '\x1b[0g',
/* SCOSC */ '\x1b[s',
/* DECIC */ '\x1b[10\'}',
/* DECDC */ '\x1b[10\'~'
];
it('cursor never advances beyond cols', async () => {
for (const seq of SEQ) {
await inputHandler.parseP('##########\x1b[2J' + seq);
assert.equal(bufferService.buffer.x <= bufferService.cols, true);
inputHandler.reset();
bufferService.reset();
}
});
});
});
describe('DECSCA and DECSED/DECSEL', () => {
it('default is unprotected', async () => {
await inputHandler.parseP('some text');
await inputHandler.parseP('\x1b[?2K');
assert.deepEqual(getLines(bufferService, 2), ['', '']);
await inputHandler.parseP('some text');
await inputHandler.parseP('\x1b[?2J');
assert.deepEqual(getLines(bufferService, 2), ['', '']);
});
it('DECSCA 1 with DECSEL', async () => {
await inputHandler.parseP('###\x1b[1"qlineerase\x1b[0"q***');
await inputHandler.parseP('\x1b[?2K');
assert.deepEqual(getLines(bufferService, 2), [' lineerase', '']);
// normal EL works as before
await inputHandler.parseP('\x1b[2K');
assert.deepEqual(getLines(bufferService, 2), ['', '']);
});
it('DECSCA 1 with DECSED', async () => {
await inputHandler.parseP('###\x1b[1"qdisplayerase\x1b[0"q***');
await inputHandler.parseP('\x1b[?2J');
assert.deepEqual(getLines(bufferService, 2), [' displayerase', '']);
// normal ED works as before
await inputHandler.parseP('\x1b[2J');
assert.deepEqual(getLines(bufferService, 2), ['', '']);
});
it('DECRQSS reports correct DECSCA state', async () => {
const sendStack: string[] = [];
coreService.onData(d => sendStack.push(d));
// DCS $ q " q ST
await inputHandler.parseP('\x1bP$q"q\x1b\\');
// default - DECSCA unset (0 or 2)
assert.deepEqual(sendStack.pop(), '\x1bP1$r0"q\x1b\\');
// DECSCA 1 - protected set
await inputHandler.parseP('###\x1b[1"q');
await inputHandler.parseP('\x1bP$q"q\x1b\\');
assert.deepEqual(sendStack.pop(), '\x1bP1$r1"q\x1b\\');
// DECSCA 2 - protected reset (same as 0)
await inputHandler.parseP('###\x1b[2"q');
await inputHandler.parseP('\x1bP$q"q\x1b\\');
assert.deepEqual(sendStack.pop(), '\x1bP1$r0"q\x1b\\'); // reported as DECSCA 0
});
});
describe('DECRQM', () => {
const reportStack: string[] = [];
beforeEach(() => {
reportStack.length = 0;
coreService.onData(data => reportStack.push(data));
});
it('ANSI 2 (keyboard action mode)', async () => {
await inputHandler.parseP('\x1b[2$p');
assert.deepEqual(reportStack.pop(), '\x1b[2;4$y'); // always reset
});
it('ANSI 4 (insert mode)', async () => {
await inputHandler.parseP('\x1b[4$p');
assert.deepEqual(reportStack.pop(), '\x1b[4;2$y'); // reset by default
await inputHandler.parseP('\x1b[4h');
await inputHandler.parseP('\x1b[4$p');
assert.deepEqual(reportStack.pop(), '\x1b[4;1$y'); // now active
await inputHandler.parseP('\x1b[4l');
await inputHandler.parseP('\x1b[4$p');
assert.deepEqual(reportStack.pop(), '\x1b[4;2$y'); // again reset
});
it('ANSI 12 (send/receive)', async () => {
await inputHandler.parseP('\x1b[12$p');
assert.deepEqual(reportStack.pop(), '\x1b[12;3$y'); // always set
});
it('ANSI 20 (newline mode)', async () => {
await inputHandler.parseP('\x1b[20$p');
assert.deepEqual(reportStack.pop(), '\x1b[20;2$y'); // reset by default
await inputHandler.parseP('\x1b[20h');
await inputHandler.parseP('\x1b[20$p');
assert.deepEqual(reportStack.pop(), '\x1b[20;1$y'); // now active
await inputHandler.parseP('\x1b[20l');
await inputHandler.parseP('\x1b[20$p');
assert.deepEqual(reportStack.pop(), '\x1b[20;2$y'); // again reset
});
it('ANSI unknown', async () => {
await inputHandler.parseP('\x1b[1234$p');
assert.deepEqual(reportStack.pop(), '\x1b[1234;0$y'); // not recognized
});
it('DEC privates with set/reset semantic', async () => {
// initially reset
const reset = [1, 6, 9, 12, 45, 66, 1000, 1002, 1003, 1004, 1006, 1016, 47, 1047, 1049, 2004];
for (const mode of reset) {
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};2$y`); // initial reset
await inputHandler.parseP(`\x1b[?${mode}h`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};1$y`); // now active
await inputHandler.parseP(`\x1b[?${mode}l`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};2$y`); // again reset
}
// initially set
const set = [7, 25];
for (const mode of set) {
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};1$y`); // initial set
await inputHandler.parseP(`\x1b[?${mode}l`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};2$y`); // now inactive
await inputHandler.parseP(`\x1b[?${mode}h`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};1$y`); // again set
}
});
it('DEC privates perma modes', async () => {
// [mode number, state value]
const perma = [[3, 0], [8, 3], [67, 4], [1005, 4], [1015, 4], [1048, 1]];
for (const [mode, value] of perma) {
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};${value}$y`);
}
});
});
});
describe('InputHandler - async handlers', () => {
let bufferService: IBufferService;
let coreService: ICoreService;
let optionsService: MockOptionsService;
let inputHandler: TestInputHandler;
beforeEach(() => {
optionsService = new MockOptionsService();
bufferService = new BufferService(optionsService);
bufferService.resize(80, 30);
coreService = new CoreService(bufferService, new MockLogService(), optionsService);
coreService.onData(data => { console.log(data); });
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
it('async CUP with CPR check', async () => {
const cup: number[][] = [];
const cpr: number[][] = [];
inputHandler.registerCsiHandler({ final: 'H' }, async params => {
cup.push(params.toArray() as number[]);
await new Promise(res => setTimeout(res, 50));
// late call of real repositioning
return inputHandler.cursorPosition(params);
});
coreService.onData(data => {
const m = data.match(/\x1b\[(.*?);(.*?)R/);
if (m) {
cpr.push([parseInt(m[1]), parseInt(m[2])]);
}
});
await inputHandler.parseP('aaa\x1b[3;4H\x1b[6nbbb\x1b[6;8H\x1b[6n');
assert.deepEqual(cup, cpr);
});
it('async OSC between', async () => {
inputHandler.registerOscHandler(1000, async data => {
await new Promise(res => setTimeout(res, 50));
assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']);
assert.equal(data, 'some data');
return true;
});
await inputHandler.parseP('hello world!\r\n\x1b]1000;some data\x07second line');
assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']);
});
it('async DCS between', async () => {
inputHandler.registerDcsHandler({ final: 'a' }, async (data, params) => {
await new Promise(res => setTimeout(res, 50));
assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']);
assert.equal(data, 'some data');
assert.deepEqual(params.toArray(), [1, 2]);
return true;
});
await inputHandler.parseP('hello world!\r\n\x1bP1;2asome data\x1b\\second line');
assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']);
});
});