diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js index 9b5ab898..60979cce 100644 --- a/bin/extract_vtfeatures.js +++ b/bin/extract_vtfeatures.js @@ -82,14 +82,15 @@ This document lists xterm.js' support of terminal sequences. The sequences are g - OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9D\`, 8bit) Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported, -any sequence of these types will be ignored. They are also not hookable by the API. +any sequence of these types will be silently ignored. They are also not hookable by the API. -Note that the list only contains sequences implemented in xterm.js' core codebase. Missing sequences are either -not supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences. +Note that the list only marks sequences implemented in xterm.js' core codebase as supported. Missing sequences are either +not supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences +(denoted as "External" where known). To denote the sequences the tables use the same abbreviations as xterm does: - \`Ps\`: A single (usually optional) numeric parameter, composed of one or more decimal digits. -- \`Pm\`: A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s), +- \`Pm\`: Multiple numeric parameters composed of any number of single numeric parameters, separated by ; character(s), e.g. \` Ps ; Ps ; ... \`. - \`Pt\`: A text parameter composed of printable characters. Note that for most commands with \`Pt\` only ASCII printables are specified to work. Additionally the parser will let pass any codepoint greater than C1 as printable. @@ -334,7 +335,9 @@ const MACRO = [ // #P[reason] - partial support with a reason as title [/#P\[(.*?)\]/g, (s, p1) => `Partial`], // #B[reason] - supported but broken in a certain way, reason in title - [/#B\[(.*?)\]/g, (s, p1) => `Broken`] + [/#B\[(.*?)\]/g, (s, p1) => `Broken`], + // #E[notes] - support via external resource, eg. addon + [/#E\[(.*?)\]/g, (s, p1) => `External`] ]; function applyMacros(s) { diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 143686a1..f734b002 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -2146,6 +2146,49 @@ describe('InputHandler', () => { }); }); }); + + describe('DECSCA and DECSED/DECSEL', () => { + it('default is unprotected', async () => { + await inputHandler.parseP('some text'); + await inputHandler.parseP('\x1b[?2K'); + assert.deepEqual(getLines(bufferService, 2), ['', '']); + await inputHandler.parseP('some text'); + await inputHandler.parseP('\x1b[?2J'); + assert.deepEqual(getLines(bufferService, 2), ['', '']); + }); + it('DECSCA 1 with DECSEL', async () => { + await inputHandler.parseP('###\x1b[1"qlineerase\x1b[0"q***'); + await inputHandler.parseP('\x1b[?2K'); + assert.deepEqual(getLines(bufferService, 2), [' lineerase', '']); + // normal EL works as before + await inputHandler.parseP('\x1b[2K'); + assert.deepEqual(getLines(bufferService, 2), ['', '']); + }); + it('DECSCA 1 with DECSED', async () => { + await inputHandler.parseP('###\x1b[1"qdisplayerase\x1b[0"q***'); + await inputHandler.parseP('\x1b[?2J'); + assert.deepEqual(getLines(bufferService, 2), [' displayerase', '']); + // normal ED works as before + await inputHandler.parseP('\x1b[2J'); + assert.deepEqual(getLines(bufferService, 2), ['', '']); + }); + it('DECRQSS reports correct DECSCA state', async () => { + const sendStack: string[] = []; + coreService.onData(d => sendStack.push(d)); + // DCS $ q " q ST + await inputHandler.parseP('\x1bP$q"q\x1b\\'); + // default - DECSCA unset (0 or 2) + assert.deepEqual(sendStack.pop(), '\x1bP1$r0"q\x1b\\'); + // DECSCA 1 - protected set + await inputHandler.parseP('###\x1b[1"q'); + await inputHandler.parseP('\x1bP$q"q\x1b\\'); + assert.deepEqual(sendStack.pop(), '\x1bP1$r1"q\x1b\\'); + // DECSCA 2 - protected reset (same as 0) + await inputHandler.parseP('###\x1b[2"q'); + await inputHandler.parseP('\x1bP$q"q\x1b\\'); + assert.deepEqual(sendStack.pop(), '\x1bP1$r0"q\x1b\\'); // reported as DECSCA 0 + }); + }); describe('DECRQM', () => { const reportStack: string[] = []; beforeEach(() => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 6b3801a8..b599bb7e 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,13 +4,12 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType, IOscLinkData } from 'common/Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; -import { concat } from 'common/TypedArrayUtils'; -import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder'; +import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; @@ -47,10 +46,13 @@ const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': // @vt: #Y C0 ESC "Escape" "\e, \x1B" "Start of a sequence. Cancels any other sequence." /** - * Document common VT features here that are currently unsupported + * Document xterm VT features here that are currently unsupported */ -// @vt: #N DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image starting at cursor position." -// @vt: #N OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." +// @vt: #E[Supported via xterm-addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image." +// @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps \| Pt ST" "Definitions for user-defined keys." +// @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." +// @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." +// @vt: #N OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name." /** * Max length of the UTF32 input buffer. Real memory consumption is 4 times higher. @@ -102,117 +104,6 @@ export enum WindowsOptionsReportType { // create a warning log if an async handler takes longer than the limit (in ms) const SLOW_ASYNC_LIMIT = 5000; -/** - * DCS subparser implementations - */ - -/** - * DCS $ q Pt ST - * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) - * Request Status String (DECRQSS), VT420 and up. - * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) - * - * @vt: #P[See limited support below.] DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." - * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, - * `ESC P 0 ST` for invalid requests. - * - * Supported requests and responses: - * - * | Type | Request | Response (`Pt`) | - * | -------------------------------- | ----------------- | ----------------------------------------------------- | - * | Graphic Rendition (SGR) | `DCS $ q m ST` | always reporting `0m` (currently broken) | - * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST` | `Ps ; Ps r` | - * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Ps SP q` | - * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | always reporting `0 " q` (DECSCA is unsupported) | - * | Conformance Level (DECSCL) | `DCS $ q " p ST` | always reporting `61 ; 1 " p` (DECSCL is unsupported) | - * - * - * TODO: - * - fix SGR report - * - either implement DECSCA or remove the report - * - either check which conformance is better suited or remove the report completely - * --> we are currently a mixture of all up to VT400 but dont follow anyone strictly - */ -class DECRQSS implements IDcsHandler { - private _data: Uint32Array = new Uint32Array(0); - - constructor( - private _bufferService: IBufferService, - private _coreService: ICoreService, - private _logService: ILogService, - private _optionsService: IOptionsService - ) { } - - public hook(params: IParams): void { - this._data = new Uint32Array(0); - } - - public put(data: Uint32Array, start: number, end: number): void { - this._data = concat(this._data, data.subarray(start, end)); - } - - public unhook(success: boolean): boolean { - if (!success) { - this._data = new Uint32Array(0); - return true; - } - const data = utf32ToString(this._data); - this._data = new Uint32Array(0); - switch (data) { - // valid: DCS 1 $ r Pt ST (xterm) - case '"q': // DECSCA - this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`); - break; - case '"p': // DECSCL - 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'; - this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); - break; - case 'm': // SGR - // TODO: report real settings instead of 0m - 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.rawOptions.cursorStyle]; - style -= this._optionsService.rawOptions.cursorBlink ? 1 : 0; - 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; - } -} - -/** - * DCS Ps; Ps| Pt ST - * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html) - * not supported - * - * @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps | Pt ST" "Definitions for user-defined keys." - */ - -/** - * DCS + q Pt ST (xterm) - * Request Terminfo String - * not implemented - * - * @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String." - */ - -/** - * DCS + p Pt ST (xterm) - * Set Terminfo Data - * not supported - * - * @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data." - */ /** * The terminal's standard implementation of IInputHandler, this handles all @@ -233,6 +124,7 @@ export class InputHandler extends Disposable implements IInputHandler { protected _iconNameStack: string[] = []; private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone(); + public getAttrData(): IAttributeData { return this._curAttrData; } private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone(); private _activeBuffer: IBuffer; @@ -334,10 +226,10 @@ export class InputHandler extends Disposable implements IInputHandler { 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: 'J' }, params => this.eraseInDisplay(params, false)); + this._parser.registerCsiHandler({ prefix: '?', final: 'J' }, params => this.eraseInDisplay(params, true)); + this._parser.registerCsiHandler({ final: 'K' }, params => this.eraseInLine(params, false)); + this._parser.registerCsiHandler({ prefix: '?', final: 'K' }, params => this.eraseInLine(params, true)); 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)); @@ -369,6 +261,7 @@ export class InputHandler extends Disposable implements IInputHandler { 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)); + this._parser.registerCsiHandler({ intermediates: '"', final: 'q' }, params => this.selectProtected(params)); this._parser.registerCsiHandler({ intermediates: '$', final: 'p' }, params => this.requestMode(params, true)); this._parser.registerCsiHandler({ prefix: '?', intermediates: '$', final: 'p' }, params => this.requestMode(params, false)); @@ -483,7 +376,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * DCS handler */ - this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService)); + this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DcsHandler((data, params) => this.requestStatusString(data, params))); } public dispose(): void { @@ -1208,6 +1101,18 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } + /** + * CSI Ps " q Select Character Protection Attribute (DECSCA). + * + * @vt: #Y CSI DECSCA "Select Character Protection Attribute" "CSI Ps " q" "Whether DECSED and DECSEL can erase (0=default, 2) or not (1)." + */ + public selectProtected(params: IParams): boolean { + const p = params.params[0]; + if (p === 1) this._curAttrData.bg |= BgFlags.PROTECTED; + if (p === 2 || p === 0) this._curAttrData.bg &= ~BgFlags.PROTECTED; + return true; + } + /** * Helper method to erase cells in a terminal row. @@ -1217,13 +1122,14 @@ export class InputHandler extends Disposable implements IInputHandler { * @param end end - 1 is last erased cell * @param cleanWrap clear the isWrapped flag */ - private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void { + private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false, respectProtect: boolean = false): void { const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; line.replaceCells( start, end, this._activeBuffer.getNullCell(this._eraseAttrData()), - this._eraseAttrData() + this._eraseAttrData(), + respectProtect ); if (clearWrap) { line.isWrapped = false; @@ -1235,9 +1141,9 @@ export class InputHandler extends Disposable implements IInputHandler { * The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false. * @param y row index */ - private _resetBufferLine(y: number): void { + private _resetBufferLine(y: number, respectProtect: boolean = false): void { const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; - line.fill(this._activeBuffer.getNullCell(this._eraseAttrData())); + line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()), respectProtect); this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y); line.isWrapped = false; } @@ -1264,18 +1170,18 @@ export class InputHandler extends Disposable implements IInputHandler { * | 2 | Erase complete viewport. | * | 3 | Erase scrollback. | * - * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." + * @vt: #Y CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Same as ED with respecting protection flag." */ - public eraseInDisplay(params: IParams): boolean { + public eraseInDisplay(params: IParams, respectProtect: boolean = false): boolean { this._restrictCursor(this._bufferService.cols); let j; switch (params.params[0]) { case 0: j = this._activeBuffer.y; this._dirtyRowService.markDirty(j); - this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0); + this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect); for (; j < this._bufferService.rows; j++) { - this._resetBufferLine(j); + this._resetBufferLine(j, respectProtect); } this._dirtyRowService.markDirty(j); break; @@ -1283,13 +1189,13 @@ export class InputHandler extends Disposable implements IInputHandler { j = this._activeBuffer.y; this._dirtyRowService.markDirty(j); // Deleted front part of line and everything before. This line will no longer be wrapped. - this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true); + this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect); if (this._activeBuffer.x + 1 >= this._bufferService.cols) { // Deleted entire previous line. This next line can no longer be wrapped. this._activeBuffer.lines.get(j + 1)!.isWrapped = false; } while (j--) { - this._resetBufferLine(j); + this._resetBufferLine(j, respectProtect); } this._dirtyRowService.markDirty(0); break; @@ -1297,7 +1203,7 @@ export class InputHandler extends Disposable implements IInputHandler { j = this._bufferService.rows; this._dirtyRowService.markDirty(j - 1); while (j--) { - this._resetBufferLine(j); + this._resetBufferLine(j, respectProtect); } this._dirtyRowService.markDirty(0); break; @@ -1336,19 +1242,19 @@ export class InputHandler extends Disposable implements IInputHandler { * | 1 | Erase from the beginning of the line through the cursor. | * | 2 | Erase complete line. | * - * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." + * @vt: #Y CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Same as EL with respecting protecting flag." */ - public eraseInLine(params: IParams): boolean { + public eraseInLine(params: IParams, respectProtect: boolean = false): boolean { this._restrictCursor(this._bufferService.cols); switch (params.params[0]) { case 0: - this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0); + this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect); break; case 1: - this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false); + this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false, respectProtect); break; case 2: - this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true); + this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect); break; } this._dirtyRowService.markDirty(this._activeBuffer.y); @@ -2155,7 +2061,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 1005 | Disable UTF-8 Mouse Mode. | #N | * | 1006 | Disable SGR Mouse Mode. | #Y | * | 1015 | Disable urxvt Mouse Mode. | #N | - * | 1006 | Disable SGR-Pixels Mouse Mode. | #Y | + * | 1016 | Disable SGR-Pixels Mouse Mode. | #Y | * | 1047 | Use Normal Screen Buffer (clearing screen if in alt). | #Y | * | 1048 | Restore cursor as in DECRC. | #Y | * | 1049 | Use Normal Screen Buffer and restore cursor. | #Y | @@ -2470,7 +2376,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 7 | Inverse. Flips foreground and background color. | #Y | * | 8 | Invisible (hidden). | #Y | * | 9 | Crossed-out characters (strikethrough). | #Y | - * | 21 | Doubly underlined. | #P[Currently outputs a single underline.] | + * | 21 | Doubly underlined. | #Y | * | 22 | Normal (neither bold nor faint). | #Y | * | 23 | No italic. | #Y | * | 24 | Not underlined. | #Y | @@ -2498,6 +2404,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 47 | Background color: White. | #Y | * | 48 | Background color: Extended color. | #P[Support for RGB and indexed colors, see below.] | * | 49 | Background color: Default (original). | #Y | + * | 58 | Underline color: Extended color. | #P[Support for RGB and indexed colors, see below.] | * | 90 - 97 | Bright foreground color (analogous to 30 - 37). | #Y | * | 100 - 107 | Bright background color (analogous to 40 - 47). | #Y | * @@ -2507,13 +2414,13 @@ export class InputHandler extends Disposable implements IInputHandler { * | ------ | ------------------------------------------------------------- | ------- | * | 0 | No underline. Same as `SGR 24 m`. | #Y | * | 1 | Single underline. Same as `SGR 4 m`. | #Y | - * | 2 | Double underline. | #P[Currently outputs a single underline.] | - * | 3 | Curly underline. | #P[Currently outputs a single underline.] | - * | 4 | Dotted underline. | #P[Currently outputs a single underline.] | - * | 5 | Dashed underline. | #P[Currently outputs a single underline.] | + * | 2 | Double underline. | #Y | + * | 3 | Curly underline. | #Y | + * | 4 | Dotted underline. | #Y | + * | 5 | Dashed underline. | #Y | * | other | Single underline. Same as `SGR 4 m`. | #Y | * - * Extended colors are supported for foreground (Ps=38) and background (Ps=48) as follows: + * Extended colors are supported for foreground (Ps=38), background (Ps=48) and underline (Ps=58) as follows: * * | Ps + 1 | Meaning | Support | * | ------ | ------------------------------------------------------------- | ------- | @@ -3390,4 +3297,51 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); return true; } + + + /** + * DCS $ q Pt ST + * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) + * Request Status String (DECRQSS), VT420 and up. + * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) + * + * @vt: #P[Limited support, see below.] DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." + * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, + * `ESC P 0 ST` for invalid requests. + * + * Supported requests and responses: + * + * | Type | Request | Response (`Pt`) | + * | -------------------------------- | ----------------- | ----------------------------------------------------- | + * | Graphic Rendition (SGR) | `DCS $ q m ST` | always reporting `0m` (currently broken) | + * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST` | `Ps ; Ps r` | + * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Ps SP q` | + * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | `Ps " q` (DECSCA 2 is reported as Ps = 0) | + * | Conformance Level (DECSCL) | `DCS $ q " p ST` | always reporting `61 ; 1 " p` (DECSCL is unsupported) | + * + * + * TODO: + * - fix SGR report + * - either check which conformance is better suited or remove the report completely + * --> we are currently a mixture of all up to VT400 but dont follow anyone strictly + */ + public requestStatusString(data: string, params: IParams): boolean { + const f = (s: string): boolean => { + this._coreService.triggerDataEvent(`${C0.ESC}${s}${C0.ESC}\\`); + return true; + }; + + // access helpers + const b = this._bufferService.buffer; + const opts = this._optionsService.rawOptions; + const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 }; + + if (data === '"q') return f(`P1$r${this._curAttrData.isProtected() ? 1 : 0}"q`); + if (data === '"p') return f(`P1$r61;1"p`); + if (data === 'r') return f(`P1$r${b.scrollTop + 1};${b.scrollBottom + 1}r`); + // FIXME: report real SGR settings instead of 0m + if (data === 'm') return f(`P1$r0m`); + if (data === ' q') return f(`P1$r${STYLES[opts.cursorStyle] - (opts.cursorBlink ? 1 : 0)} q`); + return f(`P0$r`); + } } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 7d15d6a1..d44bb197 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -150,6 +150,7 @@ export interface IAttributeData { isItalic(): number; isDim(): number; isStrikethrough(): number; + isProtected(): number; // color modes getFgColorMode(): number; @@ -203,9 +204,9 @@ export interface IBufferLine { addCodepointToCell(index: number, codePoint: number): void; insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void; deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void; - replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData): void; + replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData, respectProtect?: boolean): void; resize(cols: number, fill: ICellData): void; - fill(fillCellData: ICellData): void; + fill(fillCellData: ICellData, respectProtect?: boolean): void; copyFrom(line: IBufferLine): void; clone(): IBufferLine; getTrimmedLength(): number; diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index 3af3d293..c9f4cd61 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -46,6 +46,7 @@ export class AttributeData implements IAttributeData { public isItalic(): number { return this.bg & BgFlags.ITALIC; } public isDim(): number { return this.bg & BgFlags.DIM; } public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; } + public isProtected(): number { return this.bg & BgFlags.PROTECTED; } // color modes public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; } diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index a1c7f257..43e89839 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -168,6 +168,11 @@ export class BufferLine implements IBufferLine { return ''; } + /** Get state of protected flag. */ + public isProtected(index: number): number { + return this._data[index * CELL_SIZE + Cell.BG] & BgFlags.PROTECTED; + } + /** * Load data at `index` into `cell`. This is used to access cells in a way that's more friendly * to GC as it significantly reduced the amount of new objects/references needed. @@ -298,7 +303,24 @@ export class BufferLine implements IBufferLine { } } - public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void { + public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData, respectProtect: boolean = false): void { + // full branching on respectProtect==true, hopefully getting fast JIT for standard case + if (respectProtect) { + if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) { + this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs()); + } + if (end < this.length && this.getWidth(end - 1) === 2 && !this.isProtected(end)) { + this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs()); + } + while (start < end && start < this.length) { + if (!this.isProtected(start)) { + this.setCell(start, fillCellData); + } + start++; + } + return; + } + // handle fullwidth at start: reset cell one to the left if start is second cell of a wide char if (start && this.getWidth(start - 1) === 2) { this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs()); @@ -352,7 +374,16 @@ export class BufferLine implements IBufferLine { } /** fill a line with fillCharData */ - public fill(fillCellData: ICellData): void { + public fill(fillCellData: ICellData, respectProtect: boolean = false): void { + // full branching on respectProtect==true, hopefully getting fast JIT for standard case + if (respectProtect) { + for (let i = 0; i < this.length; ++i) { + if (!this.isProtected(i)) { + this.setCell(i, fillCellData); + } + } + return; + } this._combined = {}; this._extendedAttrs = {}; for (let i = 0; i < this.length; ++i) { diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index 0dfa86fd..25999bcd 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -123,11 +123,12 @@ export const enum FgFlags { export const enum BgFlags { /** - * bit 27..32 (upper 3 unused) + * bit 27..32 (upper 2 unused) */ ITALIC = 0x4000000, DIM = 0x8000000, - HAS_EXTENDED = 0x10000000 + HAS_EXTENDED = 0x10000000, + PROTECTED = 0x20000000 } export const enum ExtFlags {