diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 32428c77..001c9995 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -146,36 +146,42 @@ class DECRQSS implements IDcsHandler { this._data = concat(this._data, data.subarray(start, end)); } - public unhook(success: boolean): void { + public unhook(success: boolean): boolean { if (!success) { this._data = new Uint32Array(0); - return; + return true; } const data = utf32ToString(this._data); this._data = new Uint32Array(0); switch (data) { // valid: DCS 1 $ r Pt ST (xterm) case '"q': // DECSCA - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`); + break; case '"p': // DECSCL - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r61;1"p${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r61;1"p${C0.ESC}\\`); + break; case 'r': // DECSTBM const pt = '' + (this._bufferService.buffer.scrollTop + 1) + ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); + break; case 'm': // SGR // TODO: report real settings instead of 0m - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`); + break; case ' q': // DECSCUSR const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6}; let style = STYLES[this._optionsService.options.cursorStyle]; style -= this._optionsService.options.cursorBlink ? 1 : 0; - return this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); + this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); + break; default: // invalid: DCS 0 $ r Pt ST (xterm) this._logService.debug('Unknown DCS $q %s', data); this._coreService.triggerDataEvent(`${C0.ESC}P0$r${C0.ESC}\\`); } + return true; } } @@ -297,53 +303,53 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI handler */ - this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); - this._parser.setCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params)); - this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); - this._parser.setCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params)); - this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); - this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); - this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); - this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); - this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); - this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); - this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params)); - this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); - this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); - this._parser.setCsiHandler({prefix: '?', final: 'J'}, params => this.eraseInDisplay(params)); - this._parser.setCsiHandler({final: 'K'}, params => this.eraseInLine(params)); - this._parser.setCsiHandler({prefix: '?', final: 'K'}, params => this.eraseInLine(params)); - this._parser.setCsiHandler({final: 'L'}, params => this.insertLines(params)); - this._parser.setCsiHandler({final: 'M'}, params => this.deleteLines(params)); - this._parser.setCsiHandler({final: 'P'}, params => this.deleteChars(params)); - this._parser.setCsiHandler({final: 'S'}, params => this.scrollUp(params)); - this._parser.setCsiHandler({final: 'T'}, params => this.scrollDown(params)); - this._parser.setCsiHandler({final: 'X'}, params => this.eraseChars(params)); - this._parser.setCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params)); - this._parser.setCsiHandler({final: '`'}, params => this.charPosAbsolute(params)); - this._parser.setCsiHandler({final: 'a'}, params => this.hPositionRelative(params)); - this._parser.setCsiHandler({final: 'b'}, params => this.repeatPrecedingCharacter(params)); - this._parser.setCsiHandler({final: 'c'}, params => this.sendDeviceAttributesPrimary(params)); - this._parser.setCsiHandler({prefix: '>', final: 'c'}, params => this.sendDeviceAttributesSecondary(params)); - this._parser.setCsiHandler({final: 'd'}, params => this.linePosAbsolute(params)); - this._parser.setCsiHandler({final: 'e'}, params => this.vPositionRelative(params)); - this._parser.setCsiHandler({final: 'f'}, params => this.hVPosition(params)); - this._parser.setCsiHandler({final: 'g'}, params => this.tabClear(params)); - this._parser.setCsiHandler({final: 'h'}, params => this.setMode(params)); - this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); - this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params)); - this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); - this._parser.setCsiHandler({final: 'm'}, (params => this.charAttributes(params)) as (p: any) => void); - this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params)); - this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); - this._parser.setCsiHandler({intermediates: '!', final: 'p'}, params => this.softReset(params)); - this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); - this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); - this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params)); - this._parser.setCsiHandler({final: 't'}, params => this.windowOptions(params)); - this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params)); - this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params)); - this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params)); + this._parser.registerCsiHandler({final: '@'}, params => this.insertChars(params)); + this._parser.registerCsiHandler({intermediates: ' ', final: '@'}, params => this.scrollLeft(params)); + this._parser.registerCsiHandler({final: 'A'}, params => this.cursorUp(params)); + this._parser.registerCsiHandler({intermediates: ' ', final: 'A'}, params => this.scrollRight(params)); + this._parser.registerCsiHandler({final: 'B'}, params => this.cursorDown(params)); + this._parser.registerCsiHandler({final: 'C'}, params => this.cursorForward(params)); + this._parser.registerCsiHandler({final: 'D'}, params => this.cursorBackward(params)); + this._parser.registerCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); + this._parser.registerCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); + this._parser.registerCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); + this._parser.registerCsiHandler({final: 'H'}, params => this.cursorPosition(params)); + this._parser.registerCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); + this._parser.registerCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); + this._parser.registerCsiHandler({prefix: '?', final: 'J'}, params => this.eraseInDisplay(params)); + this._parser.registerCsiHandler({final: 'K'}, params => this.eraseInLine(params)); + this._parser.registerCsiHandler({prefix: '?', final: 'K'}, params => this.eraseInLine(params)); + this._parser.registerCsiHandler({final: 'L'}, params => this.insertLines(params)); + this._parser.registerCsiHandler({final: 'M'}, params => this.deleteLines(params)); + this._parser.registerCsiHandler({final: 'P'}, params => this.deleteChars(params)); + this._parser.registerCsiHandler({final: 'S'}, params => this.scrollUp(params)); + this._parser.registerCsiHandler({final: 'T'}, params => this.scrollDown(params)); + this._parser.registerCsiHandler({final: 'X'}, params => this.eraseChars(params)); + this._parser.registerCsiHandler({final: 'Z'}, params => this.cursorBackwardTab(params)); + this._parser.registerCsiHandler({final: '`'}, params => this.charPosAbsolute(params)); + this._parser.registerCsiHandler({final: 'a'}, params => this.hPositionRelative(params)); + this._parser.registerCsiHandler({final: 'b'}, params => this.repeatPrecedingCharacter(params)); + this._parser.registerCsiHandler({final: 'c'}, params => this.sendDeviceAttributesPrimary(params)); + this._parser.registerCsiHandler({prefix: '>', final: 'c'}, params => this.sendDeviceAttributesSecondary(params)); + this._parser.registerCsiHandler({final: 'd'}, params => this.linePosAbsolute(params)); + this._parser.registerCsiHandler({final: 'e'}, params => this.vPositionRelative(params)); + this._parser.registerCsiHandler({final: 'f'}, params => this.hVPosition(params)); + this._parser.registerCsiHandler({final: 'g'}, params => this.tabClear(params)); + this._parser.registerCsiHandler({final: 'h'}, params => this.setMode(params)); + this._parser.registerCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); + this._parser.registerCsiHandler({final: 'l'}, params => this.resetMode(params)); + this._parser.registerCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); + this._parser.registerCsiHandler({final: 'm'}, params => this.charAttributes(params)); + this._parser.registerCsiHandler({final: 'n'}, params => this.deviceStatus(params)); + this._parser.registerCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); + this._parser.registerCsiHandler({intermediates: '!', final: 'p'}, params => this.softReset(params)); + this._parser.registerCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params)); + this._parser.registerCsiHandler({final: 'r'}, params => this.setScrollRegion(params)); + this._parser.registerCsiHandler({final: 's'}, params => this.saveCursor(params)); + this._parser.registerCsiHandler({final: 't'}, params => this.windowOptions(params)); + this._parser.registerCsiHandler({final: 'u'}, params => this.restoreCursor(params)); + this._parser.registerCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params)); + this._parser.registerCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params)); /** * execute handler @@ -367,14 +373,14 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title - this._parser.setOscHandler(0, new OscHandler((data: string) => { this.setTitle(data); this.setIconName(data); })); + this._parser.registerOscHandler(0, new OscHandler(data => { this.setTitle(data); this.setIconName(data); return true; })); // 1 - icon name - this._parser.setOscHandler(1, new OscHandler((data: string) => this.setIconName(data))); + this._parser.registerOscHandler(1, new OscHandler(data => this.setIconName(data))); // 2 - title - this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); + this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number - this._parser.setOscHandler(4, new OscHandler((data: string) => this.setAnsiColor(data))); + this._parser.registerOscHandler(4, new OscHandler(data => this.setAnsiColor(data))); // 5 - Change Special Color Number // 6 - Enable/disable Special Color Number c // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939) @@ -409,32 +415,32 @@ export class InputHandler extends Disposable implements IInputHandler { /** * ESC handlers */ - this._parser.setEscHandler({final: '7'}, () => this.saveCursor()); - this._parser.setEscHandler({final: '8'}, () => this.restoreCursor()); - this._parser.setEscHandler({final: 'D'}, () => this.index()); - this._parser.setEscHandler({final: 'E'}, () => this.nextLine()); - this._parser.setEscHandler({final: 'H'}, () => this.tabSet()); - this._parser.setEscHandler({final: 'M'}, () => this.reverseIndex()); - this._parser.setEscHandler({final: '='}, () => this.keypadApplicationMode()); - this._parser.setEscHandler({final: '>'}, () => this.keypadNumericMode()); - this._parser.setEscHandler({final: 'c'}, () => this.fullReset()); - this._parser.setEscHandler({final: 'n'}, () => this.setgLevel(2)); - this._parser.setEscHandler({final: 'o'}, () => this.setgLevel(3)); - this._parser.setEscHandler({final: '|'}, () => this.setgLevel(3)); - this._parser.setEscHandler({final: '}'}, () => this.setgLevel(2)); - this._parser.setEscHandler({final: '~'}, () => this.setgLevel(1)); - this._parser.setEscHandler({intermediates: '%', final: '@'}, () => this.selectDefaultCharset()); - this._parser.setEscHandler({intermediates: '%', final: 'G'}, () => this.selectDefaultCharset()); + this._parser.registerEscHandler({final: '7'}, () => this.saveCursor()); + this._parser.registerEscHandler({final: '8'}, () => this.restoreCursor()); + this._parser.registerEscHandler({final: 'D'}, () => this.index()); + this._parser.registerEscHandler({final: 'E'}, () => this.nextLine()); + this._parser.registerEscHandler({final: 'H'}, () => this.tabSet()); + this._parser.registerEscHandler({final: 'M'}, () => this.reverseIndex()); + this._parser.registerEscHandler({final: '='}, () => this.keypadApplicationMode()); + this._parser.registerEscHandler({final: '>'}, () => this.keypadNumericMode()); + this._parser.registerEscHandler({final: 'c'}, () => this.fullReset()); + this._parser.registerEscHandler({final: 'n'}, () => this.setgLevel(2)); + this._parser.registerEscHandler({final: 'o'}, () => this.setgLevel(3)); + this._parser.registerEscHandler({final: '|'}, () => this.setgLevel(3)); + this._parser.registerEscHandler({final: '}'}, () => this.setgLevel(2)); + this._parser.registerEscHandler({final: '~'}, () => this.setgLevel(1)); + this._parser.registerEscHandler({intermediates: '%', final: '@'}, () => this.selectDefaultCharset()); + this._parser.registerEscHandler({intermediates: '%', final: 'G'}, () => this.selectDefaultCharset()); for (const flag in CHARSETS) { - this._parser.setEscHandler({intermediates: '(', final: flag}, () => this.selectCharset('(' + flag)); - this._parser.setEscHandler({intermediates: ')', final: flag}, () => this.selectCharset(')' + flag)); - this._parser.setEscHandler({intermediates: '*', final: flag}, () => this.selectCharset('*' + flag)); - this._parser.setEscHandler({intermediates: '+', final: flag}, () => this.selectCharset('+' + flag)); - this._parser.setEscHandler({intermediates: '-', final: flag}, () => this.selectCharset('-' + flag)); - this._parser.setEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag)); - this._parser.setEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported? + this._parser.registerEscHandler({intermediates: '(', final: flag}, () => this.selectCharset('(' + flag)); + this._parser.registerEscHandler({intermediates: ')', final: flag}, () => this.selectCharset(')' + flag)); + this._parser.registerEscHandler({intermediates: '*', final: flag}, () => this.selectCharset('*' + flag)); + this._parser.registerEscHandler({intermediates: '+', final: flag}, () => this.selectCharset('+' + flag)); + this._parser.registerEscHandler({intermediates: '-', final: flag}, () => this.selectCharset('-' + flag)); + this._parser.registerEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag)); + this._parser.registerEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported? } - this._parser.setEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern()); + this._parser.registerEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern()); /** * error handler @@ -447,7 +453,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DCS handler */ - this._parser.setDcsHandler({intermediates: '$', final: 'q'}, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService)); + this._parser.registerDcsHandler({intermediates: '$', final: 'q'}, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService)); } public dispose(): void { @@ -686,35 +692,35 @@ export class InputHandler extends Disposable implements IInputHandler { public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { if (id.final === 't' && !id.prefix && !id.intermediates) { // security: always check whether window option is allowed - return this._parser.addCsiHandler(id, params => { + return this._parser.registerCsiHandler(id, params => { if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { return true; } return callback(params); }); } - return this._parser.addCsiHandler(id, callback); + return this._parser.registerCsiHandler(id, callback); } /** * Forward addDcsHandler from parser. */ public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { - return this._parser.addDcsHandler(id, new DcsHandler(callback)); + return this._parser.registerDcsHandler(id, new DcsHandler(callback)); } /** * Forward addEscHandler from parser. */ public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable { - return this._parser.addEscHandler(id, callback); + return this._parser.registerEscHandler(id, callback); } /** * Forward addOscHandler from parser. */ public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - return this._parser.addOscHandler(ident, new OscHandler(callback)); + return this._parser.registerOscHandler(ident, new OscHandler(callback)); } /** @@ -725,8 +731,9 @@ export class InputHandler extends Disposable implements IInputHandler { * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle` * and `ITerminalOptions.bellSound`. */ - public bell(): void { + public bell(): boolean { this._onRequestBell.fire(); + return true; } /** @@ -739,7 +746,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 VT "Vertical Tabulation" "\v, \x0B" "Treated as LF." * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ - public lineFeed(): void { + public lineFeed(): boolean { // make buffer local for faster access const buffer = this._bufferService.buffer; @@ -761,6 +768,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markDirty(buffer.y); this._onLineFeed.fire(); + return true; } /** @@ -769,8 +777,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row." */ - public carriageReturn(): void { + public carriageReturn(): boolean { this._bufferService.buffer.x = 0; + return true; } /** @@ -784,7 +793,7 @@ export class InputHandler extends Disposable implements IInputHandler { * to the end of the previous row. Note that it is not possible to peek back into the scrollbuffer * with the cursor, thus at the home position (top-leftmost cell) this has no effect. */ - public backspace(): void { + public backspace(): boolean { const buffer = this._bufferService.buffer; // reverse wrap-around is disabled @@ -793,7 +802,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x > 0) { buffer.x--; } - return; + return true; } // reverse wrap-around is enabled @@ -834,6 +843,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } this._restrictCursor(); + return true; } /** @@ -842,15 +852,16 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop." */ - public tab(): void { + public tab(): boolean { if (this._bufferService.buffer.x >= this._bufferService.cols) { - return; + return true; } const originalX = this._bufferService.buffer.x; this._bufferService.buffer.x = this._bufferService.buffer.nextStop(); if (this._optionsService.options.screenReaderMode) { this._onA11yTab.fire(this._bufferService.buffer.x - originalX); } + return true; } /** @@ -860,8 +871,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #P[Only limited ISO-2022 charset support.] C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." */ - public shiftOut(): void { + public shiftOut(): boolean { this._charsetService.setgLevel(1); + return true; } /** @@ -871,8 +883,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." */ - public shiftIn(): void { + public shiftIn(): boolean { this._charsetService.setgLevel(0); + return true; } /** @@ -919,7 +932,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)." * If the cursor would pass the top scroll margin, it will stop there. */ - public cursorUp(params: IParams): void { + public cursorUp(params: IParams): boolean { // stop at scrollTop const diffToTop = this._bufferService.buffer.y - this._bufferService.buffer.scrollTop; if (diffToTop >= 0) { @@ -927,6 +940,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else { this._moveCursor(0, -(params.params[0] || 1)); } + return true; } /** @@ -936,7 +950,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)." * If the cursor would pass the bottom scroll margin, it will stop there. */ - public cursorDown(params: IParams): void { + public cursorDown(params: IParams): boolean { // stop at scrollBottom const diffToBottom = this._bufferService.buffer.scrollBottom - this._bufferService.buffer.y; if (diffToBottom >= 0) { @@ -944,6 +958,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else { this._moveCursor(0, params.params[0] || 1); } + return true; } /** @@ -952,8 +967,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)." */ - public cursorForward(params: IParams): void { + public cursorForward(params: IParams): boolean { this._moveCursor(params.params[0] || 1, 0); + return true; } /** @@ -962,8 +978,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)." */ - public cursorBackward(params: IParams): void { + public cursorBackward(params: IParams): boolean { this._moveCursor(-(params.params[0] || 1), 0); + return true; } /** @@ -974,9 +991,10 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column." * Same as CUD, additionally places the cursor at the first column. */ - public cursorNextLine(params: IParams): void { + public cursorNextLine(params: IParams): boolean { this.cursorDown(params); this._bufferService.buffer.x = 0; + return true; } /** @@ -987,9 +1005,10 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column." * Same as CUU, additionally places the cursor at the first column. */ - public cursorPrecedingLine(params: IParams): void { + public cursorPrecedingLine(params: IParams): boolean { this.cursorUp(params); this._bufferService.buffer.x = 0; + return true; } /** @@ -998,8 +1017,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)." */ - public cursorCharAbsolute(params: IParams): void { + public cursorCharAbsolute(params: IParams): boolean { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); + return true; } /** @@ -1011,12 +1031,14 @@ export class InputHandler extends Disposable implements IInputHandler { * If ORIGIN mode is not set, places the cursor to the absolute position within the viewport. * Note that the coordinates are 1-based, thus the top left position starts at `1 ; 1`. */ - public cursorPosition(params: IParams): void { + public cursorPosition(params: IParams): boolean { this._setCursor( // col (params.length >= 2) ? (params.params[1] || 1) - 1 : 0, // row - (params.params[0] || 1) - 1); + (params.params[0] || 1) - 1 + ); + return true; } /** @@ -1026,8 +1048,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA." */ - public charPosAbsolute(params: IParams): void { + public charPosAbsolute(params: IParams): boolean { this._setCursor((params.params[0] || 1) - 1, this._bufferService.buffer.y); + return true; } /** @@ -1036,8 +1059,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF." */ - public hPositionRelative(params: IParams): void { + public hPositionRelative(params: IParams): boolean { this._moveCursor(params.params[0] || 1, 0); + return true; } /** @@ -1046,8 +1070,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)." */ - public linePosAbsolute(params: IParams): void { + public linePosAbsolute(params: IParams): boolean { this._setCursor(this._bufferService.buffer.x, (params.params[0] || 1) - 1); + return true; } /** @@ -1057,8 +1082,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)." */ - public vPositionRelative(params: IParams): void { + public vPositionRelative(params: IParams): boolean { this._moveCursor(0, params.params[0] || 1); + return true; } /** @@ -1069,8 +1095,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP." */ - public hVPosition(params: IParams): void { + public hVPosition(params: IParams): boolean { this.cursorPosition(params); + return true; } /** @@ -1084,13 +1111,14 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." * Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported. */ - public tabClear(params: IParams): void { + public tabClear(params: IParams): boolean { const param = params.params[0]; if (param === 0) { delete this._bufferService.buffer.tabs[this._bufferService.buffer.x]; } else if (param === 3) { this._bufferService.buffer.tabs = {}; } + return true; } /** @@ -1099,14 +1127,15 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." */ - public cursorForwardTab(params: IParams): void { + public cursorForwardTab(params: IParams): boolean { if (this._bufferService.buffer.x >= this._bufferService.cols) { - return; + return true; } let param = params.params[0] || 1; while (param--) { this._bufferService.buffer.x = this._bufferService.buffer.nextStop(); } + return true; } /** @@ -1114,9 +1143,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)." */ - public cursorBackwardTab(params: IParams): void { + public cursorBackwardTab(params: IParams): boolean { if (this._bufferService.buffer.x >= this._bufferService.cols) { - return; + return true; } let param = params.params[0] || 1; @@ -1126,6 +1155,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.x = buffer.prevStop(); } + return true; } @@ -1184,7 +1214,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ - public eraseInDisplay(params: IParams): void { + public eraseInDisplay(params: IParams): boolean { this._restrictCursor(); let j; switch (params.params[0]) { @@ -1231,6 +1261,7 @@ export class InputHandler extends Disposable implements IInputHandler { } break; } + return true; } /** @@ -1255,7 +1286,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ - public eraseInLine(params: IParams): void { + public eraseInLine(params: IParams): boolean { this._restrictCursor(); switch (params.params[0]) { case 0: @@ -1269,6 +1300,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } this._dirtyRowService.markDirty(this._bufferService.buffer.y); + return true; } /** @@ -1280,7 +1312,7 @@ export class InputHandler extends Disposable implements IInputHandler { * The cursor is set to the first column. * IL has no effect if the cursor is outside the scroll margins. */ - public insertLines(params: IParams): void { + public insertLines(params: IParams): boolean { this._restrictCursor(); let param = params.params[0] || 1; @@ -1288,7 +1320,7 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const row: number = buffer.ybase + buffer.y; @@ -1304,6 +1336,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom); buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? + return true; } /** @@ -1315,7 +1348,7 @@ export class InputHandler extends Disposable implements IInputHandler { * The cursor is set to the first column. * DL has no effect if the cursor is outside the scroll margins. */ - public deleteLines(params: IParams): void { + public deleteLines(params: IParams): boolean { this._restrictCursor(); let param = params.params[0] || 1; @@ -1323,7 +1356,7 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const row: number = buffer.ybase + buffer.y; @@ -1340,6 +1373,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._dirtyRowService.markRangeDirty(buffer.y, buffer.scrollBottom); buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? + return true; } /** @@ -1353,7 +1387,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ - public insertChars(params: IParams): void { + public insertChars(params: IParams): boolean { this._restrictCursor(); const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { @@ -1365,6 +1399,7 @@ export class InputHandler extends Disposable implements IInputHandler { ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } + return true; } /** @@ -1378,7 +1413,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) */ - public deleteChars(params: IParams): void { + public deleteChars(params: IParams): boolean { this._restrictCursor(); const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { @@ -1390,6 +1425,7 @@ export class InputHandler extends Disposable implements IInputHandler { ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } + return true; } /** @@ -1400,7 +1436,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) */ - public scrollUp(params: IParams): void { + public scrollUp(params: IParams): boolean { let param = params.params[0] || 1; // make buffer local for faster access @@ -1411,6 +1447,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(this._eraseAttrData())); } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1418,7 +1455,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." */ - public scrollDown(params: IParams): void { + public scrollDown(params: IParams): boolean { let param = params.params[0] || 1; // make buffer local for faster access @@ -1429,6 +1466,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1449,10 +1487,10 @@ export class InputHandler extends Disposable implements IInputHandler { * SL moves the content of all lines within the scroll margins `Ps` times to the left. * SL has no effect outside of the scroll margins. */ - public scrollLeft(params: IParams): void { + public scrollLeft(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1461,6 +1499,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1482,10 +1521,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Content at the right margin is lost. * SL has no effect outside of the scroll margins. */ - public scrollRight(params: IParams): void { + public scrollRight(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1494,6 +1533,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1505,10 +1545,10 @@ export class InputHandler extends Disposable implements IInputHandler { * moving content to the right. Content at the right margin is lost. * DECIC has no effect outside the scrolling margins. */ - public insertColumns(params: IParams): void { + public insertColumns(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1517,6 +1557,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1528,10 +1569,10 @@ export class InputHandler extends Disposable implements IInputHandler { * moving content to the left. Blank columns are added at the right margin. * DECDC has no effect outside the scrolling margins. */ - public deleteColumns(params: IParams): void { + public deleteColumns(params: IParams): boolean { const buffer = this._bufferService.buffer; if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { - return; + return true; } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { @@ -1540,6 +1581,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + return true; } /** @@ -1550,7 +1592,7 @@ export class InputHandler extends Disposable implements IInputHandler { * ED erases `Ps` characters from current cursor position to the right. * ED works inside or outside the scrolling margins. */ - public eraseChars(params: IParams): void { + public eraseChars(params: IParams): boolean { this._restrictCursor(); const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { @@ -1562,6 +1604,7 @@ export class InputHandler extends Disposable implements IInputHandler { ); this._dirtyRowService.markDirty(this._bufferService.buffer.y); } + return true; } /** @@ -1591,9 +1634,9 @@ export class InputHandler extends Disposable implements IInputHandler { * REP has no effect if the sequence does not follow a printable ASCII character * (NOOP for any other sequence in between or NON ASCII characters). */ - public repeatPrecedingCharacter(params: IParams): void { + public repeatPrecedingCharacter(params: IParams): boolean { if (!this._parser.precedingCodepoint) { - return; + return true; } // call print to insert the chars and handle correct wrapping const length = params.params[0] || 1; @@ -1602,6 +1645,7 @@ export class InputHandler extends Disposable implements IInputHandler { data[i] = this._parser.precedingCodepoint; } this.print(data, 0, data.length); + return true; } /** @@ -1629,15 +1673,16 @@ export class InputHandler extends Disposable implements IInputHandler { * * TODO: fix and cleanup response */ - public sendDeviceAttributesPrimary(params: IParams): void { + public sendDeviceAttributesPrimary(params: IParams): boolean { if (params.params[0] > 0) { - return; + return true; } if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); } else if (this._is('linux')) { this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } + return true; } /** @@ -1664,9 +1709,9 @@ export class InputHandler extends Disposable implements IInputHandler { * * TODO: fix and cleanup response */ - public sendDeviceAttributesSecondary(params: IParams): void { + public sendDeviceAttributesSecondary(params: IParams): boolean { if (params.params[0] > 0) { - return; + return true; } // xterm and urxvt // seem to spit this @@ -1682,6 +1727,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c'); } + return true; } /** @@ -1709,7 +1755,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 12 | Send/receive (SRM). Always off. | #N | * | 20 | Automatic Newline (LNM). Always off. | #N | */ - public setMode(params: IParams): void { + public setMode(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: @@ -1720,6 +1766,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -1835,7 +1882,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: implement DECSCNM, 1049 should clear altbuffer */ - public setModePrivate(params: IParams): void { + public setModePrivate(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 1: @@ -1928,6 +1975,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } @@ -1951,7 +1999,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: why is LNM commented out? */ - public resetMode(params: IParams): void { + public resetMode(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: @@ -1962,6 +2010,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -2073,7 +2122,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: DECCOLM is currently broken (already fixed in window options PR) */ - public resetModePrivate(params: IParams): void { + public resetModePrivate(params: IParams): boolean { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 1: @@ -2150,6 +2199,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } + return true; } /** @@ -2342,12 +2392,12 @@ export class InputHandler extends Disposable implements IInputHandler { * FIXME: blinking is implemented in attrs, but not working in renderers? * FIXME: remove dead branch for p=100 */ - public charAttributes(params: IParams): void { + public charAttributes(params: IParams): boolean { // Optimize a single SGR0. if (params.length === 1 && params.params[0] === 0) { this._curAttrData.fg = DEFAULT_ATTR_DATA.fg; this._curAttrData.bg = DEFAULT_ATTR_DATA.bg; - return; + return true; } const l = params.length; @@ -2446,6 +2496,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._logService.debug('Unknown SGR attribute: %d.', p); } } + return true; } /** @@ -2473,7 +2524,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6." */ - public deviceStatus(params: IParams): void { + public deviceStatus(params: IParams): boolean { switch (params.params[0]) { case 5: // status report @@ -2486,10 +2537,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`); break; } + return true; } // @vt: #P[Only CPR is supported.] CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)." - public deviceStatusPrivate(params: IParams): void { + public deviceStatusPrivate(params: IParams): boolean { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 switch (params.params[0]) { @@ -2516,6 +2568,7 @@ export class InputHandler extends Disposable implements IInputHandler { // this.handler(C0.ESC + '[?50n'); break; } + return true; } /** @@ -2537,7 +2590,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * FIXME: there are several more attributes missing (see VT520 manual) */ - public softReset(params: IParams): void { + public softReset(params: IParams): boolean { this._coreService.isCursorHidden = false; this._onRequestSyncScrollBar.fire(); this._bufferService.buffer.scrollTop = 0; @@ -2555,6 +2608,7 @@ export class InputHandler extends Disposable implements IInputHandler { // reset DECOM this._coreService.decPrivateModes.origin = false; + return true; } /** @@ -2576,7 +2630,7 @@ export class InputHandler extends Disposable implements IInputHandler { * - 5: steady bar * - 6: blink bar */ - public setCursorStyle(params: IParams): void { + public setCursorStyle(params: IParams): boolean { const param = params.params[0] || 1; switch (param) { case 1: @@ -2594,6 +2648,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const isBlinking = param % 2 === 1; this._optionsService.options.cursorBlink = isBlinking; + return true; } /** @@ -2603,7 +2658,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)." */ - public setScrollRegion(params: IParams): void { + public setScrollRegion(params: IParams): boolean { const top = params.params[0] || 1; let bottom: number; @@ -2616,6 +2671,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.scrollBottom = bottom - 1; this._setCursor(0, 0); } + return true; } /** @@ -2648,9 +2704,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 23 ; 2 -> Restore xterm window title from stack. supported * Ps >= 24 not implemented */ - public windowOptions(params: IParams): void { + public windowOptions(params: IParams): boolean { if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) { - return; + return true; } const second = (params.length > 1) ? params.params[1] : 0; switch (params.params[0]) { @@ -2694,6 +2750,7 @@ export class InputHandler extends Disposable implements IInputHandler { } break; } + return true; } @@ -2705,12 +2762,13 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[TODO...] CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes." * @vt: #Y ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes." */ - public saveCursor(params?: IParams): void { + public saveCursor(params?: IParams): boolean { this._bufferService.buffer.savedX = this._bufferService.buffer.x; this._bufferService.buffer.savedY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; this._bufferService.buffer.savedCurAttrData.fg = this._curAttrData.fg; this._bufferService.buffer.savedCurAttrData.bg = this._curAttrData.bg; this._bufferService.buffer.savedCharset = this._charsetService.charset; + return true; } @@ -2722,7 +2780,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[TODO...] CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes." * @vt: #Y ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes." */ - public restoreCursor(params?: IParams): void { + public restoreCursor(params?: IParams): boolean { this._bufferService.buffer.x = this._bufferService.buffer.savedX || 0; this._bufferService.buffer.y = Math.max(this._bufferService.buffer.savedY - this._bufferService.buffer.ybase, 0); this._curAttrData.fg = this._bufferService.buffer.savedCurAttrData.fg; @@ -2732,6 +2790,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._charsetService.charset = this._bufferService.buffer.savedCharset; } this._restrictCursor(); + return true; } @@ -2745,17 +2804,19 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. */ - public setTitle(data: string): void { + public setTitle(data: string): boolean { this._windowTitle = data; this._onTitleChange.fire(data); + return true; } /** * OSC 1; ST * Note: Icon name is not exposed. */ - public setIconName(data: string): void { + public setIconName(data: string): boolean { this._iconName = data; + return true; } protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { @@ -2787,7 +2848,7 @@ export class InputHandler extends Disposable implements IInputHandler { * `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits. * There may be multipe c ; spec elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0. */ - public setAnsiColor(data: string): void { + public setAnsiColor(data: string): boolean { const event = this._parseAnsiColorChange(data); if (event) { this._onAnsiColorChange.fire(event); @@ -2795,6 +2856,7 @@ export class InputHandler extends Disposable implements IInputHandler { else { this._logService.warn(`Expected format ;rgb:// but got data: ${data}`); } + return true; } /** @@ -2806,9 +2868,10 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." * @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." */ - public nextLine(): void { + public nextLine(): boolean { this._bufferService.buffer.x = 0; this.index(); + return true; } /** @@ -2816,10 +2879,11 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) * Enables the numeric keypad to send application sequences to the host. */ - public keypadApplicationMode(): void { + public keypadApplicationMode(): boolean { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; this._onRequestSyncScrollBar.fire(); + return true; } /** @@ -2827,10 +2891,11 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) * Enables the keypad to send numeric characters to the host. */ - public keypadNumericMode(): void { + public keypadNumericMode(): boolean { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; this._onRequestSyncScrollBar.fire(); + return true; } /** @@ -2839,9 +2904,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Select default character set. UTF-8 is not supported (string are unicode anyways) * therefore ESC % G does the same. */ - public selectDefaultCharset(): void { + public selectDefaultCharset(): boolean { this._charsetService.setgLevel(0); this._charsetService.setgCharset(0, DEFAULT_CHARSET); // US (default) + return true; } /** @@ -2860,16 +2926,16 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC / C * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? */ - public selectCharset(collectAndFlag: string): void { + public selectCharset(collectAndFlag: string): boolean { if (collectAndFlag.length !== 2) { this.selectDefaultCharset(); - return; + return true; } if (collectAndFlag[0] === '/') { - return; // TODO: Is this supported? + return true; // TODO: Is this supported? } this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); - return; + return true; } /** @@ -2881,7 +2947,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." * @vt: #Y ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." */ - public index(): void { + public index(): boolean { this._restrictCursor(); const buffer = this._bufferService.buffer; this._bufferService.buffer.y++; @@ -2892,6 +2958,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y = this._bufferService.rows - 1; } this._restrictCursor(); + return true; } /** @@ -2904,8 +2971,9 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." * @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." */ - public tabSet(): void { + public tabSet(): boolean { this._bufferService.buffer.tabs[this._bufferService.buffer.x] = true; + return true; } /** @@ -2917,7 +2985,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." */ - public reverseIndex(): void { + public reverseIndex(): boolean { this._restrictCursor(); const buffer = this._bufferService.buffer; if (buffer.y === buffer.scrollTop) { @@ -2932,6 +3000,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y--; this._restrictCursor(); // quickfix to not run out of bounds } + return true; } /** @@ -2939,9 +3008,10 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) * Reset to initial state. */ - public fullReset(): void { + public fullReset(): boolean { this._parser.reset(); this._onRequestReset.fire(); + return true; } public reset(): void { @@ -2968,8 +3038,9 @@ export class InputHandler extends Disposable implements IInputHandler { * When you use a locking shift, the character set remains in GL or GR until * you use another locking shift. (partly supported) */ - public setgLevel(level: number): void { + public setgLevel(level: number): boolean { this._charsetService.setgLevel(level); + return true; } /** @@ -2980,7 +3051,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." */ - public screenAlignmentPattern(): void { + public screenAlignmentPattern(): boolean { // prepare cell data const cell = new CellData(); cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0); @@ -3000,5 +3071,6 @@ export class InputHandler extends Disposable implements IInputHandler { } this._dirtyRowService.markAllDirty(); this._setCursor(0, 0); + return true; } } diff --git a/src/common/parser/DcsParser.test.ts b/src/common/parser/DcsParser.test.ts index dbabb4fa..cf174139 100644 --- a/src/common/parser/DcsParser.test.ts +++ b/src/common/parser/DcsParser.test.ts @@ -61,11 +61,12 @@ class TestHandler implements IDcsHandler { public put(data: Uint32Array, start: number, end: number): void { this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]); } - public unhook(success: boolean): void | boolean { + public unhook(success: boolean): boolean { this.output.push([this.msg, 'UNHOOK', success]); if (this.returnFalse) { return false; } + return true; } } @@ -84,7 +85,7 @@ describe('DcsParser', () => { }); describe('handler registration', () => { it('setDcsHandler', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -100,7 +101,7 @@ describe('DcsParser', () => { ]); }); it('clearDcsHandler', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th')); parser.clearHandler(identifier({intermediates: '+', final: 'p'})); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); @@ -117,8 +118,8 @@ describe('DcsParser', () => { ]); }); it('addDcsHandler', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); - parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2')); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -137,8 +138,8 @@ describe('DcsParser', () => { ]); }); it('addDcsHandler with return false', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); - parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -157,8 +158,8 @@ describe('DcsParser', () => { ]); }); it('dispose handlers', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); - const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1')); + const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true)); dispo.dispose(); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); @@ -176,7 +177,7 @@ describe('DcsParser', () => { }); describe('DcsHandlerFactory', () => { it('should be called once on end(true)', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -186,7 +187,7 @@ describe('DcsParser', () => { assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]); }); it('should not be called on end(false)', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -196,8 +197,8 @@ describe('DcsParser', () => { assert.deepEqual(reports, []); }); it('should be disposable', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data]))); - const dispo = parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['two', params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['one', params.toArray(), data]); return true; })); + const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -215,8 +216,8 @@ describe('DcsParser', () => { assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]); }); it('should respect return false', () => { - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push(['one', params.toArray(), data]))); - parser.addHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; })); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['one', params.toArray(), data]); return true; })); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push(['two', params.toArray(), data]); return false; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('Here comes'); parser.put(data, 0, data.length); @@ -227,7 +228,7 @@ describe('DcsParser', () => { }); it('should work up to payload limit', function(): void { this.timeout(10000); - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); const data = toUtf32('A'.repeat(1000)); for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) { @@ -238,7 +239,7 @@ describe('DcsParser', () => { }); it('should abort for payload limit +1', function(): void { this.timeout(10000); - parser.setHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => reports.push([params.toArray(), data]))); + parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler((data, params) => { reports.push([params.toArray(), data]); return true; })); parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3])); let data = toUtf32('A'.repeat(1000)); for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) { diff --git a/src/common/parser/DcsParser.ts b/src/common/parser/DcsParser.ts index a09fd68c..2fa28992 100644 --- a/src/common/parser/DcsParser.ts +++ b/src/common/parser/DcsParser.ts @@ -20,9 +20,10 @@ export class DcsParser implements IDcsParser { public dispose(): void { this._handlers = Object.create(null); this._handlerFb = () => {}; + this._active = EMPTY_HANDLERS; } - public addHandler(ident: number, handler: IDcsHandler): IDisposable { + public registerHandler(ident: number, handler: IDcsHandler): IDisposable { if (this._handlers[ident] === undefined) { this._handlers[ident] = []; } @@ -38,10 +39,6 @@ export class DcsParser implements IDcsParser { }; } - public setHandler(ident: number, handler: IDcsHandler): void { - this._handlers[ident] = [handler]; - } - public clearHandler(ident: number): void { if (this._handlers[ident]) delete this._handlers[ident]; } @@ -88,7 +85,7 @@ export class DcsParser implements IDcsParser { } else { let j = this._active.length - 1; for (; j >= 0; j--) { - if (this._active[j].unhook(success) !== false) { + if (this._active[j].unhook(success)) { break; } } @@ -103,19 +100,27 @@ export class DcsParser implements IDcsParser { } } +// predefine empty params as [0] (ZDM) +const EMPTY_PARAMS = new Params(); +EMPTY_PARAMS.addParam(0); + /** * Convenient class to create a DCS handler from a single callback function. * Note: The payload is currently limited to 50 MB (hardcoded). */ export class DcsHandler implements IDcsHandler { private _data = ''; - private _params: IParams | undefined; + private _params: IParams = EMPTY_PARAMS; private _hitLimit: boolean = false; - constructor(private _handler: (data: string, params: IParams) => any) {} + constructor(private _handler: (data: string, params: IParams) => boolean) {} public hook(params: IParams): void { - this._params = params.clone(); + // since we need to preserve params until `unhook`, we have to clone it + // (only borrowed from parser and spans multiple parser states) + // perf optimization: + // clone only, if we have non empty params, otherwise stick with default + this._params = (params.length > 1 || params.params[0]) ? params.clone() : EMPTY_PARAMS; this._data = ''; this._hitLimit = false; } @@ -131,14 +136,14 @@ export class DcsHandler implements IDcsHandler { } } - public unhook(success: boolean): any { - let ret; + public unhook(success: boolean): boolean { + let ret = false; if (this._hitLimit) { ret = false; } else if (success) { - ret = this._handler(this._data, this._params || new Params()); + ret = this._handler(this._data, this._params); } - this._params = undefined; + this._params = EMPTY_PARAMS; this._data = ''; this._hitLimit = false; return ret; diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index 4934d346..468dc934 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -41,7 +41,7 @@ class MockOscPutParser implements IOscParser { this._fallback(id, 'END', this.data.slice(this.data.indexOf(';') + 1)); } } - public addHandler(ident: number, handler: IOscHandler): IDisposable { + public registerHandler(ident: number, handler: IOscHandler): IDisposable { throw new Error('not implemented'); } public setHandler(ident: number, handler: IOscHandler): void { @@ -1206,11 +1206,13 @@ describe('EscapeSequenceParser', function (): void { chai.expect(print).equal(''); }); it('ESC handler', function (): void { - parser2.setEscHandler({intermediates: '%', final: 'G'}, function (): void { + parser2.registerEscHandler({intermediates: '%', final: 'G'}, function (): boolean { esc.push('%G'); + return true; }); - parser2.setEscHandler({final: 'E'}, function (): void { + parser2.registerEscHandler({final: 'E'}, function (): boolean { esc.push('E'); + return true; }); parse(parser2, INPUT); chai.expect(esc).eql(['%G', 'E']); @@ -1226,49 +1228,49 @@ describe('EscapeSequenceParser', function (): void { }); describe('ESC custom handlers', () => { it('prevent fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); parse(parser2, INPUT); chai.expect(esc).eql(['custom - %G']); }); it('allow fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return false; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return false; }); parse(parser2, INPUT); chai.expect(esc).eql(['custom - %G', 'default - %G']); }); it('Multiple custom handlers fallback once', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return false; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return false; }); parse(parser2, INPUT); chai.expect(esc).eql(['custom2 - %G', 'custom - %G']); }); it('Multiple custom handlers no fallback', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return true; }); parse(parser2, INPUT); chai.expect(esc).eql(['custom2 - %G']); }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { order.push(1); }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(2); return false; }); - parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(3); return false; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { order.push(1); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { order.push(2); return false; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { order.push(3); return false; }); parse(parser2, '\x1b%G'); chai.expect(order).eql([3, 2, 1]); }); it('Dispose should work', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); return true; }); + const dispo = parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); dispo.dispose(); parse(parser2, INPUT); chai.expect(esc).eql(['default - %G']); }); it('Should not corrupt the parser when dispose is called twice', () => { - parser2.setEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); }); - const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); + parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('default - %G'); return true; }); + const dispo = parser2.registerEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; }); dispo.dispose(); dispo.dispose(); parse(parser2, INPUT); @@ -1276,8 +1278,9 @@ describe('EscapeSequenceParser', function (): void { }); }); it('CSI handler', function (): void { - parser2.setCsiHandler({final: 'm'}, function (params: IParams): void { + parser2.registerCsiHandler({final: 'm'}, function (params: IParams): boolean { csi.push(['m', params.toArray(), '']); + return true; }); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1290,16 +1293,16 @@ describe('EscapeSequenceParser', function (): void { describe('CSI custom handlers', () => { it('Prevent fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); }); it('Allow fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return false; }); + parser2.registerCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return false; }); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1307,9 +1310,9 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers fallback once', () => { const csiCustom: [string, ParamsArray, string][] = []; const csiCustom2: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return false; }); + parser2.registerCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return false; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1318,9 +1321,9 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers no fallback', () => { const csiCustom: [string, ParamsArray, string][] = []; const csiCustom2: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); - parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return true; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([], 'Should not fallback once'); @@ -1328,16 +1331,16 @@ describe('EscapeSequenceParser', function (): void { }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setCsiHandler({final: 'm'}, () => { order.push(1); }); - parser2.addCsiHandler({final: 'm'}, () => { order.push(2); return false; }); - parser2.addCsiHandler({final: 'm'}, () => { order.push(3); return false; }); + parser2.registerCsiHandler({final: 'm'}, () => { order.push(1); return true; }); + parser2.registerCsiHandler({final: 'm'}, () => { order.push(2); return false; }); + parser2.registerCsiHandler({final: 'm'}, () => { order.push(3); return false; }); parse(parser2, '\x1b[0m'); chai.expect(order).eql([3, 2, 1]); }); it('Dispose should work', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); return true; }); + const customHandler = parser2.registerCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); customHandler.dispose(); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1345,8 +1348,8 @@ describe('EscapeSequenceParser', function (): void { }); it('Should not corrupt the parser when dispose is called twice', () => { const csiCustom: [string, ParamsArray, string][] = []; - parser2.setCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); }); - const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); + parser2.registerCsiHandler({final: 'm'}, params => { csi.push(['m', params.toArray(), '']); return true; }); + const customHandler = parser2.registerCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; }); customHandler.dispose(); customHandler.dispose(); parse(parser2, INPUT); @@ -1355,11 +1358,13 @@ describe('EscapeSequenceParser', function (): void { }); }); it('EXECUTE handler', function (): void { - parser2.setExecuteHandler('\n', function (): void { + parser2.setExecuteHandler('\n', function (): boolean { exe.push('\n'); + return true; }); - parser2.setExecuteHandler('\r', function (): void { + parser2.setExecuteHandler('\r', function (): boolean { exe.push('\r'); + return true; }); parse(parser2, INPUT); chai.expect(exe).eql(['\r', '\n']); @@ -1370,8 +1375,9 @@ describe('EscapeSequenceParser', function (): void { chai.expect(exe).eql(['\n']); }); it('OSC handler', function (): void { - parser2.setOscHandler(1, new OscHandler(function (data: string): void { + parser2.registerOscHandler(1, new OscHandler(function (data: string): boolean { osc.push([1, data]); + return true; })); parse(parser2, INPUT); chai.expect(osc).eql([[1, 'foo=bar']]); @@ -1384,16 +1390,16 @@ describe('EscapeSequenceParser', function (): void { describe('OSC custom handlers', () => { it('Prevent fallback', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); parse(parser2, INPUT); chai.expect(osc).eql([], 'Should not fallback to original handler'); chai.expect(oscCustom).eql([[1, 'foo=bar']]); }); it('Allow fallback', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return false; })); parse(parser2, INPUT); chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler'); chai.expect(oscCustom).eql([[1, 'foo=bar']]); @@ -1401,9 +1407,9 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers fallback once', () => { const oscCustom: [number, string][] = []; const oscCustom2: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return false; })); parse(parser2, INPUT); chai.expect(osc).eql([], 'Should not fallback to original handler'); chai.expect(oscCustom).eql([[1, 'foo=bar']]); @@ -1412,9 +1418,9 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers no fallback', () => { const oscCustom: [number, string][] = []; const oscCustom2: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); - parser2.addOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { oscCustom2.push([1, data]); return true; })); parse(parser2, INPUT); chai.expect(osc).eql([], 'Should not fallback to original handler'); chai.expect(oscCustom).eql([], 'Should not fallback once'); @@ -1422,16 +1428,16 @@ describe('EscapeSequenceParser', function (): void { }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setOscHandler(1, new OscHandler(() => order.push(1))); - parser2.addOscHandler(1, new OscHandler(() => { order.push(2); return false; })); - parser2.addOscHandler(1, new OscHandler(() => { order.push(3); return false; })); + parser2.registerOscHandler(1, new OscHandler(() => { order.push(1); return true; })); + parser2.registerOscHandler(1, new OscHandler(() => { order.push(2); return false; })); + parser2.registerOscHandler(1, new OscHandler(() => { order.push(3); return false; })); parse(parser2, '\x1b]1;foo=bar\x1b\\'); chai.expect(order).eql([3, 2, 1]); }); it('Dispose should work', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + const customHandler = parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); customHandler.dispose(); parse(parser2, INPUT); chai.expect(osc).eql([[1, 'foo=bar']]); @@ -1439,8 +1445,8 @@ describe('EscapeSequenceParser', function (): void { }); it('Should not corrupt the parser when dispose is called twice', () => { const oscCustom: [number, string][] = []; - parser2.setOscHandler(1, new OscHandler(data => osc.push([1, data]))); - const customHandler = parser2.addOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); + parser2.registerOscHandler(1, new OscHandler(data => { osc.push([1, data]); return true; })); + const customHandler = parser2.registerOscHandler(1, new OscHandler(data => { oscCustom.push([1, data]); return true; })); customHandler.dispose(); customHandler.dispose(); parse(parser2, INPUT); @@ -1449,7 +1455,7 @@ describe('EscapeSequenceParser', function (): void { }); }); it('DCS handler', function (): void { - parser2.setDcsHandler({intermediates: '+', final: 'p'}, { + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, { hook: function (params: IParams): void { dcs.push(['hook', '', params.toArray(), 0]); }, @@ -1460,8 +1466,9 @@ describe('EscapeSequenceParser', function (): void { } dcs.push(['put', s]); }, - unhook: function (): void { + unhook: function (): boolean { dcs.push(['unhook']); + return true; } }); parse(parser2, '\x1bP1;2;3+pabc'); @@ -1482,54 +1489,54 @@ describe('EscapeSequenceParser', function (): void { const DCS_INPUT = '\x1bP1;2;3+pabc\x1b\\'; it('Prevent fallback', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); parse(parser2, DCS_INPUT); chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc']]); }); it('Allow fallback', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; })); parse(parser2, DCS_INPUT); chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]); }); it('Multiple custom handlers fallback once', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; })); parse(parser2, DCS_INPUT); chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]); }); it('Multiple custom handlers no fallback', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; })); parse(parser2, DCS_INPUT); chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc']]); }); it('Execution order should go from latest handler down to the original', () => { const order: number[] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => order.push(1))); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(2); return false; })); - parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(3); return false; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(1); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(2); return false; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler(() => { order.push(3); return false; })); parse(parser2, DCS_INPUT); chai.expect(order).eql([3, 2, 1]); }); it('Dispose should work', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + const dispo = parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); dispo.dispose(); parse(parser2, DCS_INPUT); chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]); }); it('Should not corrupt the parser when dispose is called twice', () => { const dcsCustom: [string, (number | number[])[], string][] = []; - parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => dcsCustom.push(['A', params.toArray(), data]))); - const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); + parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['A', params.toArray(), data]); return true; })); + const dispo = parser2.registerDcsHandler({intermediates: '+', final: 'p'}, new DcsHandler((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; })); dispo.dispose(); dispo.dispose(); parse(parser2, DCS_INPUT); @@ -1591,11 +1598,11 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0x30; i <= 0x7e; ++i) { const final = String.fromCharCode(i); let handler: IDisposable | undefined; - chai.assert.doesNotThrow(() => { handler = parser.addEscHandler({final}, () => {}); }, 'final must be in range 48 .. 126'); + chai.assert.doesNotThrow(() => { handler = parser.registerEscHandler({final}, () => true); }, 'final must be in range 48 .. 126'); if (handler) handler.dispose(); } - chai.assert.throws(() => { parser.addEscHandler({final: '\x2f'}, () => {}); }, 'final must be in range 48 .. 126'); - chai.assert.throws(() => { parser.addEscHandler({final: '\x7f'}, () => {}); }, 'final must be in range 48 .. 126'); + chai.assert.throws(() => { parser.registerEscHandler({final: '\x2f'}, () => true); }, 'final must be in range 48 .. 126'); + chai.assert.throws(() => { parser.registerEscHandler({final: '\x7f'}, () => true); }, 'final must be in range 48 .. 126'); }); it('id calculation - should stacking prefix -> intermediate -> final', () => { chai.expect(parser.identToString(parser.identifier({final: 'z'}))).eql('z'); @@ -1608,9 +1615,9 @@ describe('EscapeSequenceParser', function (): void { describe('identifier invocation', () => { it('ESC', () => { const callstack: string[] = []; - const h1 = parser.addEscHandler({final: 'z'}, () => { callstack.push('z'); }); - const h2 = parser.addEscHandler({intermediates: '!', final: 'z'}, () => { callstack.push('!z'); }); - const h3 = parser.addEscHandler({intermediates: '!!', final: 'z'}, () => { callstack.push('!!z'); }); + const h1 = parser.registerEscHandler({final: 'z'}, () => { callstack.push('z'); return true; }); + const h2 = parser.registerEscHandler({intermediates: '!', final: 'z'}, () => { callstack.push('!z'); return true; }); + const h3 = parser.registerEscHandler({intermediates: '!!', final: 'z'}, () => { callstack.push('!!z'); return true; }); parse(parser, '\x1bz\x1b!z\x1b!!z'); h1.dispose(); h2.dispose(); @@ -1620,12 +1627,12 @@ describe('EscapeSequenceParser', function (): void { }); it('CSI', () => { const callstack: any[] = []; - const h1 = parser.addCsiHandler({final: 'z'}, params => { callstack.push(['z', params.toArray()]); }); - const h2 = parser.addCsiHandler({intermediates: '!', final: 'z'}, params => { callstack.push(['!z', params.toArray()]); }); - const h3 = parser.addCsiHandler({intermediates: '!!', final: 'z'}, params => { callstack.push(['!!z', params.toArray()]); }); - const h4 = parser.addCsiHandler({prefix: '?', final: 'z'}, params => { callstack.push(['?z', params.toArray()]); }); - const h5 = parser.addCsiHandler({prefix: '?', intermediates: '!', final: 'z'}, params => { callstack.push(['?!z', params.toArray()]); }); - const h6 = parser.addCsiHandler({prefix: '?', intermediates: '!!', final: 'z'}, params => { callstack.push(['?!!z', params.toArray()]); }); + const h1 = parser.registerCsiHandler({final: 'z'}, params => { callstack.push(['z', params.toArray()]); return true; }); + const h2 = parser.registerCsiHandler({intermediates: '!', final: 'z'}, params => { callstack.push(['!z', params.toArray()]); return true; }); + const h3 = parser.registerCsiHandler({intermediates: '!!', final: 'z'}, params => { callstack.push(['!!z', params.toArray()]); return true; }); + const h4 = parser.registerCsiHandler({prefix: '?', final: 'z'}, params => { callstack.push(['?z', params.toArray()]); return true; }); + const h5 = parser.registerCsiHandler({prefix: '?', intermediates: '!', final: 'z'}, params => { callstack.push(['?!z', params.toArray()]); return true; }); + const h6 = parser.registerCsiHandler({prefix: '?', intermediates: '!!', final: 'z'}, params => { callstack.push(['?!!z', params.toArray()]); return true; }); parse(parser, '\x1b[1;z\x1b[1;!z\x1b[1;!!z\x1b[?1;z\x1b[?1;!z\x1b[?1;!!z'); h1.dispose(); h2.dispose(); @@ -1638,12 +1645,12 @@ describe('EscapeSequenceParser', function (): void { }); it('DCS', () => { const callstack: any[] = []; - const h1 = parser.addDcsHandler({final: 'z'}, new DcsHandler((data, params) => { callstack.push(['z', params.toArray(), data]); })); - const h2 = parser.addDcsHandler({intermediates: '!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['!z', params.toArray(), data]); })); - const h3 = parser.addDcsHandler({intermediates: '!!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['!!z', params.toArray(), data]); })); - const h4 = parser.addDcsHandler({prefix: '?', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?z', params.toArray(), data]); })); - const h5 = parser.addDcsHandler({prefix: '?', intermediates: '!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?!z', params.toArray(), data]); })); - const h6 = parser.addDcsHandler({prefix: '?', intermediates: '!!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?!!z', params.toArray(), data]); })); + const h1 = parser.registerDcsHandler({final: 'z'}, new DcsHandler((data, params) => { callstack.push(['z', params.toArray(), data]); return true; })); + const h2 = parser.registerDcsHandler({intermediates: '!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['!z', params.toArray(), data]); return true; })); + const h3 = parser.registerDcsHandler({intermediates: '!!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['!!z', params.toArray(), data]); return true; })); + const h4 = parser.registerDcsHandler({prefix: '?', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?z', params.toArray(), data]); return true; })); + const h5 = parser.registerDcsHandler({prefix: '?', intermediates: '!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?!z', params.toArray(), data]); return true; })); + const h6 = parser.registerDcsHandler({prefix: '?', intermediates: '!!', final: 'z'}, new DcsHandler((data, params) => { callstack.push(['?!!z', params.toArray(), data]); return true; })); parse(parser, '\x1bP1;zAB\x1b\\\x1bP1;!zAB\x1b\\\x1bP1;!!zAB\x1b\\\x1bP?1;zAB\x1b\\\x1bP?1;!zAB\x1b\\\x1bP?1;!!zAB\x1b\\'); h1.dispose(); h2.dispose(); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index daf9e6eb..3635ca78 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -280,7 +280,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._errorHandler = this._errorHandlerFb; // swallow 7bit ST (ESC+\) - this.setEscHandler({final: '\\'}, () => {}); + this.registerEscHandler({final: '\\'}, () => true); } protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number { @@ -344,7 +344,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._printHandler = this._printHandlerFb; } - public addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable { + public registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable { const ident = this._identifier(id, [0x30, 0x7e]); if (this._escHandlers[ident] === undefined) { this._escHandlers[ident] = []; @@ -360,9 +360,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } }; } - public setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void { - this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [handler]; - } public clearEscHandler(id: IFunctionIdentifier): void { if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])]; } @@ -380,7 +377,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._executeHandlerFb = handler; } - public addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable { + public registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable { const ident = this._identifier(id); if (this._csiHandlers[ident] === undefined) { this._csiHandlers[ident] = []; @@ -396,9 +393,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } }; } - public setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void { - this._csiHandlers[this._identifier(id)] = [handler]; - } public clearCsiHandler(id: IFunctionIdentifier): void { if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)]; } @@ -406,11 +400,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._csiHandlerFb = callback; } - public addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable { - return this._dcsParser.addHandler(this._identifier(id), handler); - } - public setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void { - this._dcsParser.setHandler(this._identifier(id), handler); + public registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable { + return this._dcsParser.registerHandler(this._identifier(id), handler); } public clearDcsHandler(id: IFunctionIdentifier): void { this._dcsParser.clearHandler(this._identifier(id)); @@ -419,11 +410,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._dcsParser.setHandlerFallback(handler); } - public addOscHandler(ident: number, handler: IOscHandler): IDisposable { - return this._oscParser.addHandler(ident, handler); - } - public setOscHandler(ident: number, handler: IOscHandler): void { - this._oscParser.setHandler(ident, handler); + public registerOscHandler(ident: number, handler: IOscHandler): IDisposable { + return this._oscParser.registerHandler(ident, handler); } public clearOscHandler(ident: number): void { this._oscParser.clearHandler(ident); @@ -622,14 +610,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP let j = handlers ? handlers.length - 1 : -1; for (; j >= 0; j--) { // undefined or true means success and to stop bubbling - // FIXME: remove setHandler interface, always use addHandler with proper return value in true|false - // background - result of undefined leads to nonsense instanceof Promise test below - if ((res = handlers[j](params)) !== false) { - if (res && res instanceof Promise) { - this._preserveStack('CSI', handlers, j, transition, currentState, collect, i); - return res; - } + if ((res = handlers[j](params)) === true) { break; + } else if (res instanceof Promise) { + this._preserveStack('CSI', handlers, j, transition, currentState, collect, i); + return res; } } if (j < 0) { @@ -662,12 +647,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP let jj = handlersEsc ? handlersEsc.length - 1 : -1; for (; jj >= 0; jj--) { // undefined or true means success and to stop bubbling - if ((res = handlersEsc[jj]()) !== false) { - if (res && res instanceof Promise) { - this._preserveStack('ESC', handlersEsc, jj, transition, currentState, collect, i); - return res; - } + if ((res = handlersEsc[jj]()) === true) { break; + } else if (res instanceof Promise) { + this._preserveStack('ESC', handlersEsc, jj, transition, currentState, collect, i); + return res; } } if (jj < 0) { @@ -708,7 +692,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP case ParserAction.OSC_PUT: // inner loop: 0x20 (SP) included, 0x7F (DEL) included 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 < NON_ASCII_PRINTABLE)) { osc.put(data, i, j); i = j - 1; break; diff --git a/src/common/parser/OscParser.test.ts b/src/common/parser/OscParser.test.ts index 127bd83a..e2c8641c 100644 --- a/src/common/parser/OscParser.test.ts +++ b/src/common/parser/OscParser.test.ts @@ -23,11 +23,12 @@ class TestHandler implements IOscHandler { public put(data: Uint32Array, start: number, end: number): void { this.output.push([this.msg, this.id, 'PUT', utf32ToString(data, start, end)]); } - public end(success: boolean): void | boolean { + public end(success: boolean): boolean { this.output.push([this.msg, this.id, 'END', success]); if (this.returnFalse) { return false; } + return true; } } @@ -78,7 +79,7 @@ describe('OscParser', () => { }); describe('handler registration', () => { it('setOscHandler', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th')); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -94,7 +95,7 @@ describe('OscParser', () => { ]); }); it('clearOscHandler', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th')); parser.clearHandler(1234); parser.start(); let data = toUtf32('1234;Here comes'); @@ -111,8 +112,8 @@ describe('OscParser', () => { ]); }); it('addOscHandler', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th1')); - parser.addHandler(1234, new TestHandler(1234, reports, 'th2')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th1')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th2')); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -131,8 +132,8 @@ describe('OscParser', () => { ]); }); it('addOscHandler with return false', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th1')); - parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true)); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th1')); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th2', true)); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -151,8 +152,8 @@ describe('OscParser', () => { ]); }); it('dispose handlers', () => { - parser.setHandler(1234, new TestHandler(1234, reports, 'th1')); - const dispo = parser.addHandler(1234, new TestHandler(1234, reports, 'th2', true)); + parser.registerHandler(1234, new TestHandler(1234, reports, 'th1')); + const dispo = parser.registerHandler(1234, new TestHandler(1234, reports, 'th2', true)); dispo.dispose(); parser.start(); let data = toUtf32('1234;Here comes'); @@ -170,7 +171,7 @@ describe('OscParser', () => { }); describe('OscHandlerFactory', () => { it('should be called once on end(true)', () => { - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -180,7 +181,7 @@ describe('OscParser', () => { assert.deepEqual(reports, [[1234, 'Here comes the mouse!']]); }); it('should not be called on end(false)', () => { - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -190,8 +191,8 @@ describe('OscParser', () => { assert.deepEqual(reports, []); }); it('should be disposable', () => { - parser.setHandler(1234, new OscHandler(data => reports.push(['one', data]))); - const dispo = parser.addHandler(1234, new OscHandler(data => reports.push(['two', data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push(['one', data]); return true; })); + const dispo = parser.registerHandler(1234, new OscHandler(data => { reports.push(['two', data]); return true; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -209,8 +210,8 @@ describe('OscParser', () => { assert.deepEqual(reports, [['two', 'Here comes the mouse!'], ['one', 'some other data']]); }); it('should respect return false', () => { - parser.setHandler(1234, new OscHandler(data => reports.push(['one', data]))); - parser.addHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; })); + parser.registerHandler(1234, new OscHandler(data => { reports.push(['one', data]); return true; })); + parser.registerHandler(1234, new OscHandler(data => { reports.push(['two', data]); return false; })); parser.start(); let data = toUtf32('1234;Here comes'); parser.put(data, 0, data.length); @@ -221,7 +222,7 @@ describe('OscParser', () => { }); it('should work up to payload limit', function(): void { this.timeout(10000); - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;'); parser.put(data, 0, data.length); @@ -234,7 +235,7 @@ describe('OscParser', () => { }); it('should abort for payload limit +1', function(): void { this.timeout(10000); - parser.setHandler(1234, new OscHandler(data => reports.push([1234, data]))); + parser.registerHandler(1234, new OscHandler(data => { reports.push([1234, data]); return true; })); parser.start(); let data = toUtf32('1234;'); parser.put(data, 0, data.length); diff --git a/src/common/parser/OscParser.ts b/src/common/parser/OscParser.ts index e8c5a801..e7edac66 100644 --- a/src/common/parser/OscParser.ts +++ b/src/common/parser/OscParser.ts @@ -8,14 +8,16 @@ import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants'; import { utf32ToString } from 'common/input/TextDecoder'; import { IDisposable } from 'common/Types'; +const EMPTY_HANDLERS: IOscHandler[] = []; export class OscParser implements IOscParser { private _state = OscState.START; + private _active = EMPTY_HANDLERS; private _id = -1; private _handlers: IHandlerCollection = Object.create(null); private _handlerFb: OscFallbackHandlerType = () => { }; - public addHandler(ident: number, handler: IOscHandler): IDisposable { + public registerHandler(ident: number, handler: IOscHandler): IDisposable { if (this._handlers[ident] === undefined) { this._handlers[ident] = []; } @@ -30,9 +32,6 @@ export class OscParser implements IOscParser { } }; } - public setHandler(ident: number, handler: IOscHandler): void { - this._handlers[ident] = [handler]; - } public clearHandler(ident: number): void { if (this._handlers[ident]) delete this._handlers[ident]; } @@ -43,6 +42,7 @@ export class OscParser implements IOscParser { public dispose(): void { this._handlers = Object.create(null); this._handlerFb = () => {}; + this._active = EMPTY_HANDLERS; } public reset(): void { @@ -50,28 +50,28 @@ export class OscParser implements IOscParser { if (this._state === OscState.PAYLOAD) { this.end(false); } + this._active = EMPTY_HANDLERS; this._id = -1; this._state = OscState.START; } private _start(): void { - const handlers = this._handlers[this._id]; - if (!handlers) { + this._active = this._handlers[this._id] || EMPTY_HANDLERS; + if (!this._active.length) { this._handlerFb(this._id, 'START'); } else { - for (let j = handlers.length - 1; j >= 0; j--) { - handlers[j].start(); + for (let j = this._active.length - 1; j >= 0; j--) { + this._active[j].start(); } } } private _put(data: Uint32Array, start: number, end: number): void { - const handlers = this._handlers[this._id]; - if (!handlers) { + if (!this._active.length) { 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); + for (let j = this._active.length - 1; j >= 0; j--) { + this._active[j].put(data, start, end); } } } @@ -80,20 +80,19 @@ export class OscParser implements IOscParser { // 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) { + if (!this._active.length) { this._handlerFb(this._id, 'END', success); } else { - let j = handlers.length - 1; + let j = this._active.length - 1; for (; j >= 0; j--) { - if (handlers[j].end(success) !== false) { + if (this._active[j].end(success)) { break; } } j--; // cleanup left over handlers for (; j >= 0; j--) { - handlers[j].end(false); + this._active[j].end(false); } } } @@ -101,7 +100,6 @@ export class OscParser implements IOscParser { public start(): void { // always reset leftover handlers this.reset(); - this._id = -1; this._state = OscState.ID; } @@ -158,6 +156,7 @@ export class OscParser implements IOscParser { } this._end(success); } + this._active = EMPTY_HANDLERS; this._id = -1; this._state = OscState.START; } @@ -171,7 +170,7 @@ export class OscHandler implements IOscHandler { private _data = ''; private _hitLimit: boolean = false; - constructor(private _handler: (data: string) => any) {} + constructor(private _handler: (data: string) => boolean) {} public start(): void { this._data = ''; @@ -189,8 +188,8 @@ export class OscHandler implements IOscHandler { } } - public end(success: boolean): any { - let ret; + public end(success: boolean): boolean { + let ret = false; if (this._hitLimit) { ret = false; } else if (success) { diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 8d55c51a..9db38fbd 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -69,7 +69,7 @@ export interface IParsingState { * CSI handler types. * Note: `params` is borrowed. */ -export type CsiHandlerType = (params: IParams) => boolean | void; +export type CsiHandlerType = (params: IParams) => boolean | Promise; export type CsiFallbackHandlerType = (ident: number, params: IParams) => void; /** @@ -93,20 +93,20 @@ export interface IDcsHandler { * execution of the command should depend on `success`. * To save memory also cleanup data structures here. */ - unhook(success: boolean): void | boolean; + unhook(success: boolean): boolean; } export type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void; /** * ESC handler types. */ -export type EscHandlerType = () => boolean | void; +export type EscHandlerType = () => boolean | Promise; export type EscFallbackHandlerType = (identifier: number) => void; /** * EXECUTE handler types. */ -export type ExecuteHandlerType = () => boolean | void; +export type ExecuteHandlerType = () => boolean; export type ExecuteFallbackHandlerType = (ident: number) => void; /** @@ -129,7 +129,7 @@ export interface IOscHandler { * execution of the command should depend on `success`. * To save memory also cleanup data structures here. */ - end(success: boolean): void | boolean; + end(success: boolean): boolean; } export type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void; @@ -174,29 +174,25 @@ export interface IEscapeSequenceParser extends IDisposable { setPrintHandler(handler: PrintHandlerType): void; clearPrintHandler(): void; - setEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): void; + registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable; clearEscHandler(id: IFunctionIdentifier): void; setEscHandlerFallback(handler: EscFallbackHandlerType): void; - addEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable; setExecuteHandler(flag: string, handler: ExecuteHandlerType): void; clearExecuteHandler(flag: string): void; setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void; - setCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): void; + registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable; clearCsiHandler(id: IFunctionIdentifier): void; setCsiHandlerFallback(callback: CsiFallbackHandlerType): void; - addCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable; - setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void; + registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable; clearDcsHandler(id: IFunctionIdentifier): void; setDcsHandlerFallback(handler: DcsFallbackHandlerType): void; - addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable; - setOscHandler(ident: number, handler: IOscHandler): void; + registerOscHandler(ident: number, handler: IOscHandler): IDisposable; clearOscHandler(ident: number): void; setOscHandlerFallback(handler: OscFallbackHandlerType): void; - addOscHandler(ident: number, handler: IOscHandler): IDisposable; setErrorHandler(handler: (state: IParsingState) => IParsingState): void; clearErrorHandler(): void; @@ -209,8 +205,7 @@ export interface IEscapeSequenceParser extends IDisposable { */ export interface ISubParser extends IDisposable { reset(): void; - addHandler(ident: number, handler: T): IDisposable; - setHandler(ident: number, handler: T): void; + registerHandler(ident: number, handler: T): IDisposable; clearHandler(ident: number): void; setHandlerFallback(handler: U): void; put(data: Uint32Array, start: number, end: number): void; diff --git a/test/benchmark/EscapeSequenceParser.benchmark.ts b/test/benchmark/EscapeSequenceParser.benchmark.ts index e4591dde..c2707f19 100644 --- a/test/benchmark/EscapeSequenceParser.benchmark.ts +++ b/test/benchmark/EscapeSequenceParser.benchmark.ts @@ -6,9 +6,11 @@ import { perfContext, before, beforeEach, ThroughputRuntimeCase } from 'xterm-be import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { C0, C1 } from 'common/data/EscapeSequences'; -import { IDcsHandler, IParams } from 'common/parser/Types'; +import { IDcsHandler, IOscHandler, IParams } from 'common/parser/Types'; import { OscHandler } from 'common/parser/OscParser'; +import { DcsHandler } from '../../out/common/parser/DcsParser'; +const SIZE = 5000000; function toUtf32(s: string): Uint32Array { const result = new Uint32Array(s.length); @@ -18,10 +20,16 @@ function toUtf32(s: string): Uint32Array { return result; } -class DcsHandler implements IDcsHandler { +class FastDcsHandler implements IDcsHandler { public hook(params: IParams): void {} public put(data: Uint32Array, start: number, end: number): void {} - public unhook(): void {} + public unhook(success: boolean): boolean { return true; } +} + +class FastOscHandler implements IOscHandler { + public start(): void {} + public put(data: Uint32Array, start: number, end: number): void {} + public end(success: boolean): boolean { return true; } } @@ -32,80 +40,81 @@ perfContext('Parser throughput - 50MB data', () => { beforeEach(() => { parser = new EscapeSequenceParser(); parser.setPrintHandler((data, start, end) => {}); - parser.setCsiHandler({final: '@'}, params => {}); - parser.setCsiHandler({final: 'A'}, params => {}); - parser.setCsiHandler({final: 'B'}, params => {}); - parser.setCsiHandler({final: 'C'}, params => {}); - parser.setCsiHandler({final: 'D'}, params => {}); - parser.setCsiHandler({final: 'E'}, params => {}); - parser.setCsiHandler({final: 'F'}, params => {}); - parser.setCsiHandler({final: 'G'}, params => {}); - parser.setCsiHandler({final: 'H'}, params => {}); - parser.setCsiHandler({final: 'I'}, params => {}); - parser.setCsiHandler({final: 'J'}, params => {}); - parser.setCsiHandler({final: 'K'}, params => {}); - parser.setCsiHandler({final: 'L'}, params => {}); - parser.setCsiHandler({final: 'M'}, params => {}); - parser.setCsiHandler({final: 'P'}, params => {}); - parser.setCsiHandler({final: 'S'}, params => {}); - parser.setCsiHandler({final: 'T'}, params => {}); - parser.setCsiHandler({final: 'X'}, params => {}); - parser.setCsiHandler({final: 'Z'}, params => {}); - parser.setCsiHandler({final: '`'}, params => {}); - parser.setCsiHandler({final: 'a'}, params => {}); - parser.setCsiHandler({final: 'b'}, params => {}); - parser.setCsiHandler({final: 'c'}, params => {}); - parser.setCsiHandler({final: 'd'}, params => {}); - parser.setCsiHandler({final: 'e'}, params => {}); - parser.setCsiHandler({final: 'f'}, params => {}); - parser.setCsiHandler({final: 'g'}, params => {}); - parser.setCsiHandler({final: 'h'}, params => {}); - parser.setCsiHandler({final: 'l'}, params => {}); - parser.setCsiHandler({final: 'm'}, params => {}); - parser.setCsiHandler({final: 'n'}, params => {}); - parser.setCsiHandler({final: 'p'}, params => {}); - parser.setCsiHandler({final: 'q'}, params => {}); - parser.setCsiHandler({final: 'r'}, params => {}); - parser.setCsiHandler({final: 's'}, params => {}); - parser.setCsiHandler({final: 'u'}, params => {}); - parser.setExecuteHandler(C0.BEL, () => {}); - parser.setExecuteHandler(C0.LF, () => {}); - parser.setExecuteHandler(C0.VT, () => {}); - parser.setExecuteHandler(C0.FF, () => {}); - parser.setExecuteHandler(C0.CR, () => {}); - parser.setExecuteHandler(C0.BS, () => {}); - parser.setExecuteHandler(C0.HT, () => {}); - parser.setExecuteHandler(C0.SO, () => {}); - parser.setExecuteHandler(C0.SI, () => {}); - parser.setExecuteHandler(C1.IND, () => {}); - parser.setExecuteHandler(C1.NEL, () => {}); - parser.setExecuteHandler(C1.HTS, () => {}); - parser.setOscHandler(0, new OscHandler((data) => {})); - parser.setOscHandler(2, new OscHandler((data) => {})); - parser.setEscHandler({final: '7'}, () => {}); - parser.setEscHandler({final: '8'}, () => {}); - parser.setEscHandler({final: 'D'}, () => {}); - parser.setEscHandler({final: 'E'}, () => {}); - parser.setEscHandler({final: 'H'}, () => {}); - parser.setEscHandler({final: 'M'}, () => {}); - parser.setEscHandler({final: '='}, () => {}); - parser.setEscHandler({final: '>'}, () => {}); - parser.setEscHandler({final: 'c'}, () => {}); - parser.setEscHandler({final: 'n'}, () => {}); - parser.setEscHandler({final: 'o'}, () => {}); - parser.setEscHandler({final: '|'}, () => {}); - parser.setEscHandler({final: '}'}, () => {}); - parser.setEscHandler({final: '~'}, () => {}); - parser.setEscHandler({intermediates: '%', final: '@'}, () => {}); - parser.setEscHandler({intermediates: '%', final: 'G'}, () => {}); - parser.setDcsHandler({final: 'q'}, new DcsHandler()); + parser.registerCsiHandler({final: '@'}, params => true); + parser.registerCsiHandler({final: 'A'}, params => true); + parser.registerCsiHandler({final: 'B'}, params => true); + parser.registerCsiHandler({final: 'C'}, params => true); + parser.registerCsiHandler({final: 'D'}, params => true); + parser.registerCsiHandler({final: 'E'}, params => true); + parser.registerCsiHandler({final: 'F'}, params => true); + parser.registerCsiHandler({final: 'G'}, params => true); + parser.registerCsiHandler({final: 'H'}, params => true); + parser.registerCsiHandler({final: 'I'}, params => true); + parser.registerCsiHandler({final: 'J'}, params => true); + parser.registerCsiHandler({final: 'K'}, params => true); + parser.registerCsiHandler({final: 'L'}, params => true); + parser.registerCsiHandler({final: 'M'}, params => true); + parser.registerCsiHandler({final: 'P'}, params => true); + parser.registerCsiHandler({final: 'S'}, params => true); + parser.registerCsiHandler({final: 'T'}, params => true); + parser.registerCsiHandler({final: 'X'}, params => true); + parser.registerCsiHandler({final: 'Z'}, params => true); + parser.registerCsiHandler({final: '`'}, params => true); + parser.registerCsiHandler({final: 'a'}, params => true); + parser.registerCsiHandler({final: 'b'}, params => true); + parser.registerCsiHandler({final: 'c'}, params => true); + parser.registerCsiHandler({final: 'd'}, params => true); + parser.registerCsiHandler({final: 'e'}, params => true); + parser.registerCsiHandler({final: 'f'}, params => true); + parser.registerCsiHandler({final: 'g'}, params => true); + parser.registerCsiHandler({final: 'h'}, params => true); + parser.registerCsiHandler({final: 'l'}, params => true); + parser.registerCsiHandler({final: 'm'}, params => true); + parser.registerCsiHandler({final: 'n'}, params => true); + parser.registerCsiHandler({final: 'p'}, params => true); + parser.registerCsiHandler({final: 'q'}, params => true); + parser.registerCsiHandler({final: 'r'}, params => true); + parser.registerCsiHandler({final: 's'}, params => true); + parser.registerCsiHandler({final: 'u'}, params => true); + parser.setExecuteHandler(C0.BEL, () => true); + parser.setExecuteHandler(C0.LF, () => true); + parser.setExecuteHandler(C0.VT, () => true); + parser.setExecuteHandler(C0.FF, () => true); + parser.setExecuteHandler(C0.CR, () => true); + parser.setExecuteHandler(C0.BS, () => true); + parser.setExecuteHandler(C0.HT, () => true); + parser.setExecuteHandler(C0.SO, () => true); + parser.setExecuteHandler(C0.SI, () => true); + parser.setExecuteHandler(C1.IND, () => true); + parser.setExecuteHandler(C1.NEL, () => true); + parser.setExecuteHandler(C1.HTS, () => true); + parser.registerOscHandler(0, new OscHandler(data => true)); + parser.registerOscHandler(1, new FastOscHandler()); + parser.registerEscHandler({final: '7'}, () => true); + parser.registerEscHandler({final: '8'}, () => true); + parser.registerEscHandler({final: 'D'}, () => true); + parser.registerEscHandler({final: 'E'}, () => true); + parser.registerEscHandler({final: 'H'}, () => true); + parser.registerEscHandler({final: 'M'}, () => true); + parser.registerEscHandler({final: '='}, () => true); + parser.registerEscHandler({final: '>'}, () => true); + parser.registerEscHandler({final: 'c'}, () => true); + parser.registerEscHandler({final: 'n'}, () => true); + parser.registerEscHandler({final: 'o'}, () => true); + parser.registerEscHandler({final: '|'}, () => true); + parser.registerEscHandler({final: '}'}, () => true); + parser.registerEscHandler({final: '~'}, () => true); + parser.registerEscHandler({intermediates: '%', final: '@'}, () => true); + parser.registerEscHandler({intermediates: '%', final: 'G'}, () => true); + parser.registerDcsHandler({final: 'p'}, new DcsHandler(data => true)); + parser.registerDcsHandler({final: 'q'}, new FastDcsHandler()); }); perfContext('PRINT - a', () => { before(() => { const data = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -120,7 +129,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\n\n\n\n\n\n\n'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -135,7 +144,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE\x1bE'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -150,7 +159,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G\x1b%G'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -165,7 +174,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -180,7 +189,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p\x1b[?p'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -195,7 +204,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m\x1b[1;2m'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -210,7 +219,7 @@ perfContext('Parser throughput - 50MB data', () => { before(() => { const data = '\x1b[1;2;3;4;5;6;7;8;9;0m\x1b[1;2;3;4;5;6;7;8;9;0m\x1b[1;2;3;4;5;6;7;8;9;0m'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -221,11 +230,11 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('OSC (short) - OSC 0;hi ST', () => { + perfContext('OSC string interface (short seq) - OSC 0;hi ST', () => { before(() => { const data = '\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\\x1b]0;hi\x1b\\x1b]0;hi\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -236,11 +245,11 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('OSC (long) - OSC 0; ST', () => { + perfContext('OSC string interface (long seq) - OSC 0; ST', () => { before(() => { const data = '\x1b]0;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -251,11 +260,41 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('DCS (short)', () => { + perfContext('OSC class interface (short seq) - OSC 0;hi ST', () => { before(() => { - const data = '\x1bPq~~\x1b\\'; + const data = '\x1b]1;hi\x1b\\\x1b]1;hi\x1b\\\x1b]1;hi\x1b\\\x1b]1;hi\x1b\\x1b]1;hi\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('OSC class interface (long seq) - OSC 0; ST', () => { + before(() => { + const data = '\x1b]1;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; + let content = ''; + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS string interface (short seq)', () => { + before(() => { + const data = '\x1bPphi\x1b\\\x1bPphi\x1b\\\x1bPphi\x1b\\\x1bPphi\x1b\\\x1bPphi\x1b\\'; + let content = ''; + while (content.length < SIZE) { content += data; } parsed = toUtf32(content); @@ -266,11 +305,41 @@ perfContext('Parser throughput - 50MB data', () => { }, {fork: true}).showAverageThroughput(); }); - perfContext('DCS (long)', () => { + perfContext('DCS string interface (long seq)', () => { before(() => { - const data = '\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\'; + const data = '\x1bPpLorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; let content = ''; - while (content.length < 50000000) { + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS class interface (short seq)', () => { + before(() => { + const data = '\x1bPqhi\x1b\\\x1bPqhi\x1b\\\x1bPqhi\x1b\\\x1bPqhi\x1b\\\x1bPqhi\x1b\\'; + let content = ''; + while (content.length < SIZE) { + content += data; + } + parsed = toUtf32(content); + }); + new ThroughputRuntimeCase('', async () => { + parser.parse(parsed, parsed.length); + return {payloadSize: parsed.length}; + }, {fork: true}).showAverageThroughput(); + }); + + perfContext('DCS class interface (long seq)', () => { + before(() => { + const data = '\x1bPqLorem ipsum dolor sit amet, consetetur sadipscing elitr.\x1b\\'; + let content = ''; + while (content.length < SIZE) { content += data; } parsed = toUtf32(content);