mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
async OSC/DCS registering/dispose tests on parser instance
This commit is contained in:
@@ -102,7 +102,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
|
||||
public trackedStack: IParserStackState[] = [];
|
||||
public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean> {
|
||||
const result = super.parse(data, length, promiseResult);
|
||||
if (result && this._trackStack) {
|
||||
if (result instanceof Promise && this._trackStack) {
|
||||
this.trackedStack.push({ ...this.parseStack });
|
||||
}
|
||||
return result;
|
||||
@@ -1742,9 +1742,9 @@ async function throwsAsync(fn: () => Promise<any>, message?: string | undefined)
|
||||
}
|
||||
|
||||
describe('EscapeSequenceParser - async', () => {
|
||||
// sequences: SGR 1;31 | hello SP | ESC %G | wor | ESC E | ld! | SGR 0 | EXE \r\n | $> | OSC 1;foo=bar ST
|
||||
// sequences: SGR 1;31 | hello SP | ESC %G | wor | ESC E | ld! | SGR 0 | EXE \r\n | $> | DCS 1;2 a [xyz] ST | OSC 1;foo=bar ST | FIN
|
||||
// needed handlers: CSI m, PRINT, ESC %G, ESC E, EXE \r, EXE \n, OSC 1
|
||||
const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1b]1;foo=bar\x1b\\';
|
||||
const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1bP1;2axyz\x1b\\\x1b]1;foo=bar\x1b\\FIN';
|
||||
let RESULT: any[];
|
||||
let parser: TestEscapeSequenceParser;
|
||||
const callstack: any[] = [];
|
||||
@@ -1764,7 +1764,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
['EXE \r'],
|
||||
['EXE \n'],
|
||||
['PRINT', '$>'],
|
||||
['OSC 1', 'foo=bar']
|
||||
['DCS a', ['xyz', [1, 2]]],
|
||||
['OSC 1', 'foo=bar'],
|
||||
['PRINT', 'FIN']
|
||||
];
|
||||
parser = new TestEscapeSequenceParser();
|
||||
parser.reset();
|
||||
@@ -1786,9 +1788,10 @@ describe('EscapeSequenceParser - async', () => {
|
||||
parser.setExecuteHandler('\r', () => { callstack.push(['EXE \r']); return true; });
|
||||
parser.setExecuteHandler('\n', () => { callstack.push(['EXE \n']); return true; });
|
||||
parser.registerOscHandler(1, new OscHandler(data => { callstack.push(['OSC 1', data]); return true; }));
|
||||
parser.registerDcsHandler({final: 'a'}, new DcsHandler((data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true;}));
|
||||
});
|
||||
|
||||
it('sync handlers keep parsed in sync mode', () => {
|
||||
it('sync handlers keep being parsed in sync mode', () => {
|
||||
// note: if we have only sync handlers, a parse call should never return anything
|
||||
assert.equal(!parseSync(parser, INPUT), true);
|
||||
assert.equal(parser.parseStack.state, ParserStackType.NONE); // not paused
|
||||
@@ -1819,7 +1822,8 @@ describe('EscapeSequenceParser - async', () => {
|
||||
parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['ESC E']); return true; });
|
||||
parser.setExecuteHandler('\r', () => { callstack.push(['EXE \r']); return true; });
|
||||
parser.setExecuteHandler('\n', () => { callstack.push(['EXE \n']); return true; });
|
||||
parser.registerOscHandler(1, new OscHandler(data => { callstack.push(['OSC 1', data]); return true; }));
|
||||
parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['OSC 1', data]); return true; }));
|
||||
parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true;}));
|
||||
});
|
||||
|
||||
it('sync parse call does not work anymore', () => {
|
||||
@@ -1849,7 +1853,12 @@ describe('EscapeSequenceParser - async', () => {
|
||||
await parseP(parser, INPUT);
|
||||
assert.deepEqual(callstack, RESULT);
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0], [15, ParserStackType.ESC, 0], [20, ParserStackType.ESC, 0], [27, ParserStackType.CSI, 0]
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
});
|
||||
it('correct result on chunked awaited parse calls', async () => {
|
||||
@@ -1874,7 +1883,11 @@ describe('EscapeSequenceParser - async', () => {
|
||||
['EXE \n'],
|
||||
['PRINT', '$'],
|
||||
['PRINT', '>'],
|
||||
['OSC 1', 'foo=bar']
|
||||
['DCS a', ['xyz', [1, 2]]],
|
||||
['OSC 1', 'foo=bar'],
|
||||
['PRINT', 'F'],
|
||||
['PRINT', 'I'],
|
||||
['PRINT', 'N']
|
||||
];
|
||||
|
||||
// split to single char input
|
||||
@@ -1888,7 +1901,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[0, ParserStackType.CSI, 0],
|
||||
[0, ParserStackType.ESC, 0],
|
||||
[0, ParserStackType.ESC, 0],
|
||||
[0, ParserStackType.CSI, 0]
|
||||
[0, ParserStackType.CSI, 0],
|
||||
[0, ParserStackType.DCS, 0],
|
||||
[0, ParserStackType.OSC, 0]
|
||||
]);
|
||||
});
|
||||
it('multiple async SGR handlers', async () => {
|
||||
@@ -1906,7 +1921,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 1],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
@@ -1917,7 +1934,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
|
||||
@@ -1933,7 +1952,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 1],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 1]
|
||||
[27, ParserStackType.CSI, 1],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
@@ -1944,7 +1965,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
});
|
||||
it('multiple async ESC handlers', async () => {
|
||||
@@ -1960,7 +1983,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 1],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
@@ -1971,7 +1996,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
|
||||
@@ -1986,7 +2013,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 1],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
@@ -1997,7 +2026,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
});
|
||||
it('sync/async SGR mixed', async () => {
|
||||
@@ -2020,7 +2051,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 2],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// dispose SGR2 (sync one)
|
||||
@@ -2039,7 +2072,9 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 1],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// dispose SGR3 (async one)
|
||||
@@ -2050,8 +2085,134 @@ describe('EscapeSequenceParser - async', () => {
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0]
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
});
|
||||
it('multiple async OSC handlers', async () => {
|
||||
// register with fallback
|
||||
const OSC2 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return false; }));
|
||||
await parseP(parser, INPUT);
|
||||
for (let i = 0; i < callstack.length; ++i) {
|
||||
const entry = callstack[i];
|
||||
if (entry[0] === '2# OSC 1') assert.equal(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler');
|
||||
}
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
OSC2.dispose();
|
||||
await parseP(parser, INPUT);
|
||||
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
|
||||
// register without fallback
|
||||
const OSC22 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return true; }));
|
||||
await parseP(parser, INPUT);
|
||||
for (let i = 0; i < callstack.length; ++i) {
|
||||
const entry = callstack[i];
|
||||
if (entry[0] === '2# OSC 1') assert.notEqual(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler');
|
||||
}
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
OSC22.dispose();
|
||||
await parseP(parser, INPUT);
|
||||
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
});
|
||||
it('multiple async DCS handlers', async () => {
|
||||
// register with fallback
|
||||
const DCS2 = parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return false;}));
|
||||
await parseP(parser, INPUT);
|
||||
for (let i = 0; i < callstack.length; ++i) {
|
||||
const entry = callstack[i];
|
||||
if (entry[0] === '2# DCS a') assert.equal(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler');
|
||||
}
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
DCS2.dispose();
|
||||
await parseP(parser, INPUT);
|
||||
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
|
||||
// register without fallback
|
||||
const DCS22 = parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return true;}));
|
||||
await parseP(parser, INPUT);
|
||||
for (let i = 0; i < callstack.length; ++i) {
|
||||
const entry = callstack[i];
|
||||
if (entry[0] === '2# DCS a') assert.notEqual(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler');
|
||||
}
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
// after dispose we should be back to RESULT
|
||||
DCS22.dispose();
|
||||
await parseP(parser, INPUT);
|
||||
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
|
||||
evalStackSaves(parser.trackedStack, [
|
||||
[6, ParserStackType.CSI, 0],
|
||||
[15, ParserStackType.ESC, 0],
|
||||
[20, ParserStackType.ESC, 0],
|
||||
[27, ParserStackType.CSI, 0],
|
||||
[41, ParserStackType.DCS, 0],
|
||||
[54, ParserStackType.OSC, 0]
|
||||
]);
|
||||
clearAccu();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -492,7 +492,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
* --> Maybe easier: Give up on non-mutating rule for async handlers...
|
||||
* (needs explanation in docs about exact executor/thenable/worker execution contexts)
|
||||
*
|
||||
* Example for proper parsing of multiple chunks:
|
||||
* Boilerplate for proper parsing of multiple chunks with async handlers:
|
||||
*
|
||||
* ```typescript
|
||||
* async function parseMultipleChunks(chunks: Uint32Array[]): Promise<void> {
|
||||
@@ -518,10 +518,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
|
||||
/**
|
||||
* Reject further parsing on improper continuation after pausing.
|
||||
* This will happen with sync parse calls not awaiting a returned promise.
|
||||
* It is a really bad condition with screwed up execution order,
|
||||
* therefore we exit hard with an exception.
|
||||
* FIXME: Do we need a method to escape from this broken parser state? (hard to achieve properly)
|
||||
* This is a really bad condition with screwed up execution order and messed up terminal state,
|
||||
* therefore we exit hard with an exception and reject any further parsing.
|
||||
*
|
||||
* Note: With `Terminal.write` usage this exception should never occur, as the top level
|
||||
* calls are guaranteed to handle async conditions properly. If you ever encounter this
|
||||
* exception in your terminal integration it indicates, that you injected data chunks to
|
||||
* `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
|
||||
* continuation of a running async handler.
|
||||
*/
|
||||
this._parseStack.state = ParserStackType.FAIL;
|
||||
throw new Error('improper continuation due to previous async handler, giving up parsing');
|
||||
@@ -578,13 +582,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
if (handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = 0;
|
||||
break;
|
||||
}
|
||||
// cleanup before continuing with the main loop
|
||||
// cleanup before continuing with the main sync loop
|
||||
this._parseStack.state = ParserStackType.NONE;
|
||||
start = this._parseStack.chunkPos + 1;
|
||||
this.precedingCodepoint = 0;
|
||||
|
||||
Reference in New Issue
Block a user