diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index e960387f7..6835866e5 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -1220,26 +1220,26 @@ describe('EscapeSequenceParser', function (): void { }); describe('ESC custom handlers', () => { it('prevent fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G')); + 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.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.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.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); @@ -1247,21 +1247,21 @@ describe('EscapeSequenceParser', function (): void { }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => order.push(1)); + 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')); + 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')); + 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(); @@ -1284,7 +1284,7 @@ describe('EscapeSequenceParser', function (): void { describe('CSI custom handlers', () => { it('Prevent fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), ''])); + 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'); @@ -1292,7 +1292,7 @@ describe('EscapeSequenceParser', function (): void { }); it('Allow fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), ''])); + 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'); @@ -1301,7 +1301,7 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers fallback once', () => { const csiCustom: [string, ParamsArray, string][] = []; const csiCustom2: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), ''])); + 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); @@ -1312,7 +1312,7 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers no fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; const csiCustom2: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), ''])); + 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); @@ -1322,7 +1322,7 @@ describe('EscapeSequenceParser', function (): void { }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setCsiHandler({final: 'm'}, () => order.push(1)); + 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'); @@ -1330,7 +1330,7 @@ describe('EscapeSequenceParser', function (): void { }); it('Dispose should work', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), ''])); + 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); @@ -1339,7 +1339,7 @@ describe('EscapeSequenceParser', function (): void { }); it('Should not corrupt the parser when dispose is called twice', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), ''])); + 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(); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index 2411ebdb1..96392edaa 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler, IDcsParser, DcsFallbackHandler, IFunctionIdentifier } from 'common/parser/Types'; +import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler, IDcsParser, DcsFallbackHandler, IFunctionIdentifier, ExecuteFallbackHandler, CsiFallbackHandler, EscFallbackHandler, PrintHandler, PrintFallbackHandler, ExecuteHandler } from 'common/parser/Types'; import { ParserState, ParserAction } from 'common/parser/Constants'; import { Disposable } from 'common/Lifecycle'; import { IDisposable } from 'common/Types'; @@ -238,7 +238,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP protected _collect: number; // handler lookup containers - protected _printHandler: (data: Uint32Array, start: number, end: number) => void; + protected _printHandler: PrintHandler; protected _executeHandlers: any; protected _csiHandlers: IHandlerCollection; protected _escHandlers: IHandlerCollection; @@ -247,10 +247,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP protected _errorHandler: (state: IParsingState) => IParsingState; // fallback handlers - protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void; - protected _executeHandlerFb: (code: number) => void; - protected _csiHandlerFb: (ident: number, params: IParams) => void; - protected _escHandlerFb: (ident: number) => void; + protected _printHandlerFb: PrintFallbackHandler; + protected _executeHandlerFb: ExecuteFallbackHandler; + protected _csiHandlerFb: CsiFallbackHandler; + protected _escHandlerFb: EscFallbackHandler; protected _errorHandlerFb: (state: IParsingState) => IParsingState; constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) { @@ -334,41 +334,67 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._dcsParser.dispose(); } - public setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void { - this._printHandler = callback; + public setPrintHandler(handler: PrintHandler): void { + this._printHandler = handler; } public clearPrintHandler(): void { this._printHandler = this._printHandlerFb; } - public setExecuteHandler(flag: string, callback: () => void): void { - this._executeHandlers[flag.charCodeAt(0)] = callback; - } - public clearExecuteHandler(flag: string): void { - if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; - } - public setExecuteHandlerFallback(callback: (code: number) => void): void { - this._executeHandlerFb = callback; - } - - public addCsiHandler(id: IFunctionIdentifier, callback: CsiHandler): IDisposable { - const ident = this._identifier(id); - if (this._csiHandlers[ident] === undefined) { - this._csiHandlers[ident] = []; + public addEscHandler(id: IFunctionIdentifier, handler: EscHandler): IDisposable { + const ident = this._identifier(id, [0x30, 0x7e]); + if (this._escHandlers[ident] === undefined) { + this._escHandlers[ident] = []; } - const handlerList = this._csiHandlers[ident]; - 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); } } }; } - public setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void { - this._csiHandlers[this._identifier(id)] = [callback]; + public setEscHandler(id: IFunctionIdentifier, handler: EscHandler): void { + this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler]; + } + public clearEscHandler(id: IFunctionIdentifier): void { + if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])]; + } + public setEscHandlerFallback(handler: EscFallbackHandler): void { + this._escHandlerFb = handler; + } + + public setExecuteHandler(flag: string, handler: ExecuteHandler): 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: ExecuteFallbackHandler): void { + this._executeHandlerFb = handler; + } + + public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandler): 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: CsiHandler): void { + this._csiHandlers[this._identifier(id)] = [handler]; } public clearCsiHandler(id: IFunctionIdentifier): void { if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)]; @@ -377,45 +403,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._csiHandlerFb = callback; } - public addEscHandler(id: IFunctionIdentifier, callback: EscHandler): IDisposable { - const ident = this._identifier(id, [0x30, 0x7e]); - if (this._escHandlers[ident] === undefined) { - this._escHandlers[ident] = []; - } - const handlerList = this._escHandlers[ident]; - handlerList.push(callback); - return { - dispose: () => { - const handlerIndex = handlerList.indexOf(callback); - if (handlerIndex !== -1) { - handlerList.splice(handlerIndex, 1); - } - } - }; - } - public setEscHandler(id: IFunctionIdentifier, callback: () => void): void { - this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [callback]; - } - public clearEscHandler(id: IFunctionIdentifier): void { - if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])]; - } - public setEscHandlerFallback(callback: (ident: number) => void): void { - this._escHandlerFb = callback; - } - - public addOscHandler(ident: number, handler: IOscHandler): IDisposable { - return this._oscParser.addHandler(ident, handler); - } - public setOscHandler(ident: number, handler: IOscHandler): void { - this._oscParser.setHandler(ident, handler); - } - public clearOscHandler(ident: number): void { - this._oscParser.clearHandler(ident); - } - public setOscHandlerFallback(handler: OscFallbackHandler): void { - this._oscParser.setHandlerFallback(handler); - } - public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable { return this._dcsParser.addHandler(this._identifier(id), handler); } @@ -429,6 +416,19 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._dcsParser.setHandlerFallback(handler); } + public addOscHandler(ident: number, handler: IOscHandler): IDisposable { + return this._oscParser.addHandler(ident, handler); + } + public setOscHandler(ident: number, handler: IOscHandler): void { + this._oscParser.setHandler(ident, handler); + } + public clearOscHandler(ident: number): void { + this._oscParser.clearHandler(ident); + } + public setOscHandlerFallback(handler: OscFallbackHandler): void { + this._oscParser.setHandlerFallback(handler); + } + public setErrorHandler(callback: (state: IParsingState) => IParsingState): void { this._errorHandler = callback; } diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 1a43f773e..6a165bd13 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -61,66 +61,85 @@ export interface IParsingState { abort: boolean; } -export interface IHandlerCollection { - [key: string]: T[]; -} - -export type CsiHandler = (params: IParams) => boolean | void; -export type EscHandler = () => boolean | void; +/** + * Command handler interfaces. + */ /** -* 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. `success` -* indicates whether the command was aborted. -*/ + * DCS handler types. + */ export interface IDcsHandler { + /** + * 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; + /** + * 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 DcsFallbackHandler = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void; +/** + * ESC handler types. + */ +export type EscHandler = () => boolean | void; +export type EscFallbackHandler = (identifier: number) => void; + +/** + * EXECUTE handler types. + */ +export type ExecuteHandler = () => boolean | void; +export type ExecuteFallbackHandler = (ident: number) => void; + +/** + * CSI handler types. + * Note: `params` is borrowed. + */ +export type CsiHandler = (params: IParams) => boolean | void; +export type CsiFallbackHandler = (ident: number, params: IParams) => 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 execution - * of the command should depend on `success`. - * To save memory cleanup data structures in `.end`. + * 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 OscFallbackHandler = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void; +/** + * PRINT handler types. + */ +export type PrintHandler = (data: Uint32Array, start: number, end: number) => void; +export type PrintFallbackHandler = PrintHandler; + + /** * EscapeSequenceParser interface. */ @@ -144,38 +163,42 @@ export interface IEscapeSequenceParser extends IDisposable { parse(data: Uint32Array, length: number): void; /** - * Get string from ident number. + * 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(callback: (data: Uint32Array, start: number, end: number) => void): void; + setPrintHandler(handler: PrintHandler): void; clearPrintHandler(): void; - setExecuteHandler(flag: string, callback: () => void): void; - clearExecuteHandler(flag: string): void; - setExecuteHandlerFallback(callback: (code: number) => void): void; - - setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void; - clearCsiHandler(id: IFunctionIdentifier): void; - setCsiHandlerFallback(callback: (identifier: number, params: IParams) => void): void; - addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; - - setEscHandler(id: IFunctionIdentifier, callback: () => void): void; + setEscHandler(id: IFunctionIdentifier, handler: EscHandler): void; clearEscHandler(id: IFunctionIdentifier): void; - setEscHandlerFallback(callback: (identifier: number) => void): void; + setEscHandlerFallback(handler: EscFallbackHandler): void; addEscHandler(id: IFunctionIdentifier, handler: EscHandler): IDisposable; - setOscHandler(ident: number, handler: IOscHandler): void; - clearOscHandler(ident: number): void; - setOscHandlerFallback(handler: OscFallbackHandler): void; - addOscHandler(ident: number, handler: IOscHandler): IDisposable; + setExecuteHandler(flag: string, handler: ExecuteHandler): void; + clearExecuteHandler(flag: string): void; + setExecuteHandlerFallback(handler: ExecuteFallbackHandler): void; + + setCsiHandler(id: IFunctionIdentifier, handler: CsiHandler): void; + clearCsiHandler(id: IFunctionIdentifier): void; + setCsiHandlerFallback(callback: CsiFallbackHandler): void; + addCsiHandler(id: IFunctionIdentifier, handler: CsiHandler): IDisposable; setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void; clearDcsHandler(id: IFunctionIdentifier): void; setDcsHandlerFallback(handler: DcsFallbackHandler): void; addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable; - setErrorHandler(callback: (state: IParsingState) => IParsingState): void; + setOscHandler(ident: number, handler: IOscHandler): void; + clearOscHandler(ident: number): void; + setOscHandlerFallback(handler: OscFallbackHandler): void; + addOscHandler(ident: number, handler: IOscHandler): IDisposable; + + setErrorHandler(handler: (state: IParsingState) => IParsingState): void; clearErrorHandler(): void; } @@ -207,7 +230,7 @@ export interface IDcsParser extends ISubParser * 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 faster key-value access + * The integer translation is made to allow a faster handler access * in `EscapeSequenceParser.parse`. */ export interface IFunctionIdentifier { @@ -215,3 +238,7 @@ export interface IFunctionIdentifier { intermediates?: string; final: string; } + +export interface IHandlerCollection { + [key: string]: T[]; +}