support for multiple DCS handler

This commit is contained in:
Jörg Breitbart
2019-07-26 01:58:42 +02:00
parent f985c30847
commit 6606024c2b
6 changed files with 176 additions and 71 deletions
+12 -3
View File
@@ -57,7 +57,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) {
@@ -155,8 +159,13 @@ export class InputHandler extends Disposable implements IInputHandler {
});
this._parser.setOscHandlerFallback((identifier, action, data) => {
this._logService.debug('Unknown OSC code: ', { identifier, action, data });
}
);
});
this._parser.setDcsHandlerFallback((identifier, action, payload) => {
if (payload.params) {
payload.params = payload.params.toArray();
}
this._logService.debug('Unknown DCS code: ', { identifier, action, payload });
});
/**
* print handler
+110
View File
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from 'common/Types';
import { IDcsHandler, IParams, ParamsArray, IHandlerCollection, IDcsParser, DcsFallbackHandler } from 'common/parser/Types';
import { utf32ToString } from 'common/input/TextDecoder';
export class DcsParser implements IDcsParser {
private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
private _active: IDcsHandler[] = [];
private _collectAndFlag: string = '';
private _handlerFb: DcsFallbackHandler = () => {};
public dispose(): void {
this._handlers = Object.create(null);
this._handlerFb = () => {};
}
public addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable {
if (this._handlers[collectAndFlag] === undefined) {
this._handlers[collectAndFlag] = [];
}
const handlerList = this._handlers[collectAndFlag];
handlerList.push(handler);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(handler);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
}
public setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
this._handlers[collectAndFlag] = [handler];
}
public clearDcsHandler(collectAndFlag: string): void {
if (this._handlers[collectAndFlag]) delete this._handlers[collectAndFlag];
}
public setOscHandlerFallback(handler: DcsFallbackHandler): void {
this._handlerFb = handler;
}
public reset(): void {
if (this._active.length) {
this.unhook(false);
}
this._active = [];
this._collectAndFlag = '';
}
public hook(collect: string, params: IParams, flag: number): void {
this._collectAndFlag = collect + String.fromCharCode(flag);
this._active = this._handlers[this._collectAndFlag] || [];
if (!this._active.length) {
this._handlerFb(this._collectAndFlag, 'HOOK', {collect, params, flag});
} else {
for (let j = this._active.length - 1; j >= 0; j--) {
this._active[j].hook(collect, params, flag);
}
}
}
public put(data: Uint32Array, start: number, end: number): void {
if (!this._active.length) {
this._handlerFb(this._collectAndFlag, '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._collectAndFlag, '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);
}
}
}
}
export class DcsHandlerFactory implements IDcsHandler {
private _data = '';
private _params: IParams | undefined;
constructor(private _handler: (params: ParamsArray, data: string) => any) {}
public hook(collect: string, params: IParams, flag: number): void {
this._params = params.clone();
this._data = '';
}
public put(data: Uint32Array, start: number, end: number): void {
this._data += utf32ToString(data, start, end);
}
public unhook(success: boolean): any {
let ret;
if (success) {
ret = this._handler(this._params ? this._params.toArray() : [], this._data);
}
this._params = undefined;
this._data = '';
return ret;
}
}
+14 -24
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IDcsHandler, IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandler } from 'common/parser/Types';
import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandler } from 'common/parser/Types';
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
import * as chai from 'chai';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from 'common/input/TextDecoder';
@@ -77,9 +77,6 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
public set collect(value: string) {
this._collect = value;
}
public mockActiveDcsHandler(): void {
this._activeDcsHandler = this._dcsHandlerFb;
}
public mockOscParser(): void {
this._oscParser = oscPutParser;
}
@@ -116,11 +113,7 @@ const testTerminal: any = {
actionDCSHook: function (collect: string, params: IParams, flag: string): void {
this.calls.push(['dcs hook', collect, params.toArray(), flag]);
},
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 {
@@ -128,19 +121,6 @@ const testTerminal: any = {
}
};
// 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,
@@ -176,7 +156,18 @@ testParser.setOscHandlerFallback((identifier, action, data) => {
if (identifier === -1) testTerminal.actionOSC(data); // handle error condition silently
else if (action === 'END') testTerminal.actionOSC('' + identifier + ';' + data); // collect only data at END
});
testParser.setDcsHandlerFallback(new DcsTest());
testParser.setDcsHandlerFallback((collectAndFlag, action, payload) => {
switch (action) {
case 'HOOK':
testTerminal.actionDCSHook(payload.collect, payload.params, String.fromCharCode(payload.flag));
break;
case 'PUT':
testTerminal.actionDCSPrint(payload);
break;
case 'UNHOOK':
testTerminal.actionDCSUnhook();
}
});
// translate string based parse calls into typed array based
@@ -987,7 +978,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]]]);
+19 -39
View File
@@ -3,13 +3,14 @@
* @license MIT
*/
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler } from 'common/parser/Types';
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler, IDcsParser, DcsFallbackHandler } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
import { fill } from 'common/TypedArrayUtils';
import { Params } from 'common/parser/Params';
import { OscParser } from 'common/parser/OscParser';
import { DcsParser } from 'common/parser/DcsParser';
/**
* Table values are generated like this:
@@ -196,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.
@@ -242,8 +235,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _csiHandlers: IHandlerCollection<CsiHandler>;
protected _escHandlers: IHandlerCollection<EscHandler>;
protected _oscParser: IOscParser;
protected _dcsHandlers: any;
protected _activeDcsHandler: IDcsHandler;
protected _dcsParser: IDcsParser;
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
@@ -251,7 +243,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _executeHandlerFb: (code: number) => void;
protected _csiHandlerFb: (collect: string, params: IParams, flag: number) => void;
protected _escHandlerFb: (collect: string, flag: number) => void;
protected _dcsHandlerFb: IDcsHandler;
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
@@ -269,15 +260,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._executeHandlerFb = (code: number): void => { };
this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { };
this._escHandlerFb = (collect: string, flag: number): void => { };
this._dcsHandlerFb = new DcsDummy();
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._oscParser = new OscParser();
this._dcsHandlers = Object.create(null);
this._activeDcsHandler = this._dcsHandlerFb;
this._dcsParser = new DcsParser();
this._errorHandler = this._errorHandlerFb;
// swallow 7bit ST (ESC+\)
@@ -287,9 +276,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
public dispose(): void {
this._executeHandlers = null;
this._escHandlers = Object.create(null);
this._dcsHandlers = null;
this._activeDcsHandler = new DcsDummy();
this._oscParser.dispose();
this._dcsParser.dispose();
}
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
@@ -373,19 +361,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._oscParser.setOscHandlerFallback(handler);
}
addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable {
return this._dcsParser.addDcsHandler(collectAndFlag, handler);
}
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
this._dcsHandlers[collectAndFlag] = handler;
this._dcsParser.setDcsHandler(collectAndFlag, handler);
}
clearDcsHandler(collectAndFlag: string): void {
if (this._dcsHandlers[collectAndFlag]) delete this._dcsHandlers[collectAndFlag];
this._dcsParser.clearDcsHandler(collectAndFlag);
}
setDcsHandlerFallback(handler: IDcsHandler): void {
if (this._activeDcsHandler === this._dcsHandlerFb) {
this._dcsHandlerFb = handler;
this._activeDcsHandler = handler;
} else {
this._dcsHandlerFb = handler;
}
setDcsHandlerFallback(handler: DcsFallbackHandler): void {
this._dcsParser.setOscHandlerFallback(handler);
}
setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
@@ -398,10 +384,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
reset(): void {
this.currentState = this.initialState;
this._oscParser.reset();
this._dcsParser.reset();
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = '';
this._activeDcsHandler = this._dcsHandlerFb;
// this._activeDcsHandler = this._dcsHandlerFb;
this.precedingCodepoint = 0;
}
@@ -424,10 +411,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
let transition = 0;
let currentState = this.currentState;
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 = this._activeDcsHandler;
// let dcsHandler: IDcsHandler = this._activeDcsHandler;
let callback: Function | null = null;
// process input string
@@ -535,7 +523,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (jj < 0) {
this._escHandlerFb(collect, code);
}
this.precedingCodepoint = 0;
break;
case ParserAction.CLEAR:
@@ -545,24 +532,21 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
collect = '';
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, params, code);
break;
case ParserAction.DCS_PUT:
// inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
// unhook triggered by: 0x1b, 0x9c
for (let j = i + 1; ; ++j) {
if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
dcsHandler.put(data, i, j);
dcs.put(data, i, j);
i = j - 1;
break;
}
}
break;
case ParserAction.DCS_UNHOOK:
dcsHandler.unhook();
dcsHandler = this._dcsHandlerFb;
dcs.unhook(true); // FIXME: apply abort vs. success exit rules
if (code === 0x1b) transition |= ParserState.ESCAPE;
osc.reset();
params.reset();
@@ -598,10 +582,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// save non pushable buffers
this._collect = collect;
this._params = params;
// save active dcs handler reference
this._activeDcsHandler = dcsHandler;
// save state
this.currentState = currentState;
+2 -1
View File
@@ -42,7 +42,8 @@ export class OscParser extends Disposable {
}
public dispose(): void {
this._handlers = {};
this._handlers = Object.create(null);
this._handlerFb = () => {};
}
public reset(): void {
+19 -4
View File
@@ -89,15 +89,16 @@ export type EscHandler = () => boolean | void;
* 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.
* `unhook` marks the end of the current DCS sequence. `success`
* indicates whether the command was aborted.
*/
export interface IDcsHandler {
hook(collect: string, params: IParams, flag: number): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(): void;
unhook(success: boolean): void | boolean;
}
export type OscFallbackHandler = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
export type DcsFallbackHandler = (collectAndFlag: string, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
export interface IOscHandler {
/**
@@ -120,6 +121,8 @@ export interface IOscHandler {
end(success: boolean): void | boolean;
}
export type OscFallbackHandler = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
/**
* EscapeSequenceParser interface.
*/
@@ -166,7 +169,8 @@ export interface IEscapeSequenceParser extends IDisposable {
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
clearDcsHandler(collectAndFlag: string): void;
setDcsHandlerFallback(handler: IDcsHandler): void;
setDcsHandlerFallback(handler: DcsFallbackHandler): void;
addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable;
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
clearErrorHandler(): void;
@@ -182,3 +186,14 @@ export interface IOscParser extends IDisposable {
put(data: Uint32Array, start: number, end: number): void;
end(success: boolean): void;
}
export interface IDcsParser extends IDisposable {
addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable;
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
clearDcsHandler(collectAndFlag: string): void;
setOscHandlerFallback(handler: DcsFallbackHandler): void;
reset(): void;
hook(collect: string, params: IParams, flag: number): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(success: boolean): void;
}