mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
OSC impl, OscParser tests
This commit is contained in:
@@ -548,6 +548,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
}
|
||||
}
|
||||
this._parseStack.handlers = [];
|
||||
break;
|
||||
case ParserStackType.ESC:
|
||||
if (promiseResult === false && handlerPos > -1) {
|
||||
@@ -562,10 +563,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
}
|
||||
}
|
||||
this._parseStack.handlers = [];
|
||||
break;
|
||||
case ParserStackType.DCS:
|
||||
code = data[this._parseStack.chunkPos];
|
||||
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a)) {
|
||||
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
||||
@@ -574,7 +576,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this._collect = 0;
|
||||
break;
|
||||
case ParserStackType.OSC:
|
||||
// TODO
|
||||
code = data[this._parseStack.chunkPos];
|
||||
if (handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = 0;
|
||||
break;
|
||||
}
|
||||
// cleanup before continuing with the main loop
|
||||
@@ -717,7 +726,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
break;
|
||||
case ParserAction.DCS_UNHOOK:
|
||||
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a)) {
|
||||
this._parseStack.state = ParserStackType.DCS;
|
||||
this._preserveStack(ParserStackType.DCS, [], 0, transition, i);
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
@@ -740,7 +749,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
break;
|
||||
case ParserAction.OSC_END:
|
||||
this._oscParser.end(code !== 0x18 && code !== 0x1a);
|
||||
if (handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a)) {
|
||||
this._preserveStack(ParserStackType.OSC, [], 0, transition, i);
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
|
||||
@@ -250,3 +250,204 @@ describe('OscParser', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
class TestHandlerAsync implements IOscHandler {
|
||||
constructor(public id: number, public output: any[], public msg: string, public returnFalse: boolean = false) {}
|
||||
public start(): void {
|
||||
this.output.push([this.msg, this.id, 'START']);
|
||||
}
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]);
|
||||
}
|
||||
public async end(success: boolean): Promise<boolean> {
|
||||
await new Promise(res => setTimeout(res, 20));
|
||||
this.output.push([this.msg, this.id, 'END', success]);
|
||||
if (this.returnFalse) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
async function endP(parser: OscParser, success: boolean): Promise<void> {
|
||||
let result: void | Promise<boolean>;
|
||||
let prev: boolean | undefined;
|
||||
while (result = parser.end(success, prev)) {
|
||||
prev = await result;
|
||||
}
|
||||
}
|
||||
|
||||
describe('OscParser - async tests', () => {
|
||||
let parser: OscParser;
|
||||
let reports: any[] = [];
|
||||
beforeEach(() => {
|
||||
reports = [];
|
||||
parser = new OscParser();
|
||||
parser.setHandlerFallback((id, action, data) => {
|
||||
reports.push([id, action, data]);
|
||||
});
|
||||
});
|
||||
describe('sync and async mixed', () => {
|
||||
describe('sync | async | sync', () => {
|
||||
it('first should run, cleanup action for others', async () => {
|
||||
parser.registerHandler(1234, new TestHandler(1234, reports, 's1'));
|
||||
parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 'a1'));
|
||||
parser.registerHandler(1234, new TestHandler(1234, reports, 's2'));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['s2', 1234, 'START'],
|
||||
['a1', 1234, 'START'],
|
||||
['s1', 1234, 'START'],
|
||||
['s2', 1234, 'PUT', 'Here comes'],
|
||||
['a1', 1234, 'PUT', 'Here comes'],
|
||||
['s1', 1234, 'PUT', 'Here comes'],
|
||||
['s2', 1234, 'PUT', 'the mouse!'],
|
||||
['a1', 1234, 'PUT', 'the mouse!'],
|
||||
['s1', 1234, 'PUT', 'the mouse!'],
|
||||
['s2', 1234, 'END', true],
|
||||
['a1', 1234, 'END', false],
|
||||
['s1', 1234, 'END', false]
|
||||
]);
|
||||
});
|
||||
it('all should run', async () => {
|
||||
parser.registerHandler(1234, new TestHandler(1234, reports, 's1', true));
|
||||
parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 'a1', true));
|
||||
parser.registerHandler(1234, new TestHandler(1234, reports, 's2', 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);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['s2', 1234, 'START'],
|
||||
['a1', 1234, 'START'],
|
||||
['s1', 1234, 'START'],
|
||||
['s2', 1234, 'PUT', 'Here comes'],
|
||||
['a1', 1234, 'PUT', 'Here comes'],
|
||||
['s1', 1234, 'PUT', 'Here comes'],
|
||||
['s2', 1234, 'PUT', 'the mouse!'],
|
||||
['a1', 1234, 'PUT', 'the mouse!'],
|
||||
['s1', 1234, 'PUT', 'the mouse!'],
|
||||
['s2', 1234, 'END', true],
|
||||
['a1', 1234, 'END', true],
|
||||
['s1', 1234, 'END', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('async | sync | async', () => {
|
||||
it('first should run, cleanup action for others', async () => {
|
||||
parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's1'));
|
||||
parser.registerHandler(1234, new TestHandler(1234, reports, 'a1'));
|
||||
parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's2'));
|
||||
parser.start();
|
||||
let data = toUtf32('1234;Here comes');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32('the mouse!');
|
||||
parser.put(data, 0, data.length);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['s2', 1234, 'START'],
|
||||
['a1', 1234, 'START'],
|
||||
['s1', 1234, 'START'],
|
||||
['s2', 1234, 'PUT', 'Here comes'],
|
||||
['a1', 1234, 'PUT', 'Here comes'],
|
||||
['s1', 1234, 'PUT', 'Here comes'],
|
||||
['s2', 1234, 'PUT', 'the mouse!'],
|
||||
['a1', 1234, 'PUT', 'the mouse!'],
|
||||
['s1', 1234, 'PUT', 'the mouse!'],
|
||||
['s2', 1234, 'END', true],
|
||||
['a1', 1234, 'END', false],
|
||||
['s1', 1234, 'END', false]
|
||||
]);
|
||||
});
|
||||
it('all should run', async () => {
|
||||
parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's1', true));
|
||||
parser.registerHandler(1234, new TestHandler(1234, reports, 'a1', true));
|
||||
parser.registerHandler(1234, new TestHandlerAsync(1234, reports, 's2', 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);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['s2', 1234, 'START'],
|
||||
['a1', 1234, 'START'],
|
||||
['s1', 1234, 'START'],
|
||||
['s2', 1234, 'PUT', 'Here comes'],
|
||||
['a1', 1234, 'PUT', 'Here comes'],
|
||||
['s1', 1234, 'PUT', 'Here comes'],
|
||||
['s2', 1234, 'PUT', 'the mouse!'],
|
||||
['a1', 1234, 'PUT', 'the mouse!'],
|
||||
['s1', 1234, 'PUT', 'the mouse!'],
|
||||
['s2', 1234, 'END', true],
|
||||
['a1', 1234, 'END', true],
|
||||
['s1', 1234, 'END', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('OscHandlerFactory', () => {
|
||||
it('should be called once on end(true)', async () => {
|
||||
parser.registerHandler(1234, new OscHandler(async data => { reports.push([1234, data]); return 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);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]);
|
||||
});
|
||||
it('should not be called on end(false)', async () => {
|
||||
parser.registerHandler(1234, new OscHandler(async data => { reports.push([1234, data]); return 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);
|
||||
await endP(parser, false);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
it('should be disposable', async () => {
|
||||
parser.registerHandler(1234, new OscHandler(async data => { reports.push(['one', data]); return true; }));
|
||||
const dispo = parser.registerHandler(1234, new OscHandler(async data => { reports.push(['two', data]); return 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);
|
||||
await endP(parser, 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);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]);
|
||||
});
|
||||
it('should respect return false', async () => {
|
||||
parser.registerHandler(1234, new OscHandler(async data => { reports.push(['one', data]); return true; }));
|
||||
parser.registerHandler(1234, new OscHandler(async 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);
|
||||
await endP(parser, true);
|
||||
assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'Here comes the mouse!']]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ export class OscParser implements IOscParser {
|
||||
|
||||
public dispose(): void {
|
||||
this._handlers = Object.create(null);
|
||||
this._handlerFb = () => {};
|
||||
this._handlerFb = () => { };
|
||||
this._active = EMPTY_HANDLERS;
|
||||
}
|
||||
|
||||
@@ -76,27 +76,6 @@ export class OscParser implements IOscParser {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (!this._active.length) {
|
||||
this._handlerFb(this._id, 'END', success);
|
||||
} else {
|
||||
let j = this._active.length - 1;
|
||||
for (; j >= 0; j--) {
|
||||
if (this._active[j].end(success)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j--;
|
||||
// cleanup left over handlers
|
||||
for (; j >= 0; j--) {
|
||||
this._active[j].end(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
// always reset leftover handlers
|
||||
this.reset();
|
||||
@@ -137,12 +116,18 @@ export class OscParser implements IOscParser {
|
||||
}
|
||||
}
|
||||
|
||||
private _stack = {
|
||||
paused: false,
|
||||
loopPosition: 0,
|
||||
fallThrough: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates end of an OSC command.
|
||||
* Whether the OSC got aborted or finished normally
|
||||
* is indicated by `success`.
|
||||
*/
|
||||
public end(success: boolean): void {
|
||||
public end(success: boolean, promiseResult?: boolean): void | Promise<boolean> {
|
||||
if (this._state === OscState.START) {
|
||||
return;
|
||||
}
|
||||
@@ -154,7 +139,46 @@ export class OscParser implements IOscParser {
|
||||
if (this._state === OscState.ID) {
|
||||
this._start();
|
||||
}
|
||||
this._end(success);
|
||||
|
||||
if (!this._active.length) {
|
||||
this._handlerFb(this._id, 'END', success);
|
||||
} else {
|
||||
let handlerResult: any = false;
|
||||
let j = this._active.length - 1;
|
||||
let fallThrough = false;
|
||||
if (this._stack.paused) {
|
||||
j = this._stack.loopPosition - 1;
|
||||
handlerResult = promiseResult;
|
||||
fallThrough = this._stack.fallThrough;
|
||||
this._stack.paused = false;
|
||||
}
|
||||
if (!fallThrough && handlerResult === false) {
|
||||
for (; j >= 0; j--) {
|
||||
if ((handlerResult = this._active[j].end(success)) !== false) {
|
||||
if (handlerResult instanceof Promise) {
|
||||
this._stack.paused = true;
|
||||
this._stack.loopPosition = j;
|
||||
this._stack.fallThrough = false;
|
||||
return handlerResult;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
j--;
|
||||
}
|
||||
// cleanup left over handlers
|
||||
// we always have to call .end for proper cleanup,
|
||||
// here we use `success` to indicate whether a handler should execute
|
||||
for (; j >= 0; j--) {
|
||||
if ((handlerResult = this._active[j].end(false)) instanceof Promise) {
|
||||
this._stack.paused = true;
|
||||
this._stack.loopPosition = j;
|
||||
this._stack.fallThrough = true;
|
||||
return handlerResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
this._active = EMPTY_HANDLERS;
|
||||
this._id = -1;
|
||||
@@ -170,7 +194,7 @@ export class OscHandler implements IOscHandler {
|
||||
private _data = '';
|
||||
private _hitLimit: boolean = false;
|
||||
|
||||
constructor(private _handler: (data: string) => boolean) {}
|
||||
constructor(private _handler: (data: string) => boolean | Promise<boolean>) { }
|
||||
|
||||
public start(): void {
|
||||
this._data = '';
|
||||
@@ -188,12 +212,18 @@ export class OscHandler implements IOscHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public end(success: boolean): boolean {
|
||||
let ret = false;
|
||||
public end(success: boolean): boolean | Promise<boolean> {
|
||||
let ret: boolean | Promise<boolean> = false;
|
||||
if (this._hitLimit) {
|
||||
ret = false;
|
||||
} else if (success) {
|
||||
ret = this._handler(this._data);
|
||||
if ((ret = this._handler(this._data)) instanceof Promise) {
|
||||
return ret.then(res => {
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
|
||||
Vendored
+2
-2
@@ -130,7 +130,7 @@ export interface IOscHandler {
|
||||
* execution of the command should depend on `success`.
|
||||
* To save memory also cleanup data structures here.
|
||||
*/
|
||||
end(success: boolean): boolean;
|
||||
end(success: boolean): boolean | Promise<boolean>;
|
||||
}
|
||||
export type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
|
||||
|
||||
@@ -214,7 +214,7 @@ export interface ISubParser<T, U> extends IDisposable {
|
||||
|
||||
export interface IOscParser extends ISubParser<IOscHandler, OscFallbackHandlerType> {
|
||||
start(): void;
|
||||
end(success: boolean): void;
|
||||
end(success: boolean, promiseResult?: boolean): void | Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface IDcsParser extends ISubParser<IDcsHandler, DcsFallbackHandlerType> {
|
||||
|
||||
Reference in New Issue
Block a user