From 6a6043daa3ff2b62c4c70fb8da0aaf6311fdf2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 22 Oct 2021 18:17:52 +0200 Subject: [PATCH 01/20] properly parse xcolor names --- src/common/InputHandler.test.ts | 61 ++++++++++++++++++ src/common/InputHandler.ts | 109 ++++++++++++++++++++++++++++---- 2 files changed, 159 insertions(+), 11 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index e25c3df1..a09f0020 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -54,6 +54,10 @@ class TestInputHandler extends InputHandler { prev = await result; } } + + public parseXColorName(data: string): void | [number, number, number] { + return this._parseXColorName(data); + } } describe('InputHandler', () => { @@ -1865,6 +1869,63 @@ describe('InputHandler', () => { }); }); describe('OSC', () => { + describe.only('parse xcolor names', () => { + it('rgb:// scheme in 4/8/12/16 bit', () => { + // 4 bit + assert.deepEqual(inputHandler.parseXColorName('rgb:0/0/0'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('rgb:f/f/f'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('rgb:1/2/3'), [17, 34, 51]); + // 8 bit + assert.deepEqual(inputHandler.parseXColorName('rgb:00/00/00'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('rgb:ff/ff/ff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('rgb:11/22/33'), [17, 34, 51]); + // 12 bit + assert.deepEqual(inputHandler.parseXColorName('rgb:000/000/000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('rgb:fff/fff/fff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('rgb:111/222/333'), [17, 34, 51]); + // 16 bit + assert.deepEqual(inputHandler.parseXColorName('rgb:0000/0000/0000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('rgb:ffff/ffff/ffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('rgb:1111/2222/3333'), [17, 34, 51]); + }); + it('#RGB scheme in 4/8/12/16 bit', () => { + // 4 bit + assert.deepEqual(inputHandler.parseXColorName('#000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('#fff'), [240, 240, 240]); + assert.deepEqual(inputHandler.parseXColorName('#123'), [16, 32, 48]); + // 8 bit + assert.deepEqual(inputHandler.parseXColorName('#000000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('#ffffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('#112233'), [17, 34, 51]); + // 12 bit + assert.deepEqual(inputHandler.parseXColorName('#000000000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('#fffffffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('#111222333'), [17, 34, 51]); + // 16 bit + assert.deepEqual(inputHandler.parseXColorName('#000000000000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseXColorName('#ffffffffffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseXColorName('#111122223333'), [17, 34, 51]); + }); + it('supports upper case', () => { + assert.deepEqual(inputHandler.parseXColorName('RGB:0/A/F'), [0, 170, 255]); + assert.deepEqual(inputHandler.parseXColorName('#FFF'), [240, 240, 240]); + }); + it('does not parse illegal combinations', () => { + // shifting bit width + assert.equal(inputHandler.parseXColorName('rgb:0/11/222'), undefined); + // unsupported scheme + assert.equal(inputHandler.parseXColorName('rgbi:00/11/22'), undefined); + // broken # specifier + assert.equal(inputHandler.parseXColorName('#aabbbcc'), undefined); + // out of range + assert.equal(inputHandler.parseXColorName('#aabbgg'), undefined); + assert.equal(inputHandler.parseXColorName('rgb:aa/bb/gg'), undefined); + }); + }); + + + + it('4: should parse correct Ansi color change data', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3'); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 9371e5f5..a6c425b6 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -25,7 +25,7 @@ import { IBuffer } from 'common/buffer/Types'; /** * Map collect to glevel. Used in `selectCharset`. */ -const GLEVEL: {[key: string]: number} = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; +const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; /** * VT commands done by the parser - FIXME: move this to the parser? @@ -167,7 +167,7 @@ class DECRQSS implements IDcsHandler { break; case 'r': // DECSTBM const pt = '' + (this._bufferService.buffer.scrollTop + 1) + - ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; + ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); break; case 'm': // SGR @@ -175,7 +175,7 @@ class DECRQSS implements IDcsHandler { this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`); break; case ' q': // DECSCUSR - const STYLES: {[key: string]: number} = { 'block': 2, 'underline': 4, 'bar': 6 }; + 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; this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); @@ -403,7 +403,9 @@ export class InputHandler extends Disposable implements IInputHandler { // 6 - Enable/disable Special Color Number c // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939) // 10 - Change VT100 text foreground color to Pt. + this._parser.registerOscHandler(10, new OscHandler(data => this.queryOrSetFgColor(data))); // 11 - Change VT100 text background color to Pt. + this._parser.registerOscHandler(11, new OscHandler(data => this.queryOrSetBgColor(data))); // 12 - Change text cursor color to Pt. // 13 - Change mouse foreground color to Pt. // 14 - Change mouse background color to Pt. @@ -855,10 +857,9 @@ export class InputHandler extends Disposable implements IInputHandler { * - any cursor movement sequence keeps working as expected */ if (this._activeBuffer.x === 0 - && this._activeBuffer.y > this._activeBuffer.scrollTop - && this._activeBuffer.y <= this._activeBuffer.scrollBottom - && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) - { + && this._activeBuffer.y > this._activeBuffer.scrollTop + && this._activeBuffer.y <= this._activeBuffer.scrollBottom + && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) { this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false; this._activeBuffer.y--; this._activeBuffer.x = this._bufferService.cols - 1; @@ -1977,7 +1978,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 1049: // alt screen buffer cursor this.saveCursor(); - // FALL-THROUGH + // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); @@ -2197,7 +2198,7 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); break; case 1049: // alt screen buffer cursor - // FALL-THROUGH + // FALL-THROUGH case 47: // normal screen buffer case 1047: // normal screen buffer - clearing it first // Ensure the selection manager has the correct buffer @@ -2264,7 +2265,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // exit early if can decide color mode with semicolons if ((accu[1] === 5 && advance + cSpace >= 2) - || (accu[1] === 2 && advance + cSpace >= 5)) { + || (accu[1] === 2 && advance + cSpace >= 5)) { break; } // offset colorSpace slot for semicolon mode @@ -2683,7 +2684,7 @@ export class InputHandler extends Disposable implements IInputHandler { const top = params.params[0] || 1; let bottom: number; - if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) { + if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) { bottom = this._bufferService.rows; } @@ -2862,6 +2863,56 @@ export class InputHandler extends Disposable implements IInputHandler { return result; } + /** + * Parse xcolor name to RGB values (8 bit per channel). + * See `man xparsecolor` for details about certain format specifications. + * + * Supported formats: + * - rgb:// with , , in h | hh | hhh | hhhh + * - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB + * + * All other formats like rgbi: or device-independent string specifications + * with float numbering are not supported. + */ + protected _parseXColorName(data: string): [number, number, number] | void { + // also handle uppercases + data = data.toLowerCase(); + if (data.indexOf('rgb:') === 0) { + // 'rgb:' specifier + data = data.slice(4); + const rex = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; + const m = rex.exec(data); + if (m) { + const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535; + return [ + Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255), + Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255), + Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255), + ]; + } + } else if (data.indexOf('#') === 0) { + // '#' specifier + data = data.slice(1); + const rex = /^[\da-f]+$/; + if (rex.exec(data) && [3, 6, 9, 12].includes(data.length)) { + const adv = data.length / 3; + const r = parseInt(data.slice(0, adv), 16); + const g = parseInt(data.slice(adv, 2 * adv), 16); + const b = parseInt(data.slice(2 * adv, 3 * adv), 16); + switch (adv) { + case 1: + return [r << 4, g << 4, b << 4]; + case 2: + return [r, g, b]; + case 3: + return [r >> 4, g >> 4, b >> 4]; + case 4: + return [r >> 8, g >> 8, b >> 8]; + } + } + } + } + /** * OSC 4; ; ST (set ANSI color to ) * @@ -2880,6 +2931,42 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } + public queryOrSetFgColor(data: string): boolean { + // note: data may contain multiple ? or color names separated with ; + // Multiple values will map through to OSC 10 - 19, but we only support 10 and 11 currently, + // thus truncate to max. 2 occurences. + const slots = data.split(';').slice(0, 2); + if (slots[0] === '?') { + // TODO: query FG color + console.log('query FG'); + } else { + const color = this._parseXColorName(slots[0]); + if (color) { + // set new FG color + console.log('set FG', color); + } + } + if (slots.length === 2) { + this.queryOrSetBgColor(slots[1]); + } + return true; + } + + public queryOrSetBgColor(data: string): boolean { + const slots = data.split(';').slice(0, 1); + if (slots[0] === '?') { + // TODO: query BG color + console.log('query BG'); + } else { + const color = this._parseXColorName(slots[0]); + if (color) { + // set new BG color + console.log('set BG', color); + } + } + return true; + } + /** * ESC E * C1.NEL From 0f4d71b8f81b0133d335e4e8c4b4ccea7c60d039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 22 Oct 2021 18:26:46 +0200 Subject: [PATCH 02/20] make linter happy --- src/common/InputHandler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index a6c425b6..3505b548 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2866,11 +2866,11 @@ export class InputHandler extends Disposable implements IInputHandler { /** * Parse xcolor name to RGB values (8 bit per channel). * See `man xparsecolor` for details about certain format specifications. - * + * * Supported formats: * - rgb:// with , , in h | hh | hhh | hhhh * - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB - * + * * All other formats like rgbi: or device-independent string specifications * with float numbering are not supported. */ @@ -2887,7 +2887,7 @@ export class InputHandler extends Disposable implements IInputHandler { return [ Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255), Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255), - Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255), + Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255) ]; } } else if (data.indexOf('#') === 0) { From 8e71cec20877308a8dce5adb8d18a426dd4cde13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 22 Oct 2021 18:32:30 +0200 Subject: [PATCH 03/20] remove only from tests --- src/common/InputHandler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index a09f0020..8d1c3b4f 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1869,7 +1869,7 @@ describe('InputHandler', () => { }); }); describe('OSC', () => { - describe.only('parse xcolor names', () => { + describe('parse xcolor names', () => { it('rgb:// scheme in 4/8/12/16 bit', () => { // 4 bit assert.deepEqual(inputHandler.parseXColorName('rgb:0/0/0'), [0, 0, 0]); From 01a732274c40cc87e88d4eeb7f0d518c43d11c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 22 Oct 2021 22:48:57 +0200 Subject: [PATCH 04/20] OSC 10/11 working, OSC 4 partially fixed --- src/browser/Color.ts | 17 +++++++++ src/browser/Terminal.ts | 64 +++++++++++++++++++++++++++------ src/common/InputHandler.test.ts | 34 +++++++++--------- src/common/InputHandler.ts | 30 +++++++++------- src/common/Types.d.ts | 12 +++---- 5 files changed, 109 insertions(+), 48 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index c43c5eb5..42a65876 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -5,6 +5,8 @@ import { IColor } from 'browser/Types'; +// FIXME: Move Color.ts lib to common? + /** * Helper functions where the source type is "channels" (individual color channels as numbers). */ @@ -17,6 +19,8 @@ export namespace channels { } export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number { + // Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32 + // on LE systems, before it can be used for direct 32-bit buffer writes. // >>> 0 forces an unsigned int return (r << 24 | g << 16 | b << 8 | a) >>> 0; } @@ -81,6 +85,11 @@ export namespace color { rgba: channels.toRgba(r, g, b, a) }; } + + export function toXColorName(color: IColor): string { + const [r, g, b] = rgba.toChannels(color.rgba); + return `rgb:${toPaddedHex(r)}/${toPaddedHex(g)}/${toPaddedHex(b)}`; + } } /** @@ -197,6 +206,7 @@ export namespace rgba { return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } + // FIXME: Move this to channels NS? export function toChannels(value: number): [number, number, number, number] { return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; } @@ -207,6 +217,13 @@ export namespace rgba { rgba: channels.toRgba(r, g, b) }; } + + /** + * convert 0xRRGGBBAA to 0xAABBGGRR (32-bit representation on LE systems) + */ + export function toABGR32(rgba: number): number { + return ((rgba & 0xFF) << 24 | (rgba >>> 8 & 0xFF) << 16 | (rgba >>> 16 & 0xFF) << 8 | rgba >>> 24 & 0xFF) >>> 0; + } } export function toPaddedHex(c: number): string { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 2cd9bf99..4dcf8460 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler, IColor } from 'browser/Types'; import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -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, IAnsiColorChangeEvent } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -53,7 +53,7 @@ import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; -import { rgba } from 'browser/Color'; +import { color, rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; // Let it work inside Node.js for automated testing purposes. @@ -167,7 +167,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); - this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event))); + this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); @@ -177,17 +177,59 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); } - private _changeAnsiColor(event: IAnsiColorChangeEvent): void { + private _handleColorEvent(event: IColorEvent): void { if (!this._colorManager) { return; } - for (const ansiColor of event.colors) { - const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue); - - this._colorManager!.colors.ansi[ansiColor.colorIndex] = color; + let hasSet = false; + const query: string[] = []; + for (const req of event.requests) { + if (req.color === '?') { + // query color + let ident = ''; + let c: IColor; + switch (req.index) { + case 256: + ident = '10'; + c = this._colorManager.colors.foreground; + break; + case 257: + ident = '11'; + c = this._colorManager.colors.background; + break; + default: + if (0 <= req.index && req.index < 256) { + ident = '4;' + req.index; + c = this._colorManager.colors.ansi[req.index]; + } + } + if (ident) { + query.push(`${C0.ESC}]${ident};${color.toXColorName(c!)}${C0.BEL}`); + } + } else { + // set color + hasSet = true; + switch (req.index) { + case 256: + this._colorManager.colors.foreground = rgba.toColor(...req.color); + break; + case 257: + this._colorManager.colors.background = rgba.toColor(...req.color); + break; + default: + if (0 <= req.index && req.index < 256) { + this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); + } + } + } } - this._renderService?.setColors(this._colorManager!.colors); - this.viewport?.onThemeChange(this._colorManager!.colors); + if (query.length) { + this.coreService.triggerDataEvent(query.join('')); + } + if (hasSet) { + this._renderService?.setColors(this._colorManager.colors); + this.viewport?.onThemeChange(this._colorManager.colors); + } } public dispose(): void { diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 8d1c3b4f..f3b0f41f 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, IAnsiColorChangeEvent } from 'common/Types'; +import { IBufferLine, IAttributeData, IColorEvent } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; @@ -41,7 +41,7 @@ class TestInputHandler extends InputHandler { public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } public get windowTitleStack(): string[] { return this._windowTitleStack; } public get iconNameStack(): string[] { return this._iconNameStack; } - public parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { return this._parseAnsiColorChange(data); } + public parseAnsiColorChange(data: string): IColorEvent | null { return this._parseAnsiColorChange(data); } /** * Promise based parse call to await the full resolve of given input data. @@ -1924,14 +1924,12 @@ describe('InputHandler', () => { }); - - it('4: should parse correct Ansi color change data', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3'); assert.isNotNull(event); - assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); + assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); }); it('4: should ignore incorrect Ansi color change data', () => { @@ -1947,33 +1945,33 @@ describe('InputHandler', () => { const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d'); assert.isNotNull(event); - assert.equal(event!.colors.length, 3); - assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); - assert.deepEqual(event!.colors[1], { colorIndex: 17, red: 0x00, green: 0x11, blue: 0x22 }); - assert.deepEqual(event!.colors[2], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d }); + assert.equal(event!.requests.length, 3); + assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + assert.deepEqual(event!.requests[1], { index: 17, color: [0x00, 0x11, 0x22] }); + assert.deepEqual(event!.requests[2], { index: 255, color: [0x01, 0xef, 0x2d] }); }); it('4: should ignore incorrect colors in a list of Ansi color changes', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d'); - assert.equal(event!.colors.length, 2); - assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); - assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d }); + assert.equal(event!.requests.length, 2); + assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + assert.deepEqual(event!.requests[1], { index: 255, color: [0x01, 0xef, 0x2d] }); }); it('4: should be case insensitive when parsing Ansi color changes', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3'); - assert.equal(event!.colors.length, 1); - assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); + assert.equal(event!.requests.length, 1); + assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); }); it('4: should fire event on Ansi color change', async () => { return new Promise(async r => { - inputHandler.onAnsiColorChange(e => { + inputHandler.onColor(e => { assert.isNotNull(e); - assert.isNotNull(e!.colors); - assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c }); - assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 }); + assert.isNotNull(e!.requests); + assert.deepEqual(e!.requests[0], { index: 17, color: [0x1a, 0x2b, 0x3c] }); + assert.deepEqual(e!.requests[1], { index: 12, color: [0x11, 0x22, 0x33] }); r(); }); await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\'); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 3505b548..ba51ebdd 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent, IParseStack } from 'common/Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -262,8 +262,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onScroll(): IEvent { return this._onScroll.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } - private _onAnsiColorChange = new EventEmitter(); - public get onAnsiColorChange(): IEvent { return this._onAnsiColorChange.event; } + private _onColor = new EventEmitter(); + public get onColor(): IEvent { return this._onColor.event; } private _parseStack: IParseStack = { paused: false, @@ -2841,22 +2841,24 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } - protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { - const result: IAnsiColorChangeEvent = { colors: [] }; + protected _parseAnsiColorChange(data: string): IColorEvent | null { + const result: IColorEvent = { requests: [] }; // example data: 5;rgb:aa/bb/cc const regex = /(\d+);rgb:([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})/gi; let match; while ((match = regex.exec(data)) !== null) { - result.colors.push({ - colorIndex: parseInt(match[1]), - red: parseInt(match[2], 16), - green: parseInt(match[3], 16), - blue: parseInt(match[4], 16) + result.requests.push({ + index: parseInt(match[1]), + color: [ + parseInt(match[2], 16), + parseInt(match[3], 16), + parseInt(match[4], 16) + ] }); } - if (result.colors.length === 0) { + if (result.requests.length === 0) { return null; } @@ -2923,7 +2925,7 @@ export class InputHandler extends Disposable implements IInputHandler { public setAnsiColor(data: string): boolean { const event = this._parseAnsiColorChange(data); if (event) { - this._onAnsiColorChange.fire(event); + this._onColor.fire(event); } else { this._logService.warn(`Expected format ;rgb:// but got data: ${data}`); @@ -2939,11 +2941,13 @@ export class InputHandler extends Disposable implements IInputHandler { if (slots[0] === '?') { // TODO: query FG color console.log('query FG'); + this._onColor.fire({ requests: [{ index: 256, color: '?' }] }); } else { const color = this._parseXColorName(slots[0]); if (color) { // set new FG color console.log('set FG', color); + this._onColor.fire({ requests: [{ index: 256, color }] }); } } if (slots.length === 2) { @@ -2957,11 +2961,13 @@ export class InputHandler extends Disposable implements IInputHandler { if (slots[0] === '?') { // TODO: query BG color console.log('query BG'); + this._onColor.fire({ requests: [{ index: 257, color: '?' }] }); } else { const color = this._parseXColorName(slots[0]); if (color) { // set new BG color console.log('set BG', color); + this._onColor.fire({ requests: [{ index: 257, color }] }); } } return true; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 78e2e62d..58adf25e 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -347,18 +347,16 @@ export interface IWindowOptions { setWinLines?: boolean; } -export interface IAnsiColorChangeEventColor { - colorIndex: number; - red: number; - green: number; - blue: number; +export interface IColorEventColor { + index: number; + color: [number, number, number] | '?'; } /** * Event fired for OSC 4 command - to change ANSI color based on its index. */ -export interface IAnsiColorChangeEvent { - colors: IAnsiColorChangeEventColor[]; +export interface IColorEvent { + requests: IColorEventColor[]; } /** From 258178fe8557f078f20e8fa232fe822945f1df6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 23 Oct 2021 00:13:58 +0200 Subject: [PATCH 05/20] cleanup & docs for OSC 10/11 --- src/common/InputHandler.test.ts | 66 ++++++++++++++++----------------- src/common/InputHandler.ts | 60 +++++++++++++++++------------- 2 files changed, 68 insertions(+), 58 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index f3b0f41f..ff1879bc 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -55,8 +55,8 @@ class TestInputHandler extends InputHandler { } } - public parseXColorName(data: string): void | [number, number, number] { - return this._parseXColorName(data); + public parseColorSpec(data: string): void | [number, number, number] { + return this._parseColorSpec(data); } } @@ -1872,54 +1872,54 @@ describe('InputHandler', () => { describe('parse xcolor names', () => { it('rgb:// scheme in 4/8/12/16 bit', () => { // 4 bit - assert.deepEqual(inputHandler.parseXColorName('rgb:0/0/0'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('rgb:f/f/f'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('rgb:1/2/3'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:0/0/0'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:f/f/f'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:1/2/3'), [17, 34, 51]); // 8 bit - assert.deepEqual(inputHandler.parseXColorName('rgb:00/00/00'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('rgb:ff/ff/ff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('rgb:11/22/33'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:00/00/00'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:ff/ff/ff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:11/22/33'), [17, 34, 51]); // 12 bit - assert.deepEqual(inputHandler.parseXColorName('rgb:000/000/000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('rgb:fff/fff/fff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('rgb:111/222/333'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:000/000/000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:fff/fff/fff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:111/222/333'), [17, 34, 51]); // 16 bit - assert.deepEqual(inputHandler.parseXColorName('rgb:0000/0000/0000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('rgb:ffff/ffff/ffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('rgb:1111/2222/3333'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:0000/0000/0000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:ffff/ffff/ffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('rgb:1111/2222/3333'), [17, 34, 51]); }); it('#RGB scheme in 4/8/12/16 bit', () => { // 4 bit - assert.deepEqual(inputHandler.parseXColorName('#000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('#fff'), [240, 240, 240]); - assert.deepEqual(inputHandler.parseXColorName('#123'), [16, 32, 48]); + assert.deepEqual(inputHandler.parseColorSpec('#000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('#fff'), [240, 240, 240]); + assert.deepEqual(inputHandler.parseColorSpec('#123'), [16, 32, 48]); // 8 bit - assert.deepEqual(inputHandler.parseXColorName('#000000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('#ffffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('#112233'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('#000000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('#ffffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('#112233'), [17, 34, 51]); // 12 bit - assert.deepEqual(inputHandler.parseXColorName('#000000000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('#fffffffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('#111222333'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('#000000000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('#fffffffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('#111222333'), [17, 34, 51]); // 16 bit - assert.deepEqual(inputHandler.parseXColorName('#000000000000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseXColorName('#ffffffffffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseXColorName('#111122223333'), [17, 34, 51]); + assert.deepEqual(inputHandler.parseColorSpec('#000000000000'), [0, 0, 0]); + assert.deepEqual(inputHandler.parseColorSpec('#ffffffffffff'), [255, 255, 255]); + assert.deepEqual(inputHandler.parseColorSpec('#111122223333'), [17, 34, 51]); }); it('supports upper case', () => { - assert.deepEqual(inputHandler.parseXColorName('RGB:0/A/F'), [0, 170, 255]); - assert.deepEqual(inputHandler.parseXColorName('#FFF'), [240, 240, 240]); + assert.deepEqual(inputHandler.parseColorSpec('RGB:0/A/F'), [0, 170, 255]); + assert.deepEqual(inputHandler.parseColorSpec('#FFF'), [240, 240, 240]); }); it('does not parse illegal combinations', () => { // shifting bit width - assert.equal(inputHandler.parseXColorName('rgb:0/11/222'), undefined); + assert.equal(inputHandler.parseColorSpec('rgb:0/11/222'), undefined); // unsupported scheme - assert.equal(inputHandler.parseXColorName('rgbi:00/11/22'), undefined); + assert.equal(inputHandler.parseColorSpec('rgbi:00/11/22'), undefined); // broken # specifier - assert.equal(inputHandler.parseXColorName('#aabbbcc'), undefined); + assert.equal(inputHandler.parseColorSpec('#aabbbcc'), undefined); // out of range - assert.equal(inputHandler.parseXColorName('#aabbgg'), undefined); - assert.equal(inputHandler.parseXColorName('rgb:aa/bb/gg'), undefined); + assert.equal(inputHandler.parseColorSpec('#aabbgg'), undefined); + assert.equal(inputHandler.parseColorSpec('rgb:aa/bb/gg'), undefined); }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index ba51ebdd..f206ac8b 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2866,7 +2866,7 @@ export class InputHandler extends Disposable implements IInputHandler { } /** - * Parse xcolor name to RGB values (8 bit per channel). + * Parse color spec to RGB values (8 bit per channel). * See `man xparsecolor` for details about certain format specifications. * * Supported formats: @@ -2876,7 +2876,7 @@ export class InputHandler extends Disposable implements IInputHandler { * All other formats like rgbi: or device-independent string specifications * with float numbering are not supported. */ - protected _parseXColorName(data: string): [number, number, number] | void { + protected _parseColorSpec(data: string): [number, number, number] | void { // also handle uppercases data = data.toLowerCase(); if (data.indexOf('rgb:') === 0) { @@ -2898,19 +2898,12 @@ export class InputHandler extends Disposable implements IInputHandler { const rex = /^[\da-f]+$/; if (rex.exec(data) && [3, 6, 9, 12].includes(data.length)) { const adv = data.length / 3; - const r = parseInt(data.slice(0, adv), 16); - const g = parseInt(data.slice(adv, 2 * adv), 16); - const b = parseInt(data.slice(2 * adv, 3 * adv), 16); - switch (adv) { - case 1: - return [r << 4, g << 4, b << 4]; - case 2: - return [r, g, b]; - case 3: - return [r >> 4, g >> 4, b >> 4]; - case 4: - return [r >> 8, g >> 8, b >> 8]; + const result: [number, number, number] = [0, 0, 0]; + for (let i = 0; i < 3; ++i) { + const c = parseInt(data.slice(adv * i, adv * i + adv), 16); + result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8; } + return result; } } } @@ -2933,20 +2926,36 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } + /** + * OSC 10 ; | ST - set or query default foreground color + * + * @vt: #Y OSC 10 "Set or query default foreground color" "OSC 10 ; Pt BEL" "Set or query default foreground color." + * To set the color, the following color specification formats are supported: + * - `rgb://` for `, , ` in `h | hh | hhh | hhhh`, where + * `h` is a single hexadecimal digit (case insignificant). The different widths scale + * from 4 bit (`h`) to 16 bit (`hhhh`) and get converted to 8 bit (`hh`). + * - `#RGB` - 4 bits per channel, expanded to `#R0G0B0` + * - `#RRGGBB` - 8 bits per channel + * - `#RRRGGGBBB` - 12 bits per channel, truncated to `#RRGGBB` + * - `#RRRRGGGGBBBB` - 16 bits per channel, truncated to `#RRGGBB` + * + * If `Pt` contains `?` instead of a color specification, the terminal + * returns a sequence with the current default foreground color + * (use that sequence to restore the color after changes). + * + * **Note:** Other than xterm, xterm.js does not support OSC 12 - 19. + * Therefore stacking multiple `Pt` separated by `;` only works for the first two entries. + */ public queryOrSetFgColor(data: string): boolean { // note: data may contain multiple ? or color names separated with ; // Multiple values will map through to OSC 10 - 19, but we only support 10 and 11 currently, - // thus truncate to max. 2 occurences. + // thus truncate to max. 2 entries. const slots = data.split(';').slice(0, 2); if (slots[0] === '?') { - // TODO: query FG color - console.log('query FG'); this._onColor.fire({ requests: [{ index: 256, color: '?' }] }); } else { - const color = this._parseXColorName(slots[0]); + const color = this._parseColorSpec(slots[0]); if (color) { - // set new FG color - console.log('set FG', color); this._onColor.fire({ requests: [{ index: 256, color }] }); } } @@ -2956,17 +2965,18 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } + /** + * OSC 11 ; | ST - set or query default background color + * + * @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background." + */ public queryOrSetBgColor(data: string): boolean { const slots = data.split(';').slice(0, 1); if (slots[0] === '?') { - // TODO: query BG color - console.log('query BG'); this._onColor.fire({ requests: [{ index: 257, color: '?' }] }); } else { - const color = this._parseXColorName(slots[0]); + const color = this._parseColorSpec(slots[0]); if (color) { - // set new BG color - console.log('set BG', color); this._onColor.fire({ requests: [{ index: 257, color }] }); } } From 9eb72ee0cc2c256482d69e3309691542628d2c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 23 Oct 2021 00:22:55 +0200 Subject: [PATCH 06/20] report 16 bit color spec for query --- src/browser/Color.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 42a65876..2e71c9e6 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -88,7 +88,7 @@ export namespace color { export function toXColorName(color: IColor): string { const [r, g, b] = rgba.toChannels(color.rgba); - return `rgb:${toPaddedHex(r)}/${toPaddedHex(g)}/${toPaddedHex(b)}`; + return `rgb:${toPaddedHex(r)}${toPaddedHex(r)}/${toPaddedHex(g)}${toPaddedHex(g)}/${toPaddedHex(b)}${toPaddedHex(b)}`; } } From 0871ace481232eaf9d497a64fef91429b610effa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 25 Oct 2021 00:21:33 +0200 Subject: [PATCH 07/20] create minified color names module --- fixtures/x11-colornames/README.md | 14 + fixtures/x11-colornames/create_module.js | 447 ++++++++++++++ fixtures/x11-colornames/rgb.txt | 754 +++++++++++++++++++++++ src/common/InputHandler.ts | 21 +- src/common/data/ColorNames.ts | 91 +++ 5 files changed, 1318 insertions(+), 9 deletions(-) create mode 100644 fixtures/x11-colornames/README.md create mode 100644 fixtures/x11-colornames/create_module.js create mode 100644 fixtures/x11-colornames/rgb.txt create mode 100644 src/common/data/ColorNames.ts diff --git a/fixtures/x11-colornames/README.md b/fixtures/x11-colornames/README.md new file mode 100644 index 00000000..ee6d7b6d --- /dev/null +++ b/fixtures/x11-colornames/README.md @@ -0,0 +1,14 @@ +### Fixture for 11 color names + +`rgb.txt` contains X11's defined color names, copied over from `/etc/X11/rgb.txt` on Ubuntu 18. + +Run `create_module.js` to create a TS module containing the color definitions. The script performs these steps: +- extract color definitions from `rgb.txt` +- remove gray definitions (re-added programmatically later) +- create a perfect hash function +- calculate crc10 for basic collision prevention +- run several collision tests +- compress table and color data +- write data with loading shim to `ColorNames.ts` + +The final file is meant to be copied over to `../../src/common/data/`. diff --git a/fixtures/x11-colornames/create_module.js b/fixtures/x11-colornames/create_module.js new file mode 100644 index 00000000..edc1f314 --- /dev/null +++ b/fixtures/x11-colornames/create_module.js @@ -0,0 +1,447 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * For decoder side: + * - reconstruct grays with f = lambda x: (x * 256 - x + 50) / 100 (needs value rounding check) + * - apply nameFilter + * - build lookup & extract functions (runtime decoding?) + * - transfer hashtable and color table + */ + +// match color entry in rgb.txt +const rexFile = /^\s*(\d+)\s*(\d+)\s*(\d+)\s*?[\t]+(.*)$/; +// match greyXX|grayXX names +const rexGrey = /^gr[ae]y\d+/; +// name match +const rexName = /^\w?[A-Za-z0-9 ]+\w/; + +function parseX11Colors(filename) { + const fileData = fs.readFileSync(filename, {encoding: 'utf8'}); + const colors = []; + for (const line of fileData.split('\n')) { + const m = rexFile.exec(line); + if (m) { + colors.push({ + color: [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])], + name: m[4] + }); + } + } + return colors; +} + +// Use the FNV algorithm from http://isthe.com/chongo/tech/comp/fnv/ +function hash(d, name) { + if (!d) d = 0x01000193; + for (const c of name) { + d = ( (d * 0x01000193) ^ c.charCodeAt(0) ) & 0xffffffff; + } + return d >>> 0; +} + +// inspired from http://stevehanov.ca/blog/?id=119 +function createMinimalPerfectHash(nameList) { + const nameMap = Object.create(null); + for (const [idx, name] of nameList.entries()) { + nameMap[name] = idx; + } + const size = nameList.length; + + const buckets = []; + for (let i = 0; i < size; ++i) buckets.push([]); + const g = new Array(size).fill(0); + const v = new Array(size).fill(null); + + for (const key of nameList) { + buckets[hash(0, key) % size].push(key); + } + + buckets.sort((a, b) => b.length - a.length); + let breakIdx = 0; + for (let i = 0; i < size; ++i) { + const bucket = buckets[i]; + if (bucket.length <= 1) { + breakIdx = i; + break; + } + let d = 1; + let item = 0; + let slots = []; + + while (item < bucket.length) { + const slot = hash(d, bucket[item]) % size; + if (v[slot] !== null || slots.includes(slot)) { + d++; + item = 0; + slots = []; + } else { + slots.push(slot); + item++; + } + } + + g[hash(0, bucket[0]) % size] = d; + for (let k = 0; k < bucket.length; ++k) { + v[slots[k]] = nameMap[bucket[k]]; + } + } + + const freeList = []; + for (let i = 0; i < size; ++i) { + if (v[i] === null) freeList.push(i); + } + + for (let i = breakIdx; i < size; ++i) { + const bucket = buckets[i]; + if (!bucket.length) break; + const slot = freeList.pop(); + g[hash(0, bucket[0]) % size] = -slot - 1; + v[slot] = nameMap[bucket[0]]; + } + + return [g, v]; +} + +function lookupPerfectHash(g, v, key) { + const d = g[hash(0, key) % g.length]; + if (d < 0) return v[-d - 1]; + return v[hash(d, key) % v.length]; +} + +function checkPerfectHashTables(g, v, nameList) { + let allPassed = true; + for (const [idx, name] of nameList.entries()) { + const lookup = lookupPerfectHash(g, v, name); + if (lookup !== idx) { + console.log(`\x1b[33mmismatch: '${name}' returns ${lookup} (orig: ${idx})\x1b[m`); + allPassed = false; + } + } + return allPassed; +} + +function crc10atm(name, crc) { + if (!crc) crc = 0; + for (const c of name) { + const v = c.charCodeAt(0); + crc ^= v << 2; + for (let k = 0; k < 8; k++) { + crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; + } + } + crc &= 0x3ff; + return crc >>> 0; +} + +function checkColorList(g, v, crc, colorNames) { + let match = []; + for (const word of colorNames) { + if (!nameFilter(word)) continue; + const idx = lookupPerfectHash(g, v, word); + const crc10 = crc10atm(word); + if (crc[idx] === crc10) { + match.push({idx, word, colorName: colorNames[idx]}); + } + } + return {tested: colorNames.length, match}; +} + +function checkWordlists(g, v, crc, colorNames, filename) { + const fileData = fs.readFileSync(filename, {encoding: 'utf8'}); + const words = fileData.split('\n').filter(el => lookupPerfectHash.length !== 0); + let collisions = []; + for (const word of words) { + if (!nameFilter(word)) continue; + const idx = lookupPerfectHash(g, v, word); + const crc10 = crc10atm(word); + if (crc[idx] === crc10 && word !== colorNames[idx]) { + collisions.push({idx, word, colorName: colorNames[idx]}); + } + } + return {tested: words.length, collisions}; +} + +function nameFilter(name) { + // length 3 - 22 + if (name.length < 3 || name.length > 22) return; + // chars only in [A-Za-z0-9 ] + if (!rexName.exec(name)) return; + return name; +} + +function compressTables(g, v, crc, al) { + // g | v | crc: all in 10 bit (<1024) + // --> 30 bit + // --> fits into 5 bytes of a 64-bit char alphabet + + // g needs an offset for proper zero alignment + const gOffset = -Math.min(...g); + g = g.map(el => el + gOffset); + + // assert we are in 0..2^10 + if (Math.min(...g) < 0 || Math.min(...v) < 0 || Math.min(...crc) < 0 + || Math.max(...g) > 1023 || Math.max(...v) > 1023 || Math.max(...crc) > 1023 + ) { + console.log('\x1b[31mTables out of compressible range, manual fix needed.\x1b[m'); + process.exit(1); + } + + // assert we have same length + if (g.length !== v.length || g.length !== crc.length) { + console.log('\x1b[31mTables length mismatch, manual fix needed.\x1b[m'); + process.exit(1); + } + + // construct compressed data string + let result = ''; + const length = g.length; + for (let i = 0; i < length; ++i) { + let value = (g[i] << 20) | (v[i] << 10) | crc[i]; + let bucket = ''; + for (let k = 0; k < 5; ++k) { + bucket += al[value % al.length]; + value = Math.floor(value / al.length); + } + result += bucket.split('').reverse().join(''); + } + + return [result, length, gOffset]; +} + +function compressColors(colors, al) { + let result = ''; + for (const color of colors) { + let value = (color[0] << 16) | (color[1] << 8) | color[2]; + let bucket = ''; + for (let k = 0; k < 4; ++k) { + bucket += al[value % al.length]; + value = Math.floor(value / al.length); + } + result += bucket.split('').reverse().join(''); + } + return result; +} + +function loadData(bucket, al) { + let value = 0; + for (const c of bucket) { + value *= al.length; + value += al.indexOf(c); + } + return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; +} + +function loadColor(al, data, idx) { + // color buckets are hardcoded to 4 chars + let value = 0; + for (let i = idx * 4; i < idx * 4 + 4; ++i) { + value *= al.length; + value += al.indexOf(data[i]); + } + return [value >>> 16, (value >> 8) & 0xFF, value & 0xFF]; +} + +function lookupIdx(al, data, length, gOffset, name) { + const bl = data.length / length; + let offset = (hash(0, name) % length) * bl; + let [g, v, crc] = loadData(data.slice(offset, offset + bl), al); + offset = g < gOffset + ? (-(g - gOffset) - 1) * bl + : (hash(g - gOffset, name) % length) * bl; + [_, v, _] = loadData(data.slice(offset, offset + bl), al); + offset = v * bl; + [_, _, crc] = loadData(data.slice(offset, offset + bl), al); + return crc10atm(name) === crc ? v : -1; +} + +function lookup(al, data, colorData, length, gOffset, name) { + const idx = lookupIdx(al, data, length, gOffset, name); + if (idx === -1) return; + return loadColor(al, colorData, idx); +} + +function createModule(tableData, colorData, alphabet, gOffset, length) { + const TMPL = `/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * This module enables X11 color name lookups with the help of a perfect hash function + * to not penalize the package size too much. + * saving: ~70% (from 17kB down to 5kB) + */ + +// Note: module and table data created with fixtures/x11-colornames/create_module.js +const TABLE = '${tableData}'; +const COLORS = '${colorData}'; +const ALPHABET = '${alphabet}'; +const OFFSET = ${gOffset}; +const LENGTH = ${length}; +const BUCKET_LENGTH = TABLE.length / LENGTH; + +// match greyXX|grayXX names +const rexGrey = /^gr[ae]y(\\d+)/; +// name match +const rexName = /^\\w?[A-Za-z0-9 ]+\\w/; + +function gray(n: number): [number, number, number] | undefined { + if (0 <= n && n <= 100) { + const v = Math.floor((n * 256 - n + 50) / 100); + return [v, v, v]; + } + return; +} + +function hash(d: number, name: string): number { + if (!d) d = 0x01000193; + for (const c of name) { + d = ((d * 0x01000193) ^ c.charCodeAt(0)) & 0xffffffff; + } + return d >>> 0; +} + +function crc10(name: string, crc?: number): number { + if (!crc) crc = 0; + for (const c of name) { + crc ^= c.charCodeAt(0) << 2; + for (let k = 0; k < 8; k++) { + crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; + } + } + crc &= 0x3ff; + return crc >>> 0; +} + +function loadData(idx: number): [number, number, number] { + let value = 0; + for (let i = idx * BUCKET_LENGTH; i < idx * BUCKET_LENGTH + BUCKET_LENGTH; ++i) { + value *= ALPHABET.length; + value += ALPHABET.indexOf(TABLE[i]); + } + return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; +} + +function loadColor(idx: number): [number, number, number] { + // color buckets are hardcoded to 4 chars + let v = 0; + for (let i = idx * 4; i < idx * 4 + 4; ++i) { + v *= ALPHABET.length; + v += ALPHABET.indexOf(COLORS[i]); + } + return [v >>> 16, (v >> 8) & 0xFF, v & 0xFF]; +} + +function lookupIdx(name: string): number { + let b = loadData(hash(0, name) % LENGTH); + b = loadData(b[0] < OFFSET ? (-(b[0] - OFFSET) - 1) : hash(b[0] - OFFSET, name) % LENGTH); + const [ , , crc] = loadData(b[1]); + return crc10(name) === crc ? b[1] : -1; +} + +export function getColorFromName(name: string): [number, number, number] | undefined { + // basic name filtering + if (name.length < 3 || name.length > 22 || !rexName.exec(name)) return; + + // handle grays special + const m = rexGrey.exec(name); + if (m) return gray(parseInt(m[1])); + + // grab crc checked idx from PHF + const idx = lookupIdx(name); + if (idx === -1) return; + return loadColor(idx); +} +`; + return TMPL; +} + + +function main() { + // parse definitions from X11 file + const colorList = parseX11Colors(path.join(__dirname, '/rgb.txt')) + .filter(el => !rexGrey.exec(el.name)); // remove greys, as we can reconstruct them later + const nameList = colorList.map(el => el.name); + + // PHF creation and test + const [g, v] = createMinimalPerfectHash(nameList); + if (!checkPerfectHashTables(g, v, nameList)) { + console.error('\x1b[31mPHF creation failed.\x1b[m'); + process.exit(1); + } else { + console.log('\x1b[32mPHF creation successful.\x1b[m'); + } + + // calc 10-bit CRCs to rule to avoid collisions at ~1:1024 + const crc = nameList.map(el => crc10atm(el)); + + // collision check against colorNames - must all collide + console.log('Self collision test:') + const collSelf = checkColorList(g, v, crc, nameList); + console.log('self:', { + entries: collSelf.tested, + matches: collSelf.match.length, + rate: collSelf.tested / collSelf.match.length + }); + if (collSelf.match.length !== collSelf.tested) { + console.error('\x1b[31mSelf collision tested failed.\x1b[m'); + process.exit(1); + } + + // basic collision checks against dictionaries (rates should be greater than 900:1) + console.log('Dictionary collision test:') + const collEnglish = checkWordlists(g, v, crc, nameList, '/usr/share/dict/words'); + console.log('english:', { + entries: collEnglish.tested, + collisions: collEnglish.collisions.length, + rate: collEnglish.tested / collEnglish.collisions.length + }); + //console.log(collEnglish.collisions); + const collnGerman = checkWordlists(g, v, crc, nameList, '/usr/share/dict/ngerman'); + console.log('ngerman:', { + entries: collnGerman.tested, + collisions: collnGerman.collisions.length, + rate: collnGerman.tested / collnGerman.collisions.length + }); + const colloGerman = checkWordlists(g, v, crc, nameList, '/usr/share/dict/ogerman'); + console.log('ogerman:', { + entries: colloGerman.tested, + collisions: colloGerman.collisions.length, + rate: colloGerman.tested / colloGerman.collisions.length + }); + + // compression with custom alphabet + const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?'; + const [tableData, length, gOffset] = compressTables(g, v, crc, ALPHABET); + const colorData = compressColors(colorList.map(el => el.color), ALPHABET); + + // test loading of compressed table data + let failed = false; + for (const name of nameList) { + const orig = lookupPerfectHash(g, v, name); + const idx = lookupIdx(ALPHABET, tableData, length, gOffset, name); + if (idx !== orig) { + console.log('\x1b[31mcompressed loading failed for:\x1b[m', {name, idx, orig}); + failed = true; + } + } + if (failed) process.exit(1); + + // test full color decoding + for (const color of colorList) { + const loaded = lookup(ALPHABET, tableData, colorData, length, gOffset, color.name); + if (loaded[0] !== color.color[0] || loaded[1] !== color.color[1] || loaded[2] !== color.color[2]) { + console.log('\x1b[31mcolor loading failed for:\x1b[m', {color, loaded}); + failed = true; + } + } + if (failed) process.exit(1); + + // if we made it up to here, create decoding boilerplate + const moduleData = createModule(tableData, colorData, ALPHABET, gOffset, length); + fs.writeFileSync(path.join(__dirname, '/ColorNames.ts'), moduleData); + console.log(`${moduleData.length} bytes written to ${path.join(__dirname, '/ColorNames.ts')}`); +} + +main(); diff --git a/fixtures/x11-colornames/rgb.txt b/fixtures/x11-colornames/rgb.txt new file mode 100644 index 00000000..b9e56c60 --- /dev/null +++ b/fixtures/x11-colornames/rgb.txt @@ -0,0 +1,754 @@ +! $Xorg: rgb.txt,v 1.3 2000/08/17 19:54:00 cpqbld Exp $ +255 250 250 snow +248 248 255 ghost white +248 248 255 GhostWhite +245 245 245 white smoke +245 245 245 WhiteSmoke +220 220 220 gainsboro +255 250 240 floral white +255 250 240 FloralWhite +253 245 230 old lace +253 245 230 OldLace +250 240 230 linen +250 235 215 antique white +250 235 215 AntiqueWhite +255 239 213 papaya whip +255 239 213 PapayaWhip +255 235 205 blanched almond +255 235 205 BlanchedAlmond +255 228 196 bisque +255 218 185 peach puff +255 218 185 PeachPuff +255 222 173 navajo white +255 222 173 NavajoWhite +255 228 181 moccasin +255 248 220 cornsilk +255 255 240 ivory +255 250 205 lemon chiffon +255 250 205 LemonChiffon +255 245 238 seashell +240 255 240 honeydew +245 255 250 mint cream +245 255 250 MintCream +240 255 255 azure +240 248 255 alice blue +240 248 255 AliceBlue +230 230 250 lavender +255 240 245 lavender blush +255 240 245 LavenderBlush +255 228 225 misty rose +255 228 225 MistyRose +255 255 255 white + 0 0 0 black + 47 79 79 dark slate gray + 47 79 79 DarkSlateGray + 47 79 79 dark slate grey + 47 79 79 DarkSlateGrey +105 105 105 dim gray +105 105 105 DimGray +105 105 105 dim grey +105 105 105 DimGrey +112 128 144 slate gray +112 128 144 SlateGray +112 128 144 slate grey +112 128 144 SlateGrey +119 136 153 light slate gray +119 136 153 LightSlateGray +119 136 153 light slate grey +119 136 153 LightSlateGrey +190 190 190 gray +190 190 190 grey +211 211 211 light grey +211 211 211 LightGrey +211 211 211 light gray +211 211 211 LightGray + 25 25 112 midnight blue + 25 25 112 MidnightBlue + 0 0 128 navy + 0 0 128 navy blue + 0 0 128 NavyBlue +100 149 237 cornflower blue +100 149 237 CornflowerBlue + 72 61 139 dark slate blue + 72 61 139 DarkSlateBlue +106 90 205 slate blue +106 90 205 SlateBlue +123 104 238 medium slate blue +123 104 238 MediumSlateBlue +132 112 255 light slate blue +132 112 255 LightSlateBlue + 0 0 205 medium blue + 0 0 205 MediumBlue + 65 105 225 royal blue + 65 105 225 RoyalBlue + 0 0 255 blue + 30 144 255 dodger blue + 30 144 255 DodgerBlue + 0 191 255 deep sky blue + 0 191 255 DeepSkyBlue +135 206 235 sky blue +135 206 235 SkyBlue +135 206 250 light sky blue +135 206 250 LightSkyBlue + 70 130 180 steel blue + 70 130 180 SteelBlue +176 196 222 light steel blue +176 196 222 LightSteelBlue +173 216 230 light blue +173 216 230 LightBlue +176 224 230 powder blue +176 224 230 PowderBlue +175 238 238 pale turquoise +175 238 238 PaleTurquoise + 0 206 209 dark turquoise + 0 206 209 DarkTurquoise + 72 209 204 medium turquoise + 72 209 204 MediumTurquoise + 64 224 208 turquoise + 0 255 255 cyan +224 255 255 light cyan +224 255 255 LightCyan + 95 158 160 cadet blue + 95 158 160 CadetBlue +102 205 170 medium aquamarine +102 205 170 MediumAquamarine +127 255 212 aquamarine + 0 100 0 dark green + 0 100 0 DarkGreen + 85 107 47 dark olive green + 85 107 47 DarkOliveGreen +143 188 143 dark sea green +143 188 143 DarkSeaGreen + 46 139 87 sea green + 46 139 87 SeaGreen + 60 179 113 medium sea green + 60 179 113 MediumSeaGreen + 32 178 170 light sea green + 32 178 170 LightSeaGreen +152 251 152 pale green +152 251 152 PaleGreen + 0 255 127 spring green + 0 255 127 SpringGreen +124 252 0 lawn green +124 252 0 LawnGreen + 0 255 0 green +127 255 0 chartreuse + 0 250 154 medium spring green + 0 250 154 MediumSpringGreen +173 255 47 green yellow +173 255 47 GreenYellow + 50 205 50 lime green + 50 205 50 LimeGreen +154 205 50 yellow green +154 205 50 YellowGreen + 34 139 34 forest green + 34 139 34 ForestGreen +107 142 35 olive drab +107 142 35 OliveDrab +189 183 107 dark khaki +189 183 107 DarkKhaki +240 230 140 khaki +238 232 170 pale goldenrod +238 232 170 PaleGoldenrod +250 250 210 light goldenrod yellow +250 250 210 LightGoldenrodYellow +255 255 224 light yellow +255 255 224 LightYellow +255 255 0 yellow +255 215 0 gold +238 221 130 light goldenrod +238 221 130 LightGoldenrod +218 165 32 goldenrod +184 134 11 dark goldenrod +184 134 11 DarkGoldenrod +188 143 143 rosy brown +188 143 143 RosyBrown +205 92 92 indian red +205 92 92 IndianRed +139 69 19 saddle brown +139 69 19 SaddleBrown +160 82 45 sienna +205 133 63 peru +222 184 135 burlywood +245 245 220 beige +245 222 179 wheat +244 164 96 sandy brown +244 164 96 SandyBrown +210 180 140 tan +210 105 30 chocolate +178 34 34 firebrick +165 42 42 brown +233 150 122 dark salmon +233 150 122 DarkSalmon +250 128 114 salmon +255 160 122 light salmon +255 160 122 LightSalmon +255 165 0 orange +255 140 0 dark orange +255 140 0 DarkOrange +255 127 80 coral +240 128 128 light coral +240 128 128 LightCoral +255 99 71 tomato +255 69 0 orange red +255 69 0 OrangeRed +255 0 0 red +255 105 180 hot pink +255 105 180 HotPink +255 20 147 deep pink +255 20 147 DeepPink +255 192 203 pink +255 182 193 light pink +255 182 193 LightPink +219 112 147 pale violet red +219 112 147 PaleVioletRed +176 48 96 maroon +199 21 133 medium violet red +199 21 133 MediumVioletRed +208 32 144 violet red +208 32 144 VioletRed +255 0 255 magenta +238 130 238 violet +221 160 221 plum +218 112 214 orchid +186 85 211 medium orchid +186 85 211 MediumOrchid +153 50 204 dark orchid +153 50 204 DarkOrchid +148 0 211 dark violet +148 0 211 DarkViolet +138 43 226 blue violet +138 43 226 BlueViolet +160 32 240 purple +147 112 219 medium purple +147 112 219 MediumPurple +216 191 216 thistle +255 250 250 snow1 +238 233 233 snow2 +205 201 201 snow3 +139 137 137 snow4 +255 245 238 seashell1 +238 229 222 seashell2 +205 197 191 seashell3 +139 134 130 seashell4 +255 239 219 AntiqueWhite1 +238 223 204 AntiqueWhite2 +205 192 176 AntiqueWhite3 +139 131 120 AntiqueWhite4 +255 228 196 bisque1 +238 213 183 bisque2 +205 183 158 bisque3 +139 125 107 bisque4 +255 218 185 PeachPuff1 +238 203 173 PeachPuff2 +205 175 149 PeachPuff3 +139 119 101 PeachPuff4 +255 222 173 NavajoWhite1 +238 207 161 NavajoWhite2 +205 179 139 NavajoWhite3 +139 121 94 NavajoWhite4 +255 250 205 LemonChiffon1 +238 233 191 LemonChiffon2 +205 201 165 LemonChiffon3 +139 137 112 LemonChiffon4 +255 248 220 cornsilk1 +238 232 205 cornsilk2 +205 200 177 cornsilk3 +139 136 120 cornsilk4 +255 255 240 ivory1 +238 238 224 ivory2 +205 205 193 ivory3 +139 139 131 ivory4 +240 255 240 honeydew1 +224 238 224 honeydew2 +193 205 193 honeydew3 +131 139 131 honeydew4 +255 240 245 LavenderBlush1 +238 224 229 LavenderBlush2 +205 193 197 LavenderBlush3 +139 131 134 LavenderBlush4 +255 228 225 MistyRose1 +238 213 210 MistyRose2 +205 183 181 MistyRose3 +139 125 123 MistyRose4 +240 255 255 azure1 +224 238 238 azure2 +193 205 205 azure3 +131 139 139 azure4 +131 111 255 SlateBlue1 +122 103 238 SlateBlue2 +105 89 205 SlateBlue3 + 71 60 139 SlateBlue4 + 72 118 255 RoyalBlue1 + 67 110 238 RoyalBlue2 + 58 95 205 RoyalBlue3 + 39 64 139 RoyalBlue4 + 0 0 255 blue1 + 0 0 238 blue2 + 0 0 205 blue3 + 0 0 139 blue4 + 30 144 255 DodgerBlue1 + 28 134 238 DodgerBlue2 + 24 116 205 DodgerBlue3 + 16 78 139 DodgerBlue4 + 99 184 255 SteelBlue1 + 92 172 238 SteelBlue2 + 79 148 205 SteelBlue3 + 54 100 139 SteelBlue4 + 0 191 255 DeepSkyBlue1 + 0 178 238 DeepSkyBlue2 + 0 154 205 DeepSkyBlue3 + 0 104 139 DeepSkyBlue4 +135 206 255 SkyBlue1 +126 192 238 SkyBlue2 +108 166 205 SkyBlue3 + 74 112 139 SkyBlue4 +176 226 255 LightSkyBlue1 +164 211 238 LightSkyBlue2 +141 182 205 LightSkyBlue3 + 96 123 139 LightSkyBlue4 +198 226 255 SlateGray1 +185 211 238 SlateGray2 +159 182 205 SlateGray3 +108 123 139 SlateGray4 +202 225 255 LightSteelBlue1 +188 210 238 LightSteelBlue2 +162 181 205 LightSteelBlue3 +110 123 139 LightSteelBlue4 +191 239 255 LightBlue1 +178 223 238 LightBlue2 +154 192 205 LightBlue3 +104 131 139 LightBlue4 +224 255 255 LightCyan1 +209 238 238 LightCyan2 +180 205 205 LightCyan3 +122 139 139 LightCyan4 +187 255 255 PaleTurquoise1 +174 238 238 PaleTurquoise2 +150 205 205 PaleTurquoise3 +102 139 139 PaleTurquoise4 +152 245 255 CadetBlue1 +142 229 238 CadetBlue2 +122 197 205 CadetBlue3 + 83 134 139 CadetBlue4 + 0 245 255 turquoise1 + 0 229 238 turquoise2 + 0 197 205 turquoise3 + 0 134 139 turquoise4 + 0 255 255 cyan1 + 0 238 238 cyan2 + 0 205 205 cyan3 + 0 139 139 cyan4 +151 255 255 DarkSlateGray1 +141 238 238 DarkSlateGray2 +121 205 205 DarkSlateGray3 + 82 139 139 DarkSlateGray4 +127 255 212 aquamarine1 +118 238 198 aquamarine2 +102 205 170 aquamarine3 + 69 139 116 aquamarine4 +193 255 193 DarkSeaGreen1 +180 238 180 DarkSeaGreen2 +155 205 155 DarkSeaGreen3 +105 139 105 DarkSeaGreen4 + 84 255 159 SeaGreen1 + 78 238 148 SeaGreen2 + 67 205 128 SeaGreen3 + 46 139 87 SeaGreen4 +154 255 154 PaleGreen1 +144 238 144 PaleGreen2 +124 205 124 PaleGreen3 + 84 139 84 PaleGreen4 + 0 255 127 SpringGreen1 + 0 238 118 SpringGreen2 + 0 205 102 SpringGreen3 + 0 139 69 SpringGreen4 + 0 255 0 green1 + 0 238 0 green2 + 0 205 0 green3 + 0 139 0 green4 +127 255 0 chartreuse1 +118 238 0 chartreuse2 +102 205 0 chartreuse3 + 69 139 0 chartreuse4 +192 255 62 OliveDrab1 +179 238 58 OliveDrab2 +154 205 50 OliveDrab3 +105 139 34 OliveDrab4 +202 255 112 DarkOliveGreen1 +188 238 104 DarkOliveGreen2 +162 205 90 DarkOliveGreen3 +110 139 61 DarkOliveGreen4 +255 246 143 khaki1 +238 230 133 khaki2 +205 198 115 khaki3 +139 134 78 khaki4 +255 236 139 LightGoldenrod1 +238 220 130 LightGoldenrod2 +205 190 112 LightGoldenrod3 +139 129 76 LightGoldenrod4 +255 255 224 LightYellow1 +238 238 209 LightYellow2 +205 205 180 LightYellow3 +139 139 122 LightYellow4 +255 255 0 yellow1 +238 238 0 yellow2 +205 205 0 yellow3 +139 139 0 yellow4 +255 215 0 gold1 +238 201 0 gold2 +205 173 0 gold3 +139 117 0 gold4 +255 193 37 goldenrod1 +238 180 34 goldenrod2 +205 155 29 goldenrod3 +139 105 20 goldenrod4 +255 185 15 DarkGoldenrod1 +238 173 14 DarkGoldenrod2 +205 149 12 DarkGoldenrod3 +139 101 8 DarkGoldenrod4 +255 193 193 RosyBrown1 +238 180 180 RosyBrown2 +205 155 155 RosyBrown3 +139 105 105 RosyBrown4 +255 106 106 IndianRed1 +238 99 99 IndianRed2 +205 85 85 IndianRed3 +139 58 58 IndianRed4 +255 130 71 sienna1 +238 121 66 sienna2 +205 104 57 sienna3 +139 71 38 sienna4 +255 211 155 burlywood1 +238 197 145 burlywood2 +205 170 125 burlywood3 +139 115 85 burlywood4 +255 231 186 wheat1 +238 216 174 wheat2 +205 186 150 wheat3 +139 126 102 wheat4 +255 165 79 tan1 +238 154 73 tan2 +205 133 63 tan3 +139 90 43 tan4 +255 127 36 chocolate1 +238 118 33 chocolate2 +205 102 29 chocolate3 +139 69 19 chocolate4 +255 48 48 firebrick1 +238 44 44 firebrick2 +205 38 38 firebrick3 +139 26 26 firebrick4 +255 64 64 brown1 +238 59 59 brown2 +205 51 51 brown3 +139 35 35 brown4 +255 140 105 salmon1 +238 130 98 salmon2 +205 112 84 salmon3 +139 76 57 salmon4 +255 160 122 LightSalmon1 +238 149 114 LightSalmon2 +205 129 98 LightSalmon3 +139 87 66 LightSalmon4 +255 165 0 orange1 +238 154 0 orange2 +205 133 0 orange3 +139 90 0 orange4 +255 127 0 DarkOrange1 +238 118 0 DarkOrange2 +205 102 0 DarkOrange3 +139 69 0 DarkOrange4 +255 114 86 coral1 +238 106 80 coral2 +205 91 69 coral3 +139 62 47 coral4 +255 99 71 tomato1 +238 92 66 tomato2 +205 79 57 tomato3 +139 54 38 tomato4 +255 69 0 OrangeRed1 +238 64 0 OrangeRed2 +205 55 0 OrangeRed3 +139 37 0 OrangeRed4 +255 0 0 red1 +238 0 0 red2 +205 0 0 red3 +139 0 0 red4 +215 7 81 DebianRed +255 20 147 DeepPink1 +238 18 137 DeepPink2 +205 16 118 DeepPink3 +139 10 80 DeepPink4 +255 110 180 HotPink1 +238 106 167 HotPink2 +205 96 144 HotPink3 +139 58 98 HotPink4 +255 181 197 pink1 +238 169 184 pink2 +205 145 158 pink3 +139 99 108 pink4 +255 174 185 LightPink1 +238 162 173 LightPink2 +205 140 149 LightPink3 +139 95 101 LightPink4 +255 130 171 PaleVioletRed1 +238 121 159 PaleVioletRed2 +205 104 137 PaleVioletRed3 +139 71 93 PaleVioletRed4 +255 52 179 maroon1 +238 48 167 maroon2 +205 41 144 maroon3 +139 28 98 maroon4 +255 62 150 VioletRed1 +238 58 140 VioletRed2 +205 50 120 VioletRed3 +139 34 82 VioletRed4 +255 0 255 magenta1 +238 0 238 magenta2 +205 0 205 magenta3 +139 0 139 magenta4 +255 131 250 orchid1 +238 122 233 orchid2 +205 105 201 orchid3 +139 71 137 orchid4 +255 187 255 plum1 +238 174 238 plum2 +205 150 205 plum3 +139 102 139 plum4 +224 102 255 MediumOrchid1 +209 95 238 MediumOrchid2 +180 82 205 MediumOrchid3 +122 55 139 MediumOrchid4 +191 62 255 DarkOrchid1 +178 58 238 DarkOrchid2 +154 50 205 DarkOrchid3 +104 34 139 DarkOrchid4 +155 48 255 purple1 +145 44 238 purple2 +125 38 205 purple3 + 85 26 139 purple4 +171 130 255 MediumPurple1 +159 121 238 MediumPurple2 +137 104 205 MediumPurple3 + 93 71 139 MediumPurple4 +255 225 255 thistle1 +238 210 238 thistle2 +205 181 205 thistle3 +139 123 139 thistle4 + 0 0 0 gray0 + 0 0 0 grey0 + 3 3 3 gray1 + 3 3 3 grey1 + 5 5 5 gray2 + 5 5 5 grey2 + 8 8 8 gray3 + 8 8 8 grey3 + 10 10 10 gray4 + 10 10 10 grey4 + 13 13 13 gray5 + 13 13 13 grey5 + 15 15 15 gray6 + 15 15 15 grey6 + 18 18 18 gray7 + 18 18 18 grey7 + 20 20 20 gray8 + 20 20 20 grey8 + 23 23 23 gray9 + 23 23 23 grey9 + 26 26 26 gray10 + 26 26 26 grey10 + 28 28 28 gray11 + 28 28 28 grey11 + 31 31 31 gray12 + 31 31 31 grey12 + 33 33 33 gray13 + 33 33 33 grey13 + 36 36 36 gray14 + 36 36 36 grey14 + 38 38 38 gray15 + 38 38 38 grey15 + 41 41 41 gray16 + 41 41 41 grey16 + 43 43 43 gray17 + 43 43 43 grey17 + 46 46 46 gray18 + 46 46 46 grey18 + 48 48 48 gray19 + 48 48 48 grey19 + 51 51 51 gray20 + 51 51 51 grey20 + 54 54 54 gray21 + 54 54 54 grey21 + 56 56 56 gray22 + 56 56 56 grey22 + 59 59 59 gray23 + 59 59 59 grey23 + 61 61 61 gray24 + 61 61 61 grey24 + 64 64 64 gray25 + 64 64 64 grey25 + 66 66 66 gray26 + 66 66 66 grey26 + 69 69 69 gray27 + 69 69 69 grey27 + 71 71 71 gray28 + 71 71 71 grey28 + 74 74 74 gray29 + 74 74 74 grey29 + 77 77 77 gray30 + 77 77 77 grey30 + 79 79 79 gray31 + 79 79 79 grey31 + 82 82 82 gray32 + 82 82 82 grey32 + 84 84 84 gray33 + 84 84 84 grey33 + 87 87 87 gray34 + 87 87 87 grey34 + 89 89 89 gray35 + 89 89 89 grey35 + 92 92 92 gray36 + 92 92 92 grey36 + 94 94 94 gray37 + 94 94 94 grey37 + 97 97 97 gray38 + 97 97 97 grey38 + 99 99 99 gray39 + 99 99 99 grey39 +102 102 102 gray40 +102 102 102 grey40 +105 105 105 gray41 +105 105 105 grey41 +107 107 107 gray42 +107 107 107 grey42 +110 110 110 gray43 +110 110 110 grey43 +112 112 112 gray44 +112 112 112 grey44 +115 115 115 gray45 +115 115 115 grey45 +117 117 117 gray46 +117 117 117 grey46 +120 120 120 gray47 +120 120 120 grey47 +122 122 122 gray48 +122 122 122 grey48 +125 125 125 gray49 +125 125 125 grey49 +127 127 127 gray50 +127 127 127 grey50 +130 130 130 gray51 +130 130 130 grey51 +133 133 133 gray52 +133 133 133 grey52 +135 135 135 gray53 +135 135 135 grey53 +138 138 138 gray54 +138 138 138 grey54 +140 140 140 gray55 +140 140 140 grey55 +143 143 143 gray56 +143 143 143 grey56 +145 145 145 gray57 +145 145 145 grey57 +148 148 148 gray58 +148 148 148 grey58 +150 150 150 gray59 +150 150 150 grey59 +153 153 153 gray60 +153 153 153 grey60 +156 156 156 gray61 +156 156 156 grey61 +158 158 158 gray62 +158 158 158 grey62 +161 161 161 gray63 +161 161 161 grey63 +163 163 163 gray64 +163 163 163 grey64 +166 166 166 gray65 +166 166 166 grey65 +168 168 168 gray66 +168 168 168 grey66 +171 171 171 gray67 +171 171 171 grey67 +173 173 173 gray68 +173 173 173 grey68 +176 176 176 gray69 +176 176 176 grey69 +179 179 179 gray70 +179 179 179 grey70 +181 181 181 gray71 +181 181 181 grey71 +184 184 184 gray72 +184 184 184 grey72 +186 186 186 gray73 +186 186 186 grey73 +189 189 189 gray74 +189 189 189 grey74 +191 191 191 gray75 +191 191 191 grey75 +194 194 194 gray76 +194 194 194 grey76 +196 196 196 gray77 +196 196 196 grey77 +199 199 199 gray78 +199 199 199 grey78 +201 201 201 gray79 +201 201 201 grey79 +204 204 204 gray80 +204 204 204 grey80 +207 207 207 gray81 +207 207 207 grey81 +209 209 209 gray82 +209 209 209 grey82 +212 212 212 gray83 +212 212 212 grey83 +214 214 214 gray84 +214 214 214 grey84 +217 217 217 gray85 +217 217 217 grey85 +219 219 219 gray86 +219 219 219 grey86 +222 222 222 gray87 +222 222 222 grey87 +224 224 224 gray88 +224 224 224 grey88 +227 227 227 gray89 +227 227 227 grey89 +229 229 229 gray90 +229 229 229 grey90 +232 232 232 gray91 +232 232 232 grey91 +235 235 235 gray92 +235 235 235 grey92 +237 237 237 gray93 +237 237 237 grey93 +240 240 240 gray94 +240 240 240 grey94 +242 242 242 gray95 +242 242 242 grey95 +245 245 245 gray96 +245 245 245 grey96 +247 247 247 gray97 +247 247 247 grey97 +250 250 250 gray98 +250 250 250 grey98 +252 252 252 gray99 +252 252 252 grey99 +255 255 255 gray100 +255 255 255 grey100 +169 169 169 dark grey +169 169 169 DarkGrey +169 169 169 dark gray +169 169 169 DarkGray +0 0 139 dark blue +0 0 139 DarkBlue +0 139 139 dark cyan +0 139 139 DarkCyan +139 0 139 dark magenta +139 0 139 DarkMagenta +139 0 0 dark red +139 0 0 DarkRed +144 238 144 light green +144 238 144 LightGreen diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index f206ac8b..9a56de6f 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -21,6 +21,7 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; +import { getColorFromName } from 'common/data/ColorNames'; /** * Map collect to glevel. Used in `selectCharset`. @@ -2877,13 +2878,14 @@ export class InputHandler extends Disposable implements IInputHandler { * with float numbering are not supported. */ protected _parseColorSpec(data: string): [number, number, number] | void { + if (!data) return; // also handle uppercases - data = data.toLowerCase(); - if (data.indexOf('rgb:') === 0) { + let low = data.toLowerCase(); + if (low.indexOf('rgb:') === 0) { // 'rgb:' specifier - data = data.slice(4); + low = low.slice(4); const rex = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; - const m = rex.exec(data); + const m = rex.exec(low); if (m) { const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535; return [ @@ -2892,20 +2894,21 @@ export class InputHandler extends Disposable implements IInputHandler { Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255) ]; } - } else if (data.indexOf('#') === 0) { + } else if (low.indexOf('#') === 0) { // '#' specifier - data = data.slice(1); + low = low.slice(1); const rex = /^[\da-f]+$/; - if (rex.exec(data) && [3, 6, 9, 12].includes(data.length)) { - const adv = data.length / 3; + if (rex.exec(low) && [3, 6, 9, 12].includes(low.length)) { + const adv = low.length / 3; const result: [number, number, number] = [0, 0, 0]; for (let i = 0; i < 3; ++i) { - const c = parseInt(data.slice(adv * i, adv * i + adv), 16); + const c = parseInt(low.slice(adv * i, adv * i + adv), 16); result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8; } return result; } } + return getColorFromName(data); } /** diff --git a/src/common/data/ColorNames.ts b/src/common/data/ColorNames.ts new file mode 100644 index 00000000..68dd06b6 --- /dev/null +++ b/src/common/data/ColorNames.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * This module enables X11 color name lookups with the help of a perfect hash function + * to not penalize the package size too much. + * saving: ~70% (from 17kB down to 5kB) + */ + +// Note: module and table data created with fixtures/x11-colornames/create_module.js +const TABLE = 'AB!!!id!jsih!ZYigHp3AkceNiiImVihW3Uih?DFihrenihDEQiwgziieGJvAoMnsidOM3idOtQiciazAsFrtieFZyidKSgAx8odip818ieFiXieF97ic34eihOxpA4UPNidz51A8znjBAX?uBU9ynitOZdigRMNBYgIEBcaXPBk9LVB0xlOB9mcFihmlWCRq0yidmG7idqsHid7RmCV7pCClqRAimG5kCpsb4ipt78CtqLeig17aigJb7ioC4rihzLdigmoNigEDTihzZKCwIz1C4j5sih4QQihlg2igwsHDBFTuDESshDJFzIig1cbihZ3wipZphigiKcihZUxihZLODRm7RilG1Cid3tmDVGffDYJsPDc1JrixgxGidDn3Dli9uDsOl0imDQPiyDhbimDyZihU00icbZSEJs6VieEzpicF2YEqB?PicemNiduxiihEL9ieBjnEuI53icdLPiqBYWiduYFExunsFB3JqFEXPuihnyvFQkNAFVVjlic8SLicIeOFYvurFwrh5icQ4RicHUgidVbJidVA3idN3LGECqgGJiKSGRl8UidT9qipssVGZHNyidy3widTu8idgHsidgcgGdggRGkODXihH!8ilyhpihWj0ihWcailWHjGwO?OilD4BG888ZHJzmGHZtoyicqlkiwWfkicEbdHg3uEHtMkRH0TgkH5MBRIQE9VIUh5VIYeSyieFFgIlC6Qidvc5idv65icprGicNfhItjoJI4N3mJRFpIicLVGJgMY7J2GebKAPoGKyITuidFGWic2olicYLfLFuJDidnoticCc!icx2bieAcuLIv3UicoYuic594LRQ86LZc3NidcphicnOVic09yisjDiitRyFLc7NTicTaFicFbvic6jPLk3JtigqSiL1QJUjJQZpidQvJicUVVidYVsidNOjidNQfidNnLicyCAicHGeidY17icL3cL8DSKMcPzVMgc8dicEg6ig61OMwImmM06KXM6Hl!M96LNicQEbicUjBNAt2Aid4Hwix3w0Ndba5Nt5pqNwuVljBbJaicYjeigAxGN1b!LOEvTnisIIoORf1cOVV!TiczJbiddd5iddgpiclyxidfd8idfuEieACwid?zWih2rlOp2waid?r2OtUKQOxMyjihUpcigKWQO4y32PNAPFidAU6PZAhBPhjAni0PYUiceDridjwXPqEBxPsZ5Cicoi9icBWoieHXOP0B?9QF50CigWlyQJBkUQMs8nici3YQQ?MXidedxicjVCidhu9icqIFidhZjidhBQid0avicmIYQZLp!QdLBNQwFKyipSwfikPB5Q1SZKipSC1idL08Q44Vaic4tpikVWWQ9x5KikK4sig4Afit?bgihvhKRA?wsidxIfidGjgikw?ORRruoRYQWbRh2dkRk!EPRo!Vpig!taR92Glicf4gicxZGSCIB1jED6KSF0tyid0MUieEUnidLfYSRRoKSUk5sSczRfih06gSkpOgikVvGio5X1icp8Kio5NFik0sjix9EQSp8Pvide28icanaidyPpidekWSx71CTANkkicssXTE1nojEbGQTJp32ic?fFTcYU6icuDuicNCIT1B67T4DhEice8dihBd7ismVIUIRe3UVJeiUZBPEVNXn3idXcIj0jici5yT6VgGbJVlXA2idcRxicfAXicT4kicRjbVpnXZieEm?V4feMigLlzV95OGip4?git4nTWF3csicyWQWJGH2ikH?FWMo06ieCn5WSC2fieDOsWUm1Tidor2WZoaQidoCjicz5cWlxroicTKOW4dS9ido5CidnBwic0AWXJX4lXNdDaicS!nXkWPBXotnyYAgsNicrXhicnmHidHe0idHlLicaNvicG8Jida06YMOfFidTV!icY0YidDcrYRiXUYZivaigDO8ig8GPigGNwi88iCikuzkiharXicoAoihJPbihae9ihJmOihJ9xigV9UiwsMyidaEBichf!ic9ZEYk9oiigCARY9kLuidkWPidknpidk?aihh0likBkKZEJFsidjdfieJmgZIU5dieH47isgQIZNxb3ZQn7hidI6HZVIi0il9ZLix9nYih92!ihCeNZZClyZhISFi1IDjid6bQicwQvZl66Eid6riZoGjRicpYuieBC5ic5jfZwlrsicKATimA?cZ8ZO6icxGJieAt2iculVaA40zidYsAacin?aglNzalwMVi9d0mictfZascgqicbxMbABJ?ignSAjYM8?bIhNZigd1qig7eVieJNPbQq8pikKkaigVPligW!fjgR?5bWCHKidCM1bcrI5idppfidpRsi5P6TbkXgIbo2xucEAZdihvCiidPJXidPYxicAACcIXb9isr67cMtDzcgfiVctsKmc8khZigZjTilrC1ihpKGjQhh5dJr30igymSjKJehihMVeig3VrigwENipRc!idKnBdRRFodhKEOdxEc9idEnCidE8zic!9VidK1md18eZit!FHieGkhicJ8SieCXticlSNidbjridA7YeIdgneNtbMeVlKqeZlSZicbpmixtEVesAlzfU2dAfdwW?fsSYbf5w59gB1xOicMKxgN1q8icZbagZPjpidSoWid1Y0ic2JSid1Bhgo7qeidcLjilYKFiw6S2isa5Jgxwl9g00UbicSMohA73XhELGKhUQqNi8cGshgsaridfCIhkkePhtUeth0vCqileCnit5YaiE?plieHKPiJTPmiN7Kt'; +const COLORS = '??r6!Pj?!Pj?9fX19fX13Nzc??rw??rw?fXm?fXm!vDm!uvX!uvX?!?V?!?V?!vN?!vN?!TE?9q5?9q5?96t?96t?!S1??jc???w??rN??rN??Xu8P?w9f?69f?68P??8Pj?8Pj?5ub6??D1??D1?!Th?!Th????AAAAL09PL09PL09PL09PaWlpaWlpaWlpaWlpcICQcICQcICQcICQd4iZd4iZd4iZd4iZvr6!vr6!09PT09PT09PT09PTGRlwGRlwAACAAACAAACAZJXtZJXtSD2LSD2LalrNalrNe2jue2juhHD?hHD?AADNAADNQWnhQWnhAAD?HpD?HpD?AL??AL??h87rh87rh876h876RoK0RoK0sMTesMTerdjmrdjmsODmsODmr!7ur!7uAM7RAM7RSNHMSNHMQODQAP??4P??4P??X56gX56gZs2qZs2qf??UAGQAAGQAVWsvVWsvj7yPj7yPLotXLotXPLNxPLNxILKqILKqmPuYmPuYAP9?AP9?fPwAfPwAAP8Af?8AAPqaAPqarf8vrf8vMs0yMs0yms0yms0yIosiIosia44ja44jvbdrvbdr8OaM7uiq7uiq!vrS!vrS???g???g??8A?9cA7t2C7t2C2qUguIYLuIYLvI!PvI!PzVxczVxci0UTi0UToFItzYU?3riH9fXc9d6z9KRg9KRg0rSM0mkesiIipSoq6ZZ66ZZ6!oBy?6B6?6B6?6UA?4wA?4wA?39Q8ICA8ICA?2NH?0UA?0UA?wAA?2m0?2m0?xST?xST?8DL?7bB?7bB23CT23CTsDBgxxWFxxWF0CCQ0CCQ?wD?7oLu3aDd2nDWulXTulXTmTLMmTLMlADTlADTiiviiivioCDwk3Dbk3Db2L?Y??r67unpzcnJi4mJ??Xu7uXezcW?i4aC?!?b7t?MzcCwi4N4?!TE7tW3zbeei31r?9q57sutza!Vi3dl?96t7s!hzbOLi3le??rN7um?zcmli4lw??jc7ujNzcixi4h4???w7u7gzc3Bi4uD8P?w4O7gwc3Bg4uD??D17uDlzcHFi4OG?!Th7tXSzbe1i3178P??4O7uwc3Ng4uLg2??emfuaVnNRzyLSHb?Q27uOl?NJ0CLAAD?AADuAADNAACLHpD?HIbuGHTNEE6LY7j?XKzuT5TNNmSLAL??ALLuAJrNAGiLh87?fsDubKbNSnCLsOL?pNPujbbNYHuLxuL?udPun7bNbHuLyuH?vNLuorXNbnuLv!??st?umsDNaIOL4P??0e7utM3NeouLu???ru7uls3NZouLmPX?juXuesXNU4aLAPX?AOXuAMXNAIaLAP??AO7uAM3NAIuLl???je7uec3NUouLf??Udu7GZs2qRYt0wf?BtO60m82baYtpVP!fTu6UQ82ALotXmv!akO6QfM18VItUAP9?AO52AM1mAItFAP8AAO4AAM0AAIsAf?8Adu4AZs0ARYsAwP8!s!46ms0yaYsiyv9wvO5oos1abos9??aP7uaFzcZzi4ZO?!yL7tyCzb5wi4FM???g7u7Rzc20i4t6??8A7u4Azc0Ai4sA?9cA7skAza0Ai3UA?8El7rQizZsdi2kU?7kP7q0OzZUMi2UI?8HB7rS0zZubi2lp?2pq7mNjzVVVizo6?4JH7nlCzWg5i0cm?9Ob7sWRzap9i3NV?!e67tiuzbqWi35m?6VP7ppJzYU?i1or?38k7nYhzWYdi0UT?zAw7iwszSYmixoa?0BA7js7zTMziyMj?4xp7oJizXBUi0w5?6B67pVyzYFii1dC?6UA7poAzYUAi1oA?38A7nYAzWYAi0UA?3JW7mpQzVtFiz4v?2NH7lxCzU85izYm?0UA7kAAzTcAiyUA?wAA7gAAzQAAiwAA1wdR?xST7hKJzRB2iwpQ?2607mqnzWCQizpi?7XF7qm4zZGei2Ns?6657qKtzYyVi19l?4Kr7nmfzWiJi0dd?zSz7jCnzSmQixxi?z6W7jqMzTJ4iyJS?wD?7gDuzQDNiwCL?4P67nrpzWnJi0eJ?7v?7q7uzZbNi2aL4Gb?0V?utFLNejeLvz7?sjrumjLNaCKLmzD?kSzufSbNVRqLq4L?n3nuiWjNXUeL?!H?7tLuzbXNi3uLqampqampqampqampAACLAACLAIuLAIuLiwCLiwCLiwAAiwAAkO6QkO6Q'; +const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?'; +const OFFSET = 551; +const LENGTH = 551; +const BUCKET_LENGTH = TABLE.length / LENGTH; + +// match greyXX|grayXX names +const rexGrey = /^gr[ae]y(\d+)/; +// name match +const rexName = /^\w?[A-Za-z0-9 ]+\w/; + +function gray(n: number): [number, number, number] | undefined { + if (0 <= n && n <= 100) { + const v = Math.floor((n * 256 - n + 50) / 100); + return [v, v, v]; + } + return; +} + +function hash(d: number, name: string): number { + if (!d) d = 0x01000193; + for (const c of name) { + d = ((d * 0x01000193) ^ c.charCodeAt(0)) & 0xffffffff; + } + return d >>> 0; +} + +function crc10(name: string, crc?: number): number { + if (!crc) crc = 0; + for (const c of name) { + crc ^= c.charCodeAt(0) << 2; + for (let k = 0; k < 8; k++) { + crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; + } + } + crc &= 0x3ff; + return crc >>> 0; +} + +function loadData(idx: number): [number, number, number] { + let value = 0; + for (let i = idx * BUCKET_LENGTH; i < idx * BUCKET_LENGTH + BUCKET_LENGTH; ++i) { + value *= ALPHABET.length; + value += ALPHABET.indexOf(TABLE[i]); + } + return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; +} + +function loadColor(idx: number): [number, number, number] { + // color buckets are hardcoded to 4 chars + let v = 0; + for (let i = idx * 4; i < idx * 4 + 4; ++i) { + v *= ALPHABET.length; + v += ALPHABET.indexOf(COLORS[i]); + } + return [v >>> 16, (v >> 8) & 0xFF, v & 0xFF]; +} + +function lookupIdx(name: string): number { + let b = loadData(hash(0, name) % LENGTH); + b = loadData(b[0] < OFFSET ? (-(b[0] - OFFSET) - 1) : hash(b[0] - OFFSET, name) % LENGTH); + const [ , , crc] = loadData(b[1]); + return crc10(name) === crc ? b[1] : -1; +} + +export function getColorFromName(name: string): [number, number, number] | undefined { + // basic name filtering + if (name.length < 3 || name.length > 22 || !rexName.exec(name)) return; + + // handle grays special + const m = rexGrey.exec(name); + if (m) return gray(parseInt(m[1])); + + // grab crc checked idx from PHF + const idx = lookupIdx(name); + if (idx === -1) return; + return loadColor(idx); +} From b7560be2b5c65ceb418b8540bcf8f338f119477b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 14:03:36 +0100 Subject: [PATCH 08/20] remove named colors --- fixtures/x11-colornames/README.md | 14 - fixtures/x11-colornames/create_module.js | 447 -------------- fixtures/x11-colornames/rgb.txt | 754 ----------------------- src/common/InputHandler.test.ts | 2 +- src/common/InputHandler.ts | 4 +- src/common/data/ColorNames.ts | 91 --- 6 files changed, 2 insertions(+), 1310 deletions(-) delete mode 100644 fixtures/x11-colornames/README.md delete mode 100644 fixtures/x11-colornames/create_module.js delete mode 100644 fixtures/x11-colornames/rgb.txt delete mode 100644 src/common/data/ColorNames.ts diff --git a/fixtures/x11-colornames/README.md b/fixtures/x11-colornames/README.md deleted file mode 100644 index ee6d7b6d..00000000 --- a/fixtures/x11-colornames/README.md +++ /dev/null @@ -1,14 +0,0 @@ -### Fixture for 11 color names - -`rgb.txt` contains X11's defined color names, copied over from `/etc/X11/rgb.txt` on Ubuntu 18. - -Run `create_module.js` to create a TS module containing the color definitions. The script performs these steps: -- extract color definitions from `rgb.txt` -- remove gray definitions (re-added programmatically later) -- create a perfect hash function -- calculate crc10 for basic collision prevention -- run several collision tests -- compress table and color data -- write data with loading shim to `ColorNames.ts` - -The final file is meant to be copied over to `../../src/common/data/`. diff --git a/fixtures/x11-colornames/create_module.js b/fixtures/x11-colornames/create_module.js deleted file mode 100644 index edc1f314..00000000 --- a/fixtures/x11-colornames/create_module.js +++ /dev/null @@ -1,447 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -/** - * For decoder side: - * - reconstruct grays with f = lambda x: (x * 256 - x + 50) / 100 (needs value rounding check) - * - apply nameFilter - * - build lookup & extract functions (runtime decoding?) - * - transfer hashtable and color table - */ - -// match color entry in rgb.txt -const rexFile = /^\s*(\d+)\s*(\d+)\s*(\d+)\s*?[\t]+(.*)$/; -// match greyXX|grayXX names -const rexGrey = /^gr[ae]y\d+/; -// name match -const rexName = /^\w?[A-Za-z0-9 ]+\w/; - -function parseX11Colors(filename) { - const fileData = fs.readFileSync(filename, {encoding: 'utf8'}); - const colors = []; - for (const line of fileData.split('\n')) { - const m = rexFile.exec(line); - if (m) { - colors.push({ - color: [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])], - name: m[4] - }); - } - } - return colors; -} - -// Use the FNV algorithm from http://isthe.com/chongo/tech/comp/fnv/ -function hash(d, name) { - if (!d) d = 0x01000193; - for (const c of name) { - d = ( (d * 0x01000193) ^ c.charCodeAt(0) ) & 0xffffffff; - } - return d >>> 0; -} - -// inspired from http://stevehanov.ca/blog/?id=119 -function createMinimalPerfectHash(nameList) { - const nameMap = Object.create(null); - for (const [idx, name] of nameList.entries()) { - nameMap[name] = idx; - } - const size = nameList.length; - - const buckets = []; - for (let i = 0; i < size; ++i) buckets.push([]); - const g = new Array(size).fill(0); - const v = new Array(size).fill(null); - - for (const key of nameList) { - buckets[hash(0, key) % size].push(key); - } - - buckets.sort((a, b) => b.length - a.length); - let breakIdx = 0; - for (let i = 0; i < size; ++i) { - const bucket = buckets[i]; - if (bucket.length <= 1) { - breakIdx = i; - break; - } - let d = 1; - let item = 0; - let slots = []; - - while (item < bucket.length) { - const slot = hash(d, bucket[item]) % size; - if (v[slot] !== null || slots.includes(slot)) { - d++; - item = 0; - slots = []; - } else { - slots.push(slot); - item++; - } - } - - g[hash(0, bucket[0]) % size] = d; - for (let k = 0; k < bucket.length; ++k) { - v[slots[k]] = nameMap[bucket[k]]; - } - } - - const freeList = []; - for (let i = 0; i < size; ++i) { - if (v[i] === null) freeList.push(i); - } - - for (let i = breakIdx; i < size; ++i) { - const bucket = buckets[i]; - if (!bucket.length) break; - const slot = freeList.pop(); - g[hash(0, bucket[0]) % size] = -slot - 1; - v[slot] = nameMap[bucket[0]]; - } - - return [g, v]; -} - -function lookupPerfectHash(g, v, key) { - const d = g[hash(0, key) % g.length]; - if (d < 0) return v[-d - 1]; - return v[hash(d, key) % v.length]; -} - -function checkPerfectHashTables(g, v, nameList) { - let allPassed = true; - for (const [idx, name] of nameList.entries()) { - const lookup = lookupPerfectHash(g, v, name); - if (lookup !== idx) { - console.log(`\x1b[33mmismatch: '${name}' returns ${lookup} (orig: ${idx})\x1b[m`); - allPassed = false; - } - } - return allPassed; -} - -function crc10atm(name, crc) { - if (!crc) crc = 0; - for (const c of name) { - const v = c.charCodeAt(0); - crc ^= v << 2; - for (let k = 0; k < 8; k++) { - crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; - } - } - crc &= 0x3ff; - return crc >>> 0; -} - -function checkColorList(g, v, crc, colorNames) { - let match = []; - for (const word of colorNames) { - if (!nameFilter(word)) continue; - const idx = lookupPerfectHash(g, v, word); - const crc10 = crc10atm(word); - if (crc[idx] === crc10) { - match.push({idx, word, colorName: colorNames[idx]}); - } - } - return {tested: colorNames.length, match}; -} - -function checkWordlists(g, v, crc, colorNames, filename) { - const fileData = fs.readFileSync(filename, {encoding: 'utf8'}); - const words = fileData.split('\n').filter(el => lookupPerfectHash.length !== 0); - let collisions = []; - for (const word of words) { - if (!nameFilter(word)) continue; - const idx = lookupPerfectHash(g, v, word); - const crc10 = crc10atm(word); - if (crc[idx] === crc10 && word !== colorNames[idx]) { - collisions.push({idx, word, colorName: colorNames[idx]}); - } - } - return {tested: words.length, collisions}; -} - -function nameFilter(name) { - // length 3 - 22 - if (name.length < 3 || name.length > 22) return; - // chars only in [A-Za-z0-9 ] - if (!rexName.exec(name)) return; - return name; -} - -function compressTables(g, v, crc, al) { - // g | v | crc: all in 10 bit (<1024) - // --> 30 bit - // --> fits into 5 bytes of a 64-bit char alphabet - - // g needs an offset for proper zero alignment - const gOffset = -Math.min(...g); - g = g.map(el => el + gOffset); - - // assert we are in 0..2^10 - if (Math.min(...g) < 0 || Math.min(...v) < 0 || Math.min(...crc) < 0 - || Math.max(...g) > 1023 || Math.max(...v) > 1023 || Math.max(...crc) > 1023 - ) { - console.log('\x1b[31mTables out of compressible range, manual fix needed.\x1b[m'); - process.exit(1); - } - - // assert we have same length - if (g.length !== v.length || g.length !== crc.length) { - console.log('\x1b[31mTables length mismatch, manual fix needed.\x1b[m'); - process.exit(1); - } - - // construct compressed data string - let result = ''; - const length = g.length; - for (let i = 0; i < length; ++i) { - let value = (g[i] << 20) | (v[i] << 10) | crc[i]; - let bucket = ''; - for (let k = 0; k < 5; ++k) { - bucket += al[value % al.length]; - value = Math.floor(value / al.length); - } - result += bucket.split('').reverse().join(''); - } - - return [result, length, gOffset]; -} - -function compressColors(colors, al) { - let result = ''; - for (const color of colors) { - let value = (color[0] << 16) | (color[1] << 8) | color[2]; - let bucket = ''; - for (let k = 0; k < 4; ++k) { - bucket += al[value % al.length]; - value = Math.floor(value / al.length); - } - result += bucket.split('').reverse().join(''); - } - return result; -} - -function loadData(bucket, al) { - let value = 0; - for (const c of bucket) { - value *= al.length; - value += al.indexOf(c); - } - return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; -} - -function loadColor(al, data, idx) { - // color buckets are hardcoded to 4 chars - let value = 0; - for (let i = idx * 4; i < idx * 4 + 4; ++i) { - value *= al.length; - value += al.indexOf(data[i]); - } - return [value >>> 16, (value >> 8) & 0xFF, value & 0xFF]; -} - -function lookupIdx(al, data, length, gOffset, name) { - const bl = data.length / length; - let offset = (hash(0, name) % length) * bl; - let [g, v, crc] = loadData(data.slice(offset, offset + bl), al); - offset = g < gOffset - ? (-(g - gOffset) - 1) * bl - : (hash(g - gOffset, name) % length) * bl; - [_, v, _] = loadData(data.slice(offset, offset + bl), al); - offset = v * bl; - [_, _, crc] = loadData(data.slice(offset, offset + bl), al); - return crc10atm(name) === crc ? v : -1; -} - -function lookup(al, data, colorData, length, gOffset, name) { - const idx = lookupIdx(al, data, length, gOffset, name); - if (idx === -1) return; - return loadColor(al, colorData, idx); -} - -function createModule(tableData, colorData, alphabet, gOffset, length) { - const TMPL = `/** - * Copyright (c) 2021 The xterm.js authors. All rights reserved. - * @license MIT - */ - -/** - * This module enables X11 color name lookups with the help of a perfect hash function - * to not penalize the package size too much. - * saving: ~70% (from 17kB down to 5kB) - */ - -// Note: module and table data created with fixtures/x11-colornames/create_module.js -const TABLE = '${tableData}'; -const COLORS = '${colorData}'; -const ALPHABET = '${alphabet}'; -const OFFSET = ${gOffset}; -const LENGTH = ${length}; -const BUCKET_LENGTH = TABLE.length / LENGTH; - -// match greyXX|grayXX names -const rexGrey = /^gr[ae]y(\\d+)/; -// name match -const rexName = /^\\w?[A-Za-z0-9 ]+\\w/; - -function gray(n: number): [number, number, number] | undefined { - if (0 <= n && n <= 100) { - const v = Math.floor((n * 256 - n + 50) / 100); - return [v, v, v]; - } - return; -} - -function hash(d: number, name: string): number { - if (!d) d = 0x01000193; - for (const c of name) { - d = ((d * 0x01000193) ^ c.charCodeAt(0)) & 0xffffffff; - } - return d >>> 0; -} - -function crc10(name: string, crc?: number): number { - if (!crc) crc = 0; - for (const c of name) { - crc ^= c.charCodeAt(0) << 2; - for (let k = 0; k < 8; k++) { - crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; - } - } - crc &= 0x3ff; - return crc >>> 0; -} - -function loadData(idx: number): [number, number, number] { - let value = 0; - for (let i = idx * BUCKET_LENGTH; i < idx * BUCKET_LENGTH + BUCKET_LENGTH; ++i) { - value *= ALPHABET.length; - value += ALPHABET.indexOf(TABLE[i]); - } - return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; -} - -function loadColor(idx: number): [number, number, number] { - // color buckets are hardcoded to 4 chars - let v = 0; - for (let i = idx * 4; i < idx * 4 + 4; ++i) { - v *= ALPHABET.length; - v += ALPHABET.indexOf(COLORS[i]); - } - return [v >>> 16, (v >> 8) & 0xFF, v & 0xFF]; -} - -function lookupIdx(name: string): number { - let b = loadData(hash(0, name) % LENGTH); - b = loadData(b[0] < OFFSET ? (-(b[0] - OFFSET) - 1) : hash(b[0] - OFFSET, name) % LENGTH); - const [ , , crc] = loadData(b[1]); - return crc10(name) === crc ? b[1] : -1; -} - -export function getColorFromName(name: string): [number, number, number] | undefined { - // basic name filtering - if (name.length < 3 || name.length > 22 || !rexName.exec(name)) return; - - // handle grays special - const m = rexGrey.exec(name); - if (m) return gray(parseInt(m[1])); - - // grab crc checked idx from PHF - const idx = lookupIdx(name); - if (idx === -1) return; - return loadColor(idx); -} -`; - return TMPL; -} - - -function main() { - // parse definitions from X11 file - const colorList = parseX11Colors(path.join(__dirname, '/rgb.txt')) - .filter(el => !rexGrey.exec(el.name)); // remove greys, as we can reconstruct them later - const nameList = colorList.map(el => el.name); - - // PHF creation and test - const [g, v] = createMinimalPerfectHash(nameList); - if (!checkPerfectHashTables(g, v, nameList)) { - console.error('\x1b[31mPHF creation failed.\x1b[m'); - process.exit(1); - } else { - console.log('\x1b[32mPHF creation successful.\x1b[m'); - } - - // calc 10-bit CRCs to rule to avoid collisions at ~1:1024 - const crc = nameList.map(el => crc10atm(el)); - - // collision check against colorNames - must all collide - console.log('Self collision test:') - const collSelf = checkColorList(g, v, crc, nameList); - console.log('self:', { - entries: collSelf.tested, - matches: collSelf.match.length, - rate: collSelf.tested / collSelf.match.length - }); - if (collSelf.match.length !== collSelf.tested) { - console.error('\x1b[31mSelf collision tested failed.\x1b[m'); - process.exit(1); - } - - // basic collision checks against dictionaries (rates should be greater than 900:1) - console.log('Dictionary collision test:') - const collEnglish = checkWordlists(g, v, crc, nameList, '/usr/share/dict/words'); - console.log('english:', { - entries: collEnglish.tested, - collisions: collEnglish.collisions.length, - rate: collEnglish.tested / collEnglish.collisions.length - }); - //console.log(collEnglish.collisions); - const collnGerman = checkWordlists(g, v, crc, nameList, '/usr/share/dict/ngerman'); - console.log('ngerman:', { - entries: collnGerman.tested, - collisions: collnGerman.collisions.length, - rate: collnGerman.tested / collnGerman.collisions.length - }); - const colloGerman = checkWordlists(g, v, crc, nameList, '/usr/share/dict/ogerman'); - console.log('ogerman:', { - entries: colloGerman.tested, - collisions: colloGerman.collisions.length, - rate: colloGerman.tested / colloGerman.collisions.length - }); - - // compression with custom alphabet - const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?'; - const [tableData, length, gOffset] = compressTables(g, v, crc, ALPHABET); - const colorData = compressColors(colorList.map(el => el.color), ALPHABET); - - // test loading of compressed table data - let failed = false; - for (const name of nameList) { - const orig = lookupPerfectHash(g, v, name); - const idx = lookupIdx(ALPHABET, tableData, length, gOffset, name); - if (idx !== orig) { - console.log('\x1b[31mcompressed loading failed for:\x1b[m', {name, idx, orig}); - failed = true; - } - } - if (failed) process.exit(1); - - // test full color decoding - for (const color of colorList) { - const loaded = lookup(ALPHABET, tableData, colorData, length, gOffset, color.name); - if (loaded[0] !== color.color[0] || loaded[1] !== color.color[1] || loaded[2] !== color.color[2]) { - console.log('\x1b[31mcolor loading failed for:\x1b[m', {color, loaded}); - failed = true; - } - } - if (failed) process.exit(1); - - // if we made it up to here, create decoding boilerplate - const moduleData = createModule(tableData, colorData, ALPHABET, gOffset, length); - fs.writeFileSync(path.join(__dirname, '/ColorNames.ts'), moduleData); - console.log(`${moduleData.length} bytes written to ${path.join(__dirname, '/ColorNames.ts')}`); -} - -main(); diff --git a/fixtures/x11-colornames/rgb.txt b/fixtures/x11-colornames/rgb.txt deleted file mode 100644 index b9e56c60..00000000 --- a/fixtures/x11-colornames/rgb.txt +++ /dev/null @@ -1,754 +0,0 @@ -! $Xorg: rgb.txt,v 1.3 2000/08/17 19:54:00 cpqbld Exp $ -255 250 250 snow -248 248 255 ghost white -248 248 255 GhostWhite -245 245 245 white smoke -245 245 245 WhiteSmoke -220 220 220 gainsboro -255 250 240 floral white -255 250 240 FloralWhite -253 245 230 old lace -253 245 230 OldLace -250 240 230 linen -250 235 215 antique white -250 235 215 AntiqueWhite -255 239 213 papaya whip -255 239 213 PapayaWhip -255 235 205 blanched almond -255 235 205 BlanchedAlmond -255 228 196 bisque -255 218 185 peach puff -255 218 185 PeachPuff -255 222 173 navajo white -255 222 173 NavajoWhite -255 228 181 moccasin -255 248 220 cornsilk -255 255 240 ivory -255 250 205 lemon chiffon -255 250 205 LemonChiffon -255 245 238 seashell -240 255 240 honeydew -245 255 250 mint cream -245 255 250 MintCream -240 255 255 azure -240 248 255 alice blue -240 248 255 AliceBlue -230 230 250 lavender -255 240 245 lavender blush -255 240 245 LavenderBlush -255 228 225 misty rose -255 228 225 MistyRose -255 255 255 white - 0 0 0 black - 47 79 79 dark slate gray - 47 79 79 DarkSlateGray - 47 79 79 dark slate grey - 47 79 79 DarkSlateGrey -105 105 105 dim gray -105 105 105 DimGray -105 105 105 dim grey -105 105 105 DimGrey -112 128 144 slate gray -112 128 144 SlateGray -112 128 144 slate grey -112 128 144 SlateGrey -119 136 153 light slate gray -119 136 153 LightSlateGray -119 136 153 light slate grey -119 136 153 LightSlateGrey -190 190 190 gray -190 190 190 grey -211 211 211 light grey -211 211 211 LightGrey -211 211 211 light gray -211 211 211 LightGray - 25 25 112 midnight blue - 25 25 112 MidnightBlue - 0 0 128 navy - 0 0 128 navy blue - 0 0 128 NavyBlue -100 149 237 cornflower blue -100 149 237 CornflowerBlue - 72 61 139 dark slate blue - 72 61 139 DarkSlateBlue -106 90 205 slate blue -106 90 205 SlateBlue -123 104 238 medium slate blue -123 104 238 MediumSlateBlue -132 112 255 light slate blue -132 112 255 LightSlateBlue - 0 0 205 medium blue - 0 0 205 MediumBlue - 65 105 225 royal blue - 65 105 225 RoyalBlue - 0 0 255 blue - 30 144 255 dodger blue - 30 144 255 DodgerBlue - 0 191 255 deep sky blue - 0 191 255 DeepSkyBlue -135 206 235 sky blue -135 206 235 SkyBlue -135 206 250 light sky blue -135 206 250 LightSkyBlue - 70 130 180 steel blue - 70 130 180 SteelBlue -176 196 222 light steel blue -176 196 222 LightSteelBlue -173 216 230 light blue -173 216 230 LightBlue -176 224 230 powder blue -176 224 230 PowderBlue -175 238 238 pale turquoise -175 238 238 PaleTurquoise - 0 206 209 dark turquoise - 0 206 209 DarkTurquoise - 72 209 204 medium turquoise - 72 209 204 MediumTurquoise - 64 224 208 turquoise - 0 255 255 cyan -224 255 255 light cyan -224 255 255 LightCyan - 95 158 160 cadet blue - 95 158 160 CadetBlue -102 205 170 medium aquamarine -102 205 170 MediumAquamarine -127 255 212 aquamarine - 0 100 0 dark green - 0 100 0 DarkGreen - 85 107 47 dark olive green - 85 107 47 DarkOliveGreen -143 188 143 dark sea green -143 188 143 DarkSeaGreen - 46 139 87 sea green - 46 139 87 SeaGreen - 60 179 113 medium sea green - 60 179 113 MediumSeaGreen - 32 178 170 light sea green - 32 178 170 LightSeaGreen -152 251 152 pale green -152 251 152 PaleGreen - 0 255 127 spring green - 0 255 127 SpringGreen -124 252 0 lawn green -124 252 0 LawnGreen - 0 255 0 green -127 255 0 chartreuse - 0 250 154 medium spring green - 0 250 154 MediumSpringGreen -173 255 47 green yellow -173 255 47 GreenYellow - 50 205 50 lime green - 50 205 50 LimeGreen -154 205 50 yellow green -154 205 50 YellowGreen - 34 139 34 forest green - 34 139 34 ForestGreen -107 142 35 olive drab -107 142 35 OliveDrab -189 183 107 dark khaki -189 183 107 DarkKhaki -240 230 140 khaki -238 232 170 pale goldenrod -238 232 170 PaleGoldenrod -250 250 210 light goldenrod yellow -250 250 210 LightGoldenrodYellow -255 255 224 light yellow -255 255 224 LightYellow -255 255 0 yellow -255 215 0 gold -238 221 130 light goldenrod -238 221 130 LightGoldenrod -218 165 32 goldenrod -184 134 11 dark goldenrod -184 134 11 DarkGoldenrod -188 143 143 rosy brown -188 143 143 RosyBrown -205 92 92 indian red -205 92 92 IndianRed -139 69 19 saddle brown -139 69 19 SaddleBrown -160 82 45 sienna -205 133 63 peru -222 184 135 burlywood -245 245 220 beige -245 222 179 wheat -244 164 96 sandy brown -244 164 96 SandyBrown -210 180 140 tan -210 105 30 chocolate -178 34 34 firebrick -165 42 42 brown -233 150 122 dark salmon -233 150 122 DarkSalmon -250 128 114 salmon -255 160 122 light salmon -255 160 122 LightSalmon -255 165 0 orange -255 140 0 dark orange -255 140 0 DarkOrange -255 127 80 coral -240 128 128 light coral -240 128 128 LightCoral -255 99 71 tomato -255 69 0 orange red -255 69 0 OrangeRed -255 0 0 red -255 105 180 hot pink -255 105 180 HotPink -255 20 147 deep pink -255 20 147 DeepPink -255 192 203 pink -255 182 193 light pink -255 182 193 LightPink -219 112 147 pale violet red -219 112 147 PaleVioletRed -176 48 96 maroon -199 21 133 medium violet red -199 21 133 MediumVioletRed -208 32 144 violet red -208 32 144 VioletRed -255 0 255 magenta -238 130 238 violet -221 160 221 plum -218 112 214 orchid -186 85 211 medium orchid -186 85 211 MediumOrchid -153 50 204 dark orchid -153 50 204 DarkOrchid -148 0 211 dark violet -148 0 211 DarkViolet -138 43 226 blue violet -138 43 226 BlueViolet -160 32 240 purple -147 112 219 medium purple -147 112 219 MediumPurple -216 191 216 thistle -255 250 250 snow1 -238 233 233 snow2 -205 201 201 snow3 -139 137 137 snow4 -255 245 238 seashell1 -238 229 222 seashell2 -205 197 191 seashell3 -139 134 130 seashell4 -255 239 219 AntiqueWhite1 -238 223 204 AntiqueWhite2 -205 192 176 AntiqueWhite3 -139 131 120 AntiqueWhite4 -255 228 196 bisque1 -238 213 183 bisque2 -205 183 158 bisque3 -139 125 107 bisque4 -255 218 185 PeachPuff1 -238 203 173 PeachPuff2 -205 175 149 PeachPuff3 -139 119 101 PeachPuff4 -255 222 173 NavajoWhite1 -238 207 161 NavajoWhite2 -205 179 139 NavajoWhite3 -139 121 94 NavajoWhite4 -255 250 205 LemonChiffon1 -238 233 191 LemonChiffon2 -205 201 165 LemonChiffon3 -139 137 112 LemonChiffon4 -255 248 220 cornsilk1 -238 232 205 cornsilk2 -205 200 177 cornsilk3 -139 136 120 cornsilk4 -255 255 240 ivory1 -238 238 224 ivory2 -205 205 193 ivory3 -139 139 131 ivory4 -240 255 240 honeydew1 -224 238 224 honeydew2 -193 205 193 honeydew3 -131 139 131 honeydew4 -255 240 245 LavenderBlush1 -238 224 229 LavenderBlush2 -205 193 197 LavenderBlush3 -139 131 134 LavenderBlush4 -255 228 225 MistyRose1 -238 213 210 MistyRose2 -205 183 181 MistyRose3 -139 125 123 MistyRose4 -240 255 255 azure1 -224 238 238 azure2 -193 205 205 azure3 -131 139 139 azure4 -131 111 255 SlateBlue1 -122 103 238 SlateBlue2 -105 89 205 SlateBlue3 - 71 60 139 SlateBlue4 - 72 118 255 RoyalBlue1 - 67 110 238 RoyalBlue2 - 58 95 205 RoyalBlue3 - 39 64 139 RoyalBlue4 - 0 0 255 blue1 - 0 0 238 blue2 - 0 0 205 blue3 - 0 0 139 blue4 - 30 144 255 DodgerBlue1 - 28 134 238 DodgerBlue2 - 24 116 205 DodgerBlue3 - 16 78 139 DodgerBlue4 - 99 184 255 SteelBlue1 - 92 172 238 SteelBlue2 - 79 148 205 SteelBlue3 - 54 100 139 SteelBlue4 - 0 191 255 DeepSkyBlue1 - 0 178 238 DeepSkyBlue2 - 0 154 205 DeepSkyBlue3 - 0 104 139 DeepSkyBlue4 -135 206 255 SkyBlue1 -126 192 238 SkyBlue2 -108 166 205 SkyBlue3 - 74 112 139 SkyBlue4 -176 226 255 LightSkyBlue1 -164 211 238 LightSkyBlue2 -141 182 205 LightSkyBlue3 - 96 123 139 LightSkyBlue4 -198 226 255 SlateGray1 -185 211 238 SlateGray2 -159 182 205 SlateGray3 -108 123 139 SlateGray4 -202 225 255 LightSteelBlue1 -188 210 238 LightSteelBlue2 -162 181 205 LightSteelBlue3 -110 123 139 LightSteelBlue4 -191 239 255 LightBlue1 -178 223 238 LightBlue2 -154 192 205 LightBlue3 -104 131 139 LightBlue4 -224 255 255 LightCyan1 -209 238 238 LightCyan2 -180 205 205 LightCyan3 -122 139 139 LightCyan4 -187 255 255 PaleTurquoise1 -174 238 238 PaleTurquoise2 -150 205 205 PaleTurquoise3 -102 139 139 PaleTurquoise4 -152 245 255 CadetBlue1 -142 229 238 CadetBlue2 -122 197 205 CadetBlue3 - 83 134 139 CadetBlue4 - 0 245 255 turquoise1 - 0 229 238 turquoise2 - 0 197 205 turquoise3 - 0 134 139 turquoise4 - 0 255 255 cyan1 - 0 238 238 cyan2 - 0 205 205 cyan3 - 0 139 139 cyan4 -151 255 255 DarkSlateGray1 -141 238 238 DarkSlateGray2 -121 205 205 DarkSlateGray3 - 82 139 139 DarkSlateGray4 -127 255 212 aquamarine1 -118 238 198 aquamarine2 -102 205 170 aquamarine3 - 69 139 116 aquamarine4 -193 255 193 DarkSeaGreen1 -180 238 180 DarkSeaGreen2 -155 205 155 DarkSeaGreen3 -105 139 105 DarkSeaGreen4 - 84 255 159 SeaGreen1 - 78 238 148 SeaGreen2 - 67 205 128 SeaGreen3 - 46 139 87 SeaGreen4 -154 255 154 PaleGreen1 -144 238 144 PaleGreen2 -124 205 124 PaleGreen3 - 84 139 84 PaleGreen4 - 0 255 127 SpringGreen1 - 0 238 118 SpringGreen2 - 0 205 102 SpringGreen3 - 0 139 69 SpringGreen4 - 0 255 0 green1 - 0 238 0 green2 - 0 205 0 green3 - 0 139 0 green4 -127 255 0 chartreuse1 -118 238 0 chartreuse2 -102 205 0 chartreuse3 - 69 139 0 chartreuse4 -192 255 62 OliveDrab1 -179 238 58 OliveDrab2 -154 205 50 OliveDrab3 -105 139 34 OliveDrab4 -202 255 112 DarkOliveGreen1 -188 238 104 DarkOliveGreen2 -162 205 90 DarkOliveGreen3 -110 139 61 DarkOliveGreen4 -255 246 143 khaki1 -238 230 133 khaki2 -205 198 115 khaki3 -139 134 78 khaki4 -255 236 139 LightGoldenrod1 -238 220 130 LightGoldenrod2 -205 190 112 LightGoldenrod3 -139 129 76 LightGoldenrod4 -255 255 224 LightYellow1 -238 238 209 LightYellow2 -205 205 180 LightYellow3 -139 139 122 LightYellow4 -255 255 0 yellow1 -238 238 0 yellow2 -205 205 0 yellow3 -139 139 0 yellow4 -255 215 0 gold1 -238 201 0 gold2 -205 173 0 gold3 -139 117 0 gold4 -255 193 37 goldenrod1 -238 180 34 goldenrod2 -205 155 29 goldenrod3 -139 105 20 goldenrod4 -255 185 15 DarkGoldenrod1 -238 173 14 DarkGoldenrod2 -205 149 12 DarkGoldenrod3 -139 101 8 DarkGoldenrod4 -255 193 193 RosyBrown1 -238 180 180 RosyBrown2 -205 155 155 RosyBrown3 -139 105 105 RosyBrown4 -255 106 106 IndianRed1 -238 99 99 IndianRed2 -205 85 85 IndianRed3 -139 58 58 IndianRed4 -255 130 71 sienna1 -238 121 66 sienna2 -205 104 57 sienna3 -139 71 38 sienna4 -255 211 155 burlywood1 -238 197 145 burlywood2 -205 170 125 burlywood3 -139 115 85 burlywood4 -255 231 186 wheat1 -238 216 174 wheat2 -205 186 150 wheat3 -139 126 102 wheat4 -255 165 79 tan1 -238 154 73 tan2 -205 133 63 tan3 -139 90 43 tan4 -255 127 36 chocolate1 -238 118 33 chocolate2 -205 102 29 chocolate3 -139 69 19 chocolate4 -255 48 48 firebrick1 -238 44 44 firebrick2 -205 38 38 firebrick3 -139 26 26 firebrick4 -255 64 64 brown1 -238 59 59 brown2 -205 51 51 brown3 -139 35 35 brown4 -255 140 105 salmon1 -238 130 98 salmon2 -205 112 84 salmon3 -139 76 57 salmon4 -255 160 122 LightSalmon1 -238 149 114 LightSalmon2 -205 129 98 LightSalmon3 -139 87 66 LightSalmon4 -255 165 0 orange1 -238 154 0 orange2 -205 133 0 orange3 -139 90 0 orange4 -255 127 0 DarkOrange1 -238 118 0 DarkOrange2 -205 102 0 DarkOrange3 -139 69 0 DarkOrange4 -255 114 86 coral1 -238 106 80 coral2 -205 91 69 coral3 -139 62 47 coral4 -255 99 71 tomato1 -238 92 66 tomato2 -205 79 57 tomato3 -139 54 38 tomato4 -255 69 0 OrangeRed1 -238 64 0 OrangeRed2 -205 55 0 OrangeRed3 -139 37 0 OrangeRed4 -255 0 0 red1 -238 0 0 red2 -205 0 0 red3 -139 0 0 red4 -215 7 81 DebianRed -255 20 147 DeepPink1 -238 18 137 DeepPink2 -205 16 118 DeepPink3 -139 10 80 DeepPink4 -255 110 180 HotPink1 -238 106 167 HotPink2 -205 96 144 HotPink3 -139 58 98 HotPink4 -255 181 197 pink1 -238 169 184 pink2 -205 145 158 pink3 -139 99 108 pink4 -255 174 185 LightPink1 -238 162 173 LightPink2 -205 140 149 LightPink3 -139 95 101 LightPink4 -255 130 171 PaleVioletRed1 -238 121 159 PaleVioletRed2 -205 104 137 PaleVioletRed3 -139 71 93 PaleVioletRed4 -255 52 179 maroon1 -238 48 167 maroon2 -205 41 144 maroon3 -139 28 98 maroon4 -255 62 150 VioletRed1 -238 58 140 VioletRed2 -205 50 120 VioletRed3 -139 34 82 VioletRed4 -255 0 255 magenta1 -238 0 238 magenta2 -205 0 205 magenta3 -139 0 139 magenta4 -255 131 250 orchid1 -238 122 233 orchid2 -205 105 201 orchid3 -139 71 137 orchid4 -255 187 255 plum1 -238 174 238 plum2 -205 150 205 plum3 -139 102 139 plum4 -224 102 255 MediumOrchid1 -209 95 238 MediumOrchid2 -180 82 205 MediumOrchid3 -122 55 139 MediumOrchid4 -191 62 255 DarkOrchid1 -178 58 238 DarkOrchid2 -154 50 205 DarkOrchid3 -104 34 139 DarkOrchid4 -155 48 255 purple1 -145 44 238 purple2 -125 38 205 purple3 - 85 26 139 purple4 -171 130 255 MediumPurple1 -159 121 238 MediumPurple2 -137 104 205 MediumPurple3 - 93 71 139 MediumPurple4 -255 225 255 thistle1 -238 210 238 thistle2 -205 181 205 thistle3 -139 123 139 thistle4 - 0 0 0 gray0 - 0 0 0 grey0 - 3 3 3 gray1 - 3 3 3 grey1 - 5 5 5 gray2 - 5 5 5 grey2 - 8 8 8 gray3 - 8 8 8 grey3 - 10 10 10 gray4 - 10 10 10 grey4 - 13 13 13 gray5 - 13 13 13 grey5 - 15 15 15 gray6 - 15 15 15 grey6 - 18 18 18 gray7 - 18 18 18 grey7 - 20 20 20 gray8 - 20 20 20 grey8 - 23 23 23 gray9 - 23 23 23 grey9 - 26 26 26 gray10 - 26 26 26 grey10 - 28 28 28 gray11 - 28 28 28 grey11 - 31 31 31 gray12 - 31 31 31 grey12 - 33 33 33 gray13 - 33 33 33 grey13 - 36 36 36 gray14 - 36 36 36 grey14 - 38 38 38 gray15 - 38 38 38 grey15 - 41 41 41 gray16 - 41 41 41 grey16 - 43 43 43 gray17 - 43 43 43 grey17 - 46 46 46 gray18 - 46 46 46 grey18 - 48 48 48 gray19 - 48 48 48 grey19 - 51 51 51 gray20 - 51 51 51 grey20 - 54 54 54 gray21 - 54 54 54 grey21 - 56 56 56 gray22 - 56 56 56 grey22 - 59 59 59 gray23 - 59 59 59 grey23 - 61 61 61 gray24 - 61 61 61 grey24 - 64 64 64 gray25 - 64 64 64 grey25 - 66 66 66 gray26 - 66 66 66 grey26 - 69 69 69 gray27 - 69 69 69 grey27 - 71 71 71 gray28 - 71 71 71 grey28 - 74 74 74 gray29 - 74 74 74 grey29 - 77 77 77 gray30 - 77 77 77 grey30 - 79 79 79 gray31 - 79 79 79 grey31 - 82 82 82 gray32 - 82 82 82 grey32 - 84 84 84 gray33 - 84 84 84 grey33 - 87 87 87 gray34 - 87 87 87 grey34 - 89 89 89 gray35 - 89 89 89 grey35 - 92 92 92 gray36 - 92 92 92 grey36 - 94 94 94 gray37 - 94 94 94 grey37 - 97 97 97 gray38 - 97 97 97 grey38 - 99 99 99 gray39 - 99 99 99 grey39 -102 102 102 gray40 -102 102 102 grey40 -105 105 105 gray41 -105 105 105 grey41 -107 107 107 gray42 -107 107 107 grey42 -110 110 110 gray43 -110 110 110 grey43 -112 112 112 gray44 -112 112 112 grey44 -115 115 115 gray45 -115 115 115 grey45 -117 117 117 gray46 -117 117 117 grey46 -120 120 120 gray47 -120 120 120 grey47 -122 122 122 gray48 -122 122 122 grey48 -125 125 125 gray49 -125 125 125 grey49 -127 127 127 gray50 -127 127 127 grey50 -130 130 130 gray51 -130 130 130 grey51 -133 133 133 gray52 -133 133 133 grey52 -135 135 135 gray53 -135 135 135 grey53 -138 138 138 gray54 -138 138 138 grey54 -140 140 140 gray55 -140 140 140 grey55 -143 143 143 gray56 -143 143 143 grey56 -145 145 145 gray57 -145 145 145 grey57 -148 148 148 gray58 -148 148 148 grey58 -150 150 150 gray59 -150 150 150 grey59 -153 153 153 gray60 -153 153 153 grey60 -156 156 156 gray61 -156 156 156 grey61 -158 158 158 gray62 -158 158 158 grey62 -161 161 161 gray63 -161 161 161 grey63 -163 163 163 gray64 -163 163 163 grey64 -166 166 166 gray65 -166 166 166 grey65 -168 168 168 gray66 -168 168 168 grey66 -171 171 171 gray67 -171 171 171 grey67 -173 173 173 gray68 -173 173 173 grey68 -176 176 176 gray69 -176 176 176 grey69 -179 179 179 gray70 -179 179 179 grey70 -181 181 181 gray71 -181 181 181 grey71 -184 184 184 gray72 -184 184 184 grey72 -186 186 186 gray73 -186 186 186 grey73 -189 189 189 gray74 -189 189 189 grey74 -191 191 191 gray75 -191 191 191 grey75 -194 194 194 gray76 -194 194 194 grey76 -196 196 196 gray77 -196 196 196 grey77 -199 199 199 gray78 -199 199 199 grey78 -201 201 201 gray79 -201 201 201 grey79 -204 204 204 gray80 -204 204 204 grey80 -207 207 207 gray81 -207 207 207 grey81 -209 209 209 gray82 -209 209 209 grey82 -212 212 212 gray83 -212 212 212 grey83 -214 214 214 gray84 -214 214 214 grey84 -217 217 217 gray85 -217 217 217 grey85 -219 219 219 gray86 -219 219 219 grey86 -222 222 222 gray87 -222 222 222 grey87 -224 224 224 gray88 -224 224 224 grey88 -227 227 227 gray89 -227 227 227 grey89 -229 229 229 gray90 -229 229 229 grey90 -232 232 232 gray91 -232 232 232 grey91 -235 235 235 gray92 -235 235 235 grey92 -237 237 237 gray93 -237 237 237 grey93 -240 240 240 gray94 -240 240 240 grey94 -242 242 242 gray95 -242 242 242 grey95 -245 245 245 gray96 -245 245 245 grey96 -247 247 247 gray97 -247 247 247 grey97 -250 250 250 gray98 -250 250 250 grey98 -252 252 252 gray99 -252 252 252 grey99 -255 255 255 gray100 -255 255 255 grey100 -169 169 169 dark grey -169 169 169 DarkGrey -169 169 169 dark gray -169 169 169 DarkGray -0 0 139 dark blue -0 0 139 DarkBlue -0 139 139 dark cyan -0 139 139 DarkCyan -139 0 139 dark magenta -139 0 139 DarkMagenta -139 0 0 dark red -139 0 0 DarkRed -144 238 144 light green -144 238 144 LightGreen diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index ff1879bc..e8c4c8bf 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -55,7 +55,7 @@ class TestInputHandler extends InputHandler { } } - public parseColorSpec(data: string): void | [number, number, number] { + public parseColorSpec(data: string): undefined | [number, number, number] { return this._parseColorSpec(data); } } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 9a56de6f..5384bf8d 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -21,7 +21,6 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; -import { getColorFromName } from 'common/data/ColorNames'; /** * Map collect to glevel. Used in `selectCharset`. @@ -2877,7 +2876,7 @@ export class InputHandler extends Disposable implements IInputHandler { * All other formats like rgbi: or device-independent string specifications * with float numbering are not supported. */ - protected _parseColorSpec(data: string): [number, number, number] | void { + protected _parseColorSpec(data: string): [number, number, number] | undefined { if (!data) return; // also handle uppercases let low = data.toLowerCase(); @@ -2908,7 +2907,6 @@ export class InputHandler extends Disposable implements IInputHandler { return result; } } - return getColorFromName(data); } /** diff --git a/src/common/data/ColorNames.ts b/src/common/data/ColorNames.ts deleted file mode 100644 index 68dd06b6..00000000 --- a/src/common/data/ColorNames.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2021 The xterm.js authors. All rights reserved. - * @license MIT - */ - -/** - * This module enables X11 color name lookups with the help of a perfect hash function - * to not penalize the package size too much. - * saving: ~70% (from 17kB down to 5kB) - */ - -// Note: module and table data created with fixtures/x11-colornames/create_module.js -const TABLE = 'AB!!!id!jsih!ZYigHp3AkceNiiImVihW3Uih?DFihrenihDEQiwgziieGJvAoMnsidOM3idOtQiciazAsFrtieFZyidKSgAx8odip818ieFiXieF97ic34eihOxpA4UPNidz51A8znjBAX?uBU9ynitOZdigRMNBYgIEBcaXPBk9LVB0xlOB9mcFihmlWCRq0yidmG7idqsHid7RmCV7pCClqRAimG5kCpsb4ipt78CtqLeig17aigJb7ioC4rihzLdigmoNigEDTihzZKCwIz1C4j5sih4QQihlg2igwsHDBFTuDESshDJFzIig1cbihZ3wipZphigiKcihZUxihZLODRm7RilG1Cid3tmDVGffDYJsPDc1JrixgxGidDn3Dli9uDsOl0imDQPiyDhbimDyZihU00icbZSEJs6VieEzpicF2YEqB?PicemNiduxiihEL9ieBjnEuI53icdLPiqBYWiduYFExunsFB3JqFEXPuihnyvFQkNAFVVjlic8SLicIeOFYvurFwrh5icQ4RicHUgidVbJidVA3idN3LGECqgGJiKSGRl8UidT9qipssVGZHNyidy3widTu8idgHsidgcgGdggRGkODXihH!8ilyhpihWj0ihWcailWHjGwO?OilD4BG888ZHJzmGHZtoyicqlkiwWfkicEbdHg3uEHtMkRH0TgkH5MBRIQE9VIUh5VIYeSyieFFgIlC6Qidvc5idv65icprGicNfhItjoJI4N3mJRFpIicLVGJgMY7J2GebKAPoGKyITuidFGWic2olicYLfLFuJDidnoticCc!icx2bieAcuLIv3UicoYuic594LRQ86LZc3NidcphicnOVic09yisjDiitRyFLc7NTicTaFicFbvic6jPLk3JtigqSiL1QJUjJQZpidQvJicUVVidYVsidNOjidNQfidNnLicyCAicHGeidY17icL3cL8DSKMcPzVMgc8dicEg6ig61OMwImmM06KXM6Hl!M96LNicQEbicUjBNAt2Aid4Hwix3w0Ndba5Nt5pqNwuVljBbJaicYjeigAxGN1b!LOEvTnisIIoORf1cOVV!TiczJbiddd5iddgpiclyxidfd8idfuEieACwid?zWih2rlOp2waid?r2OtUKQOxMyjihUpcigKWQO4y32PNAPFidAU6PZAhBPhjAni0PYUiceDridjwXPqEBxPsZ5Cicoi9icBWoieHXOP0B?9QF50CigWlyQJBkUQMs8nici3YQQ?MXidedxicjVCidhu9icqIFidhZjidhBQid0avicmIYQZLp!QdLBNQwFKyipSwfikPB5Q1SZKipSC1idL08Q44Vaic4tpikVWWQ9x5KikK4sig4Afit?bgihvhKRA?wsidxIfidGjgikw?ORRruoRYQWbRh2dkRk!EPRo!Vpig!taR92Glicf4gicxZGSCIB1jED6KSF0tyid0MUieEUnidLfYSRRoKSUk5sSczRfih06gSkpOgikVvGio5X1icp8Kio5NFik0sjix9EQSp8Pvide28icanaidyPpidekWSx71CTANkkicssXTE1nojEbGQTJp32ic?fFTcYU6icuDuicNCIT1B67T4DhEice8dihBd7ismVIUIRe3UVJeiUZBPEVNXn3idXcIj0jici5yT6VgGbJVlXA2idcRxicfAXicT4kicRjbVpnXZieEm?V4feMigLlzV95OGip4?git4nTWF3csicyWQWJGH2ikH?FWMo06ieCn5WSC2fieDOsWUm1Tidor2WZoaQidoCjicz5cWlxroicTKOW4dS9ido5CidnBwic0AWXJX4lXNdDaicS!nXkWPBXotnyYAgsNicrXhicnmHidHe0idHlLicaNvicG8Jida06YMOfFidTV!icY0YidDcrYRiXUYZivaigDO8ig8GPigGNwi88iCikuzkiharXicoAoihJPbihae9ihJmOihJ9xigV9UiwsMyidaEBichf!ic9ZEYk9oiigCARY9kLuidkWPidknpidk?aihh0likBkKZEJFsidjdfieJmgZIU5dieH47isgQIZNxb3ZQn7hidI6HZVIi0il9ZLix9nYih92!ihCeNZZClyZhISFi1IDjid6bQicwQvZl66Eid6riZoGjRicpYuieBC5ic5jfZwlrsicKATimA?cZ8ZO6icxGJieAt2iculVaA40zidYsAacin?aglNzalwMVi9d0mictfZascgqicbxMbABJ?ignSAjYM8?bIhNZigd1qig7eVieJNPbQq8pikKkaigVPligW!fjgR?5bWCHKidCM1bcrI5idppfidpRsi5P6TbkXgIbo2xucEAZdihvCiidPJXidPYxicAACcIXb9isr67cMtDzcgfiVctsKmc8khZigZjTilrC1ihpKGjQhh5dJr30igymSjKJehihMVeig3VrigwENipRc!idKnBdRRFodhKEOdxEc9idEnCidE8zic!9VidK1md18eZit!FHieGkhicJ8SieCXticlSNidbjridA7YeIdgneNtbMeVlKqeZlSZicbpmixtEVesAlzfU2dAfdwW?fsSYbf5w59gB1xOicMKxgN1q8icZbagZPjpidSoWid1Y0ic2JSid1Bhgo7qeidcLjilYKFiw6S2isa5Jgxwl9g00UbicSMohA73XhELGKhUQqNi8cGshgsaridfCIhkkePhtUeth0vCqileCnit5YaiE?plieHKPiJTPmiN7Kt'; -const COLORS = '??r6!Pj?!Pj?9fX19fX13Nzc??rw??rw?fXm?fXm!vDm!uvX!uvX?!?V?!?V?!vN?!vN?!TE?9q5?9q5?96t?96t?!S1??jc???w??rN??rN??Xu8P?w9f?69f?68P??8Pj?8Pj?5ub6??D1??D1?!Th?!Th????AAAAL09PL09PL09PL09PaWlpaWlpaWlpaWlpcICQcICQcICQcICQd4iZd4iZd4iZd4iZvr6!vr6!09PT09PT09PT09PTGRlwGRlwAACAAACAAACAZJXtZJXtSD2LSD2LalrNalrNe2jue2juhHD?hHD?AADNAADNQWnhQWnhAAD?HpD?HpD?AL??AL??h87rh87rh876h876RoK0RoK0sMTesMTerdjmrdjmsODmsODmr!7ur!7uAM7RAM7RSNHMSNHMQODQAP??4P??4P??X56gX56gZs2qZs2qf??UAGQAAGQAVWsvVWsvj7yPj7yPLotXLotXPLNxPLNxILKqILKqmPuYmPuYAP9?AP9?fPwAfPwAAP8Af?8AAPqaAPqarf8vrf8vMs0yMs0yms0yms0yIosiIosia44ja44jvbdrvbdr8OaM7uiq7uiq!vrS!vrS???g???g??8A?9cA7t2C7t2C2qUguIYLuIYLvI!PvI!PzVxczVxci0UTi0UToFItzYU?3riH9fXc9d6z9KRg9KRg0rSM0mkesiIipSoq6ZZ66ZZ6!oBy?6B6?6B6?6UA?4wA?4wA?39Q8ICA8ICA?2NH?0UA?0UA?wAA?2m0?2m0?xST?xST?8DL?7bB?7bB23CT23CTsDBgxxWFxxWF0CCQ0CCQ?wD?7oLu3aDd2nDWulXTulXTmTLMmTLMlADTlADTiiviiivioCDwk3Dbk3Db2L?Y??r67unpzcnJi4mJ??Xu7uXezcW?i4aC?!?b7t?MzcCwi4N4?!TE7tW3zbeei31r?9q57sutza!Vi3dl?96t7s!hzbOLi3le??rN7um?zcmli4lw??jc7ujNzcixi4h4???w7u7gzc3Bi4uD8P?w4O7gwc3Bg4uD??D17uDlzcHFi4OG?!Th7tXSzbe1i3178P??4O7uwc3Ng4uLg2??emfuaVnNRzyLSHb?Q27uOl?NJ0CLAAD?AADuAADNAACLHpD?HIbuGHTNEE6LY7j?XKzuT5TNNmSLAL??ALLuAJrNAGiLh87?fsDubKbNSnCLsOL?pNPujbbNYHuLxuL?udPun7bNbHuLyuH?vNLuorXNbnuLv!??st?umsDNaIOL4P??0e7utM3NeouLu???ru7uls3NZouLmPX?juXuesXNU4aLAPX?AOXuAMXNAIaLAP??AO7uAM3NAIuLl???je7uec3NUouLf??Udu7GZs2qRYt0wf?BtO60m82baYtpVP!fTu6UQ82ALotXmv!akO6QfM18VItUAP9?AO52AM1mAItFAP8AAO4AAM0AAIsAf?8Adu4AZs0ARYsAwP8!s!46ms0yaYsiyv9wvO5oos1abos9??aP7uaFzcZzi4ZO?!yL7tyCzb5wi4FM???g7u7Rzc20i4t6??8A7u4Azc0Ai4sA?9cA7skAza0Ai3UA?8El7rQizZsdi2kU?7kP7q0OzZUMi2UI?8HB7rS0zZubi2lp?2pq7mNjzVVVizo6?4JH7nlCzWg5i0cm?9Ob7sWRzap9i3NV?!e67tiuzbqWi35m?6VP7ppJzYU?i1or?38k7nYhzWYdi0UT?zAw7iwszSYmixoa?0BA7js7zTMziyMj?4xp7oJizXBUi0w5?6B67pVyzYFii1dC?6UA7poAzYUAi1oA?38A7nYAzWYAi0UA?3JW7mpQzVtFiz4v?2NH7lxCzU85izYm?0UA7kAAzTcAiyUA?wAA7gAAzQAAiwAA1wdR?xST7hKJzRB2iwpQ?2607mqnzWCQizpi?7XF7qm4zZGei2Ns?6657qKtzYyVi19l?4Kr7nmfzWiJi0dd?zSz7jCnzSmQixxi?z6W7jqMzTJ4iyJS?wD?7gDuzQDNiwCL?4P67nrpzWnJi0eJ?7v?7q7uzZbNi2aL4Gb?0V?utFLNejeLvz7?sjrumjLNaCKLmzD?kSzufSbNVRqLq4L?n3nuiWjNXUeL?!H?7tLuzbXNi3uLqampqampqampqampAACLAACLAIuLAIuLiwCLiwCLiwAAiwAAkO6QkO6Q'; -const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?'; -const OFFSET = 551; -const LENGTH = 551; -const BUCKET_LENGTH = TABLE.length / LENGTH; - -// match greyXX|grayXX names -const rexGrey = /^gr[ae]y(\d+)/; -// name match -const rexName = /^\w?[A-Za-z0-9 ]+\w/; - -function gray(n: number): [number, number, number] | undefined { - if (0 <= n && n <= 100) { - const v = Math.floor((n * 256 - n + 50) / 100); - return [v, v, v]; - } - return; -} - -function hash(d: number, name: string): number { - if (!d) d = 0x01000193; - for (const c of name) { - d = ((d * 0x01000193) ^ c.charCodeAt(0)) & 0xffffffff; - } - return d >>> 0; -} - -function crc10(name: string, crc?: number): number { - if (!crc) crc = 0; - for (const c of name) { - crc ^= c.charCodeAt(0) << 2; - for (let k = 0; k < 8; k++) { - crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; - } - } - crc &= 0x3ff; - return crc >>> 0; -} - -function loadData(idx: number): [number, number, number] { - let value = 0; - for (let i = idx * BUCKET_LENGTH; i < idx * BUCKET_LENGTH + BUCKET_LENGTH; ++i) { - value *= ALPHABET.length; - value += ALPHABET.indexOf(TABLE[i]); - } - return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; -} - -function loadColor(idx: number): [number, number, number] { - // color buckets are hardcoded to 4 chars - let v = 0; - for (let i = idx * 4; i < idx * 4 + 4; ++i) { - v *= ALPHABET.length; - v += ALPHABET.indexOf(COLORS[i]); - } - return [v >>> 16, (v >> 8) & 0xFF, v & 0xFF]; -} - -function lookupIdx(name: string): number { - let b = loadData(hash(0, name) % LENGTH); - b = loadData(b[0] < OFFSET ? (-(b[0] - OFFSET) - 1) : hash(b[0] - OFFSET, name) % LENGTH); - const [ , , crc] = loadData(b[1]); - return crc10(name) === crc ? b[1] : -1; -} - -export function getColorFromName(name: string): [number, number, number] | undefined { - // basic name filtering - if (name.length < 3 || name.length > 22 || !rexName.exec(name)) return; - - // handle grays special - const m = rexGrey.exec(name); - if (m) return gray(parseInt(m[1])); - - // grab crc checked idx from PHF - const idx = lookupIdx(name); - if (idx === -1) return; - return loadColor(idx); -} From 54375713bc2b049648f03540e611f9c3abd58561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 17:17:39 +0100 Subject: [PATCH 09/20] simplify color events --- src/browser/Color.ts | 6 +- src/browser/Terminal.ts | 71 ++++--------- src/common/InputHandler.test.ts | 144 ++++++++++++--------------- src/common/InputHandler.ts | 96 +++++------------- src/common/Types.d.ts | 21 ++-- src/common/input/XParseColor.test.ts | 93 +++++++++++++++++ src/common/input/XParseColor.ts | 77 ++++++++++++++ 7 files changed, 291 insertions(+), 217 deletions(-) create mode 100644 src/common/input/XParseColor.test.ts create mode 100644 src/common/input/XParseColor.ts diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 2e71c9e6..6a9dff94 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -4,6 +4,7 @@ */ import { IColor } from 'browser/Types'; +import { IColorRGB } from 'common/Types'; // FIXME: Move Color.ts lib to common? @@ -86,9 +87,8 @@ export namespace color { }; } - export function toXColorName(color: IColor): string { - const [r, g, b] = rgba.toChannels(color.rgba); - return `rgb:${toPaddedHex(r)}${toPaddedHex(r)}/${toPaddedHex(g)}${toPaddedHex(g)}/${toPaddedHex(b)}${toPaddedHex(b)}`; + export function toColorRGB(color: IColor): IColorRGB { + return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF]; } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e8aee572..0602f604 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 } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, IColorRGB } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -55,6 +55,7 @@ import { CoreTerminal } from 'common/CoreTerminal'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; import { color, rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; +import { toRgbString } from 'common/input/XParseColor'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -178,58 +179,26 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _handleColorEvent(event: IColorEvent): void { - if (!this._colorManager) { return; } - - let hasSet = false; - const query: string[] = []; - for (const req of event.requests) { - if (req.color === '?') { - // query color - let ident = ''; - let c: IColor; - switch (req.index) { - case 256: - ident = '10'; - c = this._colorManager.colors.foreground; - break; - case 257: - ident = '11'; - c = this._colorManager.colors.background; - break; - default: - if (0 <= req.index && req.index < 256) { - ident = '4;' + req.index; - c = this._colorManager.colors.ansi[req.index]; - } - } - if (ident) { - query.push(`${C0.ESC}]${ident};${color.toXColorName(c!)}${C0.BEL}`); - } - } else { - // set color - hasSet = true; - switch (req.index) { - case 256: - this._colorManager.colors.foreground = rgba.toColor(...req.color); - break; - case 257: - this._colorManager.colors.background = rgba.toColor(...req.color); - break; - default: - if (0 <= req.index && req.index < 256) { - this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); - } - } + if (!this._colorManager) return; + for (const req of event) { + switch (req.index) { + case ColorIndex.FOREGROUND: // OSC 10 + if (req.color) this._colorManager.colors.foreground = rgba.toColor(...req.color); + else this.coreService.triggerDataEvent(`${C0.ESC}]10;${toRgbString(color.toColorRGB(this._colorManager.colors.foreground))}${C0.BEL}`); + break; + case ColorIndex.BACKGROUND: // OSC 11 + if (req.color) this._colorManager.colors.background = rgba.toColor(...req.color); + else this.coreService.triggerDataEvent(`${C0.ESC}]11;${toRgbString(color.toColorRGB(this._colorManager.colors.background))}${C0.BEL}`); + break; + default: // OSC 4 + if (0 <= req.index && req.index < 256) { + if (req.color) this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); + else this.coreService.triggerDataEvent(`${C0.ESC}]4;${req.index};${toRgbString(color.toColorRGB(this._colorManager.colors.ansi[req.index]))}${C0.BEL}`); + } } } - - if (query.length) { - this.coreService.triggerDataEvent(query.join('')); - } - if (hasSet) { - this._renderService?.setColors(this._colorManager.colors); - this.viewport?.onThemeChange(this._colorManager.colors); - } + this._renderService?.setColors(this._colorManager.colors); + this.viewport?.onThemeChange(this._colorManager.colors); } public dispose(): void { diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index e8c4c8bf..cc963809 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 } from 'common/Types'; +import { IBufferLine, IAttributeData, IColorEvent, ColorIndex } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; @@ -41,7 +41,7 @@ class TestInputHandler extends InputHandler { public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } public get windowTitleStack(): string[] { return this._windowTitleStack; } public get iconNameStack(): string[] { return this._iconNameStack; } - public parseAnsiColorChange(data: string): IColorEvent | null { return this._parseAnsiColorChange(data); } + public parseAnsiColorChange(data: string): IColorEvent{ return this._parseAnsiColorChange(data); } /** * Promise based parse call to await the full resolve of given input data. @@ -54,10 +54,6 @@ class TestInputHandler extends InputHandler { prev = await result; } } - - public parseColorSpec(data: string): undefined | [number, number, number] { - return this._parseColorSpec(data); - } } describe('InputHandler', () => { @@ -1869,114 +1865,98 @@ describe('InputHandler', () => { }); }); describe('OSC', () => { - describe('parse xcolor names', () => { - it('rgb:// scheme in 4/8/12/16 bit', () => { - // 4 bit - assert.deepEqual(inputHandler.parseColorSpec('rgb:0/0/0'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:f/f/f'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:1/2/3'), [17, 34, 51]); - // 8 bit - assert.deepEqual(inputHandler.parseColorSpec('rgb:00/00/00'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:ff/ff/ff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:11/22/33'), [17, 34, 51]); - // 12 bit - assert.deepEqual(inputHandler.parseColorSpec('rgb:000/000/000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:fff/fff/fff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:111/222/333'), [17, 34, 51]); - // 16 bit - assert.deepEqual(inputHandler.parseColorSpec('rgb:0000/0000/0000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:ffff/ffff/ffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('rgb:1111/2222/3333'), [17, 34, 51]); - }); - it('#RGB scheme in 4/8/12/16 bit', () => { - // 4 bit - assert.deepEqual(inputHandler.parseColorSpec('#000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('#fff'), [240, 240, 240]); - assert.deepEqual(inputHandler.parseColorSpec('#123'), [16, 32, 48]); - // 8 bit - assert.deepEqual(inputHandler.parseColorSpec('#000000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('#ffffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('#112233'), [17, 34, 51]); - // 12 bit - assert.deepEqual(inputHandler.parseColorSpec('#000000000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('#fffffffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('#111222333'), [17, 34, 51]); - // 16 bit - assert.deepEqual(inputHandler.parseColorSpec('#000000000000'), [0, 0, 0]); - assert.deepEqual(inputHandler.parseColorSpec('#ffffffffffff'), [255, 255, 255]); - assert.deepEqual(inputHandler.parseColorSpec('#111122223333'), [17, 34, 51]); - }); - it('supports upper case', () => { - assert.deepEqual(inputHandler.parseColorSpec('RGB:0/A/F'), [0, 170, 255]); - assert.deepEqual(inputHandler.parseColorSpec('#FFF'), [240, 240, 240]); - }); - it('does not parse illegal combinations', () => { - // shifting bit width - assert.equal(inputHandler.parseColorSpec('rgb:0/11/222'), undefined); - // unsupported scheme - assert.equal(inputHandler.parseColorSpec('rgbi:00/11/22'), undefined); - // broken # specifier - assert.equal(inputHandler.parseColorSpec('#aabbbcc'), undefined); - // out of range - assert.equal(inputHandler.parseColorSpec('#aabbgg'), undefined); - assert.equal(inputHandler.parseColorSpec('rgb:aa/bb/gg'), undefined); - }); - }); - - it('4: should parse correct Ansi color change data', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3'); assert.isNotNull(event); - assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + assert.deepEqual(event![0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); }); - it('4: should ignore incorrect Ansi color change data', () => { // this is testing a private method - assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:a/b/c')); - assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc')); - assert.isNull(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc')); - assert.isNull(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc')); + assert.equal(inputHandler.parseAnsiColorChange('17;rgb:a/b/c').length, 0); + assert.equal(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc').length, 0); + assert.equal(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc').length, 0); + assert.equal(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc').length, 0); }); - it('4: should parse a list of Ansi color changes', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d'); - assert.isNotNull(event); - assert.equal(event!.requests.length, 3); - assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); - assert.deepEqual(event!.requests[1], { index: 17, color: [0x00, 0x11, 0x22] }); - assert.deepEqual(event!.requests[2], { index: 255, color: [0x01, 0xef, 0x2d] }); + assert.equal(event.length, 3); + assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + assert.deepEqual(event[1], { index: 17, color: [0x00, 0x11, 0x22] }); + assert.deepEqual(event[2], { index: 255, color: [0x01, 0xef, 0x2d] }); }); it('4: should ignore incorrect colors in a list of Ansi color changes', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d'); - assert.equal(event!.requests.length, 2); - assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); - assert.deepEqual(event!.requests[1], { index: 255, color: [0x01, 0xef, 0x2d] }); + assert.equal(event.length, 2); + assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + assert.deepEqual(event[1], { index: 255, color: [0x01, 0xef, 0x2d] }); }); it('4: should be case insensitive when parsing Ansi color changes', () => { // this is testing a private method const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3'); - assert.equal(event!.requests.length, 1); - assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + assert.equal(event.length, 1); + assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); }); it('4: should fire event on Ansi color change', async () => { return new Promise(async r => { inputHandler.onColor(e => { - assert.isNotNull(e); - assert.isNotNull(e!.requests); - assert.deepEqual(e!.requests[0], { index: 17, color: [0x1a, 0x2b, 0x3c] }); - assert.deepEqual(e!.requests[1], { index: 12, color: [0x11, 0x22, 0x33] }); + assert.deepEqual(e[0], { index: 17, color: [0x1a, 0x2b, 0x3c] }); + assert.deepEqual(e[1], { index: 12, color: [0x11, 0x22, 0x33] }); r(); }); await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\'); }); }); + + it('10: should create appropriate 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 }]]); + stack.length = 0; + // OSC with multiple values maps to OSC 10 & OSC 11 + await inputHandler.parseP('\x1b]10;?;?;?;?\x07'); + assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }], [{ index: ColorIndex.BACKGROUND }]]); + 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] }]]); + stack.length = 0; + await inputHandler.parseP('\x1b]10;#aabbcc\x07'); + assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]); + stack.length = 0; + // set FG and BG at once + await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122\x07'); + assert.deepEqual(stack, [ + [{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }], + [{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }], + ]); + }); + it('11: should create appropriate events', async () => { + const stack: IColorEvent[] = []; + inputHandler.onColor(ev => stack.push(ev)); + // single foreground query --> color undefined + await inputHandler.parseP('\x1b]11;?\x07'); + assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]); + stack.length = 0; + // OSC 11 with multiple values creates only one BG event + await inputHandler.parseP('\x1b]11;?;?;?;?\x07'); + assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]); + 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] }]]); + stack.length = 0; + await inputHandler.parseP('\x1b]11;#aabbcc\x07'); + assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]); + }); }); // issue #3362 and #2979 diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 5384bf8d..a8d15db1 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack } from 'common/Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -21,6 +21,7 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; +import { parseColor } from 'common/input/XParseColor'; /** * Map collect to glevel. Used in `selectCharset`. @@ -403,9 +404,9 @@ export class InputHandler extends Disposable implements IInputHandler { // 6 - Enable/disable Special Color Number c // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939) // 10 - Change VT100 text foreground color to Pt. - this._parser.registerOscHandler(10, new OscHandler(data => this.queryOrSetFgColor(data))); + this._parser.registerOscHandler(10, new OscHandler(data => this.setOrReportFgColor(data))); // 11 - Change VT100 text background color to Pt. - this._parser.registerOscHandler(11, new OscHandler(data => this.queryOrSetBgColor(data))); + this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data))); // 12 - Change text cursor color to Pt. // 13 - Change mouse foreground color to Pt. // 14 - Change mouse background color to Pt. @@ -2841,14 +2842,13 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } - protected _parseAnsiColorChange(data: string): IColorEvent | null { - const result: IColorEvent = { requests: [] }; + protected _parseAnsiColorChange(data: string): IColorEvent { + const result: IColorEvent = []; // example data: 5;rgb:aa/bb/cc const regex = /(\d+);rgb:([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})/gi; let match; - while ((match = regex.exec(data)) !== null) { - result.requests.push({ + result.push({ index: parseInt(match[1]), color: [ parseInt(match[2], 16), @@ -2857,58 +2857,9 @@ export class InputHandler extends Disposable implements IInputHandler { ] }); } - - if (result.requests.length === 0) { - return null; - } - return result; } - /** - * Parse color spec to RGB values (8 bit per channel). - * See `man xparsecolor` for details about certain format specifications. - * - * Supported formats: - * - rgb:// with , , in h | hh | hhh | hhhh - * - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB - * - * All other formats like rgbi: or device-independent string specifications - * with float numbering are not supported. - */ - protected _parseColorSpec(data: string): [number, number, number] | undefined { - if (!data) return; - // also handle uppercases - let low = data.toLowerCase(); - if (low.indexOf('rgb:') === 0) { - // 'rgb:' specifier - low = low.slice(4); - const rex = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; - const m = rex.exec(low); - if (m) { - const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535; - return [ - Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255), - Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255), - Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255) - ]; - } - } else if (low.indexOf('#') === 0) { - // '#' specifier - low = low.slice(1); - const rex = /^[\da-f]+$/; - if (rex.exec(low) && [3, 6, 9, 12].includes(low.length)) { - const adv = low.length / 3; - const result: [number, number, number] = [0, 0, 0]; - for (let i = 0; i < 3; ++i) { - const c = parseInt(low.slice(adv * i, adv * i + adv), 16); - result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8; - } - return result; - } - } - } - /** * OSC 4; ; ST (set ANSI color to ) * @@ -2918,7 +2869,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setAnsiColor(data: string): boolean { const event = this._parseAnsiColorChange(data); - if (event) { + if (event.length) { this._onColor.fire(event); } else { @@ -2940,6 +2891,8 @@ export class InputHandler extends Disposable implements IInputHandler { * - `#RRRGGGBBB` - 12 bits per channel, truncated to `#RRGGBB` * - `#RRRRGGGGBBBB` - 16 bits per channel, truncated to `#RRGGBB` * + * **Note:** X11 named colors are currently unsupported. + * * If `Pt` contains `?` instead of a color specification, the terminal * returns a sequence with the current default foreground color * (use that sequence to restore the color after changes). @@ -2947,21 +2900,20 @@ export class InputHandler extends Disposable implements IInputHandler { * **Note:** Other than xterm, xterm.js does not support OSC 12 - 19. * Therefore stacking multiple `Pt` separated by `;` only works for the first two entries. */ - public queryOrSetFgColor(data: string): boolean { - // note: data may contain multiple ? or color names separated with ; - // Multiple values will map through to OSC 10 - 19, but we only support 10 and 11 currently, - // thus truncate to max. 2 entries. - const slots = data.split(';').slice(0, 2); + public setOrReportFgColor(data: string): boolean { + // Note: data may contain multiple values separated with ; mapping to OSC 10 - 19 + const slots = data.split(';'); if (slots[0] === '?') { - this._onColor.fire({ requests: [{ index: 256, color: '?' }] }); + this._onColor.fire([{ index: ColorIndex.FOREGROUND }]); } else { - const color = this._parseColorSpec(slots[0]); + const color = parseColor(slots[0]); if (color) { - this._onColor.fire({ requests: [{ index: 256, color }] }); + this._onColor.fire([{ index: ColorIndex.FOREGROUND, color }]); } } - if (slots.length === 2) { - this.queryOrSetBgColor(slots[1]); + // forward second slot to OSC 11 (higher slots are not supported) + if (slots.length > 1) { + this.setOrReportBgColor(slots[1]); } return true; } @@ -2971,14 +2923,14 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background." */ - public queryOrSetBgColor(data: string): boolean { - const slots = data.split(';').slice(0, 1); + public setOrReportBgColor(data: string): boolean { + const slots = data.split(';'); if (slots[0] === '?') { - this._onColor.fire({ requests: [{ index: 257, color: '?' }] }); + this._onColor.fire([{ index: ColorIndex.BACKGROUND }]); } else { - const color = this._parseColorSpec(slots[0]); + const color = parseColor(slots[0]); if (color) { - this._onColor.fire({ requests: [{ index: 257, color }] }); + this._onColor.fire([{ index: ColorIndex.BACKGROUND, color }]); } } return true; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 9cc6dbe7..b38a0643 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -348,17 +348,20 @@ export interface IWindowOptions { setWinLines?: boolean; } -export interface IColorEventColor { - index: number; - color: [number, number, number] | '?'; +// color events from common, used for OSC 4/10/11 +export const enum ColorIndex { + FOREGROUND = 256, + BACKGROUND = 257 } +export interface IColorReportRequest { + index: ColorIndex; + color?: IColorRGB; +} +export interface IColorSetRequest extends IColorReportRequest { + color: IColorRGB; +} +export type IColorEvent = (IColorReportRequest | IColorSetRequest)[]; -/** - * Event fired for OSC 4 command - to change ANSI color based on its index. - */ -export interface IColorEvent { - requests: IColorEventColor[]; -} /** * Calls the parser and handles actions generated by the parser. diff --git a/src/common/input/XParseColor.test.ts b/src/common/input/XParseColor.test.ts new file mode 100644 index 00000000..4a73cdbb --- /dev/null +++ b/src/common/input/XParseColor.test.ts @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { parseColor, toRgbString } from 'common/input/XParseColor'; + +describe('XParseColor', () => { + describe('parseColor', () => { + it('rgb:// scheme in 4/8/12/16 bit', () => { + // 4 bit + assert.deepEqual(parseColor('rgb:0/0/0'), [0, 0, 0]); + assert.deepEqual(parseColor('rgb:f/f/f'), [255, 255, 255]); + assert.deepEqual(parseColor('rgb:1/2/3'), [17, 34, 51]); + // 8 bit + assert.deepEqual(parseColor('rgb:00/00/00'), [0, 0, 0]); + assert.deepEqual(parseColor('rgb:ff/ff/ff'), [255, 255, 255]); + assert.deepEqual(parseColor('rgb:11/22/33'), [17, 34, 51]); + // 12 bit + assert.deepEqual(parseColor('rgb:000/000/000'), [0, 0, 0]); + assert.deepEqual(parseColor('rgb:fff/fff/fff'), [255, 255, 255]); + assert.deepEqual(parseColor('rgb:111/222/333'), [17, 34, 51]); + // 16 bit + assert.deepEqual(parseColor('rgb:0000/0000/0000'), [0, 0, 0]); + assert.deepEqual(parseColor('rgb:ffff/ffff/ffff'), [255, 255, 255]); + assert.deepEqual(parseColor('rgb:1111/2222/3333'), [17, 34, 51]); + }); + it('#RGB scheme in 4/8/12/16 bit', () => { + // 4 bit + assert.deepEqual(parseColor('#000'), [0, 0, 0]); + assert.deepEqual(parseColor('#fff'), [240, 240, 240]); + assert.deepEqual(parseColor('#123'), [16, 32, 48]); + // 8 bit + assert.deepEqual(parseColor('#000000'), [0, 0, 0]); + assert.deepEqual(parseColor('#ffffff'), [255, 255, 255]); + assert.deepEqual(parseColor('#112233'), [17, 34, 51]); + // 12 bit + assert.deepEqual(parseColor('#000000000'), [0, 0, 0]); + assert.deepEqual(parseColor('#fffffffff'), [255, 255, 255]); + assert.deepEqual(parseColor('#111222333'), [17, 34, 51]); + // 16 bit + assert.deepEqual(parseColor('#000000000000'), [0, 0, 0]); + assert.deepEqual(parseColor('#ffffffffffff'), [255, 255, 255]); + assert.deepEqual(parseColor('#111122223333'), [17, 34, 51]); + }); + it('supports upper case', () => { + assert.deepEqual(parseColor('RGB:0/A/F'), [0, 170, 255]); + assert.deepEqual(parseColor('#FFF'), [240, 240, 240]); + }); + it('does not parse illegal combinations', () => { + // shifting bit width + assert.equal(parseColor('rgb:0/11/222'), undefined); + // unsupported scheme + assert.equal(parseColor('rgbi:00/11/22'), undefined); + // broken # specifier + assert.equal(parseColor('#aabbbcc'), undefined); + // out of range + assert.equal(parseColor('#aabbgg'), undefined); + assert.equal(parseColor('rgb:aa/bb/gg'), undefined); + }); + }); + describe('toXColorRgb', () => { + it('rgb:// scheme in 4/8/12/16 bit', () => { + // 4 bit + assert.equal(toRgbString(parseColor('rgb:0/0/0')!, 4), 'rgb:0/0/0'); + assert.equal(toRgbString(parseColor('rgb:f/f/f')!, 4), 'rgb:f/f/f'); + assert.equal(toRgbString(parseColor('rgb:1/2/3')!, 4), 'rgb:1/2/3'); + // 8 bit + assert.equal(toRgbString(parseColor('rgb:00/00/00')!, 8), 'rgb:00/00/00'); + assert.equal(toRgbString(parseColor('rgb:ff/ff/ff')!, 8), 'rgb:ff/ff/ff'); + assert.equal(toRgbString(parseColor('rgb:11/22/33')!, 8), 'rgb:11/22/33'); + // 12 bit + assert.equal(toRgbString(parseColor('rgb:000/000/000')!, 12), 'rgb:000/000/000'); + assert.equal(toRgbString(parseColor('rgb:fff/fff/fff')!, 12), 'rgb:fff/fff/fff'); + assert.equal(toRgbString(parseColor('rgb:111/222/333')!, 12), 'rgb:111/222/333'); + // 16 bit + assert.equal(toRgbString(parseColor('rgb:0000/0000/0000')!, 16), 'rgb:0000/0000/0000'); + assert.equal(toRgbString(parseColor('rgb:ffff/ffff/ffff')!, 16), 'rgb:ffff/ffff/ffff'); + assert.equal(toRgbString(parseColor('rgb:1111/2222/3333')!, 16), 'rgb:1111/2222/3333'); + }); + it('defaults to 16 bit output', () => { + assert.equal(toRgbString(parseColor('rgb:1/2/3')!), 'rgb:1111/2222/3333'); + assert.equal(toRgbString(parseColor('rgb:11/22/33')!), 'rgb:1111/2222/3333'); + assert.equal(toRgbString(parseColor('rgb:111/222/333')!), 'rgb:1111/2222/3333'); + assert.equal(toRgbString(parseColor('rgb:123/123/123')!), 'rgb:1212/1212/1212'); + }); + it('reduces colors to 8 bit resolution', () => { + assert.equal(toRgbString(parseColor('rgb:123/123/123')!, 12), 'rgb:121/121/121'); + assert.equal(toRgbString(parseColor('rgb:1234/1234/1234')!, 16), 'rgb:1212/1212/1212'); + }); + }); +}); diff --git a/src/common/input/XParseColor.ts b/src/common/input/XParseColor.ts new file mode 100644 index 00000000..922bf8a9 --- /dev/null +++ b/src/common/input/XParseColor.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + + +// 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits) +const RGB_REX = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; +// '#...' rule - matching any hex digits +const HASH_REX = /^[\da-f]+$/; + +/** + * Parse color spec to RGB values (8 bit per channel). + * See `man xparsecolor` for details about certain format specifications. + * + * Supported formats: + * - rgb:// with , , in h | hh | hhh | hhhh + * - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB + * + * All other formats like rgbi: or device-independent string specifications + * with float numbering are not supported. + */ +export function parseColor(data: string): [number, number, number] | undefined { + if (!data) return; + // also handle uppercases + let low = data.toLowerCase(); + if (low.indexOf('rgb:') === 0) { + // 'rgb:' specifier + low = low.slice(4); + const m = RGB_REX.exec(low); + if (m) { + const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535; + return [ + Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255), + Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255), + Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255) + ]; + } + } else if (low.indexOf('#') === 0) { + // '#' specifier + low = low.slice(1); + if (HASH_REX.exec(low) && [3, 6, 9, 12].includes(low.length)) { + const adv = low.length / 3; + const result: [number, number, number] = [0, 0, 0]; + for (let i = 0; i < 3; ++i) { + const c = parseInt(low.slice(adv * i, adv * i + adv), 16); + result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8; + } + return result; + } + } + // FIXME: Once #3530 is resolved, implement named colors. +} + +// pad hex output to requested bit width +function pad(n: number, bits: number): string { + const s = n.toString(16); + const s2 = s.length < 2 ? '0' + s : s; + switch (bits) { + case 4: + return s[0]; + case 8: + return s2; + case 12: + return (s2 + s2).slice(0, 3); + default: + return s2 + s2; + } +} + +/** + * Convert a given color to rgb:../../.. string of `bits` depth. + */ +export function toRgbString(color: [number, number, number], bits: number = 16): string { + const [r, g, b] = color; + return `rgb:${pad(r, bits)}/${pad(g, bits)}/${pad(b, bits)}`; +} From 064f6837819a9894b161bf79630f6c2aaaae01a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 17:23:34 +0100 Subject: [PATCH 10/20] make linter happy --- src/common/InputHandler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index cc963809..e17b23fd 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1936,7 +1936,7 @@ describe('InputHandler', () => { await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122\x07'); assert.deepEqual(stack, [ [{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }], - [{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }], + [{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }] ]); }); it('11: should create appropriate events', async () => { From 56f0e69e1f63b875b8c94fb9a4c66fd5205ea3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 18:36:11 +0100 Subject: [PATCH 11/20] api tests for OSC 10 & 11 --- test/api/InputHandler.api.ts | 46 +++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 54ee6957..f236c6cb 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { pollFor, openTerminal, getBrowserType, launchBrowser } from './TestUtils'; +import { pollFor, openTerminal, getBrowserType, launchBrowser, writeSync } from './TestUtils'; import { Browser, Page } from 'playwright'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -386,6 +386,50 @@ describe('InputHandler Integration Tests', function(): void { }); }); + describe('OSC', () => { + describe('OSC 10 & 11', () => { + 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('query FG color', async () => { + await writeSync(page, '\x1b]10;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); + }); + it('query BG color', async () => { + await writeSync(page, '\x1b]11;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); + }); + it('query FG & BG color in one call', async () => { + await writeSync(page, '\x1b]10;?;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']); + }); + it('set & query FG', 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 writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']); + }); + it('set & query BG', 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 writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']); + }); + it('set & query FG & BG color in one call', async () => { + await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07'); + 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'); + }); + }); + + }); + describe('ESC', () => { describe('DECRC: Save cursor, ESC 7', () => { it('should save the absolute cursor position so resizing restores to the correct position', async () => { From b55ee772b4dbd7dea192be00c136ed04fc58f7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 19:01:30 +0100 Subject: [PATCH 12/20] fix inputhandler type definition --- src/common/InputHandler.ts | 4 +- src/common/Types.d.ts | 126 +++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index a8d15db1..168990bc 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -399,7 +399,7 @@ export class InputHandler extends Disposable implements IInputHandler { 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.registerOscHandler(4, new OscHandler(data => this.setAnsiColor(data))); + this._parser.registerOscHandler(4, new OscHandler(data => this.setOrReportIndexedColor(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) @@ -2867,7 +2867,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): boolean { + public setOrReportIndexedColor(data: string): boolean { const event = this._parseAnsiColorChange(data); if (event.length) { this._onColor.fire(event); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b38a0643..741f8b7c 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -376,80 +376,84 @@ export interface IInputHandler { registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; - /** C0 BEL */ bell(): void; - /** C0 LF */ lineFeed(): void; - /** C0 CR */ carriageReturn(): void; - /** C0 BS */ backspace(): void; - /** C0 HT */ tab(): void; - /** C0 SO */ shiftOut(): void; - /** C0 SI */ shiftIn(): void; + /** C0 BEL */ bell(): boolean; + /** C0 LF */ lineFeed(): boolean; + /** C0 CR */ carriageReturn(): boolean; + /** C0 BS */ backspace(): boolean; + /** C0 HT */ tab(): boolean; + /** C0 SO */ shiftOut(): boolean; + /** C0 SI */ shiftIn(): boolean; + + /** CSI @ */ insertChars(params: IParams): boolean; + /** CSI SP @ */ scrollLeft(params: IParams): boolean; + /** CSI A */ cursorUp(params: IParams): boolean; + /** CSI SP A */ scrollRight(params: IParams): boolean; + /** CSI B */ cursorDown(params: IParams): boolean; + /** CSI C */ cursorForward(params: IParams): boolean; + /** CSI D */ cursorBackward(params: IParams): boolean; + /** CSI E */ cursorNextLine(params: IParams): boolean; + /** CSI F */ cursorPrecedingLine(params: IParams): boolean; + /** CSI G */ cursorCharAbsolute(params: IParams): boolean; + /** CSI H */ cursorPosition(params: IParams): boolean; + /** CSI I */ cursorForwardTab(params: IParams): boolean; + /** CSI J */ eraseInDisplay(params: IParams): boolean; + /** CSI K */ eraseInLine(params: IParams): boolean; + /** CSI L */ insertLines(params: IParams): boolean; + /** CSI M */ deleteLines(params: IParams): boolean; + /** CSI P */ deleteChars(params: IParams): boolean; + /** CSI S */ scrollUp(params: IParams): boolean; + /** CSI T */ scrollDown(params: IParams, collect?: string): boolean; + /** CSI X */ eraseChars(params: IParams): boolean; + /** CSI Z */ cursorBackwardTab(params: IParams): boolean; + /** CSI ` */ charPosAbsolute(params: IParams): boolean; + /** CSI a */ hPositionRelative(params: IParams): boolean; + /** CSI b */ repeatPrecedingCharacter(params: IParams): boolean; + /** CSI c */ sendDeviceAttributesPrimary(params: IParams): boolean; + /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): boolean; + /** CSI d */ linePosAbsolute(params: IParams): boolean; + /** CSI e */ vPositionRelative(params: IParams): boolean; + /** CSI f */ hVPosition(params: IParams): boolean; + /** CSI g */ tabClear(params: IParams): boolean; + /** CSI h */ setMode(params: IParams, collect?: string): boolean; + /** CSI l */ resetMode(params: IParams, collect?: string): boolean; + /** CSI m */ charAttributes(params: IParams): boolean; + /** CSI n */ deviceStatus(params: IParams, collect?: string): boolean; + /** CSI p */ softReset(params: IParams, collect?: string): boolean; + /** CSI q */ setCursorStyle(params: IParams, collect?: string): boolean; + /** CSI r */ setScrollRegion(params: IParams, collect?: string): boolean; + /** CSI s */ saveCursor(params: IParams): boolean; + /** CSI u */ restoreCursor(params: IParams): boolean; + /** CSI ' } */ insertColumns(params: IParams): boolean; + /** CSI ' ~ */ deleteColumns(params: IParams): boolean; - /** CSI @ */ insertChars(params: IParams): void; - /** CSI SP @ */ scrollLeft(params: IParams): void; - /** CSI A */ cursorUp(params: IParams): void; - /** CSI SP A */ scrollRight(params: IParams): void; - /** CSI B */ cursorDown(params: IParams): void; - /** CSI C */ cursorForward(params: IParams): void; - /** CSI D */ cursorBackward(params: IParams): void; - /** CSI E */ cursorNextLine(params: IParams): void; - /** CSI F */ cursorPrecedingLine(params: IParams): void; - /** CSI G */ cursorCharAbsolute(params: IParams): void; - /** CSI H */ cursorPosition(params: IParams): void; - /** CSI I */ cursorForwardTab(params: IParams): void; - /** CSI J */ eraseInDisplay(params: IParams): void; - /** CSI K */ eraseInLine(params: IParams): void; - /** CSI L */ insertLines(params: IParams): void; - /** CSI M */ deleteLines(params: IParams): void; - /** CSI P */ deleteChars(params: IParams): void; - /** CSI S */ scrollUp(params: IParams): void; - /** CSI T */ scrollDown(params: IParams, collect?: string): void; - /** CSI X */ eraseChars(params: IParams): void; - /** CSI Z */ cursorBackwardTab(params: IParams): void; - /** CSI ` */ charPosAbsolute(params: IParams): void; - /** CSI a */ hPositionRelative(params: IParams): void; - /** CSI b */ repeatPrecedingCharacter(params: IParams): void; - /** CSI c */ sendDeviceAttributesPrimary(params: IParams): void; - /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void; - /** CSI d */ linePosAbsolute(params: IParams): void; - /** CSI e */ vPositionRelative(params: IParams): void; - /** CSI f */ hVPosition(params: IParams): void; - /** CSI g */ tabClear(params: IParams): void; - /** CSI h */ setMode(params: IParams, collect?: string): void; - /** CSI l */ resetMode(params: IParams, collect?: string): void; - /** CSI m */ charAttributes(params: IParams): void; - /** CSI n */ deviceStatus(params: IParams, collect?: string): void; - /** CSI p */ softReset(params: IParams, collect?: string): void; - /** CSI q */ setCursorStyle(params: IParams, collect?: string): void; - /** CSI r */ setScrollRegion(params: IParams, collect?: string): void; - /** CSI s */ saveCursor(params: IParams): void; - /** CSI u */ restoreCursor(params: IParams): void; - /** CSI ' } */ insertColumns(params: IParams): void; - /** CSI ' ~ */ deleteColumns(params: IParams): void; /** OSC 0 - OSC 2 */ setTitle(data: string): void; - /** OSC 4 */ setAnsiColor(data: string): void; - /** ESC E */ nextLine(): void; - /** ESC = */ keypadApplicationMode(): void; - /** ESC > */ keypadNumericMode(): void; + OSC 2 */ setTitle(data: string): boolean; + /** OSC 4 */ setOrReportIndexedColor(data: string): boolean; + /** OSC 10 */ setOrReportFgColor(data: string): boolean; + /** OSC 11 */ setOrReportBgColor(data: string): boolean; + + /** ESC E */ nextLine(): boolean; + /** ESC = */ keypadApplicationMode(): boolean; + /** ESC > */ keypadNumericMode(): boolean; /** ESC % G - ESC % @ */ selectDefaultCharset(): void; + ESC % @ */ selectDefaultCharset(): boolean; /** ESC ( C ESC ) C ESC * C ESC + C ESC - C ESC . C - ESC / C */ selectCharset(collectAndFlag: string): void; - /** ESC D */ index(): void; - /** ESC H */ tabSet(): void; - /** ESC M */ reverseIndex(): void; - /** ESC c */ fullReset(): void; + ESC / C */ selectCharset(collectAndFlag: string): boolean; + /** ESC D */ index(): boolean; + /** ESC H */ tabSet(): boolean; + /** ESC M */ reverseIndex(): boolean; + /** ESC c */ fullReset(): boolean; /** ESC n ESC o ESC | ESC } - ESC ~ */ setgLevel(level: number): void; - /** ESC # 8 */ screenAlignmentPattern(): void; + ESC ~ */ setgLevel(level: number): boolean; + /** ESC # 8 */ screenAlignmentPattern(): boolean; } interface IParseStack { From 54822af73ae63eb051668e29fd6b0d4c940359b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 19:40:35 +0100 Subject: [PATCH 13/20] use xcolor parser for OSC 4 --- src/common/InputHandler.test.ts | 82 ++++++++++++++------------------- src/common/InputHandler.ts | 38 +++++++-------- 2 files changed, 51 insertions(+), 69 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index e17b23fd..d63a9654 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -17,7 +17,7 @@ import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; import { BufferService } from 'common/services/BufferService'; import { CoreService } from 'common/services/CoreService'; -import { OscHandler } from 'common/parser/OscParser'; + function getCursor(bufferService: IBufferService): number[] { return [ @@ -41,7 +41,6 @@ class TestInputHandler extends InputHandler { public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } public get windowTitleStack(): string[] { return this._windowTitleStack; } public get iconNameStack(): string[] { return this._iconNameStack; } - public parseAnsiColorChange(data: string): IColorEvent{ return this._parseAnsiColorChange(data); } /** * Promise based parse call to await the full resolve of given input data. @@ -1865,53 +1864,42 @@ describe('InputHandler', () => { }); }); describe('OSC', () => { - it('4: should parse correct Ansi color change data', () => { - // this is testing a private method - const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3'); - - assert.isNotNull(event); - assert.deepEqual(event![0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); + it('4: query color events', async () => { + const stack: IColorEvent[] = []; + inputHandler.onColor(ev => stack.push(ev)); + // single color query + await inputHandler.parseP('\x1b]4;0;?\x07'); + assert.deepEqual(stack, [[{ index: 0 }]]); + stack.length = 0; + await inputHandler.parseP('\x1b]4;123;?\x07'); + assert.deepEqual(stack, [[{ index: 123 }]]); + stack.length = 0; + // multiple queries + await inputHandler.parseP('\x1b]4;0;?;123;?\x07'); + assert.deepEqual(stack, [[{ index: 0 }, { index: 123 }]]); + stack.length = 0; }); - it('4: should ignore incorrect Ansi color change data', () => { - // this is testing a private method - assert.equal(inputHandler.parseAnsiColorChange('17;rgb:a/b/c').length, 0); - assert.equal(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc').length, 0); - assert.equal(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc').length, 0); - assert.equal(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc').length, 0); + it('4: set color events', async () => { + const stack: IColorEvent[] = []; + 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] }]]); + stack.length = 0; + await inputHandler.parseP('\x1b]4;123;#aabbcc\x07'); + assert.deepEqual(stack, [[{ 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] }]]); + stack.length = 0; }); - it('4: should parse a list of Ansi color changes', () => { - // this is testing a private method - const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d'); - - assert.equal(event.length, 3); - assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); - assert.deepEqual(event[1], { index: 17, color: [0x00, 0x11, 0x22] }); - assert.deepEqual(event[2], { index: 255, color: [0x01, 0xef, 0x2d] }); - }); - it('4: should ignore incorrect colors in a list of Ansi color changes', () => { - // this is testing a private method - const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d'); - - assert.equal(event.length, 2); - assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); - assert.deepEqual(event[1], { index: 255, color: [0x01, 0xef, 0x2d] }); - }); - it('4: should be case insensitive when parsing Ansi color changes', () => { - // this is testing a private method - const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3'); - - assert.equal(event.length, 1); - assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] }); - }); - it('4: should fire event on Ansi color change', async () => { - return new Promise(async r => { - inputHandler.onColor(e => { - assert.deepEqual(e[0], { index: 17, color: [0x1a, 0x2b, 0x3c] }); - assert.deepEqual(e[1], { index: 12, color: [0x11, 0x22, 0x33] }); - r(); - }); - await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\'); - }); + 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] }]]); + stack.length = 0; }); it('10: should create appropriate events', async () => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 168990bc..00943601 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2842,24 +2842,6 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } - protected _parseAnsiColorChange(data: string): IColorEvent { - const result: IColorEvent = []; - // example data: 5;rgb:aa/bb/cc - const regex = /(\d+);rgb:([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})/gi; - let match; - while ((match = regex.exec(data)) !== null) { - result.push({ - index: parseInt(match[1]), - color: [ - parseInt(match[2], 16), - parseInt(match[3], 16), - parseInt(match[4], 16) - ] - }); - } - return result; - } - /** * OSC 4; ; ST (set ANSI color to ) * @@ -2868,13 +2850,25 @@ export class InputHandler extends Disposable implements IInputHandler { * 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 setOrReportIndexedColor(data: string): boolean { - const event = this._parseAnsiColorChange(data); + const event: IColorEvent = []; + const slots = data.split(';'); + while (slots.length > 1) { + const idx = slots.shift() as string; + const spec = slots.shift() as string; + if (/^\d+$/.exec(idx)) { + if (spec === '?') { + event.push({ index: parseInt(idx) }); + } else { + const color = parseColor(spec); + if (color) { + event.push({ index: parseInt(idx), color }); + } + } + } + } if (event.length) { this._onColor.fire(event); } - else { - this._logService.warn(`Expected format ;rgb:// but got data: ${data}`); - } return true; } From b8097f53a6619e1f46998b50c8ebbc06efefc03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 20:55:47 +0100 Subject: [PATCH 14/20] OSC 4 integration tests --- test/api/InputHandler.api.ts | 50 +++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index f236c6cb..b803d05d 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -386,7 +386,55 @@ describe('InputHandler Integration Tests', function(): void { }); }); - describe('OSC', () => { + describe.only('OSC', () => { + describe('OSC 4', () => { + 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('query single color', async () => { + await writeSync(page, '\x1b]4;0;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']); + await writeSync(page, '\x1b]4;77;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); + }); + it('query multiple colors', async () => { + await writeSync(page, '\x1b]4;0;?;77;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); + }); + it('set & query single color', async () => { + await writeSync(page, '\x1b]4;0;?\x07'); + const restore: string[] = await page.evaluate('window._recordedData'); + assert.deepEqual(await page.evaluate('window._recordedData'), restore); + // set new color & query + await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']); + // restore should set old color + await writeSync(page, restore[0] + '\x1b]4;0;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]); + }); + it('query & set colors mixed', async () => { + await writeSync(page, '\x1b]4;0;?;77;?\x07'); + const restore: string[] = await page.evaluate('window._recordedData'); + await page.evaluate('window._recordedData.length = 0;'); + // mixed call - change 0, query 43, change 77 + await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']); + await page.evaluate('window._recordedData.length = 0;'); + // query new values for 0 + 77 + await writeSync(page, '\x1b]4;0;?;77;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']); + await page.evaluate('window._recordedData.length = 0;'); + // restore old values for 0 + 77 + await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), restore); + }); + }); describe('OSC 10 & 11', () => { before(async () => { await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()'); From 855ed636e7566025ee95f7fe06af127822298d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Nov 2021 20:57:22 +0100 Subject: [PATCH 15/20] remove only from tests --- test/api/InputHandler.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index b803d05d..42681a4e 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -386,7 +386,7 @@ describe('InputHandler Integration Tests', function(): void { }); }); - describe.only('OSC', () => { + describe('OSC', () => { describe('OSC 4', () => { before(async () => { await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()'); From 896f5a7e4cc472600d3234a0072be1925e3f7819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 9 Nov 2021 13:46:54 +0100 Subject: [PATCH 16/20] extend logic to OSC 12 cursor color --- src/browser/Color.ts | 7 --- src/browser/Terminal.ts | 37 +++++++++++---- src/common/InputHandler.test.ts | 46 +++++++++++++++---- src/common/InputHandler.ts | 81 +++++++++++++++++++-------------- src/common/Types.d.ts | 6 ++- test/api/InputHandler.api.ts | 6 +++ 6 files changed, 123 insertions(+), 60 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 6a9dff94..a4e415af 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -217,13 +217,6 @@ export namespace rgba { rgba: channels.toRgba(r, g, b) }; } - - /** - * convert 0xRRGGBBAA to 0xAABBGGRR (32-bit representation on LE systems) - */ - export function toABGR32(rgba: number): number { - return ((rgba & 0xFF) << 24 | (rgba >>> 8 & 0xFF) << 16 | (rgba >>> 16 & 0xFF) << 8 | rgba >>> 24 & 0xFF) >>> 0; - } } export function toPaddedHex(c: number): string { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 0602f604..9e0d7f54 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -178,23 +178,44 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); } + /** + * 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. + */ private _handleColorEvent(event: IColorEvent): void { if (!this._colorManager) return; for (const req of event) { + let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined; + let ident = ''; switch (req.index) { case ColorIndex.FOREGROUND: // OSC 10 - if (req.color) this._colorManager.colors.foreground = rgba.toColor(...req.color); - else this.coreService.triggerDataEvent(`${C0.ESC}]10;${toRgbString(color.toColorRGB(this._colorManager.colors.foreground))}${C0.BEL}`); + acc = 'foreground'; + ident = '10'; break; case ColorIndex.BACKGROUND: // OSC 11 - if (req.color) this._colorManager.colors.background = rgba.toColor(...req.color); - else this.coreService.triggerDataEvent(`${C0.ESC}]11;${toRgbString(color.toColorRGB(this._colorManager.colors.background))}${C0.BEL}`); + acc = 'background'; + ident = '11'; + break; + case ColorIndex.CURSOR: // OSC 12 + acc = 'cursor'; + ident = '12'; break; default: // OSC 4 - if (0 <= req.index && req.index < 256) { - if (req.color) this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); - else this.coreService.triggerDataEvent(`${C0.ESC}]4;${req.index};${toRgbString(color.toColorRGB(this._colorManager.colors.ansi[req.index]))}${C0.BEL}`); - } + // 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}`); + } } } this._renderService?.setColors(this._colorManager.colors); diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index d63a9654..fe48cafc 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1902,16 +1902,16 @@ describe('InputHandler', () => { stack.length = 0; }); - it('10: should create appropriate events', async () => { + 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 }]]); stack.length = 0; - // OSC with multiple values maps to OSC 10 & OSC 11 + // 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 }]]); + assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }], [{ index: ColorIndex.BACKGROUND }], [{ index: ColorIndex.CURSOR }]]); stack.length = 0; // set foreground color events await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07'); @@ -1920,23 +1920,24 @@ describe('InputHandler', () => { await inputHandler.parseP('\x1b]10;#aabbcc\x07'); assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]); stack.length = 0; - // set FG and BG at once - await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122\x07'); + // 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.BACKGROUND, color: [0, 17, 34] }], + [{ index: ColorIndex.CURSOR, color: [18, 52, 86] }] ]); }); - it('11: should create appropriate events', async () => { + it('11: BG set & query events', async () => { const stack: IColorEvent[] = []; inputHandler.onColor(ev => stack.push(ev)); - // single foreground query --> color undefined + // single background query --> color undefined await inputHandler.parseP('\x1b]11;?\x07'); assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]); stack.length = 0; - // OSC 11 with multiple values creates only one BG event + // OSC 11 with multiple values creates only BG and cursor event await inputHandler.parseP('\x1b]11;?;?;?;?\x07'); - assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]); + assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }], [{ index: ColorIndex.CURSOR }]]); stack.length = 0; // set background color events await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07'); @@ -1944,6 +1945,31 @@ describe('InputHandler', () => { stack.length = 0; await inputHandler.parseP('\x1b]11;#aabbcc\x07'); assert.deepEqual(stack, [[{ 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] }] + ]); + }); + 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 }]]); + stack.length = 0; + // OSC 12 with multiple values creates only cursor event + await inputHandler.parseP('\x1b]12;?;?;?;?\x07'); + assert.deepEqual(stack, [[{ 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] }]]); + stack.length = 0; + await inputHandler.parseP('\x1b]12;#aabbcc\x07'); + assert.deepEqual(stack, [[{ index: ColorIndex.CURSOR, color: [170, 187, 204] }]]); }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 00943601..5377bc03 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -408,6 +408,7 @@ export class InputHandler extends Disposable implements IInputHandler { // 11 - Change VT100 text background color to Pt. this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data))); // 12 - Change text cursor color to Pt. + this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data))); // 13 - Change mouse foreground color to Pt. // 14 - Change mouse background color to Pt. // 15 - Change Tektronix foreground color to Pt. @@ -2846,8 +2847,9 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 4; ; ST (set ANSI color to ) * * @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`." - * `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. + * `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats). + * There may be multipe `c ; spec` pairs present in the same instruction. + * If `spec` contains `?` the terminal returns a sequence with the currently set color. */ public setOrReportIndexedColor(data: string): boolean { const event: IColorEvent = []; @@ -2856,12 +2858,15 @@ export class InputHandler extends Disposable implements IInputHandler { const idx = slots.shift() as string; const spec = slots.shift() as string; if (/^\d+$/.exec(idx)) { - if (spec === '?') { - event.push({ index: parseInt(idx) }); - } else { - const color = parseColor(spec); - if (color) { - event.push({ index: parseInt(idx), color }); + const index = parseInt(idx); + if (0 <= index && index < 256) { + if (spec === '?') { + event.push({ index }); + } else { + const color = parseColor(spec); + if (color) { + event.push({ index, color }); + } } } } @@ -2872,6 +2877,30 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } + // special colors - OSC 10 | 11 | 12 + private _specialColors = [ColorIndex.FOREGROUND, ColorIndex.BACKGROUND, ColorIndex.CURSOR]; + + /** + * Apply colors requests for special colors in OSC 10 | 11 | 12. + * Since these commands are stacking from multiple parameters, + * we handle them in a loop with an entry offset to `_specialColors`. + */ + private _setOrReportSpecialColor(data: string, offset: number): boolean { + const slots = data.split(';'); + 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] }]); + } else { + const color = parseColor(slots[i]); + if (color) { + this._onColor.fire([{ index: this._specialColors[offset], color }]); + } + } + } + return true; + } + /** * OSC 10 ; | ST - set or query default foreground color * @@ -2895,21 +2924,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Therefore stacking multiple `Pt` separated by `;` only works for the first two entries. */ public setOrReportFgColor(data: string): boolean { - // Note: data may contain multiple values separated with ; mapping to OSC 10 - 19 - const slots = data.split(';'); - if (slots[0] === '?') { - this._onColor.fire([{ index: ColorIndex.FOREGROUND }]); - } else { - const color = parseColor(slots[0]); - if (color) { - this._onColor.fire([{ index: ColorIndex.FOREGROUND, color }]); - } - } - // forward second slot to OSC 11 (higher slots are not supported) - if (slots.length > 1) { - this.setOrReportBgColor(slots[1]); - } - return true; + return this._setOrReportSpecialColor(data, 0); } /** @@ -2918,16 +2933,16 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background." */ public setOrReportBgColor(data: string): boolean { - const slots = data.split(';'); - if (slots[0] === '?') { - this._onColor.fire([{ index: ColorIndex.BACKGROUND }]); - } else { - const color = parseColor(slots[0]); - if (color) { - this._onColor.fire([{ index: ColorIndex.BACKGROUND, color }]); - } - } - return true; + return this._setOrReportSpecialColor(data, 1); + } + + /** + * OSC 12 ; | ST - set or query default cursor color + * + * @vt: #Y OSC 12 "Set or query default cursor color" "OSC 12 ; Pt BEL" "Same as OSC 10, but for default cursor color." + */ + public setOrReportCursorColor(data: string): boolean { + return this._setOrReportSpecialColor(data, 2); } /** diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 741f8b7c..7e1778a9 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -348,10 +348,11 @@ export interface IWindowOptions { setWinLines?: boolean; } -// color events from common, used for OSC 4/10/11 +// color events from common, used for OSC 4/10/11/12 export const enum ColorIndex { FOREGROUND = 256, - BACKGROUND = 257 + BACKGROUND = 257, + CURSOR = 258 } export interface IColorReportRequest { index: ColorIndex; @@ -431,6 +432,7 @@ export interface IInputHandler { /** OSC 4 */ setOrReportIndexedColor(data: string): boolean; /** OSC 10 */ setOrReportFgColor(data: string): boolean; /** OSC 11 */ setOrReportBgColor(data: string): boolean; + /** OSC 12 */ setOrReportCursorColor(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 42681a4e..fc8aadab 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -469,6 +469,12 @@ describe('InputHandler Integration Tests', function(): void { await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07'); assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']); }); + it('set & query 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 writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07'); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']); + }); it('set & query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07'); assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']); From 9964210ac4c6af300693dd4714183f93273d1beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Nov 2021 22:15:35 +0100 Subject: [PATCH 17/20] implement OSC 104|110|111|112 --- src/browser/ColorManager.ts | 43 +++++++++++++++++ src/browser/Terminal.ts | 39 +++++++++------- src/common/InputHandler.test.ts | 82 +++++++++++++++++++++++---------- src/common/InputHandler.ts | 73 +++++++++++++++++++++++++++-- src/common/Types.d.ts | 23 +++++++-- test/api/InputHandler.api.ts | 71 +++++++++++++++++++++++++++- 6 files changed, 279 insertions(+), 52 deletions(-) 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', () => { From 84325627aaa1c509e89c880326374816b2b00d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Nov 2021 22:36:50 +0100 Subject: [PATCH 18/20] cleanup --- src/browser/ColorManager.ts | 16 +++++++++------- src/browser/Terminal.ts | 6 +++--- src/browser/renderer/atlas/CharAtlasUtils.ts | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index ec7bdc8a..b4b57c67 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -9,6 +9,15 @@ import { channels, color, css } from 'browser/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; import { ColorIndex } from 'common/Types'; + +interface IRestoreColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + ansi: IColor[]; +} + + const DEFAULT_FOREGROUND = css.toColor('#ffffff'); const DEFAULT_BACKGROUND = css.toColor('#000000'); const DEFAULT_CURSOR = css.toColor('#ffffff'); @@ -66,13 +75,6 @@ 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. */ diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 7273eebb..4ea5afb7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler, IColor } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -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, ColorRequestType } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, 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'; @@ -181,7 +181,7 @@ export class Terminal extends CoreTerminal implements ITerminal { /** * 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), + * or none restore requests (resetting all), * while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request. */ private _handleColorEvent(event: IColorEvent): void { diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index b196b373..be92727a 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -16,7 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: undefined, cursorAccent: undefined, selection: undefined, - ansi: colors.ansi + ansi: [...colors.ansi] }; return { devicePixelRatio: window.devicePixelRatio, From 0c20589a7f99168f829f9fe9092b61fe8ee5c14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Nov 2021 23:00:36 +0100 Subject: [PATCH 19/20] remove temp atlas fix --- src/browser/renderer/atlas/CharAtlasUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index be92727a..b196b373 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -16,7 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: undefined, cursorAccent: undefined, selection: undefined, - ansi: [...colors.ansi] + ansi: colors.ansi }; return { devicePixelRatio: window.devicePixelRatio, From 086ca58eceff7393f6655439f0f479b3ab5ee991 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 22 Dec 2021 09:10:12 -0800 Subject: [PATCH 20/20] Comment tweaks --- src/browser/Color.ts | 2 -- src/common/input/XParseColor.ts | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index a4e415af..32e311db 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -6,8 +6,6 @@ import { IColor } from 'browser/Types'; import { IColorRGB } from 'common/Types'; -// FIXME: Move Color.ts lib to common? - /** * Helper functions where the source type is "channels" (individual color channels as numbers). */ diff --git a/src/common/input/XParseColor.ts b/src/common/input/XParseColor.ts index 922bf8a9..8c023a38 100644 --- a/src/common/input/XParseColor.ts +++ b/src/common/input/XParseColor.ts @@ -49,7 +49,10 @@ export function parseColor(data: string): [number, number, number] | undefined { return result; } } - // FIXME: Once #3530 is resolved, implement named colors. + + // Named colors are currently not supported due to the large addition to the xterm.js bundle size + // they would add. In order to support named colors, we would need some way of optionally loading + // additional payloads so startup/download time is not bloated (see #3530). } // pad hex output to requested bit width