diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index b6950d28..ec7bdc8a 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -7,6 +7,7 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/T import { ITheme } from 'common/services/Services'; import { channels, color, css } from 'browser/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; +import { ColorIndex } from 'common/Types'; const DEFAULT_FOREGROUND = css.toColor('#ffffff'); const DEFAULT_BACKGROUND = css.toColor('#000000'); @@ -65,6 +66,13 @@ export const DEFAULT_ANSI_COLORS = Object.freeze((() => { return colors; })()); +interface IRestoreColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + ansi: IColor[]; +} + /** * Manages the source of truth for a terminal's colors. */ @@ -73,6 +81,7 @@ export class ColorManager implements IColorManager { private _ctx: CanvasRenderingContext2D; private _litmusColor: CanvasGradient; private _contrastCache: IColorContrastCache; + private _restoreColors!: IRestoreColorSet; constructor(document: Document, public allowTransparency: boolean) { const canvas = document.createElement('canvas'); @@ -96,6 +105,7 @@ export class ColorManager implements IColorManager { ansi: DEFAULT_ANSI_COLORS.slice(), contrastCache: this._contrastCache }; + this._updateRestoreColors(); } public onOptionsChange(key: string): void { @@ -142,6 +152,39 @@ export class ColorManager implements IColorManager { this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); // Clear our the cache this._contrastCache.clear(); + this._updateRestoreColors(); + } + + public restoreColor(slot?: ColorIndex): void { + // unset slot restores all ansi colors + if (slot === undefined) { + for (let i = 0; i < this._restoreColors.ansi.length; ++i) { + this.colors.ansi[i] = this._restoreColors.ansi[i]; + } + return; + } + switch (slot) { + case ColorIndex.FOREGROUND: + this.colors.foreground = this._restoreColors.foreground; + break; + case ColorIndex.BACKGROUND: + this.colors.background = this._restoreColors.background; + break; + case ColorIndex.CURSOR: + this.colors.cursor = this._restoreColors.cursor; + break; + default: + this.colors.ansi[slot] = this._restoreColors.ansi[slot]; + } + } + + private _updateRestoreColors(): void { + this._restoreColors = { + foreground: this.colors.foreground, + background: this.colors.background, + cursor: this.colors.cursor, + ansi: [...this.colors.ansi] + }; } private _parseColor( diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 7bc8f978..7273eebb 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, IColorRGB } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, IColorRGB, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -179,9 +179,10 @@ export class Terminal extends CoreTerminal implements ITerminal { } /** - * Handle color event from inputhandler for OSC 4 | 10 | 11 | 12. - * An event from OSC 4 may contain multiple set or report requests, - * while OSC 10 | 11 | 12 create single requests. + * Handle color event from inputhandler for OSC 4|104 | 10|110 | 11|111 | 12|112. + * An event from OSC 4|104 may contain multiple set or report requests, and multiple + * or none restore requests (restting all), + * while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request. */ private _handleColorEvent(event: IColorEvent): void { if (!this._colorManager) return; @@ -189,32 +190,38 @@ export class Terminal extends CoreTerminal implements ITerminal { let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined; let ident = ''; switch (req.index) { - case ColorIndex.FOREGROUND: // OSC 10 + case ColorIndex.FOREGROUND: // OSC 10 | 110 acc = 'foreground'; ident = '10'; break; - case ColorIndex.BACKGROUND: // OSC 11 + case ColorIndex.BACKGROUND: // OSC 11 | 111 acc = 'background'; ident = '11'; break; - case ColorIndex.CURSOR: // OSC 12 + case ColorIndex.CURSOR: // OSC 12 | 112 acc = 'cursor'; ident = '12'; break; - default: // OSC 4 + default: // OSC 4 | 104 // we can skip the [0..255] range check here (already done in inputhandler) acc = 'ansi'; ident = '4;' + req.index; } if (acc) { - if (req.color) { - if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); - else this._colorManager.colors[acc] = rgba.toColor(...req.color); - } else { - const channels = color.toColorRGB(acc === 'ansi' - ? this._colorManager.colors.ansi[req.index] - : this._colorManager.colors[acc]); - this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`); + switch (req.type) { + case ColorRequestType.REPORT: + const channels = color.toColorRGB(acc === 'ansi' + ? this._colorManager.colors.ansi[req.index] + : this._colorManager.colors[acc]); + this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`); + break; + case ColorRequestType.SET: + if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); + else this._colorManager.colors[acc] = rgba.toColor(...req.color); + break; + case ColorRequestType.RESTORE: + this._colorManager.restoreColor(req.index); + break; } } } diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index fe48cafc..b2830ae6 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { InputHandler } from 'common/InputHandler'; -import { IBufferLine, IAttributeData, IColorEvent, ColorIndex } from 'common/Types'; +import { IBufferLine, IAttributeData, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; @@ -1869,14 +1869,14 @@ describe('InputHandler', () => { inputHandler.onColor(ev => stack.push(ev)); // single color query await inputHandler.parseP('\x1b]4;0;?\x07'); - assert.deepEqual(stack, [[{ index: 0 }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }]]); stack.length = 0; await inputHandler.parseP('\x1b]4;123;?\x07'); - assert.deepEqual(stack, [[{ index: 123 }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 123 }]]); stack.length = 0; // multiple queries await inputHandler.parseP('\x1b]4;0;?;123;?\x07'); - assert.deepEqual(stack, [[{ index: 0 }, { index: 123 }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }, { type: ColorRequestType.REPORT, index: 123 }]]); stack.length = 0; }); it('4: set color events', async () => { @@ -1884,92 +1884,124 @@ describe('InputHandler', () => { inputHandler.onColor(ev => stack.push(ev)); // single color query await inputHandler.parseP('\x1b]4;0;rgb:01/02/03\x07'); - assert.deepEqual(stack, [[{ index: 0, color: [1, 2, 3] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [1, 2, 3] }]]); stack.length = 0; await inputHandler.parseP('\x1b]4;123;#aabbcc\x07'); - assert.deepEqual(stack, [[{ index: 123, color: [170, 187, 204] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 123, color: [170, 187, 204] }]]); stack.length = 0; // multiple queries await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;123;#001122\x07'); - assert.deepEqual(stack, [[{ index: 0, color: [170, 187, 204] }, { index: 123, color: [0, 17, 34] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]); stack.length = 0; }); it('4: should ignore invalid values', async () => { const stack: IColorEvent[] = []; inputHandler.onColor(ev => stack.push(ev)); await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;45;rgb:1/22/333;123;#001122\x07'); - assert.deepEqual(stack, [[{ index: 0, color: [170, 187, 204] }, { index: 123, color: [0, 17, 34] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]); stack.length = 0; }); + it('104: restore events', async () => { + const stack: IColorEvent[] = []; + inputHandler.onColor(ev => stack.push(ev)); + await inputHandler.parseP('\x1b]104;0\x07\x1b]104;43\x07'); + assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }], [{ type: ColorRequestType.RESTORE, index: 43 }]]); + stack.length = 0; + // multiple in one command + await inputHandler.parseP('\x1b]104;0;43\x07'); + assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }, { type: ColorRequestType.RESTORE, index: 43 }]]); + stack.length = 0; + // full ANSI table restore + await inputHandler.parseP('\x1b]104\x07'); + assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE}]]); + }); it('10: FG set & query events', async () => { const stack: IColorEvent[] = []; inputHandler.onColor(ev => stack.push(ev)); // single foreground query --> color undefined await inputHandler.parseP('\x1b]10;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.FOREGROUND }]]); stack.length = 0; // OSC with multiple values maps to OSC 10 & OSC 11 & OSC 12 await inputHandler.parseP('\x1b]10;?;?;?;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }], [{ index: ColorIndex.BACKGROUND }], [{ index: ColorIndex.CURSOR }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.FOREGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]); stack.length = 0; // set foreground color events await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [1, 2, 3] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [1, 2, 3] }]]); stack.length = 0; await inputHandler.parseP('\x1b]10;#aabbcc\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]); stack.length = 0; // set FG, BG and cursor color at once await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122;rgb:12/34/56\x07'); assert.deepEqual(stack, [ - [{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }], - [{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }], - [{ index: ColorIndex.CURSOR, color: [18, 52, 86] }] + [{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [170, 187, 204] }], + [{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [0, 17, 34] }], + [{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [18, 52, 86] }] ]); }); + it('110: restore FG color', async () => { + const stack: IColorEvent[] = []; + inputHandler.onColor(ev => stack.push(ev)); + await inputHandler.parseP('\x1b]110\x07'); + assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]]); + }); it('11: BG set & query events', async () => { const stack: IColorEvent[] = []; inputHandler.onColor(ev => stack.push(ev)); // single background query --> color undefined await inputHandler.parseP('\x1b]11;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }]]); stack.length = 0; // OSC 11 with multiple values creates only BG and cursor event await inputHandler.parseP('\x1b]11;?;?;?;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }], [{ index: ColorIndex.CURSOR }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]); stack.length = 0; // set background color events await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND, color: [1, 2, 3] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [1, 2, 3] }]]); stack.length = 0; await inputHandler.parseP('\x1b]11;#aabbcc\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]); stack.length = 0; // set BG and cursor color at once await inputHandler.parseP('\x1b]11;#001122;rgb:12/34/56\x07'); assert.deepEqual(stack, [ - [{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }], - [{ index: ColorIndex.CURSOR, color: [18, 52, 86] }] + [{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [0, 17, 34] }], + [{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [18, 52, 86] }] ]); }); + it('111: restore BG color', async () => { + const stack: IColorEvent[] = []; + inputHandler.onColor(ev => stack.push(ev)); + await inputHandler.parseP('\x1b]111\x07'); + assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]]); + }); it('12: cursor color set & query events', async () => { const stack: IColorEvent[] = []; inputHandler.onColor(ev => stack.push(ev)); // single cursor query --> color undefined await inputHandler.parseP('\x1b]12;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]); stack.length = 0; // OSC 12 with multiple values creates only cursor event await inputHandler.parseP('\x1b]12;?;?;?;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]); stack.length = 0; // set cursor color events await inputHandler.parseP('\x1b]12;rgb:01/02/03\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR, color: [1, 2, 3] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [1, 2, 3] }]]); stack.length = 0; await inputHandler.parseP('\x1b]12;#aabbcc\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR, color: [170, 187, 204] }]]); + assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [170, 187, 204] }]]); + }); + it('112: restore cursor color', async () => { + const stack: IColorEvent[] = []; + inputHandler.onColor(ev => stack.push(ev)); + await inputHandler.parseP('\x1b]112\x07'); + assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]]); }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 5377bc03..600e34aa 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex } 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'; @@ -421,11 +421,15 @@ export class InputHandler extends Disposable implements IInputHandler { // 51 - reserved for Emacs shell. // 52 - Manipulate Selection Data. // 104 ; c - Reset Color Number c. + this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data))); // 105 ; c - Reset Special Color Number c. // 106 ; c; f - Enable/disable Special Color Number c. // 110 - Reset VT100 text foreground color. + this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data))); // 111 - Reset VT100 text background color. + this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data))); // 112 - Reset text cursor color. + this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data))); // 113 - Reset mouse foreground color. // 114 - Reset mouse background color. // 115 - Reset Tektronix foreground color. @@ -2861,11 +2865,11 @@ export class InputHandler extends Disposable implements IInputHandler { const index = parseInt(idx); if (0 <= index && index < 256) { if (spec === '?') { - event.push({ index }); + event.push({ type: ColorRequestType.REPORT, index }); } else { const color = parseColor(spec); if (color) { - event.push({ index, color }); + event.push({ type: ColorRequestType.SET, index, color }); } } } @@ -2890,11 +2894,11 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < slots.length; ++i, ++offset) { if (offset >= this._specialColors.length) break; if (slots[i] === '?') { - this._onColor.fire([{ index: this._specialColors[offset] }]); + this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); } else { const color = parseColor(slots[i]); if (color) { - this._onColor.fire([{ index: this._specialColors[offset], color }]); + this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); } } } @@ -2945,6 +2949,65 @@ export class InputHandler extends Disposable implements IInputHandler { return this._setOrReportSpecialColor(data, 2); } + /** + * OSC 104 ; ST - restore ANSI color + * + * @vt: #Y OSC 104 "Reset ANSI color" "OSC 104 ; c BEL" "Reset color number `c` to themed color." + * `c` is the color index between 0 and 255. This function restores the default color for `c` as + * specified by the loaded theme. Any number of `c` parameters may be given. + * If no parameters are given, the entire indexed color table will be reset. + */ + public restoreIndexedColor(data: string): boolean { + if (!data) { + this._onColor.fire([{ type: ColorRequestType.RESTORE }]); + return true; + } + const event: IColorEvent = []; + const slots = data.split(';'); + for (let i = 0; i < slots.length; ++i) { + if (/^\d+$/.exec(slots[i])) { + const index = parseInt(slots[i]); + if (0 <= index && index < 256) { + event.push({ type: ColorRequestType.RESTORE, index }); + } + } + } + if (event.length) { + this._onColor.fire(event); + } + return true; + } + + /** + * OSC 110 ST - restore default foreground color + * + * @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color." + */ + public restoreFgColor(data: string): boolean { + this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); + return true; + } + + /** + * OSC 111 ST - restore default background color + * + * @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color." + */ + public restoreBgColor(data: string): boolean { + this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); + return true; + } + + /** + * OSC 112 ST - restore default cursor color + * + * @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color." + */ + public restoreCursorColor(data: string): boolean { + this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); + return true; + } + /** * ESC E * C1.NEL diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 7e1778a9..cef64356 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -348,20 +348,31 @@ export interface IWindowOptions { setWinLines?: boolean; } -// color events from common, used for OSC 4/10/11/12 +// color events from common, used for OSC 4/10/11/12 and 104/110/111/112 +export const enum ColorRequestType { + REPORT = 0, + SET = 1, + RESTORE = 2 +} export const enum ColorIndex { FOREGROUND = 256, BACKGROUND = 257, CURSOR = 258 } export interface IColorReportRequest { + type: ColorRequestType.REPORT; index: ColorIndex; - color?: IColorRGB; } -export interface IColorSetRequest extends IColorReportRequest { +export interface IColorSetRequest { + type: ColorRequestType.SET; + index: ColorIndex; color: IColorRGB; } -export type IColorEvent = (IColorReportRequest | IColorSetRequest)[]; +export interface IColorRestoreRequest { + type: ColorRequestType.RESTORE; + index?: ColorIndex; +} +export type IColorEvent = (IColorReportRequest | IColorSetRequest | IColorRestoreRequest)[]; /** @@ -433,6 +444,10 @@ export interface IInputHandler { /** OSC 10 */ setOrReportFgColor(data: string): boolean; /** OSC 11 */ setOrReportBgColor(data: string): boolean; /** OSC 12 */ setOrReportCursorColor(data: string): boolean; + /** OSC 104 */ restoreIndexedColor(data: string): boolean; + /** OSC 110 */ restoreFgColor(data: string): boolean; + /** OSC 111 */ restoreBgColor(data: string): boolean; + /** OSC 112 */ restoreCursorColor(data: string): boolean; /** ESC E */ nextLine(): boolean; /** ESC = */ keypadApplicationMode(): boolean; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index fc8aadab..4d3a15dd 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -435,7 +435,51 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await page.evaluate('window._recordedData'), restore); }); }); - describe('OSC 10 & 11', () => { + describe('OSC 4 & 104', () => { + before(async () => { + await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()'); + }); + after(async () => { + await page.evaluate('window._h.dispose()'); + }); + beforeEach(async () => { + await page.evaluate('window._recordedData.length = 0;'); + }); + it('change & restore single color', async () => { + // test for some random color slots + for (const i of [0, 43, 77, 255]) { + await writeSync(page, `\x1b]4;${i};?\x07`); + const restore: string[] = await page.evaluate('window._recordedData'); + await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]); + // restore slot color + await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]); + await page.evaluate('window._recordedData.length = 0;'); + } + }); + it('restore multiple at once', async () => { + // change 3 random slots + await writeSync(page, `\x1b]4;0;?;43;?;77;?\x07`); + const restore: string[] = await page.evaluate('window._recordedData'); + await page.evaluate('window._recordedData.length = 0;'); + await writeSync(page, `\x1b]4;0;rgb:01/02/03;43;#aabbcc;77;#123456\x07`); + // restore specific slots + await writeSync(page, `\x1b]104;0;43;77\x07` + `\x1b]4;0;?;43;?;77;?\x07`); + assert.deepEqual(await page.evaluate('window._recordedData'), restore); + }); + it('restore full table', async () => { + // change 3 random slots + await writeSync(page, `\x1b]4;0;?;43;?;77;?\x07`); + const restore: string[] = await page.evaluate('window._recordedData'); + await page.evaluate('window._recordedData.length = 0;'); + await writeSync(page, `\x1b]4;0;rgb:01/02/03;43;#aabbcc;77;#123456\x07`); + // restore all + await writeSync(page, `\x1b]104\x07` + `\x1b]4;0;?;43;?;77;?\x07`); + assert.deepEqual(await page.evaluate('window._recordedData'), restore); + }); + }); + describe('OSC 10 & 11 + 110 | 111 | 112', () => { before(async () => { await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()'); }); @@ -480,8 +524,31 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']); await writeSync(page, '\x1b]10;#ffffff;#000000\x07'); }); + it('OSC 110: restore FG color', async () => { + await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); + await page.evaluate('window._recordedData.length = 0;'); + // restore + await writeSync(page, '\x1b]110\x07\x1b]10;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); + }); + it('OSC 111: restore BG color', async () => { + await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); + await page.evaluate('window._recordedData.length = 0;'); + // restore + await writeSync(page, '\x1b]111\x07\x1b]11;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); + }); + it('OSC 112: restore cursor color', async () => { + await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); + await page.evaluate('window._recordedData.length = 0;'); + // restore + await writeSync(page, '\x1b]112\x07\x1b]12;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']); + }); }); - }); describe('ESC', () => {