mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
make reset async aware
This commit is contained in:
@@ -48,9 +48,13 @@ export class DcsParser implements IDcsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
|
// force cleanup leftover handlers
|
||||||
if (this._active.length) {
|
if (this._active.length) {
|
||||||
this.unhook(false);
|
for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {
|
||||||
|
this._active[j].unhook(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this._stack.paused = false;
|
||||||
this._active = EMPTY_HANDLERS;
|
this._active = EMPTY_HANDLERS;
|
||||||
this._ident = 0;
|
this._ident = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1847,7 +1847,9 @@ describe('EscapeSequenceParser - async', () => {
|
|||||||
// keeps being broken for further parse calls (sync and async)
|
// keeps being broken for further parse calls (sync and async)
|
||||||
assert.throws(() => parseSync(parser, 'random'), 'improper continuation due to previous async handler, giving up parsing');
|
assert.throws(() => parseSync(parser, 'random'), 'improper continuation due to previous async handler, giving up parsing');
|
||||||
await throwsAsync(() => parseP(parser, 'foobar'), 'improper continuation due to previous async handler, giving up parsing');
|
await throwsAsync(() => parseP(parser, 'foobar'), 'improper continuation due to previous async handler, giving up parsing');
|
||||||
// FIXME: come up with a good recovery strategy
|
// reset should lift the error condition
|
||||||
|
parser.reset();
|
||||||
|
await parseP(parser, INPUT); // does not throw anymore
|
||||||
});
|
});
|
||||||
it('correct result on awaited parse call', async () => {
|
it('correct result on awaited parse call', async () => {
|
||||||
await parseP(parser, INPUT);
|
await parseP(parser, INPUT);
|
||||||
|
|||||||
@@ -427,6 +427,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._errorHandler = this._errorHandlerFb;
|
this._errorHandler = this._errorHandlerFb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset parser to initial values.
|
||||||
|
*
|
||||||
|
* This can also be used to lift the improper continuation error condition
|
||||||
|
* when dealing with async handlers. Use this only as a last resort to silence
|
||||||
|
* that error when the terminal has no pending data to be processed. Note that
|
||||||
|
* the interrupted async handler might continue its work in the future messing
|
||||||
|
* up the terminal state even further.
|
||||||
|
*/
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this.currentState = this.initialState;
|
this.currentState = this.initialState;
|
||||||
this._oscParser.reset();
|
this._oscParser.reset();
|
||||||
@@ -435,6 +444,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
this._params.addParam(0); // ZDM
|
this._params.addParam(0); // ZDM
|
||||||
this._collect = 0;
|
this._collect = 0;
|
||||||
this.precedingCodepoint = 0;
|
this.precedingCodepoint = 0;
|
||||||
|
// abort pending continuation from async handler
|
||||||
|
// Here the RESET type indicates, that the next parse call will
|
||||||
|
// ignore any saved stack, instead continues sync with next codepoint from GROUND
|
||||||
|
if (this._parseStack.state !== ParserStackType.NONE) {
|
||||||
|
this._parseStack.state = ParserStackType.RESET;
|
||||||
|
this._parseStack.handlers = []; // also release handlers ref
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -486,12 +502,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
* Important: With only sync handlers defined, parsing is completely synchronous as well.
|
* Important: With only sync handlers defined, parsing is completely synchronous as well.
|
||||||
* As soon as an async handler is involved, synchronous parsing is not possible anymore.
|
* As soon as an async handler is involved, synchronous parsing is not possible anymore.
|
||||||
*
|
*
|
||||||
* FIXME: to be discussed
|
|
||||||
* While awaiting parse promises the terminal buffer state may not change.
|
|
||||||
* --> Implement lock semantics / promise chaining on buffer alterations? Waah, pandora's box ;)
|
|
||||||
* --> Maybe easier: Give up on non-mutating rule for async handlers...
|
|
||||||
* (needs explanation in docs about exact executor/thenable/worker execution contexts)
|
|
||||||
*
|
|
||||||
* Boilerplate for proper parsing of multiple chunks with async handlers:
|
* Boilerplate for proper parsing of multiple chunks with async handlers:
|
||||||
*
|
*
|
||||||
* ```typescript
|
* ```typescript
|
||||||
@@ -515,84 +525,95 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
|||||||
|
|
||||||
// resume from async handler
|
// resume from async handler
|
||||||
if (this._parseStack.state) {
|
if (this._parseStack.state) {
|
||||||
if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
|
// allow sync parser reset even in continuation mode
|
||||||
/**
|
// Note: can be used to recover parser from improper continuation error above
|
||||||
* Reject further parsing on improper continuation after pausing.
|
if (this._parseStack.state === ParserStackType.RESET) {
|
||||||
* This is a really bad condition with screwed up execution order and messed up terminal state,
|
this._parseStack.state = ParserStackType.NONE;
|
||||||
* therefore we exit hard with an exception and reject any further parsing.
|
start = this._parseStack.chunkPos + 1; // continue with next codepoint in GROUND
|
||||||
*
|
} else {
|
||||||
* Note: With `Terminal.write` usage this exception should never occur, as the top level
|
if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
|
||||||
* 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
|
* Reject further parsing on improper continuation after pausing.
|
||||||
* `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
|
* This is a really bad condition with screwed up execution order and messed up terminal state,
|
||||||
* continuation of a running async handler.
|
* therefore we exit hard with an exception and reject any further parsing.
|
||||||
*/
|
*
|
||||||
this._parseStack.state = ParserStackType.FAIL;
|
* Note: With `Terminal.write` usage this exception should never occur, as the top level
|
||||||
throw new Error('improper continuation due to previous async handler, giving up parsing');
|
* 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.
|
||||||
|
*
|
||||||
|
* Its possible to get rid of this error condition by calling `reset`, but dont rely on that,
|
||||||
|
* as the pending async handler might mess up the terminal even further. Instead fix the faulty
|
||||||
|
* async handling, so this error will not be thrown anymore.
|
||||||
|
*/
|
||||||
|
this._parseStack.state = ParserStackType.FAIL;
|
||||||
|
throw new Error('improper continuation due to previous async handler, giving up parsing');
|
||||||
|
}
|
||||||
|
|
||||||
// we have to resume the old handler loop if:
|
// we have to resume the old handler loop if:
|
||||||
// - return value of the promise was `false`
|
// - return value of the promise was `false`
|
||||||
// - handlers are not exhausted yet
|
// - handlers are not exhausted yet
|
||||||
// FIXME: removing handlers from within a handler of the same sequence
|
// FIXME: removing handlers from within a handler of the same sequence
|
||||||
// is not supported atm (also true for sync handlers)!!
|
// is not supported atm (also true for sync handlers)!!
|
||||||
const handlers = this._parseStack.handlers;
|
const handlers = this._parseStack.handlers;
|
||||||
let handlerPos = this._parseStack.handlerPos - 1;
|
let handlerPos = this._parseStack.handlerPos - 1;
|
||||||
switch (this._parseStack.state) {
|
switch (this._parseStack.state) {
|
||||||
case ParserStackType.CSI:
|
case ParserStackType.CSI:
|
||||||
if (promiseResult === false && handlerPos > -1) {
|
if (promiseResult === false && handlerPos > -1) {
|
||||||
for (; handlerPos >= 0; handlerPos--) {
|
for (; handlerPos >= 0; handlerPos--) {
|
||||||
if ((handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params)) !== false) {
|
if ((handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params)) !== false) {
|
||||||
if (handlerResult instanceof Promise) {
|
if (handlerResult instanceof Promise) {
|
||||||
this._parseStack.handlerPos = handlerPos;
|
this._parseStack.handlerPos = handlerPos;
|
||||||
return handlerResult;
|
return handlerResult;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
this._parseStack.handlers = [];
|
||||||
this._parseStack.handlers = [];
|
break;
|
||||||
break;
|
case ParserStackType.ESC:
|
||||||
case ParserStackType.ESC:
|
if (promiseResult === false && handlerPos > -1) {
|
||||||
if (promiseResult === false && handlerPos > -1) {
|
for (; handlerPos >= 0; handlerPos--) {
|
||||||
for (; handlerPos >= 0; handlerPos--) {
|
if ((handlerResult = (handlers as EscHandlerType[])[handlerPos]()) !== false) {
|
||||||
if ((handlerResult = (handlers as EscHandlerType[])[handlerPos]()) !== false) {
|
if (handlerResult instanceof Promise) {
|
||||||
if (handlerResult instanceof Promise) {
|
this._parseStack.handlerPos = handlerPos;
|
||||||
this._parseStack.handlerPos = handlerPos;
|
return handlerResult;
|
||||||
return handlerResult;
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
this._parseStack.handlers = [];
|
||||||
this._parseStack.handlers = [];
|
break;
|
||||||
break;
|
case ParserStackType.DCS:
|
||||||
case ParserStackType.DCS:
|
code = data[this._parseStack.chunkPos];
|
||||||
code = data[this._parseStack.chunkPos];
|
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
||||||
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
return handlerResult;
|
||||||
return handlerResult;
|
}
|
||||||
}
|
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
||||||
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
this._params.reset();
|
||||||
this._params.reset();
|
this._params.addParam(0); // ZDM
|
||||||
this._params.addParam(0); // ZDM
|
this._collect = 0;
|
||||||
this._collect = 0;
|
break;
|
||||||
break;
|
case ParserStackType.OSC:
|
||||||
case ParserStackType.OSC:
|
code = data[this._parseStack.chunkPos];
|
||||||
code = data[this._parseStack.chunkPos];
|
if (handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
||||||
if (handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult)) {
|
return handlerResult;
|
||||||
return handlerResult;
|
}
|
||||||
}
|
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
||||||
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
this._params.reset();
|
||||||
this._params.reset();
|
this._params.addParam(0); // ZDM
|
||||||
this._params.addParam(0); // ZDM
|
this._collect = 0;
|
||||||
this._collect = 0;
|
break;
|
||||||
break;
|
}
|
||||||
|
// cleanup before continuing with the main sync loop
|
||||||
|
this._parseStack.state = ParserStackType.NONE;
|
||||||
|
start = this._parseStack.chunkPos + 1;
|
||||||
|
this.precedingCodepoint = 0;
|
||||||
|
this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;
|
||||||
}
|
}
|
||||||
// cleanup before continuing with the main sync loop
|
|
||||||
this._parseStack.state = ParserStackType.NONE;
|
|
||||||
start = this._parseStack.chunkPos + 1;
|
|
||||||
this.precedingCodepoint = 0;
|
|
||||||
this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// continue with main sync loop
|
// continue with main sync loop
|
||||||
|
|||||||
@@ -46,10 +46,13 @@ export class OscParser implements IOscParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
// cleanup handlers if payload was already sent
|
// force cleanup handlers if payload was already sent
|
||||||
if (this._state === OscState.PAYLOAD) {
|
if (this._state === OscState.PAYLOAD) {
|
||||||
this.end(false);
|
for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {
|
||||||
|
this._active[j].end(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this._stack.paused = false;
|
||||||
this._active = EMPTY_HANDLERS;
|
this._active = EMPTY_HANDLERS;
|
||||||
this._id = -1;
|
this._id = -1;
|
||||||
this._state = OscState.START;
|
this._state = OscState.START;
|
||||||
|
|||||||
Vendored
+1
@@ -245,6 +245,7 @@ export interface IHandlerCollection<T> {
|
|||||||
export const enum ParserStackType {
|
export const enum ParserStackType {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
FAIL,
|
FAIL,
|
||||||
|
RESET,
|
||||||
CSI,
|
CSI,
|
||||||
ESC,
|
ESC,
|
||||||
OSC,
|
OSC,
|
||||||
|
|||||||
Reference in New Issue
Block a user