add addEscHandler

This commit is contained in:
Jörg Breitbart
2019-07-25 22:44:08 +02:00
parent 1f4dde601e
commit f985c30847
10 changed files with 143 additions and 10 deletions
+7
View File
@@ -476,6 +476,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._dirtyRowService.markDirty(buffer.y);
}
/**
* Forward addEscHandler from parser.
*/
public addEscHandler(collectAndFlag: string, handler: () => boolean): IDisposable {
return this._parser.addEscHandler(collectAndFlag, handler);
}
/**
* Forward addCsiHandler from parser.
*/
+5
View File
@@ -1392,6 +1392,11 @@ 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(collectAndFlag: string, handler: () => boolean): IDisposable {
return this._inputHandler.addEscHandler(collectAndFlag, handler);
}
/** 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);
+5 -2
View File
@@ -75,10 +75,13 @@ export class MockTerminal implements ITerminal {
throw new Error('Method not implemented.');
}
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
throw new Error('Method not implemented.');
throw new Error('Method not implemented.');
}
addEscHandler(collectAndFlag: string, 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.');
+1
View File
@@ -201,6 +201,7 @@ export interface IPublicTerminal extends IDisposable {
open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
addEscHandler(collectAndFlag: string, handler: () => 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;
@@ -1199,6 +1199,57 @@ describe('EscapeSequenceParser', function (): void {
parse(parser2, INPUT);
chai.expect(esc).eql([]);
});
describe('ESC custom handlers', () => {
it('prevent fallback', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom - %G']);
});
it('allow fallback', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%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('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler('%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('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler('%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('%G', () => order.push(1));
parser2.addEscHandler('%G', () => { order.push(2); return false; });
parser2.addEscHandler('%G', () => { order.push(3); return false; });
parse(parser2, '\x1b%G');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
const dispo = parser2.addEscHandler('%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('%G', () => esc.push('default - %G'));
const dispo = parser2.addEscHandler('%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]);
+31 -7
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser } from 'common/parser/Types';
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
@@ -240,7 +240,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlers: any;
protected _csiHandlers: IHandlerCollection<CsiHandler>;
protected _escHandlers: any;
protected _escHandlers: IHandlerCollection<EscHandler>;
protected _oscParser: IOscParser;
protected _dcsHandlers: any;
protected _activeDcsHandler: IDcsHandler;
@@ -286,7 +286,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
public dispose(): void {
this._executeHandlers = null;
this._escHandlers = null;
this._escHandlers = Object.create(null);
this._dcsHandlers = null;
this._activeDcsHandler = new DcsDummy();
this._oscParser.dispose();
@@ -335,8 +335,23 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._csiHandlerFb = callback;
}
addEscHandler(collectAndFlag: string, callback: EscHandler): IDisposable {
if (this._escHandlers[collectAndFlag] === undefined) {
this._escHandlers[collectAndFlag] = [];
}
const handlerList = this._escHandlers[collectAndFlag];
handlerList.push(callback);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(callback);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
}
setEscHandler(collectAndFlag: string, callback: () => void): void {
this._escHandlers[collectAndFlag] = callback;
this._escHandlers[collectAndFlag] = [callback];
}
clearEscHandler(collectAndFlag: string): void {
if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag];
@@ -509,9 +524,18 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
collect += String.fromCharCode(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 + String.fromCharCode(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, code);
}
this.precedingCodepoint = 0;
break;
case ParserAction.CLEAR:
+3 -1
View File
@@ -68,6 +68,7 @@ export interface IHandlerCollection<T> {
}
export type CsiHandler = (params: IParams, collect: string) => boolean | void;
export type EscHandler = () => boolean | void;
/**
* DCS handler signature for EscapeSequenceParser.
@@ -152,15 +153,16 @@ export interface IEscapeSequenceParser extends IDisposable {
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, handler: IOscHandler): IDisposable;
setEscHandler(collectAndFlag: string, callback: () => void): void;
clearEscHandler(collectAndFlag: string): void;
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void;
addEscHandler(collectAndFlag: string, handler: EscHandler): IDisposable;
setOscHandler(ident: number, handler: IOscHandler): void;
clearOscHandler(ident: number): void;
setOscHandlerFallback(handler: OscFallbackHandler): void;
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
clearDcsHandler(collectAndFlag: string): void;
+3
View File
@@ -60,6 +60,9 @@ export class Terminal implements ITerminalApi {
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 addEscHandler(collectAndFlag: string, handler: () => boolean): IDisposable {
return this._core.addEscHandler(collectAndFlag, handler);
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._core.addOscHandler(ident, callback);
}
+24
View File
@@ -351,6 +351,30 @@ describe('InputHandler Integration Tests', function(): void {
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
});
});
describe('addEscHandler', () => {
it('should respects return value', async () => {
await page.evaluate(`
window.term.reset();
const _customEscHandlerCallStack = [];
const _customEscHandlerA = window.term.addEscHandler('(B', () => {
_customEscHandlerCallStack.push('A');
return false;
});
const _customEscHandlerB = window.term.addEscHandler('(B', () => {
_customEscHandlerCallStack.push('B');
return true;
});
const _customEscHandlerC = window.term.addEscHandler('(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(`
+13
View File
@@ -515,6 +515,19 @@ declare module 'xterm' {
*/
addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable;
/**
* Adds a handler for ESC escape sequences.
* @param flag The flag should be a string, which specifies the
* collect and the final character (e.g "%G" for default charset selection)
* of the ESC sequence.
* @param callback The function to handle the escape 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(collect: string, handler: () => boolean): IDisposable;
/**
* Adds a handler for OSC escape sequences.
* @param ident The number (first parameter) of the sequence.