Merge remote-tracking branch 'upstream/master' into Support-for-bubbling-scroll
This commit is contained in:
@@ -343,9 +343,12 @@ export class SearchAddon implements ITerminalAddon {
|
||||
return false;
|
||||
}
|
||||
terminal.select(result.col, result.row, result.term.length);
|
||||
let scroll = result.row - terminal.buffer.viewportY;
|
||||
scroll = scroll - Math.floor(terminal.rows / 2);
|
||||
terminal.scrollLines(scroll);
|
||||
// If it is not in the viewport then we scroll else it just gets selected
|
||||
if (result.row > (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
|
||||
let scroll = result.row - terminal.buffer.viewportY;
|
||||
scroll = scroll - Math.floor(terminal.rows / 2);
|
||||
terminal.scrollLines(scroll);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,39 +53,38 @@ describe('InputHandler', () => {
|
||||
it('should call Terminal.setOption with correct params', () => {
|
||||
const optionsService = new MockOptionsService();
|
||||
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService);
|
||||
const collect = ' ';
|
||||
|
||||
inputHandler.setCursorStyle(Params.fromArray([0]), collect);
|
||||
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]), collect);
|
||||
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]), collect);
|
||||
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]), collect);
|
||||
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]), collect);
|
||||
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]), collect);
|
||||
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]), collect);
|
||||
inputHandler.setCursorStyle(Params.fromArray([6]));
|
||||
assert.equal(optionsService.options['cursorStyle'], 'bar');
|
||||
assert.equal(optionsService.options['cursorBlink'], false);
|
||||
});
|
||||
@@ -93,14 +92,13 @@ describe('InputHandler', () => {
|
||||
describe('setMode', () => {
|
||||
it('should toggle Terminal.bracketedPasteMode', () => {
|
||||
const terminal = new MockInputHandlingTerminal();
|
||||
const collect = '?';
|
||||
terminal.bracketedPasteMode = false;
|
||||
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService());
|
||||
// Set bracketed paste mode
|
||||
inputHandler.setMode(Params.fromArray([2004]), collect);
|
||||
inputHandler.setModePrivate(Params.fromArray([2004]));
|
||||
assert.equal(terminal.bracketedPasteMode, true);
|
||||
// Reset bracketed paste mode
|
||||
inputHandler.resetMode(Params.fromArray([2004]), collect);
|
||||
inputHandler.resetModePrivate(Params.fromArray([2004]));
|
||||
assert.equal(terminal.bracketedPasteMode, false);
|
||||
});
|
||||
});
|
||||
|
||||
+229
-212
@@ -14,13 +14,15 @@ import { concat } from 'common/TypedArrayUtils';
|
||||
import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IAttributeData, IDisposable } from 'common/Types';
|
||||
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService } from 'common/services/Services';
|
||||
import { ISelectionService } from 'browser/services/Services';
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
import { DcsHandler } from 'common/parser/DcsParser';
|
||||
|
||||
/**
|
||||
* Map collect to glevel. Used in `selectCharset`.
|
||||
@@ -48,7 +50,7 @@ class DECRQSS implements IDcsHandler {
|
||||
private _optionsService: IOptionsService
|
||||
) { }
|
||||
|
||||
hook(collect: string, params: IParams, flag: number): void {
|
||||
hook(params: IParams): void {
|
||||
this._data = new Uint32Array(0);
|
||||
}
|
||||
|
||||
@@ -56,7 +58,11 @@ class DECRQSS implements IDcsHandler {
|
||||
this._data = concat(this._data, data.subarray(start, end));
|
||||
}
|
||||
|
||||
unhook(): void {
|
||||
unhook(success: boolean): void {
|
||||
if (!success) {
|
||||
this._data = new Uint32Array(0);
|
||||
return;
|
||||
}
|
||||
const data = utf32ToString(this._data);
|
||||
this._data = new Uint32Array(0);
|
||||
switch (data) {
|
||||
@@ -143,63 +149,75 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* custom fallback handlers
|
||||
*/
|
||||
this._parser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
|
||||
this._logService.debug('Unknown CSI code: ', { collect, params: params.toArray(), flag: String.fromCharCode(flag) });
|
||||
this._parser.setCsiHandlerFallback((ident, params) => {
|
||||
this._logService.debug('Unknown CSI code: ', { identifier: this._parser.identToString(ident), params: params.toArray() });
|
||||
});
|
||||
this._parser.setEscHandlerFallback((collect: string, flag: number) => {
|
||||
this._logService.debug('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) });
|
||||
this._parser.setEscHandlerFallback(ident => {
|
||||
this._logService.debug('Unknown ESC code: ', { identifier: this._parser.identToString(ident) });
|
||||
});
|
||||
this._parser.setExecuteHandlerFallback((code: number) => {
|
||||
this._parser.setExecuteHandlerFallback(code => {
|
||||
this._logService.debug('Unknown EXECUTE code: ', { code });
|
||||
});
|
||||
this._parser.setOscHandlerFallback((identifier: number, data: string) => {
|
||||
this._logService.debug('Unknown OSC code: ', { identifier, data });
|
||||
this._parser.setOscHandlerFallback((identifier, action, data) => {
|
||||
this._logService.debug('Unknown OSC code: ', { identifier, action, data });
|
||||
});
|
||||
this._parser.setDcsHandlerFallback((ident, action, payload) => {
|
||||
if (action === 'HOOK') {
|
||||
payload = payload.toArray();
|
||||
}
|
||||
this._logService.debug('Unknown DCS code: ', { identifier: this._parser.identToString(ident), action, payload });
|
||||
});
|
||||
|
||||
/**
|
||||
* print handler
|
||||
*/
|
||||
this._parser.setPrintHandler((data, start, end): void => this.print(data, start, end));
|
||||
this._parser.setPrintHandler((data, start, end) => this.print(data, start, end));
|
||||
|
||||
/**
|
||||
* CSI handler
|
||||
*/
|
||||
this._parser.setCsiHandler('@', (params, collect) => this.insertChars(params));
|
||||
this._parser.setCsiHandler('A', (params, collect) => this.cursorUp(params));
|
||||
this._parser.setCsiHandler('B', (params, collect) => this.cursorDown(params));
|
||||
this._parser.setCsiHandler('C', (params, collect) => this.cursorForward(params));
|
||||
this._parser.setCsiHandler('D', (params, collect) => this.cursorBackward(params));
|
||||
this._parser.setCsiHandler('E', (params, collect) => this.cursorNextLine(params));
|
||||
this._parser.setCsiHandler('F', (params, collect) => this.cursorPrecedingLine(params));
|
||||
this._parser.setCsiHandler('G', (params, collect) => this.cursorCharAbsolute(params));
|
||||
this._parser.setCsiHandler('H', (params, collect) => this.cursorPosition(params));
|
||||
this._parser.setCsiHandler('I', (params, collect) => this.cursorForwardTab(params));
|
||||
this._parser.setCsiHandler('J', (params, collect) => this.eraseInDisplay(params));
|
||||
this._parser.setCsiHandler('K', (params, collect) => this.eraseInLine(params));
|
||||
this._parser.setCsiHandler('L', (params, collect) => this.insertLines(params));
|
||||
this._parser.setCsiHandler('M', (params, collect) => this.deleteLines(params));
|
||||
this._parser.setCsiHandler('P', (params, collect) => this.deleteChars(params));
|
||||
this._parser.setCsiHandler('S', (params, collect) => this.scrollUp(params));
|
||||
this._parser.setCsiHandler('T', (params, collect) => this.scrollDown(params, collect));
|
||||
this._parser.setCsiHandler('X', (params, collect) => this.eraseChars(params));
|
||||
this._parser.setCsiHandler('Z', (params, collect) => this.cursorBackwardTab(params));
|
||||
this._parser.setCsiHandler('`', (params, collect) => this.charPosAbsolute(params));
|
||||
this._parser.setCsiHandler('a', (params, collect) => this.hPositionRelative(params));
|
||||
this._parser.setCsiHandler('b', (params, collect) => this.repeatPrecedingCharacter(params));
|
||||
this._parser.setCsiHandler('c', (params, collect) => this.sendDeviceAttributes(params, collect));
|
||||
this._parser.setCsiHandler('d', (params, collect) => this.linePosAbsolute(params));
|
||||
this._parser.setCsiHandler('e', (params, collect) => this.vPositionRelative(params));
|
||||
this._parser.setCsiHandler('f', (params, collect) => this.hVPosition(params));
|
||||
this._parser.setCsiHandler('g', (params, collect) => this.tabClear(params));
|
||||
this._parser.setCsiHandler('h', (params, collect) => this.setMode(params, collect));
|
||||
this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect));
|
||||
this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params));
|
||||
this._parser.setCsiHandler('n', (params, collect) => this.deviceStatus(params, collect));
|
||||
this._parser.setCsiHandler('p', (params, collect) => this.softReset(params, collect));
|
||||
this._parser.setCsiHandler('q', (params, collect) => this.setCursorStyle(params, collect));
|
||||
this._parser.setCsiHandler('r', (params, collect) => this.setScrollRegion(params, collect));
|
||||
this._parser.setCsiHandler('s', (params, collect) => this.saveCursor(params));
|
||||
this._parser.setCsiHandler('u', (params, collect) => this.restoreCursor(params));
|
||||
this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params));
|
||||
this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params));
|
||||
this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params));
|
||||
this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params));
|
||||
this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params));
|
||||
this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params));
|
||||
this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params));
|
||||
this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params));
|
||||
this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params));
|
||||
this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params));
|
||||
this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params));
|
||||
this._parser.setCsiHandler({prefix: '?', final: 'J'}, params => this.eraseInDisplay(params));
|
||||
this._parser.setCsiHandler({final: 'K'}, params => this.eraseInLine(params));
|
||||
this._parser.setCsiHandler({prefix: '?', final: 'K'}, params => this.eraseInLine(params));
|
||||
this._parser.setCsiHandler({final: 'L'}, params => this.insertLines(params));
|
||||
this._parser.setCsiHandler({final: 'M'}, params => this.deleteLines(params));
|
||||
this._parser.setCsiHandler({final: 'P'}, params => this.deleteChars(params));
|
||||
this._parser.setCsiHandler({final: 'S'}, params => this.scrollUp(params));
|
||||
this._parser.setCsiHandler({final: 'T'}, params => this.scrollDown(params));
|
||||
this._parser.setCsiHandler({final: 'X'}, params => this.eraseChars(params));
|
||||
this._parser.setCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params));
|
||||
this._parser.setCsiHandler({final: '`'}, params => this.charPosAbsolute(params));
|
||||
this._parser.setCsiHandler({final: 'a'}, params => this.hPositionRelative(params));
|
||||
this._parser.setCsiHandler({final: 'b'}, params => this.repeatPrecedingCharacter(params));
|
||||
this._parser.setCsiHandler({final: 'c'}, params => this.sendDeviceAttributesPrimary(params));
|
||||
this._parser.setCsiHandler({prefix: '>', final: 'c'}, params => this.sendDeviceAttributesSecondary(params));
|
||||
this._parser.setCsiHandler({final: 'd'}, params => this.linePosAbsolute(params));
|
||||
this._parser.setCsiHandler({final: 'e'}, params => this.vPositionRelative(params));
|
||||
this._parser.setCsiHandler({final: 'f'}, params => this.hVPosition(params));
|
||||
this._parser.setCsiHandler({final: 'g'}, params => this.tabClear(params));
|
||||
this._parser.setCsiHandler({final: 'h'}, params => this.setMode(params));
|
||||
this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params));
|
||||
this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params));
|
||||
this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params));
|
||||
this._parser.setCsiHandler({final: 'm'}, params => this.charAttributes(params));
|
||||
this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params));
|
||||
this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params));
|
||||
this._parser.setCsiHandler({intermediates: '!', final: 'p'}, params => this.softReset(params));
|
||||
this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params));
|
||||
this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params));
|
||||
this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params));
|
||||
this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params));
|
||||
|
||||
/**
|
||||
* execute handler
|
||||
@@ -215,7 +233,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.setExecuteHandler(C0.SI, () => this.shiftIn());
|
||||
// FIXME: What do to with missing? Old code just added those to print.
|
||||
|
||||
// some C1 control codes - FIXME: should those be enabled by default?
|
||||
this._parser.setExecuteHandler(C1.IND, () => this.index());
|
||||
this._parser.setExecuteHandler(C1.NEL, () => this.nextLine());
|
||||
this._parser.setExecuteHandler(C1.HTS, () => this.tabSet());
|
||||
@@ -224,10 +241,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* OSC handler
|
||||
*/
|
||||
// 0 - icon name + title
|
||||
this._parser.setOscHandler(0, (data) => this.setTitle(data));
|
||||
this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data)));
|
||||
// 1 - icon name
|
||||
// 2 - title
|
||||
this._parser.setOscHandler(2, (data) => this.setTitle(data));
|
||||
this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data)));
|
||||
// 3 - set property X in the form "prop=value"
|
||||
// 4 - Change Color Number
|
||||
// 5 - Change Special Color Number
|
||||
@@ -264,32 +281,32 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* ESC handlers
|
||||
*/
|
||||
this._parser.setEscHandler('7', () => this.saveCursor());
|
||||
this._parser.setEscHandler('8', () => this.restoreCursor());
|
||||
this._parser.setEscHandler('D', () => this.index());
|
||||
this._parser.setEscHandler('E', () => this.nextLine());
|
||||
this._parser.setEscHandler('H', () => this.tabSet());
|
||||
this._parser.setEscHandler('M', () => this.reverseIndex());
|
||||
this._parser.setEscHandler('=', () => this.keypadApplicationMode());
|
||||
this._parser.setEscHandler('>', () => this.keypadNumericMode());
|
||||
this._parser.setEscHandler('c', () => this.reset());
|
||||
this._parser.setEscHandler('n', () => this.setgLevel(2));
|
||||
this._parser.setEscHandler('o', () => this.setgLevel(3));
|
||||
this._parser.setEscHandler('|', () => this.setgLevel(3));
|
||||
this._parser.setEscHandler('}', () => this.setgLevel(2));
|
||||
this._parser.setEscHandler('~', () => this.setgLevel(1));
|
||||
this._parser.setEscHandler('%@', () => this.selectDefaultCharset());
|
||||
this._parser.setEscHandler('%G', () => this.selectDefaultCharset());
|
||||
this._parser.setEscHandler({final: '7'}, () => this.saveCursor());
|
||||
this._parser.setEscHandler({final: '8'}, () => this.restoreCursor());
|
||||
this._parser.setEscHandler({final: 'D'}, () => this.index());
|
||||
this._parser.setEscHandler({final: 'E'}, () => this.nextLine());
|
||||
this._parser.setEscHandler({final: 'H'}, () => this.tabSet());
|
||||
this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex());
|
||||
this._parser.setEscHandler({final: '='}, () => this.keypadApplicationMode());
|
||||
this._parser.setEscHandler({final: '>'}, () => this.keypadNumericMode());
|
||||
this._parser.setEscHandler({final: 'c'}, () => this.reset());
|
||||
this._parser.setEscHandler({final: 'n'}, () => this.setgLevel(2));
|
||||
this._parser.setEscHandler({final: 'o'}, () => this.setgLevel(3));
|
||||
this._parser.setEscHandler({final: '|'}, () => this.setgLevel(3));
|
||||
this._parser.setEscHandler({final: '}'}, () => this.setgLevel(2));
|
||||
this._parser.setEscHandler({final: '~'}, () => this.setgLevel(1));
|
||||
this._parser.setEscHandler({intermediates: '%', final: '@'}, () => this.selectDefaultCharset());
|
||||
this._parser.setEscHandler({intermediates: '%', final: 'G'}, () => this.selectDefaultCharset());
|
||||
for (const flag in CHARSETS) {
|
||||
this._parser.setEscHandler('(' + flag, () => this.selectCharset('(' + flag));
|
||||
this._parser.setEscHandler(')' + flag, () => this.selectCharset(')' + flag));
|
||||
this._parser.setEscHandler('*' + flag, () => this.selectCharset('*' + flag));
|
||||
this._parser.setEscHandler('+' + flag, () => this.selectCharset('+' + flag));
|
||||
this._parser.setEscHandler('-' + flag, () => this.selectCharset('-' + flag));
|
||||
this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag));
|
||||
this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // TODO: supported?
|
||||
this._parser.setEscHandler({intermediates: '(', final: flag}, () => this.selectCharset('(' + flag));
|
||||
this._parser.setEscHandler({intermediates: ')', final: flag}, () => this.selectCharset(')' + flag));
|
||||
this._parser.setEscHandler({intermediates: '*', final: flag}, () => this.selectCharset('*' + flag));
|
||||
this._parser.setEscHandler({intermediates: '+', final: flag}, () => this.selectCharset('+' + flag));
|
||||
this._parser.setEscHandler({intermediates: '-', final: flag}, () => this.selectCharset('-' + flag));
|
||||
this._parser.setEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag));
|
||||
this._parser.setEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported?
|
||||
}
|
||||
this._parser.setEscHandler('#8', () => this.screenAlignmentPattern());
|
||||
this._parser.setEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern());
|
||||
|
||||
/**
|
||||
* error handler
|
||||
@@ -302,7 +319,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* DCS handler
|
||||
*/
|
||||
this._parser.setDcsHandler('$q', new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService));
|
||||
this._parser.setDcsHandler({intermediates: '$', final: 'q'}, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -480,15 +497,29 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* Forward addCsiHandler from parser.
|
||||
*/
|
||||
public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
|
||||
return this._parser.addCsiHandler(flag, callback);
|
||||
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
|
||||
return this._parser.addCsiHandler(id, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward addDcsHandler from parser.
|
||||
*/
|
||||
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
|
||||
return this._parser.addDcsHandler(id, new DcsHandler(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward addEscHandler from parser.
|
||||
*/
|
||||
public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable {
|
||||
return this._parser.addEscHandler(id, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward addOscHandler from parser.
|
||||
*/
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._parser.addOscHandler(ident, callback);
|
||||
return this._parser.addOscHandler(ident, new OscHandler(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1021,8 +1052,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
/**
|
||||
* CSI Ps T Scroll down Ps lines (default = 1) (SD).
|
||||
*/
|
||||
public scrollDown(params: IParams, collect?: string): void {
|
||||
if (params.length < 2 && !collect) {
|
||||
public scrollDown(params: IParams): void {
|
||||
if (params.length < 2) {
|
||||
let param = params.params[0] || 1;
|
||||
|
||||
// make buffer local for faster access
|
||||
@@ -1125,32 +1156,33 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* xterm/charproc.c - line 2012, for more information.
|
||||
* vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
|
||||
*/
|
||||
public sendDeviceAttributes(params: IParams, collect?: string): void {
|
||||
public sendDeviceAttributesPrimary(params: IParams): void {
|
||||
if (params.params[0] > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!collect) {
|
||||
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[?1;2c');
|
||||
} else if (this._terminal.is('linux')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[?6c');
|
||||
}
|
||||
} else if (collect === '>') {
|
||||
// xterm and urxvt
|
||||
// seem to spit this
|
||||
// out around ~370 times (?).
|
||||
if (this._terminal.is('xterm')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c');
|
||||
} else if (this._terminal.is('rxvt-unicode')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c');
|
||||
} else if (this._terminal.is('linux')) {
|
||||
// not supported by linux console.
|
||||
// linux console echoes parameters.
|
||||
this._coreService.triggerDataEvent(params.params[0] + 'c');
|
||||
} else if (this._terminal.is('screen')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');
|
||||
}
|
||||
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[?1;2c');
|
||||
} else if (this._terminal.is('linux')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[?6c');
|
||||
}
|
||||
}
|
||||
public sendDeviceAttributesSecondary(params: IParams): void {
|
||||
if (params.params[0] > 0) {
|
||||
return;
|
||||
}
|
||||
// xterm and urxvt
|
||||
// seem to spit this
|
||||
// out around ~370 times (?).
|
||||
if (this._terminal.is('xterm')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c');
|
||||
} else if (this._terminal.is('rxvt-unicode')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c');
|
||||
} else if (this._terminal.is('linux')) {
|
||||
// not supported by linux console.
|
||||
// linux console echoes parameters.
|
||||
this._coreService.triggerDataEvent(params.params[0] + 'c');
|
||||
} else if (this._terminal.is('screen')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1240,15 +1272,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Modes:
|
||||
* http: *vt100.net/docs/vt220-rm/chapter4.html
|
||||
*/
|
||||
public setMode(params: IParams, collect?: string): void {
|
||||
public setMode(params: IParams): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
this._setMode(params.params[i], collect);
|
||||
}
|
||||
}
|
||||
|
||||
private _setMode(param: number, collect?: string): void {
|
||||
if (!collect) {
|
||||
switch (param) {
|
||||
switch (params.params[i]) {
|
||||
case 4:
|
||||
this._terminal.insertMode = true;
|
||||
break;
|
||||
@@ -1256,8 +1282,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// this._t.convertEol = true;
|
||||
break;
|
||||
}
|
||||
} else if (collect === '?') {
|
||||
switch (param) {
|
||||
}
|
||||
}
|
||||
public setModePrivate(params: IParams): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
switch (params.params[i]) {
|
||||
case 1:
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = true;
|
||||
break;
|
||||
@@ -1303,9 +1332,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
|
||||
// TODO: Why are params[0] compares nested within a switch for params[0]?
|
||||
|
||||
this._terminal.x10Mouse = param === 9;
|
||||
this._terminal.vt200Mouse = param === 1000;
|
||||
this._terminal.normalMouse = param > 1000;
|
||||
this._terminal.x10Mouse = params.params[i] === 9;
|
||||
this._terminal.vt200Mouse = params.params[i] === 1000;
|
||||
this._terminal.normalMouse = params.params[i] > 1000;
|
||||
this._terminal.mouseEvents = true;
|
||||
if (this._terminal.element) {
|
||||
this._terminal.element.classList.add('enable-mouse-events');
|
||||
@@ -1364,6 +1393,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI Pm l Reset Mode (RM).
|
||||
* Ps = 2 -> Keyboard Action Mode (AM).
|
||||
@@ -1446,15 +1476,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.
|
||||
* Ps = 2 0 0 4 -> Reset bracketed paste mode.
|
||||
*/
|
||||
public resetMode(params: IParams, collect?: string): void {
|
||||
public resetMode(params: IParams): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
this._resetMode(params.params[i], collect);
|
||||
}
|
||||
}
|
||||
|
||||
private _resetMode(param: number, collect?: string): void {
|
||||
if (!collect) {
|
||||
switch (param) {
|
||||
switch (params.params[i]) {
|
||||
case 4:
|
||||
this._terminal.insertMode = false;
|
||||
break;
|
||||
@@ -1462,8 +1486,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// this._t.convertEol = false;
|
||||
break;
|
||||
}
|
||||
} else if (collect === '?') {
|
||||
switch (param) {
|
||||
}
|
||||
}
|
||||
public resetModePrivate(params: IParams): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
switch (params.params[i]) {
|
||||
case 1:
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = false;
|
||||
break;
|
||||
@@ -1533,7 +1560,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
case 1047: // normal screen buffer - clearing it first
|
||||
// Ensure the selection manager has the correct buffer
|
||||
this._bufferService.buffers.activateNormalBuffer();
|
||||
if (param === 1049) {
|
||||
if (params.params[i] === 1049) {
|
||||
this.restoreCursor();
|
||||
}
|
||||
this._terminal.refresh(0, this._bufferService.rows - 1);
|
||||
@@ -1807,47 +1834,47 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI ? 5 3 n Locator available, if compiled-in, or
|
||||
* CSI ? 5 0 n No Locator, if not.
|
||||
*/
|
||||
public deviceStatus(params: IParams, collect?: string): void {
|
||||
if (!collect) {
|
||||
switch (params.params[0]) {
|
||||
case 5:
|
||||
// status report
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[0n`);
|
||||
break;
|
||||
case 6:
|
||||
// cursor position
|
||||
const y = this._bufferService.buffer.y + 1;
|
||||
const x = this._bufferService.buffer.x + 1;
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`);
|
||||
break;
|
||||
}
|
||||
} else if (collect === '?') {
|
||||
// modern xterm doesnt seem to
|
||||
// respond to any of these except ?6, 6, and 5
|
||||
switch (params.params[0]) {
|
||||
case 6:
|
||||
// cursor position
|
||||
const y = this._bufferService.buffer.y + 1;
|
||||
const x = this._bufferService.buffer.x + 1;
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`);
|
||||
break;
|
||||
case 15:
|
||||
// no printer
|
||||
// this.handler(C0.ESC + '[?11n');
|
||||
break;
|
||||
case 25:
|
||||
// dont support user defined keys
|
||||
// this.handler(C0.ESC + '[?21n');
|
||||
break;
|
||||
case 26:
|
||||
// north american keyboard
|
||||
// this.handler(C0.ESC + '[?27;1;0;0n');
|
||||
break;
|
||||
case 53:
|
||||
// no dec locator/mouse
|
||||
// this.handler(C0.ESC + '[?50n');
|
||||
break;
|
||||
}
|
||||
public deviceStatus(params: IParams): void {
|
||||
switch (params.params[0]) {
|
||||
case 5:
|
||||
// status report
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[0n`);
|
||||
break;
|
||||
case 6:
|
||||
// cursor position
|
||||
const y = this._bufferService.buffer.y + 1;
|
||||
const x = this._bufferService.buffer.x + 1;
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public deviceStatusPrivate(params: IParams): void {
|
||||
// modern xterm doesnt seem to
|
||||
// respond to any of these except ?6, 6, and 5
|
||||
switch (params.params[0]) {
|
||||
case 6:
|
||||
// cursor position
|
||||
const y = this._bufferService.buffer.y + 1;
|
||||
const x = this._bufferService.buffer.x + 1;
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`);
|
||||
break;
|
||||
case 15:
|
||||
// no printer
|
||||
// this.handler(C0.ESC + '[?11n');
|
||||
break;
|
||||
case 25:
|
||||
// dont support user defined keys
|
||||
// this.handler(C0.ESC + '[?21n');
|
||||
break;
|
||||
case 26:
|
||||
// north american keyboard
|
||||
// this.handler(C0.ESC + '[?27;1;0;0n');
|
||||
break;
|
||||
case 53:
|
||||
// no dec locator/mouse
|
||||
// this.handler(C0.ESC + '[?50n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1855,25 +1882,23 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* CSI ! p Soft terminal reset (DECSTR).
|
||||
* http://vt100.net/docs/vt220-rm/table4-10.html
|
||||
*/
|
||||
public softReset(params: IParams, collect?: string): void {
|
||||
if (collect === '!') {
|
||||
this._terminal.cursorHidden = false;
|
||||
this._terminal.insertMode = false;
|
||||
this._terminal.originMode = false;
|
||||
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||
this._terminal.applicationKeypad = false; // ?
|
||||
if (this._terminal.viewport) {
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
}
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = false;
|
||||
this._bufferService.buffer.scrollTop = 0;
|
||||
this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1;
|
||||
this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone();
|
||||
this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ?
|
||||
this._terminal.charset = null;
|
||||
this._terminal.glevel = 0; // ??
|
||||
this._terminal.charsets = [null]; // ??
|
||||
public softReset(params: IParams): void {
|
||||
this._terminal.cursorHidden = false;
|
||||
this._terminal.insertMode = false;
|
||||
this._terminal.originMode = false;
|
||||
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
|
||||
this._terminal.applicationKeypad = false; // ?
|
||||
if (this._terminal.viewport) {
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
}
|
||||
this._coreService.decPrivateModes.applicationCursorKeys = false;
|
||||
this._bufferService.buffer.scrollTop = 0;
|
||||
this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1;
|
||||
this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone();
|
||||
this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ?
|
||||
this._terminal.charset = null;
|
||||
this._terminal.glevel = 0; // ??
|
||||
this._terminal.charsets = [null]; // ??
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1886,40 +1911,32 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Ps = 5 -> blinking bar (xterm).
|
||||
* Ps = 6 -> steady bar (xterm).
|
||||
*/
|
||||
public setCursorStyle(params?: IParams, collect?: string): void {
|
||||
if (collect === ' ') {
|
||||
const param = params.params[0] || 1;
|
||||
switch (param) {
|
||||
case 1:
|
||||
case 2:
|
||||
this._optionsService.options.cursorStyle = 'block';
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
this._optionsService.options.cursorStyle = 'underline';
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
this._optionsService.options.cursorStyle = 'bar';
|
||||
break;
|
||||
}
|
||||
const isBlinking = param % 2 === 1;
|
||||
this._optionsService.options.cursorBlink = isBlinking;
|
||||
public setCursorStyle(params: IParams): void {
|
||||
const param = params.params[0] || 1;
|
||||
switch (param) {
|
||||
case 1:
|
||||
case 2:
|
||||
this._optionsService.options.cursorStyle = 'block';
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
this._optionsService.options.cursorStyle = 'underline';
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
this._optionsService.options.cursorStyle = 'bar';
|
||||
break;
|
||||
}
|
||||
const isBlinking = param % 2 === 1;
|
||||
this._optionsService.options.cursorBlink = isBlinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI Ps ; Ps r
|
||||
* Set Scrolling Region [top;bottom] (default = full size of win-
|
||||
* dow) (DECSTBM).
|
||||
* CSI ? Pm r
|
||||
* currently skipped
|
||||
*/
|
||||
public setScrollRegion(params: IParams, collect?: string): void {
|
||||
if (collect) {
|
||||
return;
|
||||
}
|
||||
|
||||
public setScrollRegion(params: IParams): void {
|
||||
const top = params.params[0] || 1;
|
||||
let bottom: number;
|
||||
|
||||
|
||||
+13
-3
@@ -55,7 +55,7 @@ import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferSet, IBuffer } from 'common/buffer/Types';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { MouseService } from 'browser/services/MouseService';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { LogService } from 'common/services/LogService';
|
||||
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
@@ -1402,9 +1402,19 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
}
|
||||
|
||||
/** Add handler for ESC escape sequence. See xterm.d.ts for details. */
|
||||
public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable {
|
||||
return this._inputHandler.addEscHandler(id, callback);
|
||||
}
|
||||
|
||||
/** Add handler for DCS escape sequence. See xterm.d.ts for details. */
|
||||
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
|
||||
return this._inputHandler.addDcsHandler(id, callback);
|
||||
}
|
||||
|
||||
/** Add handler for CSI escape sequence. See xterm.d.ts for details. */
|
||||
public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
|
||||
return this._inputHandler.addCsiHandler(flag, callback);
|
||||
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
|
||||
return this._inputHandler.addCsiHandler(id, callback);
|
||||
}
|
||||
/** Add handler for OSC escape sequence. See xterm.d.ts for details. */
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
|
||||
+10
-4
@@ -15,7 +15,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { ISelectionService } from 'browser/services/Services';
|
||||
|
||||
export class TestTerminal extends Terminal {
|
||||
@@ -74,11 +74,17 @@ export class MockTerminal implements ITerminal {
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
|
||||
throw new Error('Method not implemented.');
|
||||
|
||||
Vendored
+6
-3
@@ -9,7 +9,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter';
|
||||
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
|
||||
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
|
||||
@@ -113,7 +113,8 @@ export interface IInputHandler {
|
||||
/** CSI ` */ charPosAbsolute(params: IParams): void;
|
||||
/** CSI a */ hPositionRelative(params: IParams): void;
|
||||
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
|
||||
/** CSI c */ sendDeviceAttributes(params: IParams, collect?: string): void;
|
||||
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
|
||||
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
|
||||
/** CSI d */ linePosAbsolute(params: IParams): void;
|
||||
/** CSI e */ vPositionRelative(params: IParams): void;
|
||||
/** CSI f */ hVPosition(params: IParams): void;
|
||||
@@ -200,7 +201,9 @@ export interface IPublicTerminal extends IDisposable {
|
||||
writeln(data: string): void;
|
||||
open(parent: HTMLElement): void;
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
|
||||
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
|
||||
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
|
||||
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable;
|
||||
addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable;
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): void;
|
||||
|
||||
@@ -43,3 +43,16 @@ export const enum ParserAction {
|
||||
DCS_PUT = 13,
|
||||
DCS_UNHOOK = 14
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal states of OscParser.
|
||||
*/
|
||||
export const enum OscState {
|
||||
START = 0,
|
||||
ID = 1,
|
||||
PAYLOAD = 2,
|
||||
ABORT = 3
|
||||
}
|
||||
|
||||
// payload limit for OSC and DCS
|
||||
export const PAYLOAD_LIMIT = 10000000;
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { DcsParser, DcsHandler } from 'common/parser/DcsParser';
|
||||
import { IDcsHandler, IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { utf32ToString, StringToUtf32 } from 'common/input/TextDecoder';
|
||||
import { Params } from 'common/parser/Params';
|
||||
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
|
||||
|
||||
function toUtf32(s: string): Uint32Array {
|
||||
const utf32 = new Uint32Array(s.length);
|
||||
const decoder = new StringToUtf32();
|
||||
const length = decoder.decode(s, utf32);
|
||||
return utf32.subarray(0, length);
|
||||
}
|
||||
|
||||
function identifier(id: IFunctionIdentifier): number {
|
||||
let res = 0;
|
||||
if (id.prefix) {
|
||||
if (id.prefix.length > 1) {
|
||||
throw new Error('only one byte as prefix supported');
|
||||
}
|
||||
res = id.prefix.charCodeAt(0);
|
||||
if (res && 0x3c > res || res > 0x3f) {
|
||||
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
||||
}
|
||||
}
|
||||
if (id.intermediates) {
|
||||
if (id.intermediates.length > 2) {
|
||||
throw new Error('only two bytes as intermediates are supported');
|
||||
}
|
||||
for (let i = 0; i < id.intermediates.length; ++i) {
|
||||
const intermediate = id.intermediates.charCodeAt(i);
|
||||
if (0x20 > intermediate || intermediate > 0x2f) {
|
||||
throw new Error('intermediate must be in range 0x20 .. 0x2f');
|
||||
}
|
||||
res <<= 8;
|
||||
res |= intermediate;
|
||||
}
|
||||
}
|
||||
if (id.final.length !== 1) {
|
||||
throw new Error('final must be a single byte');
|
||||
}
|
||||
const finalCode = id.final.charCodeAt(0);
|
||||
if (0x40 > finalCode || finalCode > 0x7e) {
|
||||
throw new Error('final must be in range 0x40 .. 0x7e');
|
||||
}
|
||||
res <<= 8;
|
||||
res |= finalCode;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
class TestHandler implements IDcsHandler {
|
||||
constructor(public output: any[], public msg: string, public returnFalse: boolean = false) {}
|
||||
hook(params: IParams): void {
|
||||
this.output.push([this.msg, 'HOOK', params.toArray()]);
|
||||
}
|
||||
put(data: Uint32Array, start: number, end: number): void {
|
||||
this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]);
|
||||
}
|
||||
unhook(success: boolean): void | boolean {
|
||||
this.output.push([this.msg, 'UNHOOK', success]);
|
||||
if (this.returnFalse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('DcsParser', () => {
|
||||
let parser: DcsParser;
|
||||
let reports: any[] = [];
|
||||
beforeEach(() => {
|
||||
reports = [];
|
||||
parser = new DcsParser();
|
||||
parser.setHandlerFallback((id, action, data) => {
|
||||
if (action === 'HOOK') {
|
||||
data = data.toArray();
|
||||
}
|
||||
reports.push([id, action, data]);
|
||||
});
|
||||
});
|
||||
describe('handler registration', () => {
|
||||
it('setDcsHandler', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['th', 'HOOK', [1, 2, 3]],
|
||||
['th', 'PUT', 'Here comes'],
|
||||
['th', 'PUT', 'the mouse!'],
|
||||
['th', 'UNHOOK', true]
|
||||
]);
|
||||
});
|
||||
it('clearDcsHandler', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
|
||||
parser.clearHandler(identifier({intermediates: '+', final: 'p'}));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from fallback handler
|
||||
[identifier({intermediates: '+', final: 'p'}), 'HOOK', [1, 2, 3]],
|
||||
[identifier({intermediates: '+', final: 'p'}), 'PUT', 'Here comes'],
|
||||
[identifier({intermediates: '+', final: 'p'}), 'PUT', 'the mouse!'],
|
||||
[identifier({intermediates: '+', final: 'p'}), 'UNHOOK', true]
|
||||
]);
|
||||
});
|
||||
it('addDcsHandler', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
|
||||
parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2'));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [
|
||||
['th2', 'HOOK', [1, 2, 3]],
|
||||
['th1', 'HOOK', [1, 2, 3]],
|
||||
['th2', 'PUT', 'Here comes'],
|
||||
['th1', 'PUT', 'Here comes'],
|
||||
['th2', 'PUT', 'the mouse!'],
|
||||
['th1', 'PUT', 'the mouse!'],
|
||||
['th2', 'UNHOOK', true],
|
||||
['th1', 'UNHOOK', false] // false due being already handled by th2!
|
||||
]);
|
||||
});
|
||||
it('addDcsHandler with return false', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
|
||||
parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [
|
||||
['th2', 'HOOK', [1, 2, 3]],
|
||||
['th1', 'HOOK', [1, 2, 3]],
|
||||
['th2', 'PUT', 'Here comes'],
|
||||
['th1', 'PUT', 'Here comes'],
|
||||
['th2', 'PUT', 'the mouse!'],
|
||||
['th1', 'PUT', 'the mouse!'],
|
||||
['th2', 'UNHOOK', true],
|
||||
['th1', 'UNHOOK', true] // true since th2 indicated to keep bubbling
|
||||
]);
|
||||
});
|
||||
it('dispose handlers', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
|
||||
const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
|
||||
dispo.dispose();
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [
|
||||
['th1', 'HOOK', [1, 2, 3]],
|
||||
['th1', 'PUT', 'Here comes'],
|
||||
['th1', 'PUT', 'the mouse!'],
|
||||
['th1', 'UNHOOK', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('DcsHandlerFactory', () => {
|
||||
it('should be called once on end(true)', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]);
|
||||
});
|
||||
it('should not be called on end(false)', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(false);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
it('should be disposable', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data])));
|
||||
const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['two', params.toArray(), data])));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!']]);
|
||||
dispo.dispose();
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
data = toUtf32('some other');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' data');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]);
|
||||
});
|
||||
it('should respect return false', () => {
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data])));
|
||||
parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; }));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'Here comes the mouse!']]);
|
||||
});
|
||||
it('should work up to payload limit', function(): void {
|
||||
this.timeout(10000);
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
const data = toUtf32('A'.repeat(1000));
|
||||
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
|
||||
parser.put(data, 0, data.length);
|
||||
}
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, [[[1, 2, 3], 'A'.repeat(PAYLOAD_LIMIT)]]);
|
||||
});
|
||||
it('should abort for payload limit +1', function(): void {
|
||||
this.timeout(10000);
|
||||
parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data])));
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
let data = toUtf32('A'.repeat(1000));
|
||||
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
|
||||
parser.put(data, 0, data.length);
|
||||
}
|
||||
data = toUtf32('A');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.unhook(true);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType } from 'common/parser/Types';
|
||||
import { utf32ToString } from 'common/input/TextDecoder';
|
||||
import { Params } from 'common/parser/Params';
|
||||
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
|
||||
|
||||
const EMPTY_HANDLERS: IDcsHandler[] = [];
|
||||
|
||||
export class DcsParser implements IDcsParser {
|
||||
private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
|
||||
private _active: IDcsHandler[] = EMPTY_HANDLERS;
|
||||
private _ident: number = 0;
|
||||
private _handlerFb: DcsFallbackHandlerType = () => {};
|
||||
|
||||
public dispose(): void {
|
||||
this._handlers = Object.create(null);
|
||||
this._handlerFb = () => {};
|
||||
}
|
||||
|
||||
public addHandler(ident: number, handler: IDcsHandler): IDisposable {
|
||||
if (this._handlers[ident] === undefined) {
|
||||
this._handlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._handlers[ident];
|
||||
handlerList.push(handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(handler);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public setHandler(ident: number, handler: IDcsHandler): void {
|
||||
this._handlers[ident] = [handler];
|
||||
}
|
||||
|
||||
public clearHandler(ident: number): void {
|
||||
if (this._handlers[ident]) delete this._handlers[ident];
|
||||
}
|
||||
|
||||
public setHandlerFallback(handler: DcsFallbackHandlerType): void {
|
||||
this._handlerFb = handler;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
if (this._active.length) {
|
||||
this.unhook(false);
|
||||
}
|
||||
this._active = EMPTY_HANDLERS;
|
||||
this._ident = 0;
|
||||
}
|
||||
|
||||
public hook(ident: number, params: IParams): void {
|
||||
// always reset leftover handlers
|
||||
this.reset();
|
||||
this._ident = ident;
|
||||
this._active = this._handlers[ident] || EMPTY_HANDLERS;
|
||||
if (!this._active.length) {
|
||||
this._handlerFb(this._ident, 'HOOK', params);
|
||||
} else {
|
||||
for (let j = this._active.length - 1; j >= 0; j--) {
|
||||
this._active[j].hook(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
if (!this._active.length) {
|
||||
this._handlerFb(this._ident, 'PUT', utf32ToString(data, start, end));
|
||||
} else {
|
||||
for (let j = this._active.length - 1; j >= 0; j--) {
|
||||
this._active[j].put(data, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unhook(success: boolean): void {
|
||||
if (!this._active.length) {
|
||||
this._handlerFb(this._ident, 'UNHOOK', success);
|
||||
} else {
|
||||
let j = this._active.length - 1;
|
||||
for (; j >= 0; j--) {
|
||||
if (this._active[j].unhook(success) !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j--;
|
||||
// cleanup left over handlers
|
||||
for (; j >= 0; j--) {
|
||||
this._active[j].unhook(false);
|
||||
}
|
||||
}
|
||||
this._active = EMPTY_HANDLERS;
|
||||
this._ident = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient class to create a DCS handler from a single callback function.
|
||||
* Note: The payload is currently limited to 50 MB (hardcoded).
|
||||
*/
|
||||
export class DcsHandler implements IDcsHandler {
|
||||
private _data = '';
|
||||
private _params: IParams | undefined;
|
||||
private _hitLimit: boolean = false;
|
||||
|
||||
constructor(private _handler: (data: string, params: IParams) => any) {}
|
||||
|
||||
public hook(params: IParams): void {
|
||||
this._params = params.clone();
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
}
|
||||
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
if (this._hitLimit) {
|
||||
return;
|
||||
}
|
||||
this._data += utf32ToString(data, start, end);
|
||||
if (this._data.length > PAYLOAD_LIMIT) {
|
||||
this._data = '';
|
||||
this._hitLimit = true;
|
||||
}
|
||||
}
|
||||
|
||||
public unhook(success: boolean): any {
|
||||
let ret;
|
||||
if (this._hitLimit) {
|
||||
ret = false;
|
||||
} else if (success) {
|
||||
ret = this._handler(this._data, this._params ? this._params : new Params());
|
||||
}
|
||||
this._params = undefined;
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,15 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDcsHandler, IParsingState, IParams, ParamsArray } from 'common/parser/Types';
|
||||
import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType } from 'common/parser/Types';
|
||||
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
|
||||
import * as chai from 'chai';
|
||||
import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { StringToUtf32, stringFromCodePoint, utf32ToString } from 'common/input/TextDecoder';
|
||||
import { ParserState } from 'common/parser/Constants';
|
||||
import { Params } from 'common/parser/Params';
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { DcsHandler } from 'common/parser/DcsParser';
|
||||
|
||||
|
||||
function r(a: number, b: number): string[] {
|
||||
@@ -20,13 +23,46 @@ function r(a: number, b: number): string[] {
|
||||
return arr;
|
||||
}
|
||||
|
||||
class MockOscPutParser implements IOscParser {
|
||||
private _fallback: OscFallbackHandlerType = () => {};
|
||||
public data = '';
|
||||
public reset(): void {
|
||||
this.data = '';
|
||||
}
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
this.data += utf32ToString(data, start, end);
|
||||
}
|
||||
public dispose(): void { }
|
||||
public start(): void { }
|
||||
public end(success: boolean): void {
|
||||
this.data += `, success: ${success}`;
|
||||
const id = parseInt(this.data.slice(0, this.data.indexOf(';')));
|
||||
if (!isNaN(id)) {
|
||||
this._fallback(id, 'END', this.data.slice(this.data.indexOf(';') + 1));
|
||||
}
|
||||
}
|
||||
addHandler(ident: number, handler: IOscHandler): IDisposable {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
setHandler(ident: number, handler: IOscHandler): void {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
clearHandler(ident: number): void {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
setHandlerFallback(handler: OscFallbackHandlerType): void {
|
||||
this._fallback = handler;
|
||||
}
|
||||
}
|
||||
const oscPutParser = new MockOscPutParser();
|
||||
|
||||
// derived parser with access to internal states
|
||||
class TestEscapeSequenceParser extends EscapeSequenceParser {
|
||||
public get osc(): string {
|
||||
return this._osc;
|
||||
return (this._oscParser as MockOscPutParser).data;
|
||||
}
|
||||
public set osc(value: string) {
|
||||
this._osc = value;
|
||||
(this._oscParser as MockOscPutParser).data = value;
|
||||
}
|
||||
public get params(): ParamsArray {
|
||||
return this._params.toArray();
|
||||
@@ -38,13 +74,17 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
|
||||
return this._params;
|
||||
}
|
||||
public get collect(): string {
|
||||
return this._collect;
|
||||
return this.identToString(this._collect);
|
||||
}
|
||||
public set collect(value: string) {
|
||||
this._collect = value;
|
||||
this._collect = 0;
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
this._collect <<= 8;
|
||||
this._collect |= value.charCodeAt(i);
|
||||
}
|
||||
}
|
||||
public mockActiveDcsHandler(): void {
|
||||
this._activeDcsHandler = this._dcsHandlerFb;
|
||||
public mockOscParser(): void {
|
||||
this._oscParser = oscPutParser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,34 +116,17 @@ const testTerminal: any = {
|
||||
actionESC: function (collect: string, flag: string): void {
|
||||
this.calls.push(['esc', collect, flag]);
|
||||
},
|
||||
actionDCSHook: function (collect: string, params: IParams, flag: string): void {
|
||||
this.calls.push(['dcs hook', collect, params.toArray(), flag]);
|
||||
actionDCSHook: function (params: IParams): void {
|
||||
this.calls.push(['dcs hook', params.toArray()]);
|
||||
},
|
||||
actionDCSPrint: function (data: Uint32Array, start: number, end: number): void {
|
||||
let s = '';
|
||||
for (let i = start; i < end; ++i) {
|
||||
s += stringFromCodePoint(data[i]);
|
||||
}
|
||||
actionDCSPrint: function (s: string): void {
|
||||
this.calls.push(['dcs put', s]);
|
||||
},
|
||||
actionDCSUnhook: function (): void {
|
||||
this.calls.push(['dcs unhook']);
|
||||
actionDCSUnhook: function (success: boolean): void {
|
||||
this.calls.push(['dcs unhook', success]);
|
||||
}
|
||||
};
|
||||
|
||||
// dcs handler to map dcs actions into the test object `testTerminal`
|
||||
class DcsTest implements IDcsHandler {
|
||||
hook(collect: string, params: IParams, flag: number): void {
|
||||
testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag));
|
||||
}
|
||||
put(data: Uint32Array, start: number, end: number): void {
|
||||
testTerminal.actionDCSPrint(data, start, end);
|
||||
}
|
||||
unhook(): void {
|
||||
testTerminal.actionDCSUnhook();
|
||||
}
|
||||
}
|
||||
|
||||
const states: number[] = [
|
||||
ParserState.GROUND,
|
||||
ParserState.ESCAPE,
|
||||
@@ -124,21 +147,35 @@ let state: any;
|
||||
|
||||
// parser with Uint8Array based transition table
|
||||
const testParser = new TestEscapeSequenceParser();
|
||||
testParser.mockOscParser();
|
||||
testParser.setPrintHandler(testTerminal.print.bind(testTerminal));
|
||||
testParser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
|
||||
testTerminal.actionCSI(collect, params, String.fromCharCode(flag));
|
||||
testParser.setCsiHandlerFallback((ident: number, params: IParams) => {
|
||||
const id = testParser.identToString(ident);
|
||||
testTerminal.actionCSI(id.slice(0, -1), params, id.slice(-1));
|
||||
});
|
||||
testParser.setEscHandlerFallback((collect: string, flag: number) => {
|
||||
testTerminal.actionESC(collect, String.fromCharCode(flag));
|
||||
testParser.setEscHandlerFallback((ident: number) => {
|
||||
const id = testParser.identToString(ident);
|
||||
testTerminal.actionESC(id.slice(0, -1), id.slice(-1));
|
||||
});
|
||||
testParser.setExecuteHandlerFallback((code: number) => {
|
||||
testTerminal.actionExecute(String.fromCharCode(code));
|
||||
});
|
||||
testParser.setOscHandlerFallback((identifier: number, data: string) => {
|
||||
testParser.setOscHandlerFallback((identifier, action, data) => {
|
||||
if (identifier === -1) testTerminal.actionOSC(data); // handle error condition silently
|
||||
else testTerminal.actionOSC('' + identifier + ';' + data);
|
||||
else if (action === 'END') testTerminal.actionOSC('' + identifier + ';' + data); // collect only data at END
|
||||
});
|
||||
testParser.setDcsHandlerFallback((collectAndFlag, action, payload) => {
|
||||
switch (action) {
|
||||
case 'HOOK':
|
||||
testTerminal.actionDCSHook(payload);
|
||||
break;
|
||||
case 'PUT':
|
||||
testTerminal.actionDCSPrint(payload);
|
||||
break;
|
||||
case 'UNHOOK':
|
||||
testTerminal.actionDCSUnhook(payload);
|
||||
}
|
||||
});
|
||||
testParser.setDcsHandlerFallback(new DcsTest());
|
||||
|
||||
|
||||
// translate string based parse calls into typed array based
|
||||
@@ -217,7 +254,8 @@ describe('EscapeSequenceParser', function (): void {
|
||||
'\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x99', '\x9a'
|
||||
];
|
||||
const exceptions: { [key: number]: { [key: string]: any[] } } = {
|
||||
8: { '\x18': [], '\x1a': [] } // simply abort osc state
|
||||
8: { '\x18': [], '\x1a': [] }, // abort OSC_STRING
|
||||
13: { '\x18': [['dcs unhook', false]], '\x1a': [['dcs unhook', false]] } // abort DCS_PASSTHROUGH
|
||||
};
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
@@ -241,12 +279,10 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.reset();
|
||||
for (state in states) {
|
||||
parser.currentState = state;
|
||||
parser.osc = '#';
|
||||
parser.params = [23];
|
||||
parser.collect = '#';
|
||||
parse(parser, '\x1b');
|
||||
chai.expect(parser.currentState).equal(ParserState.ESCAPE);
|
||||
chai.expect(parser.osc).equal('');
|
||||
chai.expect(parser.params).eql([0]);
|
||||
chai.expect(parser.collect).equal('');
|
||||
parser.reset();
|
||||
@@ -358,24 +394,20 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.reset();
|
||||
// C0
|
||||
parser.currentState = ParserState.ESCAPE;
|
||||
parser.osc = '#';
|
||||
parser.params = [123];
|
||||
parser.collect = '#';
|
||||
parse(parser, '[');
|
||||
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
|
||||
chai.expect(parser.osc).equal('');
|
||||
chai.expect(parser.params).eql([0]);
|
||||
chai.expect(parser.collect).equal('');
|
||||
parser.reset();
|
||||
// C1
|
||||
for (state in states) {
|
||||
parser.currentState = state;
|
||||
parser.osc = '#';
|
||||
parser.params = [123];
|
||||
parser.collect = '#';
|
||||
parse(parser, '\x9b');
|
||||
chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY);
|
||||
chai.expect(parser.osc).equal('');
|
||||
chai.expect(parser.params).eql([0]);
|
||||
chai.expect(parser.collect).equal('');
|
||||
parser.reset();
|
||||
@@ -909,7 +941,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.currentState = ParserState.DCS_ENTRY;
|
||||
parse(parser, collect[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
|
||||
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
|
||||
testTerminal.compare([['dcs hook', [0]]]);
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
}
|
||||
@@ -922,7 +954,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.currentState = ParserState.DCS_PARAM;
|
||||
parse(parser, collect[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
|
||||
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
|
||||
testTerminal.compare([['dcs hook', [0]]]);
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
}
|
||||
@@ -935,7 +967,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.currentState = ParserState.DCS_INTERMEDIATE;
|
||||
parse(parser, collect[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
|
||||
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
|
||||
testTerminal.compare([['dcs hook', [0]]]);
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
}
|
||||
@@ -949,7 +981,6 @@ describe('EscapeSequenceParser', function (): void {
|
||||
puts = puts.concat(r(0x20, 0x7f));
|
||||
for (let i = 0; i < puts.length; ++i) {
|
||||
parser.currentState = ParserState.DCS_PASSTHROUGH;
|
||||
parser.mockActiveDcsHandler();
|
||||
parse(parser, puts[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
|
||||
testTerminal.compare([['dcs put', puts[i]]]);
|
||||
@@ -990,33 +1021,33 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('OSC', function (): void {
|
||||
test('\x1b]0;abc123€öäü\x07', [
|
||||
['osc', '0;abc123€öäü']
|
||||
['osc', '0;abc123€öäü, success: true']
|
||||
], null);
|
||||
});
|
||||
it('single DCS', function (): void {
|
||||
test('\x1bP1;2;3+$aäbc;däe\x9c', [
|
||||
['dcs hook', '+$', [1, 2, 3], 'a'],
|
||||
['dcs hook', [1, 2, 3]],
|
||||
['dcs put', 'äbc;däe'],
|
||||
['dcs unhook']
|
||||
['dcs unhook', true]
|
||||
], null);
|
||||
});
|
||||
it('multi DCS', function (): void {
|
||||
test('\x1bP1;2;3+$abc;de', [
|
||||
['dcs hook', '+$', [1, 2, 3], 'a'],
|
||||
['dcs hook', [1, 2, 3]],
|
||||
['dcs put', 'bc;de']
|
||||
], null);
|
||||
testTerminal.clear();
|
||||
test('abc\x9c', [
|
||||
['dcs put', 'abc'],
|
||||
['dcs unhook']
|
||||
['dcs unhook', true]
|
||||
], true);
|
||||
});
|
||||
it('print + DCS(C1)', function (): void {
|
||||
test('abc\x901;2;3+$abc;de\x9c', [
|
||||
['print', 'abc'],
|
||||
['dcs hook', '+$', [1, 2, 3], 'a'],
|
||||
['dcs hook', [1, 2, 3]],
|
||||
['dcs put', 'bc;de'],
|
||||
['dcs unhook']
|
||||
['dcs unhook', true]
|
||||
], null);
|
||||
});
|
||||
it('print + PM(C1) + print', function (): void {
|
||||
@@ -1026,9 +1057,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
], null);
|
||||
});
|
||||
it('print + OSC(C1) + print', function (): void {
|
||||
test('abc\x9d123tzf\x9cdefg', [
|
||||
test('abc\x9d123;tzf\x9cdefg', [
|
||||
['print', 'abc'],
|
||||
['osc', '123tzf'],
|
||||
['osc', '123;tzf, success: true'],
|
||||
['print', 'defg']
|
||||
], null);
|
||||
});
|
||||
@@ -1039,9 +1070,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
], null);
|
||||
});
|
||||
it('7bit ST should be swallowed', function (): void {
|
||||
test('abc\x9d123tzf\x1b\\defg', [
|
||||
test('abc\x9d123;tzf\x1b\\defg', [
|
||||
['print', 'abc'],
|
||||
['osc', '123tzf'],
|
||||
['osc', '123;tzf, success: true'],
|
||||
['print', 'defg']
|
||||
], null);
|
||||
});
|
||||
@@ -1057,9 +1088,35 @@ describe('EscapeSequenceParser', function (): void {
|
||||
it('colon notation in DCS params', function (): void {
|
||||
test('abc\x901;2::55;3+$abc;de\x9c', [
|
||||
['print', 'abc'],
|
||||
['dcs hook', '+$', [1, 2, [-1, 55], 3], 'a'],
|
||||
['dcs hook', [1, 2, [-1, 55], 3]],
|
||||
['dcs put', 'bc;de'],
|
||||
['dcs unhook']
|
||||
['dcs unhook', true]
|
||||
], null);
|
||||
});
|
||||
it('CAN should abort DCS', () => {
|
||||
test('abc\x901;2::55;3+$abc;de\x18', [
|
||||
['print', 'abc'],
|
||||
['dcs hook', [1, 2, [-1, 55], 3]],
|
||||
['dcs put', 'bc;de'],
|
||||
['dcs unhook', false] // false for abort
|
||||
], null);
|
||||
});
|
||||
it('SUB should abort DCS', () => {
|
||||
test('abc\x901;2::55;3+$abc;de\x1a', [
|
||||
['print', 'abc'],
|
||||
['dcs hook', [1, 2, [-1, 55], 3]],
|
||||
['dcs put', 'bc;de'],
|
||||
['dcs unhook', false] // false for abort
|
||||
], null);
|
||||
});
|
||||
it('CAN should abort OSC', () => {
|
||||
test('\x1b]0;abc123€öäü\x18', [
|
||||
['osc', '0;abc123€öäü, success: false']
|
||||
], null);
|
||||
});
|
||||
it('SUB should abort OSC', () => {
|
||||
test('\x1b]0;abc123€öäü\x1a', [
|
||||
['osc', '0;abc123€öäü, success: false']
|
||||
], null);
|
||||
});
|
||||
});
|
||||
@@ -1091,7 +1148,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.currentState = ParserState.DCS_PASSTHROUGH;
|
||||
parse(parser, '\x901;2;3+$a€öäü');
|
||||
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
|
||||
testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['dcs put', '€öäü']]);
|
||||
testTerminal.compare([['dcs hook', [1, 2, 3]], ['dcs put', '€öäü']]);
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
});
|
||||
@@ -1143,32 +1200,83 @@ describe('EscapeSequenceParser', function (): void {
|
||||
chai.expect(print).equal('');
|
||||
});
|
||||
it('ESC handler', function (): void {
|
||||
parser2.setEscHandler('%G', function (): void {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, function (): void {
|
||||
esc.push('%G');
|
||||
});
|
||||
parser2.setEscHandler('E', function (): void {
|
||||
parser2.setEscHandler({final: 'E'}, function (): void {
|
||||
esc.push('E');
|
||||
});
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['%G', 'E']);
|
||||
parser2.clearEscHandler('%G');
|
||||
parser2.clearEscHandler('%G'); // should not throw
|
||||
parser2.clearEscHandler({intermediates: '%', final: 'G'});
|
||||
parser2.clearEscHandler({intermediates: '%', final: 'G'}); // should not throw
|
||||
clearAccu();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['E']);
|
||||
parser2.clearEscHandler('E');
|
||||
parser2.clearEscHandler({final: 'E'});
|
||||
clearAccu();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql([]);
|
||||
});
|
||||
describe('ESC custom handlers', () => {
|
||||
it('prevent fallback', () => {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['custom - %G']);
|
||||
});
|
||||
it('allow fallback', () => {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return false; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['custom - %G', 'default - %G']);
|
||||
});
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return false; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['custom2 - %G', 'custom - %G']);
|
||||
});
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return true; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['custom2 - %G']);
|
||||
});
|
||||
it('Execution order should go from latest handler down to the original', () => {
|
||||
const order: number[] = [];
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { order.push(1); });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(2); return false; });
|
||||
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(3); return false; });
|
||||
parse(parser2, '\x1b%G');
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
|
||||
const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
|
||||
dispo.dispose();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['default - %G']);
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); });
|
||||
const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
|
||||
dispo.dispose();
|
||||
dispo.dispose();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(esc).eql(['default - %G']);
|
||||
});
|
||||
});
|
||||
it('CSI handler', function (): void {
|
||||
parser2.setCsiHandler('m', function (params: IParams, collect: string): void {
|
||||
csi.push(['m', params.toArray(), collect]);
|
||||
parser2.setCsiHandler({final: 'm'}, function (params: IParams): void {
|
||||
csi.push(['m', params.toArray(), '']);
|
||||
});
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
parser2.clearCsiHandler('m');
|
||||
parser2.clearCsiHandler('m'); // should not throw
|
||||
parser2.clearCsiHandler({final: 'm'});
|
||||
parser2.clearCsiHandler({final: 'm'}); // should not throw
|
||||
clearAccu();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([]);
|
||||
@@ -1176,16 +1284,16 @@ describe('EscapeSequenceParser', function (): void {
|
||||
describe('CSI custom handlers', () => {
|
||||
it('Prevent fallback', () => {
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
|
||||
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Allow fallback', () => {
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return false; });
|
||||
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
|
||||
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return false; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
@@ -1193,9 +1301,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
const csiCustom2: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray(), collect]); return false; });
|
||||
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
|
||||
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
|
||||
parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return false; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
@@ -1204,9 +1312,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
const csiCustom2: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
|
||||
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
|
||||
parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return true; });
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([], 'Should not fallback once');
|
||||
@@ -1214,16 +1322,16 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('Execution order should go from latest handler down to the original', () => {
|
||||
const order: number[] = [];
|
||||
parser2.setCsiHandler('m', () => order.push(1));
|
||||
parser2.addCsiHandler('m', () => { order.push(2); return false; });
|
||||
parser2.addCsiHandler('m', () => { order.push(3); return false; });
|
||||
parser2.setCsiHandler({final: 'm'}, () => { order.push(1); });
|
||||
parser2.addCsiHandler({final: 'm'}, () => { order.push(2); return false; });
|
||||
parser2.addCsiHandler({final: 'm'}, () => { order.push(3); return false; });
|
||||
parse(parser2, '\x1b[0m');
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
|
||||
const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
|
||||
customHandler.dispose();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
@@ -1231,8 +1339,8 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
const csiCustom: [string, ParamsArray, string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; });
|
||||
parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); });
|
||||
const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
|
||||
customHandler.dispose();
|
||||
customHandler.dispose();
|
||||
parse(parser2, INPUT);
|
||||
@@ -1256,9 +1364,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
chai.expect(exe).eql(['\n']);
|
||||
});
|
||||
it('OSC handler', function (): void {
|
||||
parser2.setOscHandler(1, function (data: string): void {
|
||||
parser2.setOscHandler(1, new OscHandler(function (data: string): void {
|
||||
osc.push([1, data]);
|
||||
});
|
||||
}));
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
||||
parser2.clearOscHandler(1);
|
||||
@@ -1270,16 +1378,16 @@ describe('EscapeSequenceParser', function (): void {
|
||||
describe('OSC custom handlers', () => {
|
||||
it('Prevent fallback', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
|
||||
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||
});
|
||||
it('Allow fallback', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return false; });
|
||||
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
|
||||
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; }));
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
|
||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||
@@ -1287,9 +1395,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
const oscCustom2: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return false; });
|
||||
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
|
||||
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
|
||||
parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; }));
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||
@@ -1298,9 +1406,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
const oscCustom2: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return true; });
|
||||
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
|
||||
parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
|
||||
parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; }));
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(oscCustom).eql([], 'Should not fallback once');
|
||||
@@ -1308,16 +1416,16 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('Execution order should go from latest handler down to the original', () => {
|
||||
const order: number[] = [];
|
||||
parser2.setOscHandler(1, () => order.push(1));
|
||||
parser2.addOscHandler(1, () => { order.push(2); return false; });
|
||||
parser2.addOscHandler(1, () => { order.push(3); return false; });
|
||||
parser2.setOscHandler(1, new OscHandler(() => order.push(1)));
|
||||
parser2.addOscHandler(1, new OscHandler(() => { order.push(2); return false; }));
|
||||
parser2.addOscHandler(1, new OscHandler(() => { order.push(3); return false; }));
|
||||
parse(parser2, '\x1b]1;foo=bar\x1b\\');
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
|
||||
const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
|
||||
customHandler.dispose();
|
||||
parse(parser2, INPUT);
|
||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
||||
@@ -1325,8 +1433,8 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data])));
|
||||
const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; }));
|
||||
customHandler.dispose();
|
||||
customHandler.dispose();
|
||||
parse(parser2, INPUT);
|
||||
@@ -1335,9 +1443,9 @@ describe('EscapeSequenceParser', function (): void {
|
||||
});
|
||||
});
|
||||
it('DCS handler', function (): void {
|
||||
parser2.setDcsHandler('+p', {
|
||||
hook: function (collect: string, params: IParams, flag: number): void {
|
||||
dcs.push(['hook', collect, params.toArray(), flag]);
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, {
|
||||
hook: function (params: IParams): void {
|
||||
dcs.push(['hook', '', params.toArray(), 0]);
|
||||
},
|
||||
put: function (data: Uint32Array, start: number, end: number): void {
|
||||
let s = '';
|
||||
@@ -1353,17 +1461,75 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parse(parser2, '\x1bP1;2;3+pabc');
|
||||
parse(parser2, ';de\x9c');
|
||||
chai.expect(dcs).eql([
|
||||
['hook', '+', [1, 2, 3], 'p'.charCodeAt(0)],
|
||||
['hook', '', [1, 2, 3], 0],
|
||||
['put', 'abc'], ['put', ';de'],
|
||||
['unhook']
|
||||
]);
|
||||
parser2.clearDcsHandler('+p');
|
||||
parser2.clearDcsHandler('+p'); // should not throw
|
||||
parser2.clearDcsHandler({intermediates: '+', final: 'p'});
|
||||
parser2.clearDcsHandler({intermediates: '+', final: 'p'}); // should not throw
|
||||
clearAccu();
|
||||
parse(parser2, '\x1bP1;2;3+pabc');
|
||||
parse(parser2, ';de\x9c');
|
||||
chai.expect(dcs).eql([]);
|
||||
});
|
||||
describe('DCS custom handlers', () => {
|
||||
const DCS_INPUT = '\x1bP1;2;3+pabc\x1b\\';
|
||||
it('Prevent fallback', () => {
|
||||
const dcsCustom: [string, (number | number[])[], string][] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc']]);
|
||||
});
|
||||
it('Allow fallback', () => {
|
||||
const dcsCustom: [string, (number | number[])[], string][] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; }));
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]);
|
||||
});
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
const dcsCustom: [string, (number | number[])[], string][] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; }));
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]);
|
||||
});
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
const dcsCustom: [string, (number | number[])[], string][] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; }));
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc']]);
|
||||
});
|
||||
it('Execution order should go from latest handler down to the original', () => {
|
||||
const order: number[] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => order.push(1)));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(2); return false; }));
|
||||
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(3); return false; }));
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
const dcsCustom: [string, (number | number[])[], string][] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
|
||||
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
|
||||
dispo.dispose();
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]);
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
const dcsCustom: [string, (number | number[])[], string][] = [];
|
||||
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data])));
|
||||
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
|
||||
dispo.dispose();
|
||||
dispo.dispose();
|
||||
parse(parser2, DCS_INPUT);
|
||||
chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]);
|
||||
});
|
||||
});
|
||||
it('ERROR handler', function (): void {
|
||||
let errorState: IParsingState | null = null;
|
||||
parser2.setErrorHandler(function (state: IParsingState): IParsingState {
|
||||
@@ -1375,8 +1541,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
position: 6,
|
||||
code: '€'.charCodeAt(0),
|
||||
currentState: ParserState.CSI_PARAM,
|
||||
osc: '',
|
||||
collect: '',
|
||||
collect: 0,
|
||||
params: Params.fromArray([1, 2, 0]), // extra zero here
|
||||
abort: false
|
||||
});
|
||||
|
||||
@@ -3,20 +3,14 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType } from 'common/parser/Types';
|
||||
import { ParserState, ParserAction } from 'common/parser/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { utf32ToString } from 'common/input/TextDecoder';
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { Params } from 'common/parser/Params';
|
||||
|
||||
interface IHandlerCollection<T> {
|
||||
[key: string]: T[];
|
||||
}
|
||||
|
||||
type CsiHandler = (params: IParams, collect: string) => boolean | void;
|
||||
type OscHandler = (data: string) => boolean | void;
|
||||
import { OscParser } from 'common/parser/OscParser';
|
||||
import { DcsParser } from 'common/parser/DcsParser';
|
||||
|
||||
/**
|
||||
* Table values are generated like this:
|
||||
@@ -193,7 +187,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
|
||||
table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
|
||||
table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
|
||||
table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
|
||||
table.addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
|
||||
// special handling of unicode chars
|
||||
table.add(NON_ASCII_PRINTABLE, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
|
||||
table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
|
||||
@@ -203,14 +197,6 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
return table;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Dummy DCS handler as default fallback.
|
||||
*/
|
||||
class DcsDummy implements IDcsHandler {
|
||||
hook(collect: string, params: IParams, flag: number): void { }
|
||||
put(data: Uint32Array, start: number, end: number): void { }
|
||||
unhook(): void { }
|
||||
}
|
||||
|
||||
/**
|
||||
* EscapeSequenceParser.
|
||||
@@ -220,17 +206,25 @@ class DcsDummy implements IDcsHandler {
|
||||
* To implement custom ANSI compliant escape sequences it is not needed to
|
||||
* alter this parser, instead consider registering a custom handler.
|
||||
* For non ANSI compliant sequences change the transition table with
|
||||
* the optional `transitions` contructor argument and
|
||||
* the optional `transitions` constructor argument and
|
||||
* reimplement the `parse` method.
|
||||
*
|
||||
* This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
|
||||
* as suggested by the original parser, thus empty parameters are set to 0.
|
||||
* This this is not in line with the latest ECMA specification
|
||||
* This this is not in line with the latest ECMA-48 specification
|
||||
* (ZDM was part of the early specs and got completely removed later on).
|
||||
*
|
||||
* Other than the original parser from vt100.net this parser supports
|
||||
* sub parameters in digital parameters separated by colons. Empty sub parameters
|
||||
* are set to -1.
|
||||
* are set to -1 (no ZDM for sub parameters).
|
||||
*
|
||||
* About prefix and intermediate bytes:
|
||||
* This parser follows the assumptions of the vt100.net parser with these restrictions:
|
||||
* - only one prefix byte is allowed as first parameter byte, byte range 0x3c .. 0x3f
|
||||
* - max. two intermediates are respected, byte range 0x20 .. 0x2f
|
||||
* Note that this is not in line with ECMA-48 which does not limit either of those.
|
||||
* Furthermore ECMA-48 allows the prefix byte range at any param byte position. Currently
|
||||
* there are no known sequences that follow the broader definition of the specification.
|
||||
*
|
||||
* TODO: implement error recovery hook via error handler return values
|
||||
*/
|
||||
@@ -240,27 +234,23 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
public precedingCodepoint: number;
|
||||
|
||||
// buffers over several parse calls
|
||||
protected _osc: string;
|
||||
protected _params: Params;
|
||||
protected _collect: string;
|
||||
protected _collect: number;
|
||||
|
||||
// handler lookup containers
|
||||
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
|
||||
protected _executeHandlers: any;
|
||||
protected _csiHandlers: IHandlerCollection<CsiHandler>;
|
||||
protected _escHandlers: any;
|
||||
protected _oscHandlers: IHandlerCollection<OscHandler>;
|
||||
protected _dcsHandlers: any;
|
||||
protected _activeDcsHandler: IDcsHandler | null;
|
||||
protected _printHandler: PrintHandlerType;
|
||||
protected _executeHandlers: {[flag: number]: ExecuteHandlerType};
|
||||
protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
|
||||
protected _escHandlers: IHandlerCollection<EscHandlerType>;
|
||||
protected _oscParser: IOscParser;
|
||||
protected _dcsParser: IDcsParser;
|
||||
protected _errorHandler: (state: IParsingState) => IParsingState;
|
||||
|
||||
// fallback handlers
|
||||
protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;
|
||||
protected _executeHandlerFb: (code: number) => void;
|
||||
protected _csiHandlerFb: (collect: string, params: IParams, flag: number) => void;
|
||||
protected _escHandlerFb: (collect: string, flag: number) => void;
|
||||
protected _oscHandlerFb: (identifier: number, data: string) => void;
|
||||
protected _dcsHandlerFb: IDcsHandler;
|
||||
protected _printHandlerFb: PrintFallbackHandlerType;
|
||||
protected _executeHandlerFb: ExecuteFallbackHandlerType;
|
||||
protected _csiHandlerFb: CsiFallbackHandlerType;
|
||||
protected _escHandlerFb: EscFallbackHandlerType;
|
||||
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
|
||||
|
||||
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
|
||||
@@ -268,145 +258,197 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
|
||||
this.initialState = ParserState.GROUND;
|
||||
this.currentState = this.initialState;
|
||||
this._osc = '';
|
||||
this._params = new Params(); // defaults to 32 storable params/subparams
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = '';
|
||||
this._collect = 0;
|
||||
this.precedingCodepoint = 0;
|
||||
|
||||
// set default fallback handlers and handler lookup containers
|
||||
this._printHandlerFb = (data, start, end): void => { };
|
||||
this._executeHandlerFb = (code: number): void => { };
|
||||
this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { };
|
||||
this._escHandlerFb = (collect: string, flag: number): void => { };
|
||||
this._oscHandlerFb = (identifier: number, data: string): void => { };
|
||||
this._dcsHandlerFb = new DcsDummy();
|
||||
this._csiHandlerFb = (ident: number, params: IParams): void => { };
|
||||
this._escHandlerFb = (ident: number): void => { };
|
||||
this._errorHandlerFb = (state: IParsingState): IParsingState => state;
|
||||
this._printHandler = this._printHandlerFb;
|
||||
this._executeHandlers = Object.create(null);
|
||||
this._csiHandlers = Object.create(null);
|
||||
this._escHandlers = Object.create(null);
|
||||
this._oscHandlers = Object.create(null);
|
||||
this._dcsHandlers = Object.create(null);
|
||||
this._activeDcsHandler = null;
|
||||
this._oscParser = new OscParser();
|
||||
this._dcsParser = new DcsParser();
|
||||
this._errorHandler = this._errorHandlerFb;
|
||||
|
||||
// swallow 7bit ST (ESC+\)
|
||||
this.setEscHandler('\\', () => {});
|
||||
this.setEscHandler({final: '\\'}, () => {});
|
||||
}
|
||||
|
||||
private _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
|
||||
let res = 0;
|
||||
if (id.prefix) {
|
||||
if (id.prefix.length > 1) {
|
||||
throw new Error('only one byte as prefix supported');
|
||||
}
|
||||
res = id.prefix.charCodeAt(0);
|
||||
if (res && 0x3c > res || res > 0x3f) {
|
||||
throw new Error('prefix must be in range 0x3c .. 0x3f');
|
||||
}
|
||||
}
|
||||
if (id.intermediates) {
|
||||
if (id.intermediates.length > 2) {
|
||||
throw new Error('only two bytes as intermediates are supported');
|
||||
}
|
||||
for (let i = 0; i < id.intermediates.length; ++i) {
|
||||
const intermediate = id.intermediates.charCodeAt(i);
|
||||
if (0x20 > intermediate || intermediate > 0x2f) {
|
||||
throw new Error('intermediate must be in range 0x20 .. 0x2f');
|
||||
}
|
||||
res <<= 8;
|
||||
res |= intermediate;
|
||||
}
|
||||
}
|
||||
if (id.final.length !== 1) {
|
||||
throw new Error('final must be a single byte');
|
||||
}
|
||||
const finalCode = id.final.charCodeAt(0);
|
||||
if (finalRange[0] > finalCode || finalCode > finalRange[1]) {
|
||||
throw new Error(`final must be in range ${finalRange[0]} .. ${finalRange[1]}`);
|
||||
}
|
||||
res <<= 8;
|
||||
res |= finalCode;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public identToString(ident: number): string {
|
||||
const res: string[] = [];
|
||||
while (ident) {
|
||||
res.push(String.fromCharCode(ident & 0xFF));
|
||||
ident >>= 8;
|
||||
}
|
||||
return res.reverse().join('');
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._executeHandlers = null;
|
||||
this._escHandlers = null;
|
||||
this._dcsHandlers = null;
|
||||
this._activeDcsHandler = null;
|
||||
this._csiHandlers = Object.create(null);
|
||||
this._executeHandlers = Object.create(null);
|
||||
this._escHandlers = Object.create(null);
|
||||
this._oscParser.dispose();
|
||||
this._dcsParser.dispose();
|
||||
}
|
||||
|
||||
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
|
||||
this._printHandler = callback;
|
||||
public setPrintHandler(handler: PrintHandlerType): void {
|
||||
this._printHandler = handler;
|
||||
}
|
||||
clearPrintHandler(): void {
|
||||
public clearPrintHandler(): void {
|
||||
this._printHandler = this._printHandlerFb;
|
||||
}
|
||||
|
||||
setExecuteHandler(flag: string, callback: () => void): void {
|
||||
this._executeHandlers[flag.charCodeAt(0)] = callback;
|
||||
}
|
||||
clearExecuteHandler(flag: string): void {
|
||||
if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
|
||||
}
|
||||
setExecuteHandlerFallback(callback: (code: number) => void): void {
|
||||
this._executeHandlerFb = callback;
|
||||
}
|
||||
|
||||
addCsiHandler(flag: string, callback: CsiHandler): IDisposable {
|
||||
const index = flag.charCodeAt(0);
|
||||
if (this._csiHandlers[index] === undefined) {
|
||||
this._csiHandlers[index] = [];
|
||||
public addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable {
|
||||
const ident = this._identifier(id, [0x30, 0x7e]);
|
||||
if (this._escHandlers[ident] === undefined) {
|
||||
this._escHandlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._csiHandlers[index];
|
||||
handlerList.push(callback);
|
||||
const handlerList = this._escHandlers[ident];
|
||||
handlerList.push(handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(callback);
|
||||
const handlerIndex = handlerList.indexOf(handler);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void {
|
||||
this._csiHandlers[flag.charCodeAt(0)] = [callback];
|
||||
public setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void {
|
||||
this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler];
|
||||
}
|
||||
clearCsiHandler(flag: string): void {
|
||||
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
|
||||
public clearEscHandler(id: IFunctionIdentifier): void {
|
||||
if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
|
||||
}
|
||||
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void {
|
||||
public setEscHandlerFallback(handler: EscFallbackHandlerType): void {
|
||||
this._escHandlerFb = handler;
|
||||
}
|
||||
|
||||
public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {
|
||||
this._executeHandlers[flag.charCodeAt(0)] = handler;
|
||||
}
|
||||
public clearExecuteHandler(flag: string): void {
|
||||
if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
|
||||
}
|
||||
public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {
|
||||
this._executeHandlerFb = handler;
|
||||
}
|
||||
|
||||
public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {
|
||||
const ident = this._identifier(id);
|
||||
if (this._csiHandlers[ident] === undefined) {
|
||||
this._csiHandlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._csiHandlers[ident];
|
||||
handlerList.push(handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(handler);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void {
|
||||
this._csiHandlers[this._identifier(id)] = [handler];
|
||||
}
|
||||
public clearCsiHandler(id: IFunctionIdentifier): void {
|
||||
if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
|
||||
}
|
||||
public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
|
||||
this._csiHandlerFb = callback;
|
||||
}
|
||||
|
||||
setEscHandler(collectAndFlag: string, callback: () => void): void {
|
||||
this._escHandlers[collectAndFlag] = callback;
|
||||
public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
|
||||
return this._dcsParser.addHandler(this._identifier(id), handler);
|
||||
}
|
||||
clearEscHandler(collectAndFlag: string): void {
|
||||
if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag];
|
||||
public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
|
||||
this._dcsParser.setHandler(this._identifier(id), handler);
|
||||
}
|
||||
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void {
|
||||
this._escHandlerFb = callback;
|
||||
public clearDcsHandler(id: IFunctionIdentifier): void {
|
||||
this._dcsParser.clearHandler(this._identifier(id));
|
||||
}
|
||||
public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {
|
||||
this._dcsParser.setHandlerFallback(handler);
|
||||
}
|
||||
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
if (this._oscHandlers[ident] === undefined) {
|
||||
this._oscHandlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._oscHandlers[ident];
|
||||
handlerList.push(callback);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(callback);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
public addOscHandler(ident: number, handler: IOscHandler): IDisposable {
|
||||
return this._oscParser.addHandler(ident, handler);
|
||||
}
|
||||
setOscHandler(ident: number, callback: (data: string) => void): void {
|
||||
this._oscHandlers[ident] = [callback];
|
||||
public setOscHandler(ident: number, handler: IOscHandler): void {
|
||||
this._oscParser.setHandler(ident, handler);
|
||||
}
|
||||
clearOscHandler(ident: number): void {
|
||||
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
|
||||
public clearOscHandler(ident: number): void {
|
||||
this._oscParser.clearHandler(ident);
|
||||
}
|
||||
setOscHandlerFallback(callback: (identifier: number, data: string) => void): void {
|
||||
this._oscHandlerFb = callback;
|
||||
public setOscHandlerFallback(handler: OscFallbackHandlerType): void {
|
||||
this._oscParser.setHandlerFallback(handler);
|
||||
}
|
||||
|
||||
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
|
||||
this._dcsHandlers[collectAndFlag] = handler;
|
||||
}
|
||||
clearDcsHandler(collectAndFlag: string): void {
|
||||
if (this._dcsHandlers[collectAndFlag]) delete this._dcsHandlers[collectAndFlag];
|
||||
}
|
||||
setDcsHandlerFallback(handler: IDcsHandler): void {
|
||||
this._dcsHandlerFb = handler;
|
||||
}
|
||||
|
||||
setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
|
||||
public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
|
||||
this._errorHandler = callback;
|
||||
}
|
||||
clearErrorHandler(): void {
|
||||
public clearErrorHandler(): void {
|
||||
this._errorHandler = this._errorHandlerFb;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
public reset(): void {
|
||||
this.currentState = this.initialState;
|
||||
this._osc = '';
|
||||
this._oscParser.reset();
|
||||
this._dcsParser.reset();
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = '';
|
||||
this._activeDcsHandler = null;
|
||||
this._collect = 0;
|
||||
this.precedingCodepoint = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parse UTF32 codepoints in `data` up to `length`.
|
||||
*
|
||||
@@ -421,16 +463,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
* - OSC_STRING:OSC_PUT
|
||||
* - DCS_PASSTHROUGH:DCS_PUT
|
||||
*/
|
||||
parse(data: Uint32Array, length: number): void {
|
||||
public parse(data: Uint32Array, length: number): void {
|
||||
let code = 0;
|
||||
let transition = 0;
|
||||
let currentState = this.currentState;
|
||||
let osc = this._osc;
|
||||
const osc = this._oscParser;
|
||||
const dcs = this._dcsParser;
|
||||
let collect = this._collect;
|
||||
const params = this._params;
|
||||
const table: Uint8Array = this.TRANSITIONS.table;
|
||||
let dcsHandler: IDcsHandler | null = this._activeDcsHandler;
|
||||
let callback: Function | null = null;
|
||||
|
||||
// process input string
|
||||
for (let i = 0; i < length; ++i) {
|
||||
@@ -466,8 +507,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
break;
|
||||
case ParserAction.EXECUTE:
|
||||
callback = this._executeHandlers[code];
|
||||
if (callback) callback();
|
||||
if (this._executeHandlers[code]) this._executeHandlers[code]();
|
||||
else this._executeHandlerFb(code);
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
@@ -479,7 +519,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
position: i,
|
||||
code,
|
||||
currentState,
|
||||
osc,
|
||||
collect,
|
||||
params,
|
||||
abort: false
|
||||
@@ -489,16 +528,16 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
break;
|
||||
case ParserAction.CSI_DISPATCH:
|
||||
// Trigger CSI Handler
|
||||
const handlers = this._csiHandlers[code];
|
||||
const handlers = this._csiHandlers[collect << 8 | code];
|
||||
let j = handlers ? handlers.length - 1 : -1;
|
||||
for (; j >= 0; j--) {
|
||||
// undefined or true means success and to stop bubbling
|
||||
if (handlers[j](params, collect) !== false) {
|
||||
if (handlers[j](params) !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < 0) {
|
||||
this._csiHandlerFb(collect, params, code);
|
||||
this._csiHandlerFb(collect << 8 | code, params);
|
||||
}
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
@@ -523,108 +562,76 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
i--;
|
||||
break;
|
||||
case ParserAction.COLLECT:
|
||||
collect += String.fromCharCode(code);
|
||||
collect |= code;
|
||||
break;
|
||||
case ParserAction.ESC_DISPATCH:
|
||||
callback = this._escHandlers[collect + String.fromCharCode(code)];
|
||||
if (callback) callback(collect, code);
|
||||
else this._escHandlerFb(collect, code);
|
||||
const handlersEsc = this._escHandlers[collect << 8 | code];
|
||||
let jj = handlersEsc ? handlersEsc.length - 1 : -1;
|
||||
for (; jj >= 0; jj--) {
|
||||
// undefined or true means success and to stop bubbling
|
||||
if (handlersEsc[jj]() !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (jj < 0) {
|
||||
this._escHandlerFb(collect << 8 | code);
|
||||
}
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.CLEAR:
|
||||
osc = '';
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
collect = 0;
|
||||
break;
|
||||
case ParserAction.DCS_HOOK:
|
||||
dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)];
|
||||
if (!dcsHandler) dcsHandler = this._dcsHandlerFb;
|
||||
dcsHandler.hook(collect, params, code);
|
||||
dcs.hook(collect << 8 | code, params);
|
||||
break;
|
||||
case ParserAction.DCS_PUT:
|
||||
// inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
|
||||
// unhook triggered by: 0x1b, 0x9c
|
||||
// unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort)
|
||||
for (let j = i + 1; ; ++j) {
|
||||
if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
|
||||
if (dcsHandler) {
|
||||
dcsHandler.put(data, i, j);
|
||||
}
|
||||
dcs.put(data, i, j);
|
||||
i = j - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ParserAction.DCS_UNHOOK:
|
||||
if (dcsHandler) {
|
||||
dcsHandler.unhook();
|
||||
dcsHandler = null;
|
||||
}
|
||||
dcs.unhook(code !== 0x18 && code !== 0x1a);
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
osc = '';
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
collect = 0;
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
case ParserAction.OSC_START:
|
||||
osc = '';
|
||||
osc.start();
|
||||
break;
|
||||
case ParserAction.OSC_PUT:
|
||||
// inner loop: 0x20 (SP) included, 0x7F (DEL) included
|
||||
for (let j = i + 1; ; j++) {
|
||||
if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code <= 0x9f)) {
|
||||
osc += utf32ToString(data, i, j);
|
||||
osc.put(data, i, j);
|
||||
i = j - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ParserAction.OSC_END:
|
||||
if (osc && code !== 0x18 && code !== 0x1a) {
|
||||
// NOTE: OSC subparsing is not part of the original parser
|
||||
// we do basic identifier parsing here to offer a jump table for OSC as well
|
||||
const idx = osc.indexOf(';');
|
||||
if (idx === -1) {
|
||||
this._oscHandlerFb(-1, osc); // this is an error (malformed OSC)
|
||||
} else {
|
||||
// Note: NaN is not handled here
|
||||
// either catch it with the fallback handler
|
||||
// or with an explicit NaN OSC handler
|
||||
const identifier = parseInt(osc.substring(0, idx));
|
||||
const content = osc.substring(idx + 1);
|
||||
// Trigger OSC Handler
|
||||
const handlers = this._oscHandlers[identifier];
|
||||
let j = handlers ? handlers.length - 1 : -1;
|
||||
for (; j >= 0; j--) {
|
||||
// undefined or true means success and to stop bubbling
|
||||
if (handlers[j](content) !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < 0) {
|
||||
this._oscHandlerFb(identifier, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
osc.end(code !== 0x18 && code !== 0x1a);
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
osc = '';
|
||||
params.reset();
|
||||
params.addParam(0); // ZDM
|
||||
collect = '';
|
||||
collect = 0;
|
||||
this.precedingCodepoint = 0;
|
||||
break;
|
||||
}
|
||||
currentState = transition & TableAccess.TRANSITION_STATE_MASK;
|
||||
}
|
||||
|
||||
// save non pushable buffers
|
||||
this._osc = osc;
|
||||
// save collected intermediates
|
||||
this._collect = collect;
|
||||
this._params = params;
|
||||
|
||||
// save active dcs handler reference
|
||||
this._activeDcsHandler = dcsHandler;
|
||||
|
||||
// save state
|
||||
this.currentState = currentState;
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { OscParser, OscHandler } from 'common/parser/OscParser';
|
||||
import { StringToUtf32, utf32ToString } from 'common/input/TextDecoder';
|
||||
import { IOscHandler } from 'common/parser/Types';
|
||||
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
|
||||
|
||||
function toUtf32(s: string): Uint32Array {
|
||||
const utf32 = new Uint32Array(s.length);
|
||||
const decoder = new StringToUtf32();
|
||||
const length = decoder.decode(s, utf32);
|
||||
return utf32.subarray(0, length);
|
||||
}
|
||||
|
||||
class TestHandler implements IOscHandler {
|
||||
constructor(public id: number, public output: any[], public msg: string, public returnFalse: boolean = false) {}
|
||||
start(): void {
|
||||
this.output.push([this.msg, this.id, 'START']);
|
||||
}
|
||||
put(data: Uint32Array, start: number, end: number): void {
|
||||
this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]);
|
||||
}
|
||||
end(success: boolean): void | boolean {
|
||||
this.output.push([this.msg, this.id, 'END', success]);
|
||||
if (this.returnFalse) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('OscParser', () => {
|
||||
let parser: OscParser;
|
||||
let reports: any[] = [];
|
||||
beforeEach(() => {
|
||||
reports = [];
|
||||
parser = new OscParser();
|
||||
parser.setHandlerFallback((id, action, data) => {
|
||||
reports.push([id, action, data]);
|
||||
});
|
||||
});
|
||||
describe('identifier parsing', () => {
|
||||
it('no report for illegal ids', () => {
|
||||
const data = toUtf32('hello world!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
it('no payload', () => {
|
||||
parser.start();
|
||||
let data = toUtf32('12');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('34');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [[1234, 'START', undefined], [1234, 'END', true]]);
|
||||
});
|
||||
it('with payload', () => {
|
||||
parser.start();
|
||||
let data = toUtf32('12');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('34');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(';h');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('ello');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [
|
||||
[1234, 'START', undefined],
|
||||
[1234, 'PUT', 'h'],
|
||||
[1234, 'PUT', 'ello'],
|
||||
[1234, 'END', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('handler registration', () => {
|
||||
it('setOscHandler', () => {
|
||||
parser.setHandler(1234, new TestHandler(1234, reports, 'th'));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['th', 1234, 'START'],
|
||||
['th', 1234, 'PUT', 'Here comes'],
|
||||
['th', 1234, 'PUT', 'the mouse!'],
|
||||
['th', 1234, 'END', true]
|
||||
]);
|
||||
});
|
||||
it('clearOscHandler', () => {
|
||||
parser.setHandler(1234, new TestHandler(1234, reports, 'th'));
|
||||
parser.clearHandler(1234);
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from fallback handler
|
||||
[1234, 'START', undefined],
|
||||
[1234, 'PUT', 'Here comes'],
|
||||
[1234, 'PUT', 'the mouse!'],
|
||||
[1234, 'END', true]
|
||||
]);
|
||||
});
|
||||
it('addOscHandler', () => {
|
||||
parser.setHandler(1234, new TestHandler(1234, reports, 'th1'));
|
||||
parser.addHandler(1234, new TestHandler(1234, reports, 'th2'));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [
|
||||
['th2', 1234, 'START'],
|
||||
['th1', 1234, 'START'],
|
||||
['th2', 1234, 'PUT', 'Here comes'],
|
||||
['th1', 1234, 'PUT', 'Here comes'],
|
||||
['th2', 1234, 'PUT', 'the mouse!'],
|
||||
['th1', 1234, 'PUT', 'the mouse!'],
|
||||
['th2', 1234, 'END', true],
|
||||
['th1', 1234, 'END', false] // false due being already handled by th2!
|
||||
]);
|
||||
});
|
||||
it('addOscHandler with return false', () => {
|
||||
parser.setHandler(1234, new TestHandler(1234, reports, 'th1'));
|
||||
parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [
|
||||
['th2', 1234, 'START'],
|
||||
['th1', 1234, 'START'],
|
||||
['th2', 1234, 'PUT', 'Here comes'],
|
||||
['th1', 1234, 'PUT', 'Here comes'],
|
||||
['th2', 1234, 'PUT', 'the mouse!'],
|
||||
['th1', 1234, 'PUT', 'the mouse!'],
|
||||
['th2', 1234, 'END', true],
|
||||
['th1', 1234, 'END', true] // true since th2 indicated to keep bubbling
|
||||
]);
|
||||
});
|
||||
it('dispose handlers', () => {
|
||||
parser.setHandler(1234, new TestHandler(1234, reports, 'th1'));
|
||||
const dispo = parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true));
|
||||
dispo.dispose();
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [
|
||||
['th1', 1234, 'START'],
|
||||
['th1', 1234, 'PUT', 'Here comes'],
|
||||
['th1', 1234, 'PUT', 'the mouse!'],
|
||||
['th1', 1234, 'END', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('OscHandlerFactory', () => {
|
||||
it('should be called once on end(true)', () => {
|
||||
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]);
|
||||
});
|
||||
it('should not be called on end(false)', () => {
|
||||
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(false);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
it('should be disposable', () => {
|
||||
parser.setHandler(1234, new OscHandler(data => reports.push(['one', data])));
|
||||
const dispo = parser.addHandler(1234, new OscHandler(data => reports.push(['two', data])));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [['two', 'Here comes the mouse!']]);
|
||||
dispo.dispose();
|
||||
parser.start();
|
||||
data = toUtf32('1234;some other');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' data');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]);
|
||||
});
|
||||
it('should respect return false', () => {
|
||||
parser.setHandler(1234, new OscHandler(data => reports.push(['one', data])));
|
||||
parser.addHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; }));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'Here comes the mouse!']]);
|
||||
});
|
||||
it('should work up to payload limit', function(): void {
|
||||
this.timeout(10000);
|
||||
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('A'.repeat(1000));
|
||||
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
|
||||
parser.put(data, 0, data.length);
|
||||
}
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, [[1234, 'A'.repeat(PAYLOAD_LIMIT)]]);
|
||||
});
|
||||
it('should abort for payload limit +1', function(): void {
|
||||
this.timeout(10000);
|
||||
parser.setHandler(1234, new OscHandler(data => reports.push([1234, data])));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('A'.repeat(1000));
|
||||
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
|
||||
parser.put(data, 0, data.length);
|
||||
}
|
||||
data = toUtf32('A');
|
||||
parser.put(data, 0, data.length);
|
||||
parser.end(true);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser } from 'common/parser/Types';
|
||||
import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants';
|
||||
import { utf32ToString } from 'common/input/TextDecoder';
|
||||
import { IDisposable } from 'common/Types';
|
||||
|
||||
|
||||
export class OscParser implements IOscParser {
|
||||
private _state = OscState.START;
|
||||
private _id = -1;
|
||||
private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
|
||||
private _handlerFb: OscFallbackHandlerType = () => { };
|
||||
|
||||
public addHandler(ident: number, handler: IOscHandler): IDisposable {
|
||||
if (this._handlers[ident] === undefined) {
|
||||
this._handlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._handlers[ident];
|
||||
handlerList.push(handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(handler);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public setHandler(ident: number, handler: IOscHandler): void {
|
||||
this._handlers[ident] = [handler];
|
||||
}
|
||||
public clearHandler(ident: number): void {
|
||||
if (this._handlers[ident]) delete this._handlers[ident];
|
||||
}
|
||||
public setHandlerFallback(handler: OscFallbackHandlerType): void {
|
||||
this._handlerFb = handler;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._handlers = Object.create(null);
|
||||
this._handlerFb = () => {};
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
// cleanup handlers if payload was already sent
|
||||
if (this._state === OscState.PAYLOAD) {
|
||||
this.end(false);
|
||||
}
|
||||
this._id = -1;
|
||||
this._state = OscState.START;
|
||||
}
|
||||
|
||||
private _start(): void {
|
||||
const handlers = this._handlers[this._id];
|
||||
if (!handlers) {
|
||||
this._handlerFb(this._id, 'START');
|
||||
} else {
|
||||
for (let j = handlers.length - 1; j >= 0; j--) {
|
||||
handlers[j].start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _put(data: Uint32Array, start: number, end: number): void {
|
||||
const handlers = this._handlers[this._id];
|
||||
if (!handlers) {
|
||||
this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));
|
||||
} else {
|
||||
for (let j = handlers.length - 1; j >= 0; j--) {
|
||||
handlers[j].put(data, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _end(success: boolean): void {
|
||||
// other than the old code we always have to call .end
|
||||
// to keep the bubbling we use `success` to indicate
|
||||
// whether a handler should execute
|
||||
const handlers = this._handlers[this._id];
|
||||
if (!handlers) {
|
||||
this._handlerFb(this._id, 'END', success);
|
||||
} else {
|
||||
let j = handlers.length - 1;
|
||||
for (; j >= 0; j--) {
|
||||
if (handlers[j].end(success) !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j--;
|
||||
// cleanup left over handlers
|
||||
for (; j >= 0; j--) {
|
||||
handlers[j].end(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
// always reset leftover handlers
|
||||
this.reset();
|
||||
this._id = -1;
|
||||
this._state = OscState.ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put data to current OSC command.
|
||||
* Expects the identifier of the OSC command in the form
|
||||
* OSC id ; payload ST/BEL
|
||||
* Payload chunks are not further processed and get
|
||||
* directly passed to the handlers.
|
||||
*/
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
if (this._state === OscState.ABORT) {
|
||||
return;
|
||||
}
|
||||
if (this._state === OscState.ID) {
|
||||
while (start < end) {
|
||||
const code = data[start++];
|
||||
if (code === 0x3b) {
|
||||
this._state = OscState.PAYLOAD;
|
||||
this._start();
|
||||
break;
|
||||
}
|
||||
if (code < 0x30 || 0x39 < code) {
|
||||
this._state = OscState.ABORT;
|
||||
return;
|
||||
}
|
||||
if (this._id === -1) {
|
||||
this._id = 0;
|
||||
}
|
||||
this._id = this._id * 10 + code - 48;
|
||||
}
|
||||
}
|
||||
if (this._state === OscState.PAYLOAD && end - start > 0) {
|
||||
this._put(data, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates end of an OSC command.
|
||||
* Whether the OSC got aborted or finished normally
|
||||
* is indicated by `success`.
|
||||
*/
|
||||
public end(success: boolean): void {
|
||||
if (this._state === OscState.START) {
|
||||
return;
|
||||
}
|
||||
// do nothing if command was faulty
|
||||
if (this._state !== OscState.ABORT) {
|
||||
// if we are still in ID state and get an early end
|
||||
// means that the command has no payload thus we still have
|
||||
// to announce START and send END right after
|
||||
if (this._state === OscState.ID) {
|
||||
this._start();
|
||||
}
|
||||
this._end(success);
|
||||
}
|
||||
this._id = -1;
|
||||
this._state = OscState.START;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient class to allow attaching string based handler functions
|
||||
* as OSC handlers.
|
||||
*/
|
||||
export class OscHandler implements IOscHandler {
|
||||
private _data = '';
|
||||
private _hitLimit: boolean = false;
|
||||
|
||||
constructor(private _handler: (data: string) => any) {}
|
||||
|
||||
public start(): void {
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
}
|
||||
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
if (this._hitLimit) {
|
||||
return;
|
||||
}
|
||||
this._data += utf32ToString(data, start, end);
|
||||
if (this._data.length > PAYLOAD_LIMIT) {
|
||||
this._data = '';
|
||||
this._hitLimit = true;
|
||||
}
|
||||
}
|
||||
|
||||
public end(success: boolean): any {
|
||||
let ret;
|
||||
if (this._hitLimit) {
|
||||
ret = false;
|
||||
} else if (success) {
|
||||
ret = this._handler(this._data);
|
||||
}
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
Vendored
+145
-43
@@ -53,10 +53,8 @@ export interface IParsingState {
|
||||
code: number;
|
||||
// current parser state
|
||||
currentState: ParserState;
|
||||
// osc string buffer
|
||||
osc: string;
|
||||
// collect buffer with intermediate characters
|
||||
collect: string;
|
||||
collect: number;
|
||||
// params buffer
|
||||
params: IParams;
|
||||
// should abort (default: false)
|
||||
@@ -64,31 +62,83 @@ export interface IParsingState {
|
||||
}
|
||||
|
||||
/**
|
||||
* DCS handler signature for EscapeSequenceParser.
|
||||
* EscapeSequenceParser handles DCS commands via separate
|
||||
* subparsers that get hook/unhooked and can handle
|
||||
* arbitrary amount of data.
|
||||
*
|
||||
* On entering a DSC sequence `hook` is called by
|
||||
* `EscapeSequenceParser`. Use it to initialize or reset
|
||||
* states needed to handle the current DCS sequence.
|
||||
* Note: A DCS parser is only instantiated once, therefore
|
||||
* you cannot rely on the ctor to reinitialize state.
|
||||
*
|
||||
* EscapeSequenceParser will call `put` several times if the
|
||||
* parsed data got split, therefore you might have to collect
|
||||
* `data` until `unhook` is called.
|
||||
* Note: `data` is borrowed, if you cannot process the data
|
||||
* in chunks you have to copy it, doing otherwise will lead to
|
||||
* data losses or corruption.
|
||||
*
|
||||
* `unhook` marks the end of the current DCS sequence.
|
||||
*/
|
||||
* Command handler interfaces.
|
||||
*/
|
||||
|
||||
/**
|
||||
* CSI handler types.
|
||||
* Note: `params` is borrowed.
|
||||
*/
|
||||
export type CsiHandlerType = (params: IParams) => boolean | void;
|
||||
export type CsiFallbackHandlerType = (ident: number, params: IParams) => void;
|
||||
|
||||
/**
|
||||
* DCS handler types.
|
||||
*/
|
||||
export interface IDcsHandler {
|
||||
hook(collect: string, params: IParams, flag: number): void;
|
||||
/**
|
||||
* Called when a DCS command starts.
|
||||
* Prepare needed data structures here.
|
||||
* Note: `params` is borrowed.
|
||||
*/
|
||||
hook(params: IParams): void;
|
||||
/**
|
||||
* Incoming payload chunk.
|
||||
* Note: `params` is borrowed.
|
||||
*/
|
||||
put(data: Uint32Array, start: number, end: number): void;
|
||||
unhook(): void;
|
||||
/**
|
||||
* End of DCS command. `success` indicates whether the
|
||||
* command finished normally or got aborted, thus final
|
||||
* execution of the command should depend on `success`.
|
||||
* To save memory also cleanup data structures here.
|
||||
*/
|
||||
unhook(success: boolean): void | boolean;
|
||||
}
|
||||
export type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
|
||||
|
||||
/**
|
||||
* ESC handler types.
|
||||
*/
|
||||
export type EscHandlerType = () => boolean | void;
|
||||
export type EscFallbackHandlerType = (identifier: number) => void;
|
||||
|
||||
/**
|
||||
* EXECUTE handler types.
|
||||
*/
|
||||
export type ExecuteHandlerType = () => boolean | void;
|
||||
export type ExecuteFallbackHandlerType = (ident: number) => void;
|
||||
|
||||
/**
|
||||
* OSC handler types.
|
||||
*/
|
||||
export interface IOscHandler {
|
||||
/**
|
||||
* Announces start of this OSC command.
|
||||
* Prepare needed data structures here.
|
||||
*/
|
||||
start(): void;
|
||||
/**
|
||||
* Incoming data chunk.
|
||||
* Note: Data is borrowed.
|
||||
*/
|
||||
put(data: Uint32Array, start: number, end: number): void;
|
||||
/**
|
||||
* End of OSC command. `success` indicates whether the
|
||||
* command finished normally or got aborted, thus final
|
||||
* execution of the command should depend on `success`.
|
||||
* To save memory also cleanup data structures here.
|
||||
*/
|
||||
end(success: boolean): void | boolean;
|
||||
}
|
||||
export type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
|
||||
|
||||
/**
|
||||
* PRINT handler types.
|
||||
*/
|
||||
export type PrintHandlerType = (data: Uint32Array, start: number, end: number) => void;
|
||||
export type PrintFallbackHandlerType = PrintHandlerType;
|
||||
|
||||
|
||||
/**
|
||||
* EscapeSequenceParser interface.
|
||||
@@ -112,31 +162,83 @@ export interface IEscapeSequenceParser extends IDisposable {
|
||||
*/
|
||||
parse(data: Uint32Array, length: number): void;
|
||||
|
||||
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void;
|
||||
/**
|
||||
* Get string from numercial function identifier `ident`.
|
||||
* Useful in fallback handlers which expose the low level
|
||||
* numcerical function identifier for debugging purposes.
|
||||
* Note: A full back translation to `IFunctionIdentifier`
|
||||
* is not implemented.
|
||||
*/
|
||||
identToString(ident: number): string;
|
||||
|
||||
setPrintHandler(handler: PrintHandlerType): void;
|
||||
clearPrintHandler(): void;
|
||||
|
||||
setExecuteHandler(flag: string, callback: () => void): void;
|
||||
setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void;
|
||||
clearEscHandler(id: IFunctionIdentifier): void;
|
||||
setEscHandlerFallback(handler: EscFallbackHandlerType): void;
|
||||
addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable;
|
||||
|
||||
setExecuteHandler(flag: string, handler: ExecuteHandlerType): void;
|
||||
clearExecuteHandler(flag: string): void;
|
||||
setExecuteHandlerFallback(callback: (code: number) => void): void;
|
||||
setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void;
|
||||
|
||||
setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void;
|
||||
clearCsiHandler(flag: string): void;
|
||||
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void;
|
||||
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void;
|
||||
clearCsiHandler(id: IFunctionIdentifier): void;
|
||||
setCsiHandlerFallback(callback: CsiFallbackHandlerType): void;
|
||||
addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable;
|
||||
|
||||
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
||||
clearEscHandler(collectAndFlag: string): void;
|
||||
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void;
|
||||
setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void;
|
||||
clearDcsHandler(id: IFunctionIdentifier): void;
|
||||
setDcsHandlerFallback(handler: DcsFallbackHandlerType): void;
|
||||
addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable;
|
||||
|
||||
setOscHandler(ident: number, callback: (data: string) => void): void;
|
||||
setOscHandler(ident: number, handler: IOscHandler): void;
|
||||
clearOscHandler(ident: number): void;
|
||||
setOscHandlerFallback(callback: (identifier: number, data: string) => void): void;
|
||||
setOscHandlerFallback(handler: OscFallbackHandlerType): void;
|
||||
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
|
||||
|
||||
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
|
||||
clearDcsHandler(collectAndFlag: string): void;
|
||||
setDcsHandlerFallback(handler: IDcsHandler): void;
|
||||
|
||||
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
|
||||
setErrorHandler(handler: (state: IParsingState) => IParsingState): void;
|
||||
clearErrorHandler(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subparser interfaces.
|
||||
* The subparsers are instantiated in `EscapeSequenceParser` and
|
||||
* called during `EscapeSequenceParser.parse`.
|
||||
*/
|
||||
export interface ISubParser<T, U> extends IDisposable {
|
||||
reset(): void;
|
||||
addHandler(ident: number, handler: T): IDisposable;
|
||||
setHandler(ident: number, handler: T): void;
|
||||
clearHandler(ident: number): void;
|
||||
setHandlerFallback(handler: U): void;
|
||||
put(data: Uint32Array, start: number, end: number): void;
|
||||
}
|
||||
|
||||
export interface IOscParser extends ISubParser<IOscHandler, OscFallbackHandlerType> {
|
||||
start(): void;
|
||||
end(success: boolean): void;
|
||||
}
|
||||
|
||||
export interface IDcsParser extends ISubParser<IDcsHandler, DcsFallbackHandlerType> {
|
||||
hook(ident: number, params: IParams): void;
|
||||
unhook(success: boolean): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to denote a specific ESC, CSI or DCS handler slot.
|
||||
* The values are used to create an integer respresentation during handler
|
||||
* regristation before passed to the subparsers as `ident`.
|
||||
* The integer translation is made to allow a faster handler access
|
||||
* in `EscapeSequenceParser.parse`.
|
||||
*/
|
||||
export interface IFunctionIdentifier {
|
||||
prefix?: string;
|
||||
intermediates?: string;
|
||||
final: string;
|
||||
}
|
||||
|
||||
export interface IHandlerCollection<T> {
|
||||
[key: string]: T[];
|
||||
}
|
||||
|
||||
+25
-7
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier } from 'xterm';
|
||||
import { ITerminal } from '../Types';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
@@ -16,6 +16,7 @@ import { IParams } from 'common/parser/Types';
|
||||
export class Terminal implements ITerminalApi {
|
||||
private _core: ITerminal;
|
||||
private _addonManager: AddonManager;
|
||||
private _parser: IParser;
|
||||
|
||||
constructor(options?: ITerminalOptions) {
|
||||
this._core = new TerminalCore(options);
|
||||
@@ -33,6 +34,12 @@ export class Terminal implements ITerminalApi {
|
||||
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
|
||||
|
||||
public get element(): HTMLElement { return this._core.element; }
|
||||
public get parser(): IParser {
|
||||
if (!this._parser) {
|
||||
this._parser = new ParserApi(this._core);
|
||||
}
|
||||
return this._parser;
|
||||
}
|
||||
public get textarea(): HTMLTextAreaElement { return this._core.textarea; }
|
||||
public get rows(): number { return this._core.rows; }
|
||||
public get cols(): number { return this._core.cols; }
|
||||
@@ -57,12 +64,6 @@ export class Terminal implements ITerminalApi {
|
||||
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
||||
}
|
||||
public addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable {
|
||||
return this._core.addCsiHandler(flag, (params: IParams, collect: string) => callback(params.toArray(), collect));
|
||||
}
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._core.addOscHandler(ident, callback);
|
||||
}
|
||||
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
|
||||
return this._core.registerLinkMatcher(regex, handler, options);
|
||||
}
|
||||
@@ -217,3 +218,20 @@ class BufferCellApiView implements IBufferCellApi {
|
||||
public get char(): string { return this._line.getString(this._x); }
|
||||
public get width(): number { return this._line.getWidth(this._x); }
|
||||
}
|
||||
|
||||
class ParserApi implements IParser {
|
||||
constructor(private _core: ITerminal) {}
|
||||
|
||||
public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
|
||||
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
|
||||
}
|
||||
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
|
||||
return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
|
||||
}
|
||||
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
|
||||
return this._core.addEscHandler(id, handler);
|
||||
}
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._core.addOscHandler(ident, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,23 +334,6 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addCsiHandler', () => {
|
||||
it('should call custom CSI handler with js array params', async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset();
|
||||
const _customCsiHandlerParams = [];
|
||||
const _customCsiHandler = window.term.addCsiHandler('m', (params, collect) => {
|
||||
_customCsiHandlerParams.push(params);
|
||||
return false;
|
||||
}, '');
|
||||
`);
|
||||
await page.evaluate(`
|
||||
window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
|
||||
`);
|
||||
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Parser Integration Tests', function(): void {
|
||||
this.timeout(20000);
|
||||
|
||||
before(async function(): Promise<any> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
slowMo: 80,
|
||||
args: [`--window-size=${width},${height}`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
browser.close();
|
||||
});
|
||||
|
||||
describe('addCsiHandler', () => {
|
||||
it('should call custom CSI handler with js array params', async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset();
|
||||
const _customCsiHandlerParams = [];
|
||||
const _customCsiHandler = window.term.parser.addCsiHandler({final: 'm'}, (params, collect) => {
|
||||
_customCsiHandlerParams.push(params);
|
||||
return false;
|
||||
}, '');
|
||||
`);
|
||||
await page.evaluate(`
|
||||
window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
|
||||
`);
|
||||
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
|
||||
});
|
||||
});
|
||||
describe('addDcsHandler', () => {
|
||||
it('should respects return value', async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset();
|
||||
const _customDcsHandlerCallStack = [];
|
||||
const _customDcsHandlerA = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
|
||||
_customDcsHandlerCallStack.push(['A', params, data]);
|
||||
return false;
|
||||
});
|
||||
const _customDcsHandlerB = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
|
||||
_customDcsHandlerCallStack.push(['B', params, data]);
|
||||
return true;
|
||||
});
|
||||
const _customDcsHandlerC = window.term.parser.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
|
||||
_customDcsHandlerCallStack.push(['C', params, data]);
|
||||
return false;
|
||||
});
|
||||
`);
|
||||
await page.evaluate(`
|
||||
window.term.write('\x1bP1;2+psome data\x1b\\\\');
|
||||
`);
|
||||
assert.deepEqual(await page.evaluate(`(() => _customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]);
|
||||
});
|
||||
});
|
||||
describe('addEscHandler', () => {
|
||||
it('should respects return value', async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset();
|
||||
const _customEscHandlerCallStack = [];
|
||||
const _customEscHandlerA = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => {
|
||||
_customEscHandlerCallStack.push('A');
|
||||
return false;
|
||||
});
|
||||
const _customEscHandlerB = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => {
|
||||
_customEscHandlerCallStack.push('B');
|
||||
return true;
|
||||
});
|
||||
const _customEscHandlerC = window.term.parser.addEscHandler({intermediates:'(', final: 'B'}, () => {
|
||||
_customEscHandlerCallStack.push('C');
|
||||
return false;
|
||||
});
|
||||
`);
|
||||
await page.evaluate(`
|
||||
window.term.write('\x1b(B');
|
||||
`);
|
||||
assert.deepEqual(await page.evaluate(`(() => _customEscHandlerCallStack)();`), ['C', 'B']);
|
||||
});
|
||||
});
|
||||
describe('addOscHandler', () => {
|
||||
it('should respects return value', async () => {
|
||||
await page.evaluate(`
|
||||
window.term.reset();
|
||||
const _customOscHandlerCallStack = [];
|
||||
const _customOscHandlerA = window.term.parser.addOscHandler(1234, data => {
|
||||
_customOscHandlerCallStack.push(['A', data]);
|
||||
return false;
|
||||
});
|
||||
const _customOscHandlerB = window.term.parser.addOscHandler(1234, data => {
|
||||
_customOscHandlerCallStack.push(['B', data]);
|
||||
return true;
|
||||
});
|
||||
const _customOscHandlerC = window.term.parser.addOscHandler(1234, data => {
|
||||
_customOscHandlerCallStack.push(['C', data]);
|
||||
return false;
|
||||
});
|
||||
`);
|
||||
await page.evaluate(`
|
||||
window.term.write('\x1b]1234;some data\x07');
|
||||
`);
|
||||
assert.deepEqual(await page.evaluate(`(() => _customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-be
|
||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||
import { C0, C1 } from 'common/data/EscapeSequences';
|
||||
import { IDcsHandler, IParams } from 'common/parser/Types';
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
|
||||
|
||||
function toUtf32(s: string): Uint32Array {
|
||||
@@ -18,7 +19,7 @@ function toUtf32(s: string): Uint32Array {
|
||||
}
|
||||
|
||||
class DcsHandler implements IDcsHandler {
|
||||
hook(collect: string, params: IParams, flag: number) : void {}
|
||||
hook(params: IParams) : void {}
|
||||
put(data: Uint32Array, start: number, end: number) : void {}
|
||||
unhook() :void {}
|
||||
}
|
||||
@@ -31,42 +32,42 @@ perfContext('Parser throughput - 50MB data', () => {
|
||||
beforeEach(() => {
|
||||
parser = new EscapeSequenceParser();
|
||||
parser.setPrintHandler((data, start, end) => {});
|
||||
parser.setCsiHandler('@', (params, collect) => {});
|
||||
parser.setCsiHandler('A', (params, collect) => {});
|
||||
parser.setCsiHandler('B', (params, collect) => {});
|
||||
parser.setCsiHandler('C', (params, collect) => {});
|
||||
parser.setCsiHandler('D', (params, collect) => {});
|
||||
parser.setCsiHandler('E', (params, collect) => {});
|
||||
parser.setCsiHandler('F', (params, collect) => {});
|
||||
parser.setCsiHandler('G', (params, collect) => {});
|
||||
parser.setCsiHandler('H', (params, collect) => {});
|
||||
parser.setCsiHandler('I', (params, collect) => {});
|
||||
parser.setCsiHandler('J', (params, collect) => {});
|
||||
parser.setCsiHandler('K', (params, collect) => {});
|
||||
parser.setCsiHandler('L', (params, collect) => {});
|
||||
parser.setCsiHandler('M', (params, collect) => {});
|
||||
parser.setCsiHandler('P', (params, collect) => {});
|
||||
parser.setCsiHandler('S', (params, collect) => {});
|
||||
parser.setCsiHandler('T', (params, collect) => {});
|
||||
parser.setCsiHandler('X', (params, collect) => {});
|
||||
parser.setCsiHandler('Z', (params, collect) => {});
|
||||
parser.setCsiHandler('`', (params, collect) => {});
|
||||
parser.setCsiHandler('a', (params, collect) => {});
|
||||
parser.setCsiHandler('b', (params, collect) => {});
|
||||
parser.setCsiHandler('c', (params, collect) => {});
|
||||
parser.setCsiHandler('d', (params, collect) => {});
|
||||
parser.setCsiHandler('e', (params, collect) => {});
|
||||
parser.setCsiHandler('f', (params, collect) => {});
|
||||
parser.setCsiHandler('g', (params, collect) => {});
|
||||
parser.setCsiHandler('h', (params, collect) => {});
|
||||
parser.setCsiHandler('l', (params, collect) => {});
|
||||
parser.setCsiHandler('m', (params, collect) => {});
|
||||
parser.setCsiHandler('n', (params, collect) => {});
|
||||
parser.setCsiHandler('p', (params, collect) => {});
|
||||
parser.setCsiHandler('q', (params, collect) => {});
|
||||
parser.setCsiHandler('r', (params, collect) => {});
|
||||
parser.setCsiHandler('s', (params, collect) => {});
|
||||
parser.setCsiHandler('u', (params, collect) => {});
|
||||
parser.setCsiHandler({final: '@'}, params => {});
|
||||
parser.setCsiHandler({final: 'A'}, params => {});
|
||||
parser.setCsiHandler({final: 'B'}, params => {});
|
||||
parser.setCsiHandler({final: 'C'}, params => {});
|
||||
parser.setCsiHandler({final: 'D'}, params => {});
|
||||
parser.setCsiHandler({final: 'E'}, params => {});
|
||||
parser.setCsiHandler({final: 'F'}, params => {});
|
||||
parser.setCsiHandler({final: 'G'}, params => {});
|
||||
parser.setCsiHandler({final: 'H'}, params => {});
|
||||
parser.setCsiHandler({final: 'I'}, params => {});
|
||||
parser.setCsiHandler({final: 'J'}, params => {});
|
||||
parser.setCsiHandler({final: 'K'}, params => {});
|
||||
parser.setCsiHandler({final: 'L'}, params => {});
|
||||
parser.setCsiHandler({final: 'M'}, params => {});
|
||||
parser.setCsiHandler({final: 'P'}, params => {});
|
||||
parser.setCsiHandler({final: 'S'}, params => {});
|
||||
parser.setCsiHandler({final: 'T'}, params => {});
|
||||
parser.setCsiHandler({final: 'X'}, params => {});
|
||||
parser.setCsiHandler({final: 'Z'}, params => {});
|
||||
parser.setCsiHandler({final: '`'}, params => {});
|
||||
parser.setCsiHandler({final: 'a'}, params => {});
|
||||
parser.setCsiHandler({final: 'b'}, params => {});
|
||||
parser.setCsiHandler({final: 'c'}, params => {});
|
||||
parser.setCsiHandler({final: 'd'}, params => {});
|
||||
parser.setCsiHandler({final: 'e'}, params => {});
|
||||
parser.setCsiHandler({final: 'f'}, params => {});
|
||||
parser.setCsiHandler({final: 'g'}, params => {});
|
||||
parser.setCsiHandler({final: 'h'}, params => {});
|
||||
parser.setCsiHandler({final: 'l'}, params => {});
|
||||
parser.setCsiHandler({final: 'm'}, params => {});
|
||||
parser.setCsiHandler({final: 'n'}, params => {});
|
||||
parser.setCsiHandler({final: 'p'}, params => {});
|
||||
parser.setCsiHandler({final: 'q'}, params => {});
|
||||
parser.setCsiHandler({final: 'r'}, params => {});
|
||||
parser.setCsiHandler({final: 's'}, params => {});
|
||||
parser.setCsiHandler({final: 'u'}, params => {});
|
||||
parser.setExecuteHandler(C0.BEL, () => {});
|
||||
parser.setExecuteHandler(C0.LF, () => {});
|
||||
parser.setExecuteHandler(C0.VT, () => {});
|
||||
@@ -79,25 +80,25 @@ perfContext('Parser throughput - 50MB data', () => {
|
||||
parser.setExecuteHandler(C1.IND, () => {});
|
||||
parser.setExecuteHandler(C1.NEL, () => {});
|
||||
parser.setExecuteHandler(C1.HTS, () => {});
|
||||
parser.setOscHandler(0, (data) => {});
|
||||
parser.setOscHandler(2, (data) => {});
|
||||
parser.setEscHandler('7', () => {});
|
||||
parser.setEscHandler('8', () => {});
|
||||
parser.setEscHandler('D', () => {});
|
||||
parser.setEscHandler('E', () => {});
|
||||
parser.setEscHandler('H', () => {});
|
||||
parser.setEscHandler('M', () => {});
|
||||
parser.setEscHandler('=', () => {});
|
||||
parser.setEscHandler('>', () => {});
|
||||
parser.setEscHandler('c', () => {});
|
||||
parser.setEscHandler('n', () => {});
|
||||
parser.setEscHandler('o', () => {});
|
||||
parser.setEscHandler('|', () => {});
|
||||
parser.setEscHandler('}', () => {});
|
||||
parser.setEscHandler('~', () => {});
|
||||
parser.setEscHandler('%@', () => {});
|
||||
parser.setEscHandler('%G', () => {});
|
||||
parser.setDcsHandler('q', new DcsHandler());
|
||||
parser.setOscHandler(0, new OscHandler((data) => {}));
|
||||
parser.setOscHandler(2, new OscHandler((data) => {}));
|
||||
parser.setEscHandler({final: '7'}, () => {});
|
||||
parser.setEscHandler({final: '8'}, () => {});
|
||||
parser.setEscHandler({final: 'D'}, () => {});
|
||||
parser.setEscHandler({final: 'E'}, () => {});
|
||||
parser.setEscHandler({final: 'H'}, () => {});
|
||||
parser.setEscHandler({final: 'M'}, () => {});
|
||||
parser.setEscHandler({final: '='}, () => {});
|
||||
parser.setEscHandler({final: '>'}, () => {});
|
||||
parser.setEscHandler({final: 'c'}, () => {});
|
||||
parser.setEscHandler({final: 'n'}, () => {});
|
||||
parser.setEscHandler({final: 'o'}, () => {});
|
||||
parser.setEscHandler({final: '|'}, () => {});
|
||||
parser.setEscHandler({final: '}'}, () => {});
|
||||
parser.setEscHandler({final: '~'}, () => {});
|
||||
parser.setEscHandler({intermediates: '%', final: '@'}, () => {});
|
||||
parser.setEscHandler({intermediates: '%', final: 'G'}, () => {});
|
||||
parser.setDcsHandler({final: 'q'}, new DcsHandler());
|
||||
});
|
||||
|
||||
perfContext('PRINT - a', () => {
|
||||
|
||||
Vendored
+141
-48
@@ -207,47 +207,47 @@ declare module 'xterm' {
|
||||
*/
|
||||
export interface ITheme {
|
||||
/** The default foreground color */
|
||||
foreground?: string,
|
||||
foreground?: string;
|
||||
/** The default background color */
|
||||
background?: string,
|
||||
background?: string;
|
||||
/** The cursor color */
|
||||
cursor?: string,
|
||||
cursor?: string;
|
||||
/** The accent color of the cursor (fg color for a block cursor) */
|
||||
cursorAccent?: string,
|
||||
cursorAccent?: string;
|
||||
/** The selection background color (can be transparent) */
|
||||
selection?: string,
|
||||
selection?: string;
|
||||
/** ANSI black (eg. `\x1b[30m`) */
|
||||
black?: string,
|
||||
black?: string;
|
||||
/** ANSI red (eg. `\x1b[31m`) */
|
||||
red?: string,
|
||||
red?: string;
|
||||
/** ANSI green (eg. `\x1b[32m`) */
|
||||
green?: string,
|
||||
green?: string;
|
||||
/** ANSI yellow (eg. `\x1b[33m`) */
|
||||
yellow?: string,
|
||||
yellow?: string;
|
||||
/** ANSI blue (eg. `\x1b[34m`) */
|
||||
blue?: string,
|
||||
blue?: string;
|
||||
/** ANSI magenta (eg. `\x1b[35m`) */
|
||||
magenta?: string,
|
||||
magenta?: string;
|
||||
/** ANSI cyan (eg. `\x1b[36m`) */
|
||||
cyan?: string,
|
||||
cyan?: string;
|
||||
/** ANSI white (eg. `\x1b[37m`) */
|
||||
white?: string,
|
||||
white?: string;
|
||||
/** ANSI bright black (eg. `\x1b[1;30m`) */
|
||||
brightBlack?: string,
|
||||
brightBlack?: string;
|
||||
/** ANSI bright red (eg. `\x1b[1;31m`) */
|
||||
brightRed?: string,
|
||||
brightRed?: string;
|
||||
/** ANSI bright green (eg. `\x1b[1;32m`) */
|
||||
brightGreen?: string,
|
||||
brightGreen?: string;
|
||||
/** ANSI bright yellow (eg. `\x1b[1;33m`) */
|
||||
brightYellow?: string,
|
||||
brightYellow?: string;
|
||||
/** ANSI bright blue (eg. `\x1b[1;34m`) */
|
||||
brightBlue?: string,
|
||||
brightBlue?: string;
|
||||
/** ANSI bright magenta (eg. `\x1b[1;35m`) */
|
||||
brightMagenta?: string,
|
||||
brightMagenta?: string;
|
||||
/** ANSI bright cyan (eg. `\x1b[1;36m`) */
|
||||
brightCyan?: string,
|
||||
brightCyan?: string;
|
||||
/** ANSI bright white (eg. `\x1b[1;37m`) */
|
||||
brightWhite?: string
|
||||
brightWhite?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,6 +386,12 @@ declare module 'xterm' {
|
||||
*/
|
||||
readonly markers: ReadonlyArray<IMarker>;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Get the parser interface to register
|
||||
* custom escape sequence handlers.
|
||||
*/
|
||||
readonly parser: IParser;
|
||||
|
||||
/**
|
||||
* Natural language strings that can be localized.
|
||||
*/
|
||||
@@ -500,32 +506,6 @@ declare module 'xterm' {
|
||||
*/
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a handler for CSI escape sequences.
|
||||
* @param flag The flag should be one-character string, which specifies the
|
||||
* final character (e.g "m" for SGR) of the CSI sequence.
|
||||
* @param callback The function to handle the escape sequence. The callback
|
||||
* is called with the numerical params, as well as the special characters
|
||||
* (e.g. "$" for DECSCPP). If the sequence has subparams the array will
|
||||
* contain subarrays with their numercial values.
|
||||
* Return true if the sequence was handled; false if
|
||||
* we should try a previous handler (set by addCsiHandler or setCsiHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a handler for OSC escape sequences.
|
||||
* @param ident The number (first parameter) of the sequence.
|
||||
* @param callback The function to handle the escape sequence. The callback
|
||||
* is called with OSC data string. Return true if the sequence was handled;
|
||||
* false if we should try a previous handler (set by addOscHandler or
|
||||
* setOscHandler). The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
|
||||
* be matched and handled.
|
||||
@@ -804,7 +784,7 @@ declare module 'xterm' {
|
||||
/**
|
||||
* Perform a full reset (RIS, aka '\x1bc').
|
||||
*/
|
||||
reset(): void
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* Loads an addon into this instance of xterm.js.
|
||||
@@ -943,4 +923,117 @@ declare module 'xterm' {
|
||||
*/
|
||||
readonly width: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Data type to register a CSI, DCS or ESC callback in the parser
|
||||
* in the form:
|
||||
* ESC I..I F
|
||||
* CSI Prefix P..P I..I F
|
||||
* DCS Prefix P..P I..I F data_bytes ST
|
||||
*
|
||||
* with these rules/restrictions:
|
||||
* - prefix can only be used with CSI and DCS
|
||||
* - only one leading prefix byte is recognized by the parser
|
||||
* before any other parameter bytes (P..P)
|
||||
* - intermediate bytes are recognized up to 2
|
||||
*
|
||||
* For custom sequences make sure to read ECMA-48 and the resources at
|
||||
* vt100.net to not clash with existing sequences or reserved address space.
|
||||
* General recommendations:
|
||||
* - use private address space (see ECMA-48)
|
||||
* - use max one intermediate byte (technically not limited by the spec,
|
||||
* in practice there are no sequences with more than one intermediate byte,
|
||||
* thus parsers might get confused with more intermediates)
|
||||
* - test against other common emulators to check whether they escape/ignore
|
||||
* the sequence correctly
|
||||
*
|
||||
* Notes: OSC command registration is handled differently (see addOscHandler)
|
||||
* APC, PM or SOS is currently not supported.
|
||||
*/
|
||||
export interface IFunctionIdentifier {
|
||||
/**
|
||||
* Optional prefix byte, must be in range \x3c .. \x3f.
|
||||
* Usable in CSI and DCS.
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* Optional intermediate bytes, must be in range \x20 .. \x2f.
|
||||
* Usable in CSI, DCS and ESC.
|
||||
*/
|
||||
intermediates?: string;
|
||||
/**
|
||||
* Final byte, must be in range \x40 .. \x7e for CSI and DCS,
|
||||
* \x30 .. \x7e for ESC.
|
||||
*/
|
||||
final: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Parser interface.
|
||||
*/
|
||||
export interface IParser {
|
||||
/**
|
||||
* Adds a handler for CSI escape sequences.
|
||||
* @param id Specifies the function identifier under which the callback
|
||||
* gets registered, e.g. {final: 'm'} for SGR.
|
||||
* @param callback The function to handle the sequence. The callback is
|
||||
* called with the numerical params. If the sequence has subparams the
|
||||
* array will contain subarrays with their numercial values.
|
||||
* Return true if the sequence was handled; false if we should try
|
||||
* a previous handler (set by addCsiHandler or setCsiHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* Adds a handler for DCS escape sequences.
|
||||
* @param id Specifies the function identifier under which the callback
|
||||
* gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
|
||||
* @param callback The function to handle the sequence. Note that the
|
||||
* function will only be called once if the sequence finished sucessfully.
|
||||
* There is currently no way to intercept smaller data chunks, data chunks
|
||||
* will be stored up until the sequence is finished. Since DCS sequences
|
||||
* are not limited by the amount of data this might impose a problem for
|
||||
* big payloads. Currently xterm.js limits DCS payload to 10 MB
|
||||
* which should give enough room for most use cases.
|
||||
* The function gets the payload and numerical parameters as arguments.
|
||||
* Return true if the sequence was handled; false if we should try
|
||||
* a previous handler (set by addDcsHandler or setDcsHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* Adds a handler for ESC escape sequences.
|
||||
* @param id Specifies the function identifier under which the callback
|
||||
* gets registered, e.g. {intermediates: '%' final: 'G'} for
|
||||
* default charset selection.
|
||||
* @param callback The function to handle the sequence.
|
||||
* Return true if the sequence was handled; false if we should try
|
||||
* a previous handler (set by addEscHandler or setEscHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* Adds a handler for OSC escape sequences.
|
||||
* @param ident The number (first parameter) of the sequence.
|
||||
* @param callback The function to handle the sequence. Note that the
|
||||
* function will only be called once if the sequence finished sucessfully.
|
||||
* There is currently no way to intercept smaller data chunks, data chunks
|
||||
* will be stored up until the sequence is finished. Since OSC sequences
|
||||
* are not limited by the amount of data this might impose a problem for
|
||||
* big payloads. Currently xterm.js limits OSC payload to 10 MB
|
||||
* which should give enough room for most use cases.
|
||||
* The callback is called with OSC data string.
|
||||
* Return true if the sequence was handled; false if we should try
|
||||
* a previous handler (set by addOscHandler or setOscHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user