fix OSC parsing
This commit is contained in:
+8
-6
@@ -21,6 +21,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
|
|||||||
import { IAttributeData, IDisposable } from 'common/Types';
|
import { IAttributeData, IDisposable } from 'common/Types';
|
||||||
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService } from 'common/services/Services';
|
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService } from 'common/services/Services';
|
||||||
import { ISelectionService } from 'browser/services/Services';
|
import { ISelectionService } from 'browser/services/Services';
|
||||||
|
import { OscHandlerFactory } from 'common/parser/OscParser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map collect to glevel. Used in `selectCharset`.
|
* Map collect to glevel. Used in `selectCharset`.
|
||||||
@@ -152,9 +153,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
this._parser.setExecuteHandlerFallback((code: number) => {
|
this._parser.setExecuteHandlerFallback((code: number) => {
|
||||||
this._logService.debug('Unknown EXECUTE code: ', { code });
|
this._logService.debug('Unknown EXECUTE code: ', { code });
|
||||||
});
|
});
|
||||||
this._parser.setOscHandlerFallback((identifier: number, data: string) => {
|
this._parser.setOscHandlerFallback((identifier, action, data) => {
|
||||||
this._logService.debug('Unknown OSC code: ', { identifier, data });
|
this._logService.debug('Unknown OSC code: ', { identifier, action, data });
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* print handler
|
* print handler
|
||||||
@@ -224,10 +226,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
* OSC handler
|
* OSC handler
|
||||||
*/
|
*/
|
||||||
// 0 - icon name + title
|
// 0 - icon name + title
|
||||||
this._parser.setOscHandler(0, (data) => this.setTitle(data));
|
this._parser.setOscHandler(0, new OscHandlerFactory((data: string) => this.setTitle(data)));
|
||||||
// 1 - icon name
|
// 1 - icon name
|
||||||
// 2 - title
|
// 2 - title
|
||||||
this._parser.setOscHandler(2, (data) => this.setTitle(data));
|
this._parser.setOscHandler(2, new OscHandlerFactory((data: string) => this.setTitle(data)));
|
||||||
// 3 - set property X in the form "prop=value"
|
// 3 - set property X in the form "prop=value"
|
||||||
// 4 - Change Color Number
|
// 4 - Change Color Number
|
||||||
// 5 - Change Special Color Number
|
// 5 - Change Special Color Number
|
||||||
@@ -485,7 +487,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
* Forward addOscHandler from parser.
|
* Forward addOscHandler from parser.
|
||||||
*/
|
*/
|
||||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||||
return this._parser.addOscHandler(ident, callback);
|
return this._parser.addOscHandler(ident, new OscHandlerFactory(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -43,3 +43,13 @@ export const enum ParserAction {
|
|||||||
DCS_PUT = 13,
|
DCS_PUT = 13,
|
||||||
DCS_UNHOOK = 14
|
DCS_UNHOOK = 14
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal states of OscParser.
|
||||||
|
*/
|
||||||
|
export const enum OscState {
|
||||||
|
START = 0,
|
||||||
|
ID = 1,
|
||||||
|
PAYLOAD = 2,
|
||||||
|
ABORT = 3
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDcsHandler, IParsingState, IParams, ParamsArray } from 'common/parser/Types';
|
import { IDcsHandler, IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandler } from 'common/parser/Types';
|
||||||
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
|
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
|
||||||
import * as chai from 'chai';
|
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 { ParserState } from 'common/parser/Constants';
|
||||||
import { Params } from 'common/parser/Params';
|
import { Params } from 'common/parser/Params';
|
||||||
|
import { OscHandlerFactory } from 'common/parser/OscParser';
|
||||||
|
import { IDisposable } from 'common/Types';
|
||||||
|
|
||||||
|
|
||||||
function r(a: number, b: number): string[] {
|
function r(a: number, b: number): string[] {
|
||||||
@@ -20,13 +22,45 @@ function r(a: number, b: number): string[] {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockOscPutParser implements IOscParser {
|
||||||
|
private _fallback: OscFallbackHandler = () => {};
|
||||||
|
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(): void {
|
||||||
|
const id = parseInt(this.data.slice(0, this.data.indexOf(';')));
|
||||||
|
if (!isNaN(id)) {
|
||||||
|
this._fallback(id, 'END', this.data.slice(this.data.indexOf(';') + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOscHandler(ident: number, handler: IOscHandler): IDisposable {
|
||||||
|
throw new Error('not implemented');
|
||||||
|
}
|
||||||
|
setOscHandler(ident: number, handler: IOscHandler): void {
|
||||||
|
throw new Error('not implemented');
|
||||||
|
}
|
||||||
|
clearOscHandler(ident: number): void {
|
||||||
|
throw new Error('not implemented');
|
||||||
|
}
|
||||||
|
setOscHandlerFallback(handler: OscFallbackHandler): void {
|
||||||
|
this._fallback = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const oscPutParser = new MockOscPutParser();
|
||||||
|
|
||||||
// derived parser with access to internal states
|
// derived parser with access to internal states
|
||||||
class TestEscapeSequenceParser extends EscapeSequenceParser {
|
class TestEscapeSequenceParser extends EscapeSequenceParser {
|
||||||
public get osc(): string {
|
public get osc(): string {
|
||||||
return this._osc;
|
return (this._oscParser as MockOscPutParser).data;
|
||||||
}
|
}
|
||||||
public set osc(value: string) {
|
public set osc(value: string) {
|
||||||
this._osc = value;
|
(this._oscParser as MockOscPutParser).data = value;
|
||||||
}
|
}
|
||||||
public get params(): ParamsArray {
|
public get params(): ParamsArray {
|
||||||
return this._params.toArray();
|
return this._params.toArray();
|
||||||
@@ -46,6 +80,9 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
|
|||||||
public mockActiveDcsHandler(): void {
|
public mockActiveDcsHandler(): void {
|
||||||
this._activeDcsHandler = this._dcsHandlerFb;
|
this._activeDcsHandler = this._dcsHandlerFb;
|
||||||
}
|
}
|
||||||
|
public mockOscParser(): void {
|
||||||
|
this._oscParser = oscPutParser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// test object to collect parser actions and compare them with expected values
|
// test object to collect parser actions and compare them with expected values
|
||||||
@@ -124,6 +161,7 @@ let state: any;
|
|||||||
|
|
||||||
// parser with Uint8Array based transition table
|
// parser with Uint8Array based transition table
|
||||||
const testParser = new TestEscapeSequenceParser();
|
const testParser = new TestEscapeSequenceParser();
|
||||||
|
testParser.mockOscParser();
|
||||||
testParser.setPrintHandler(testTerminal.print.bind(testTerminal));
|
testParser.setPrintHandler(testTerminal.print.bind(testTerminal));
|
||||||
testParser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
|
testParser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
|
||||||
testTerminal.actionCSI(collect, params, String.fromCharCode(flag));
|
testTerminal.actionCSI(collect, params, String.fromCharCode(flag));
|
||||||
@@ -134,9 +172,9 @@ testParser.setEscHandlerFallback((collect: string, flag: number) => {
|
|||||||
testParser.setExecuteHandlerFallback((code: number) => {
|
testParser.setExecuteHandlerFallback((code: number) => {
|
||||||
testTerminal.actionExecute(String.fromCharCode(code));
|
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
|
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(new DcsTest());
|
testParser.setDcsHandlerFallback(new DcsTest());
|
||||||
|
|
||||||
@@ -1026,9 +1064,9 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
], null);
|
], null);
|
||||||
});
|
});
|
||||||
it('print + OSC(C1) + print', function (): void {
|
it('print + OSC(C1) + print', function (): void {
|
||||||
test('abc\x9d123tzf\x9cdefg', [
|
test('abc\x9d123;tzf\x9cdefg', [
|
||||||
['print', 'abc'],
|
['print', 'abc'],
|
||||||
['osc', '123tzf'],
|
['osc', '123;tzf'],
|
||||||
['print', 'defg']
|
['print', 'defg']
|
||||||
], null);
|
], null);
|
||||||
});
|
});
|
||||||
@@ -1039,9 +1077,9 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
], null);
|
], null);
|
||||||
});
|
});
|
||||||
it('7bit ST should be swallowed', function (): void {
|
it('7bit ST should be swallowed', function (): void {
|
||||||
test('abc\x9d123tzf\x1b\\defg', [
|
test('abc\x9d123;tzf\x1b\\defg', [
|
||||||
['print', 'abc'],
|
['print', 'abc'],
|
||||||
['osc', '123tzf'],
|
['osc', '123;tzf'],
|
||||||
['print', 'defg']
|
['print', 'defg']
|
||||||
], null);
|
], null);
|
||||||
});
|
});
|
||||||
@@ -1256,9 +1294,9 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
chai.expect(exe).eql(['\n']);
|
chai.expect(exe).eql(['\n']);
|
||||||
});
|
});
|
||||||
it('OSC handler', function (): void {
|
it('OSC handler', function (): void {
|
||||||
parser2.setOscHandler(1, function (data: string): void {
|
parser2.setOscHandler(1, new OscHandlerFactory(function (data: string): void {
|
||||||
osc.push([1, data]);
|
osc.push([1, data]);
|
||||||
});
|
}));
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
chai.expect(osc).eql([[1, 'foo=bar']]);
|
||||||
parser2.clearOscHandler(1);
|
parser2.clearOscHandler(1);
|
||||||
@@ -1270,16 +1308,16 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
describe('OSC custom handlers', () => {
|
describe('OSC custom handlers', () => {
|
||||||
it('Prevent fallback', () => {
|
it('Prevent fallback', () => {
|
||||||
const oscCustom: [number, string][] = [];
|
const oscCustom: [number, string][] = [];
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||||
});
|
});
|
||||||
it('Allow fallback', () => {
|
it('Allow fallback', () => {
|
||||||
const oscCustom: [number, string][] = [];
|
const oscCustom: [number, string][] = [];
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return false; });
|
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return false; }));
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
|
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
|
||||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||||
@@ -1287,9 +1325,9 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
it('Multiple custom handlers fallback once', () => {
|
it('Multiple custom handlers fallback once', () => {
|
||||||
const oscCustom: [number, string][] = [];
|
const oscCustom: [number, string][] = [];
|
||||||
const oscCustom2: [number, string][] = [];
|
const oscCustom2: [number, string][] = [];
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
|
||||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return false; });
|
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom2.push([1, data]); return false; }));
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||||
@@ -1298,9 +1336,9 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
it('Multiple custom handlers no fallback', () => {
|
it('Multiple custom handlers no fallback', () => {
|
||||||
const oscCustom: [number, string][] = [];
|
const oscCustom: [number, string][] = [];
|
||||||
const oscCustom2: [number, string][] = [];
|
const oscCustom2: [number, string][] = [];
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
|
||||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
|
||||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return true; });
|
parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom2.push([1, data]); return true; }));
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||||
chai.expect(oscCustom).eql([], 'Should not fallback once');
|
chai.expect(oscCustom).eql([], 'Should not fallback once');
|
||||||
@@ -1308,16 +1346,16 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
});
|
});
|
||||||
it('Execution order should go from latest handler down to the original', () => {
|
it('Execution order should go from latest handler down to the original', () => {
|
||||||
const order: number[] = [];
|
const order: number[] = [];
|
||||||
parser2.setOscHandler(1, () => order.push(1));
|
parser2.setOscHandler(1, new OscHandlerFactory(() => order.push(1)));
|
||||||
parser2.addOscHandler(1, () => { order.push(2); return false; });
|
parser2.addOscHandler(1, new OscHandlerFactory(() => { order.push(2); return false; }));
|
||||||
parser2.addOscHandler(1, () => { order.push(3); return false; });
|
parser2.addOscHandler(1, new OscHandlerFactory(() => { order.push(3); return false; }));
|
||||||
parse(parser2, '\x1b]1;foo=bar\x1b\\');
|
parse(parser2, '\x1b]1;foo=bar\x1b\\');
|
||||||
chai.expect(order).eql([3, 2, 1]);
|
chai.expect(order).eql([3, 2, 1]);
|
||||||
});
|
});
|
||||||
it('Dispose should work', () => {
|
it('Dispose should work', () => {
|
||||||
const oscCustom: [number, string][] = [];
|
const oscCustom: [number, string][] = [];
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
|
||||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
const customHandler = parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
|
||||||
customHandler.dispose();
|
customHandler.dispose();
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
chai.expect(osc).eql([[1, 'foo=bar']]);
|
||||||
@@ -1325,8 +1363,8 @@ describe('EscapeSequenceParser', function (): void {
|
|||||||
});
|
});
|
||||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||||
const oscCustom: [number, string][] = [];
|
const oscCustom: [number, string][] = [];
|
||||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
parser2.setOscHandler(1, new OscHandlerFactory(data => osc.push([1, data])));
|
||||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
const customHandler = parser2.addOscHandler(1, new OscHandlerFactory(data => { oscCustom.push([1, data]); return true; }));
|
||||||
customHandler.dispose();
|
customHandler.dispose();
|
||||||
customHandler.dispose();
|
customHandler.dispose();
|
||||||
parse(parser2, INPUT);
|
parse(parser2, INPUT);
|
||||||
|
|||||||
@@ -3,20 +3,13 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types';
|
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser } from 'common/parser/Types';
|
||||||
import { ParserState, ParserAction } from 'common/parser/Constants';
|
import { ParserState, ParserAction } from 'common/parser/Constants';
|
||||||
import { Disposable } from 'common/Lifecycle';
|
import { Disposable } from 'common/Lifecycle';
|
||||||
import { utf32ToString } from 'common/input/TextDecoder';
|
|
||||||
import { IDisposable } from 'common/Types';
|
import { IDisposable } from 'common/Types';
|
||||||
import { fill } from 'common/TypedArrayUtils';
|
import { fill } from 'common/TypedArrayUtils';
|
||||||
import { Params } from 'common/parser/Params';
|
import { Params } from 'common/parser/Params';
|
||||||
|
import { OscParser } from 'common/parser/OscParser';
|
||||||
interface IHandlerCollection<T> {
|
|
||||||
[key: string]: T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type CsiHandler = (params: IParams, collect: string) => boolean | void;
|
|
||||||
type OscHandler = (data: string) => boolean | void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table values are generated like this:
|
* Table values are generated like this:
|
||||||
@@ -240,7 +233,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
public precedingCodepoint: number;
|
public precedingCodepoint: number;
|
||||||
|
|
||||||
// buffers over several parse calls
|
// buffers over several parse calls
|
||||||
protected _osc: string;
|
|
||||||
protected _params: Params;
|
protected _params: Params;
|
||||||
protected _collect: string;
|
protected _collect: string;
|
||||||
|
|
||||||
@@ -249,7 +241,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
protected _executeHandlers: any;
|
protected _executeHandlers: any;
|
||||||
protected _csiHandlers: IHandlerCollection<CsiHandler>;
|
protected _csiHandlers: IHandlerCollection<CsiHandler>;
|
||||||
protected _escHandlers: any;
|
protected _escHandlers: any;
|
||||||
protected _oscHandlers: IHandlerCollection<OscHandler>;
|
protected _oscParser: IOscParser;
|
||||||
protected _dcsHandlers: any;
|
protected _dcsHandlers: any;
|
||||||
protected _activeDcsHandler: IDcsHandler;
|
protected _activeDcsHandler: IDcsHandler;
|
||||||
protected _errorHandler: (state: IParsingState) => IParsingState;
|
protected _errorHandler: (state: IParsingState) => IParsingState;
|
||||||
@@ -259,7 +251,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
protected _executeHandlerFb: (code: number) => void;
|
protected _executeHandlerFb: (code: number) => void;
|
||||||
protected _csiHandlerFb: (collect: string, params: IParams, flag: number) => void;
|
protected _csiHandlerFb: (collect: string, params: IParams, flag: number) => void;
|
||||||
protected _escHandlerFb: (collect: string, flag: number) => void;
|
protected _escHandlerFb: (collect: string, flag: number) => void;
|
||||||
protected _oscHandlerFb: (identifier: number, data: string) => void;
|
|
||||||
protected _dcsHandlerFb: IDcsHandler;
|
protected _dcsHandlerFb: IDcsHandler;
|
||||||
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
|
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
|
||||||
|
|
||||||
@@ -268,7 +259,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
|
|
||||||
this.initialState = ParserState.GROUND;
|
this.initialState = ParserState.GROUND;
|
||||||
this.currentState = this.initialState;
|
this.currentState = this.initialState;
|
||||||
this._osc = '';
|
|
||||||
this._params = new Params(); // defaults to 32 storable params/subparams
|
this._params = new Params(); // defaults to 32 storable params/subparams
|
||||||
this._params.addParam(0); // ZDM
|
this._params.addParam(0); // ZDM
|
||||||
this._collect = '';
|
this._collect = '';
|
||||||
@@ -279,14 +269,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._executeHandlerFb = (code: number): void => { };
|
this._executeHandlerFb = (code: number): void => { };
|
||||||
this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { };
|
this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { };
|
||||||
this._escHandlerFb = (collect: string, flag: number): void => { };
|
this._escHandlerFb = (collect: string, flag: number): void => { };
|
||||||
this._oscHandlerFb = (identifier: number, data: string): void => { };
|
|
||||||
this._dcsHandlerFb = new DcsDummy();
|
this._dcsHandlerFb = new DcsDummy();
|
||||||
this._errorHandlerFb = (state: IParsingState): IParsingState => state;
|
this._errorHandlerFb = (state: IParsingState): IParsingState => state;
|
||||||
this._printHandler = this._printHandlerFb;
|
this._printHandler = this._printHandlerFb;
|
||||||
this._executeHandlers = Object.create(null);
|
this._executeHandlers = Object.create(null);
|
||||||
this._csiHandlers = Object.create(null);
|
this._csiHandlers = Object.create(null);
|
||||||
this._escHandlers = Object.create(null);
|
this._escHandlers = Object.create(null);
|
||||||
this._oscHandlers = Object.create(null);
|
this._oscParser = new OscParser();
|
||||||
this._dcsHandlers = Object.create(null);
|
this._dcsHandlers = Object.create(null);
|
||||||
this._activeDcsHandler = this._dcsHandlerFb;
|
this._activeDcsHandler = this._dcsHandlerFb;
|
||||||
this._errorHandler = this._errorHandlerFb;
|
this._errorHandler = this._errorHandlerFb;
|
||||||
@@ -300,6 +289,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._escHandlers = null;
|
this._escHandlers = null;
|
||||||
this._dcsHandlers = null;
|
this._dcsHandlers = null;
|
||||||
this._activeDcsHandler = new DcsDummy();
|
this._activeDcsHandler = new DcsDummy();
|
||||||
|
this._oscParser.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
|
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
|
||||||
@@ -355,29 +345,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._escHandlerFb = callback;
|
this._escHandlerFb = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
addOscHandler(ident: number, handler: IOscHandler): IDisposable {
|
||||||
if (this._oscHandlers[ident] === undefined) {
|
return this._oscParser.addOscHandler(ident, handler);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
setOscHandler(ident: number, callback: (data: string) => void): void {
|
setOscHandler(ident: number, handler: IOscHandler): void {
|
||||||
this._oscHandlers[ident] = [callback];
|
this._oscParser.setOscHandler(ident, handler);
|
||||||
}
|
}
|
||||||
clearOscHandler(ident: number): void {
|
clearOscHandler(ident: number): void {
|
||||||
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
|
this._oscParser.clearOscHandler(ident);
|
||||||
}
|
}
|
||||||
setOscHandlerFallback(callback: (identifier: number, data: string) => void): void {
|
setOscHandlerFallback(handler: OscFallbackHandler): void {
|
||||||
this._oscHandlerFb = callback;
|
this._oscParser.setOscHandlerFallback(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
|
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
|
||||||
@@ -404,7 +382,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.currentState = this.initialState;
|
this.currentState = this.initialState;
|
||||||
this._osc = '';
|
this._oscParser.reset();
|
||||||
this._params.reset();
|
this._params.reset();
|
||||||
this._params.addParam(0); // ZDM
|
this._params.addParam(0); // ZDM
|
||||||
this._collect = '';
|
this._collect = '';
|
||||||
@@ -430,7 +408,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
let code = 0;
|
let code = 0;
|
||||||
let transition = 0;
|
let transition = 0;
|
||||||
let currentState = this.currentState;
|
let currentState = this.currentState;
|
||||||
let osc = this._osc;
|
const osc = this._oscParser;
|
||||||
let collect = this._collect;
|
let collect = this._collect;
|
||||||
const params = this._params;
|
const params = this._params;
|
||||||
const table: Uint8Array = this.TRANSITIONS.table;
|
const table: Uint8Array = this.TRANSITIONS.table;
|
||||||
@@ -484,7 +462,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
position: i,
|
position: i,
|
||||||
code,
|
code,
|
||||||
currentState,
|
currentState,
|
||||||
osc,
|
osc: '', // FIXME: what to send here?
|
||||||
collect,
|
collect,
|
||||||
params,
|
params,
|
||||||
abort: false
|
abort: false
|
||||||
@@ -537,7 +515,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this.precedingCodepoint = 0;
|
this.precedingCodepoint = 0;
|
||||||
break;
|
break;
|
||||||
case ParserAction.CLEAR:
|
case ParserAction.CLEAR:
|
||||||
osc = '';
|
osc.reset();
|
||||||
params.reset();
|
params.reset();
|
||||||
params.addParam(0); // ZDM
|
params.addParam(0); // ZDM
|
||||||
collect = '';
|
collect = '';
|
||||||
@@ -562,54 +540,29 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
dcsHandler.unhook();
|
dcsHandler.unhook();
|
||||||
dcsHandler = this._dcsHandlerFb;
|
dcsHandler = this._dcsHandlerFb;
|
||||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||||
osc = '';
|
osc.reset();
|
||||||
params.reset();
|
params.reset();
|
||||||
params.addParam(0); // ZDM
|
params.addParam(0); // ZDM
|
||||||
collect = '';
|
collect = '';
|
||||||
this.precedingCodepoint = 0;
|
this.precedingCodepoint = 0;
|
||||||
break;
|
break;
|
||||||
case ParserAction.OSC_START:
|
case ParserAction.OSC_START:
|
||||||
osc = '';
|
osc.start();
|
||||||
break;
|
break;
|
||||||
case ParserAction.OSC_PUT:
|
case ParserAction.OSC_PUT:
|
||||||
// inner loop: 0x20 (SP) included, 0x7F (DEL) included
|
// inner loop: 0x20 (SP) included, 0x7F (DEL) included
|
||||||
for (let j = i + 1; ; j++) {
|
for (let j = i + 1; ; j++) {
|
||||||
if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code <= 0x9f)) {
|
if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code <= 0x9f)) {
|
||||||
osc += utf32ToString(data, i, j);
|
osc.put(data, i, j);
|
||||||
i = j - 1;
|
i = j - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ParserAction.OSC_END:
|
case ParserAction.OSC_END:
|
||||||
if (osc && code !== 0x18 && code !== 0x1a) {
|
osc.end(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||||
osc = '';
|
osc.reset();
|
||||||
params.reset();
|
params.reset();
|
||||||
params.addParam(0); // ZDM
|
params.addParam(0); // ZDM
|
||||||
collect = '';
|
collect = '';
|
||||||
@@ -620,7 +573,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// save non pushable buffers
|
// save non pushable buffers
|
||||||
this._osc = osc;
|
|
||||||
this._collect = collect;
|
this._collect = collect;
|
||||||
this._params = params;
|
this._params = params;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { OscParser, OscHandlerFactory } from 'common/parser/OscParser';
|
||||||
|
import { StringToUtf32, utf32ToString } from 'common/input/TextDecoder';
|
||||||
|
import { IOscHandler } from 'common/parser/Types';
|
||||||
|
|
||||||
|
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.setOscHandlerFallback((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.setOscHandler(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.setOscHandler(1234, new TestHandler(1234, reports, 'th'));
|
||||||
|
parser.clearOscHandler(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.setOscHandler(1234, new TestHandler(1234, reports, 'th1'));
|
||||||
|
parser.addOscHandler(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.setOscHandler(1234, new TestHandler(1234, reports, 'th1'));
|
||||||
|
parser.addOscHandler(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.setOscHandler(1234, new TestHandler(1234, reports, 'th1'));
|
||||||
|
const dispo = parser.addOscHandler(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.setOscHandler(1234, new OscHandlerFactory(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.setOscHandler(1234, new OscHandlerFactory(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.setOscHandler(1234, new OscHandlerFactory(data => reports.push(['one', data])));
|
||||||
|
const dispo = parser.addOscHandler(1234, new OscHandlerFactory(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.setOscHandler(1234, new OscHandlerFactory(data => reports.push(['one', data])));
|
||||||
|
parser.addOscHandler(1234, new OscHandlerFactory(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!']]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IOscHandler, IHandlerCollection, OscFallbackHandler } from 'common/parser/Types';
|
||||||
|
import { OscState } from 'common/parser/Constants';
|
||||||
|
import { Disposable } from 'common/Lifecycle';
|
||||||
|
import { utf32ToString } from 'common/input/TextDecoder';
|
||||||
|
import { IDisposable } from 'common/Types';
|
||||||
|
|
||||||
|
|
||||||
|
export class OscParser extends Disposable {
|
||||||
|
private _state = OscState.START;
|
||||||
|
private _id = -1;
|
||||||
|
private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
|
||||||
|
private _handlerFb: OscFallbackHandler = () => { };
|
||||||
|
|
||||||
|
addOscHandler(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
setOscHandler(ident: number, handler: IOscHandler): void {
|
||||||
|
this._handlers[ident] = [handler];
|
||||||
|
}
|
||||||
|
clearOscHandler(ident: number): void {
|
||||||
|
if (this._handlers[ident]) delete this._handlers[ident];
|
||||||
|
}
|
||||||
|
setOscHandlerFallback(handler: OscFallbackHandler): void {
|
||||||
|
this._handlerFb = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this._handlers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 OscHandlerFactory implements IOscHandler {
|
||||||
|
private _data = '';
|
||||||
|
constructor(private _handler: (data: string) => any) {}
|
||||||
|
public start(): void {
|
||||||
|
this._data = '';
|
||||||
|
}
|
||||||
|
public put(data: Uint32Array, start: number, end: number): void {
|
||||||
|
this._data += utf32ToString(data, start, end);
|
||||||
|
}
|
||||||
|
public end(success: boolean): any {
|
||||||
|
let ret;
|
||||||
|
if (success) {
|
||||||
|
ret = this._handler(this._data);
|
||||||
|
}
|
||||||
|
this._data = '';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+43
-3
@@ -63,6 +63,12 @@ export interface IParsingState {
|
|||||||
abort: boolean;
|
abort: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IHandlerCollection<T> {
|
||||||
|
[key: string]: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CsiHandler = (params: IParams, collect: string) => boolean | void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DCS handler signature for EscapeSequenceParser.
|
* DCS handler signature for EscapeSequenceParser.
|
||||||
* EscapeSequenceParser handles DCS commands via separate
|
* EscapeSequenceParser handles DCS commands via separate
|
||||||
@@ -90,6 +96,29 @@ export interface IDcsHandler {
|
|||||||
unhook(): void;
|
unhook(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OscFallbackHandler = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
|
||||||
|
|
||||||
|
export interface IOscHandler {
|
||||||
|
/**
|
||||||
|
* Announces start of this OSC command.
|
||||||
|
* Prepare needed data structures here.
|
||||||
|
*/
|
||||||
|
start(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incoming data chunk.
|
||||||
|
*/
|
||||||
|
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`.
|
||||||
|
*/
|
||||||
|
end(success: boolean): void | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EscapeSequenceParser interface.
|
* EscapeSequenceParser interface.
|
||||||
*/
|
*/
|
||||||
@@ -123,15 +152,15 @@ export interface IEscapeSequenceParser extends IDisposable {
|
|||||||
clearCsiHandler(flag: string): void;
|
clearCsiHandler(flag: string): void;
|
||||||
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void;
|
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void;
|
||||||
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
|
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
|
||||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
|
||||||
|
|
||||||
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
||||||
clearEscHandler(collectAndFlag: string): void;
|
clearEscHandler(collectAndFlag: string): void;
|
||||||
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void;
|
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void;
|
||||||
|
|
||||||
setOscHandler(ident: number, callback: (data: string) => void): void;
|
setOscHandler(ident: number, handler: IOscHandler): void;
|
||||||
clearOscHandler(ident: number): void;
|
clearOscHandler(ident: number): void;
|
||||||
setOscHandlerFallback(callback: (identifier: number, data: string) => void): void;
|
setOscHandlerFallback(handler: OscFallbackHandler): void;
|
||||||
|
|
||||||
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
|
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
|
||||||
clearDcsHandler(collectAndFlag: string): void;
|
clearDcsHandler(collectAndFlag: string): void;
|
||||||
@@ -140,3 +169,14 @@ export interface IEscapeSequenceParser extends IDisposable {
|
|||||||
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
|
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
|
||||||
clearErrorHandler(): void;
|
clearErrorHandler(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IOscParser extends IDisposable {
|
||||||
|
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
|
||||||
|
setOscHandler(ident: number, handler: IOscHandler): void;
|
||||||
|
clearOscHandler(ident: number): void;
|
||||||
|
setOscHandlerFallback(handler: OscFallbackHandler): void;
|
||||||
|
reset(): void;
|
||||||
|
start(): void;
|
||||||
|
put(data: Uint32Array, start: number, end: number): void;
|
||||||
|
end(success: boolean): void;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-be
|
|||||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||||
import { C0, C1 } from 'common/data/EscapeSequences';
|
import { C0, C1 } from 'common/data/EscapeSequences';
|
||||||
import { IDcsHandler, IParams } from 'common/parser/Types';
|
import { IDcsHandler, IParams } from 'common/parser/Types';
|
||||||
|
import { OscHandlerFactory } from 'common/parser/OscParser';
|
||||||
|
|
||||||
|
|
||||||
function toUtf32(s: string): Uint32Array {
|
function toUtf32(s: string): Uint32Array {
|
||||||
@@ -79,8 +80,8 @@ perfContext('Parser throughput - 50MB data', () => {
|
|||||||
parser.setExecuteHandler(C1.IND, () => {});
|
parser.setExecuteHandler(C1.IND, () => {});
|
||||||
parser.setExecuteHandler(C1.NEL, () => {});
|
parser.setExecuteHandler(C1.NEL, () => {});
|
||||||
parser.setExecuteHandler(C1.HTS, () => {});
|
parser.setExecuteHandler(C1.HTS, () => {});
|
||||||
parser.setOscHandler(0, (data) => {});
|
parser.setOscHandler(0, new OscHandlerFactory((data) => {}));
|
||||||
parser.setOscHandler(2, (data) => {});
|
parser.setOscHandler(2, new OscHandlerFactory((data) => {}));
|
||||||
parser.setEscHandler('7', () => {});
|
parser.setEscHandler('7', () => {});
|
||||||
parser.setEscHandler('8', () => {});
|
parser.setEscHandler('8', () => {});
|
||||||
parser.setEscHandler('D', () => {});
|
parser.setEscHandler('D', () => {});
|
||||||
|
|||||||
Vendored
+1
-1
@@ -516,7 +516,7 @@ declare module 'xterm' {
|
|||||||
addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable;
|
addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (EXPERIMENTAL) Adds a handler for OSC escape sequences.
|
* Adds a handler for OSC escape sequences.
|
||||||
* @param ident The number (first parameter) of the sequence.
|
* @param ident The number (first parameter) of the sequence.
|
||||||
* @param callback The function to handle the escape sequence. The callback
|
* @param callback The function to handle the escape sequence. The callback
|
||||||
* is called with OSC data string. Return true if the sequence was handled;
|
* is called with OSC data string. Return true if the sequence was handled;
|
||||||
|
|||||||
Reference in New Issue
Block a user