@@ -427,6 +427,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
427427 this . _errorHandler = this . _errorHandlerFb ;
428428 }
429429
430+ /**
431+ * Reset parser to initial values.
432+ *
433+ * This can also be used to lift the improper continuation error condition
434+ * when dealing with async handlers. Use this only as a last resort to silence
435+ * that error when the terminal has no pending data to be processed. Note that
436+ * the interrupted async handler might continue its work in the future messing
437+ * up the terminal state even further.
438+ */
430439 public reset ( ) : void {
431440 this . currentState = this . initialState ;
432441 this . _oscParser . reset ( ) ;
@@ -435,6 +444,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
435444 this . _params . addParam ( 0 ) ; // ZDM
436445 this . _collect = 0 ;
437446 this . precedingCodepoint = 0 ;
447+ // abort pending continuation from async handler
448+ // Here the RESET type indicates, that the next parse call will
449+ // ignore any saved stack, instead continues sync with next codepoint from GROUND
450+ if ( this . _parseStack . state !== ParserStackType . NONE ) {
451+ this . _parseStack . state = ParserStackType . RESET ;
452+ this . _parseStack . handlers = [ ] ; // also release handlers ref
453+ }
438454 }
439455
440456 /**
@@ -486,12 +502,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
486502 * Important: With only sync handlers defined, parsing is completely synchronous as well.
487503 * As soon as an async handler is involved, synchronous parsing is not possible anymore.
488504 *
489- * FIXME: to be discussed
490- * While awaiting parse promises the terminal buffer state may not change.
491- * --> Implement lock semantics / promise chaining on buffer alterations? Waah, pandora's box ;)
492- * --> Maybe easier: Give up on non-mutating rule for async handlers...
493- * (needs explanation in docs about exact executor/thenable/worker execution contexts)
494- *
495505 * Boilerplate for proper parsing of multiple chunks with async handlers:
496506 *
497507 * ```typescript
@@ -515,84 +525,95 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
515525
516526 // resume from async handler
517527 if ( this . _parseStack . state ) {
518- if ( promiseResult === undefined || this . _parseStack . state === ParserStackType . FAIL ) {
519- /**
520- * Reject further parsing on improper continuation after pausing.
521- * This is a really bad condition with screwed up execution order and messed up terminal state,
522- * therefore we exit hard with an exception and reject any further parsing.
523- *
524- * Note: With `Terminal.write` usage this exception should never occur, as the top level
525- * calls are guaranteed to handle async conditions properly. If you ever encounter this
526- * exception in your terminal integration it indicates, that you injected data chunks to
527- * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
528- * continuation of a running async handler.
529- */
530- this . _parseStack . state = ParserStackType . FAIL ;
531- throw new Error ( 'improper continuation due to previous async handler, giving up parsing' ) ;
532- }
528+ // allow sync parser reset even in continuation mode
529+ // Note: can be used to recover parser from improper continuation error above
530+ if ( this . _parseStack . state === ParserStackType . RESET ) {
531+ this . _parseStack . state = ParserStackType . NONE ;
532+ start = this . _parseStack . chunkPos + 1 ; // continue with next codepoint in GROUND
533+ } else {
534+ if ( promiseResult === undefined || this . _parseStack . state === ParserStackType . FAIL ) {
535+ /**
536+ * Reject further parsing on improper continuation after pausing.
537+ * This is a really bad condition with screwed up execution order and messed up terminal state,
538+ * therefore we exit hard with an exception and reject any further parsing.
539+ *
540+ * Note: With `Terminal.write` usage this exception should never occur, as the top level
541+ * calls are guaranteed to handle async conditions properly. If you ever encounter this
542+ * exception in your terminal integration it indicates, that you injected data chunks to
543+ * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
544+ * continuation of a running async handler.
545+ *
546+ * Its possible to get rid of this error condition by calling `reset`, but dont rely on that,
547+ * as the pending async handler might mess up the terminal even further. Instead fix the faulty
548+ * async handling, so this error will not be thrown anymore.
549+ */
550+ this . _parseStack . state = ParserStackType . FAIL ;
551+ throw new Error ( 'improper continuation due to previous async handler, giving up parsing' ) ;
552+ }
533553
534- // we have to resume the old handler loop if:
535- // - return value of the promise was `false`
536- // - handlers are not exhausted yet
537- // FIXME: removing handlers from within a handler of the same sequence
538- // is not supported atm (also true for sync handlers)!!
539- const handlers = this . _parseStack . handlers ;
540- let handlerPos = this . _parseStack . handlerPos - 1 ;
541- switch ( this . _parseStack . state ) {
542- case ParserStackType . CSI :
543- if ( promiseResult === false && handlerPos > - 1 ) {
544- for ( ; handlerPos >= 0 ; handlerPos -- ) {
545- if ( ( handlerResult = ( handlers as CsiHandlerType [ ] ) [ handlerPos ] ( this . _params ) ) !== false ) {
546- if ( handlerResult instanceof Promise ) {
547- this . _parseStack . handlerPos = handlerPos ;
548- return handlerResult ;
554+ // we have to resume the old handler loop if:
555+ // - return value of the promise was `false`
556+ // - handlers are not exhausted yet
557+ // FIXME: removing handlers from within a handler of the same sequence
558+ // is not supported atm (also true for sync handlers)!!
559+ const handlers = this . _parseStack . handlers ;
560+ let handlerPos = this . _parseStack . handlerPos - 1 ;
561+ switch ( this . _parseStack . state ) {
562+ case ParserStackType . CSI :
563+ if ( promiseResult === false && handlerPos > - 1 ) {
564+ for ( ; handlerPos >= 0 ; handlerPos -- ) {
565+ if ( ( handlerResult = ( handlers as CsiHandlerType [ ] ) [ handlerPos ] ( this . _params ) ) !== false ) {
566+ if ( handlerResult instanceof Promise ) {
567+ this . _parseStack . handlerPos = handlerPos ;
568+ return handlerResult ;
569+ }
570+ break ;
549571 }
550- break ;
551572 }
552573 }
553- }
554- this . _parseStack . handlers = [ ] ;
555- break ;
556- case ParserStackType . ESC :
557- if ( promiseResult === false && handlerPos > - 1 ) {
558- for ( ; handlerPos >= 0 ; handlerPos -- ) {
559- if ( ( handlerResult = ( handlers as EscHandlerType [ ] ) [ handlerPos ] ( ) ) !== false ) {
560- if ( handlerResult instanceof Promise ) {
561- this . _parseStack . handlerPos = handlerPos ;
562- return handlerResult ;
574+ this . _parseStack . handlers = [ ] ;
575+ break ;
576+ case ParserStackType . ESC :
577+ if ( promiseResult === false && handlerPos > - 1 ) {
578+ for ( ; handlerPos >= 0 ; handlerPos -- ) {
579+ if ( ( handlerResult = ( handlers as EscHandlerType [ ] ) [ handlerPos ] ( ) ) !== false ) {
580+ if ( handlerResult instanceof Promise ) {
581+ this . _parseStack . handlerPos = handlerPos ;
582+ return handlerResult ;
583+ }
584+ break ;
563585 }
564- break ;
565586 }
566587 }
567- }
568- this . _parseStack . handlers = [ ] ;
569- break ;
570- case ParserStackType . DCS :
571- code = data [ this . _parseStack . chunkPos ] ;
572- if ( handlerResult = this . _dcsParser . unhook ( code !== 0x18 && code !== 0x1a , promiseResult ) ) {
573- return handlerResult ;
574- }
575- if ( code === 0x1b ) this . _parseStack . transition |= ParserState . ESCAPE ;
576- this . _params . reset ( ) ;
577- this . _params . addParam ( 0 ) ; // ZDM
578- this . _collect = 0 ;
579- break ;
580- case ParserStackType . OSC :
581- code = data [ this . _parseStack . chunkPos ] ;
582- if ( handlerResult = this . _oscParser . end ( code !== 0x18 && code !== 0x1a , promiseResult ) ) {
583- return handlerResult ;
584- }
585- if ( code === 0x1b ) this . _parseStack . transition |= ParserState . ESCAPE ;
586- this . _params . reset ( ) ;
587- this . _params . addParam ( 0 ) ; // ZDM
588- this . _collect = 0 ;
589- break ;
588+ this . _parseStack . handlers = [ ] ;
589+ break ;
590+ case ParserStackType . DCS :
591+ code = data [ this . _parseStack . chunkPos ] ;
592+ if ( handlerResult = this . _dcsParser . unhook ( code !== 0x18 && code !== 0x1a , promiseResult ) ) {
593+ return handlerResult ;
594+ }
595+ if ( code === 0x1b ) this . _parseStack . transition |= ParserState . ESCAPE ;
596+ this . _params . reset ( ) ;
597+ this . _params . addParam ( 0 ) ; // ZDM
598+ this . _collect = 0 ;
599+ break ;
600+ case ParserStackType . OSC :
601+ code = data [ this . _parseStack . chunkPos ] ;
602+ if ( handlerResult = this . _oscParser . end ( code !== 0x18 && code !== 0x1a , promiseResult ) ) {
603+ return handlerResult ;
604+ }
605+ if ( code === 0x1b ) this . _parseStack . transition |= ParserState . ESCAPE ;
606+ this . _params . reset ( ) ;
607+ this . _params . addParam ( 0 ) ; // ZDM
608+ this . _collect = 0 ;
609+ break ;
610+ }
611+ // cleanup before continuing with the main sync loop
612+ this . _parseStack . state = ParserStackType . NONE ;
613+ start = this . _parseStack . chunkPos + 1 ;
614+ this . precedingCodepoint = 0 ;
615+ this . currentState = this . _parseStack . transition & TableAccess . TRANSITION_STATE_MASK ;
590616 }
591- // cleanup before continuing with the main sync loop
592- this . _parseStack . state = ParserStackType . NONE ;
593- start = this . _parseStack . chunkPos + 1 ;
594- this . precedingCodepoint = 0 ;
595- this . currentState = this . _parseStack . transition & TableAccess . TRANSITION_STATE_MASK ;
596617 }
597618
598619 // continue with main sync loop
0 commit comments