From 5c7644adc0bb6fc2691fe29cd5d0a69dfee5cd42 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Tue, 3 Nov 2020 18:36:28 +0100 Subject: [PATCH 01/16] Add rudimenatary and not fully working support of OSC 4 --- src/browser/Terminal.ts | 17 +++++++++++++++++ src/common/InputHandler.ts | 17 +++++++++++++++++ src/common/Types.d.ts | 1 + 3 files changed, 35 insertions(+) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index f8b8b3e4..115ccce7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -53,6 +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 { css } from 'browser/Color'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -150,6 +151,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); + this.register(this._inputHandler.onAnsiColorChange((index, color) => this.changeAnsiColor(index, color))); this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); @@ -157,6 +159,21 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); } + private changeAnsiColor(colorIndex: number, colorValue: string): void { + // colorValue = rgb:xx/yy/zz + const r = colorValue.substring(4, 6); + const g = colorValue.substring(7, 9); + const b = colorValue.substring(10, 12); + const color = `#${r}${g}${b}`; + + //TODO: remove debug + console.log(`Change ANSI color[${colorIndex}]=${colorValue} (${color})`); + + this._colorManager!.colors.ansi[colorIndex] = css.toColor(color); + this._renderService?.setColors(this._colorManager!.colors); + this.viewport?.onThemeChange(this._colorManager!.colors); + } + public dispose(): void { if (this._isDisposed) { return; diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index dbf695e6..3dd0c11b 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -250,6 +250,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; } constructor( private readonly _bufferService: IBufferService, @@ -372,6 +374,12 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number + this._parser.setOscHandler(4, new OscHandler((data: string) => { + const ansiColor = data.split(';'); + const colorIndex = parseInt(ansiColor[0]); + const colorValue = ansiColor[1]; + this.setAnsiColor(colorIndex, colorValue); + })); // 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) @@ -2711,6 +2719,15 @@ export class InputHandler extends Disposable implements IInputHandler { this._iconName = data; } + /** + * OSC 4; ; ST (set ANSI color to ) + */ + public setAnsiColor(colorIndex: number, colorData: string): void { + //TODO: remove debug + console.log(`Setting ANSI color ${colorIndex} to value ${colorData}`); + this._onAnsiColorChange.fire(colorIndex, colorData); + } + /** * ESC E * C1.NEL diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index bd0d11c6..00497932 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -389,6 +389,7 @@ export interface IInputHandler { /** CSI ' ~ */ deleteColumns(params: IParams): void; /** OSC 0 OSC 2 */ setTitle(data: string): void; + /** OSC 4 */ setAnsiColor(colorIndex: number, colorData: string): void; /** ESC E */ nextLine(): void; /** ESC = */ keypadApplicationMode(): void; /** ESC > */ keypadNumericMode(): void; From b22d7f1140676e1e39b08fcbe7b62d0ce4660243 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Wed, 4 Nov 2020 22:19:55 +0100 Subject: [PATCH 02/16] Move color parsing from Terminal to InputHandler --- src/browser/Terminal.ts | 19 ++++++------------- src/common/InputHandler.ts | 39 +++++++++++++++++++++++++------------- src/common/Types.d.ts | 2 +- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 115ccce7..216b541a 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 } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, 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'; @@ -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 { css } from 'browser/Color'; +import { rgba } from 'browser/Color'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -151,7 +151,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(this._inputHandler.onAnsiColorChange((index, color) => this.changeAnsiColor(index, color))); + this.register(this._inputHandler.onAnsiColorChange((index, color) => this._changeAnsiColor(index, color))); this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); @@ -159,17 +159,10 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); } - private changeAnsiColor(colorIndex: number, colorValue: string): void { - // colorValue = rgb:xx/yy/zz - const r = colorValue.substring(4, 6); - const g = colorValue.substring(7, 9); - const b = colorValue.substring(10, 12); - const color = `#${r}${g}${b}`; + private _changeAnsiColor(colorIndex: number, colorRGB: IColorRGB): void { + const color = rgba.toColor(colorRGB[0], colorRGB[1], colorRGB[2]); - //TODO: remove debug - console.log(`Change ANSI color[${colorIndex}]=${colorValue} (${color})`); - - this._colorManager!.colors.ansi[colorIndex] = css.toColor(color); + this._colorManager!.colors.ansi[colorIndex] = color; this._renderService?.setColors(this._colorManager!.colors); this.viewport?.onThemeChange(this._colorManager!.colors); } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 3dd0c11b..46350a14 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorRGB } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -250,8 +250,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 _onAnsiColorChange = new EventEmitter(); + public get onAnsiColorChange(): IEvent { return this._onAnsiColorChange.event; } constructor( private readonly _bufferService: IBufferService, @@ -374,12 +374,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number - this._parser.setOscHandler(4, new OscHandler((data: string) => { - const ansiColor = data.split(';'); - const colorIndex = parseInt(ansiColor[0]); - const colorValue = ansiColor[1]; - this.setAnsiColor(colorIndex, colorValue); - })); + this._parser.setOscHandler(4, new OscHandler((data: string) => this.setAnsiColor(data))); // 5 - Change Special Color Number // 6 - Enable/disable Special Color Number c // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939) @@ -2721,11 +2716,29 @@ export class InputHandler extends Disposable implements IInputHandler { /** * OSC 4; ; ST (set ANSI color to ) + * + * The expected content of data is: ;rgb:// where rr, gg, bb are hex numbers. */ - public setAnsiColor(colorIndex: number, colorData: string): void { - //TODO: remove debug - console.log(`Setting ANSI color ${colorIndex} to value ${colorData}`); - this._onAnsiColorChange.fire(colorIndex, colorData); + public setAnsiColor(data: string): void { + // example data: 5;rgb:aa/bb/cc + const regex = /(\d+);rgb:([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})/; + const match = data.match(regex); + + if (match) { + const colorIndex = parseInt(match[1]); + const color: IColorRGB = [ + parseInt(match[2], 16), + parseInt(match[3], 16), + parseInt(match[4], 16) + ]; + + //TODO: remove debug + console.log(`Setting ANSI color ${colorIndex} to RGB value ${color}`); + this._onAnsiColorChange.fire(colorIndex, color); + } + else { + this._logService.warn(`Expected format ;rgb:// but got data: ${data}`); + } } /** diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 00497932..4967da12 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -389,7 +389,7 @@ export interface IInputHandler { /** CSI ' ~ */ deleteColumns(params: IParams): void; /** OSC 0 OSC 2 */ setTitle(data: string): void; - /** OSC 4 */ setAnsiColor(colorIndex: number, colorData: string): void; + /** OSC 4 */ setAnsiColor(data: string): void; /** ESC E */ nextLine(): void; /** ESC = */ keypadApplicationMode(): void; /** ESC > */ keypadNumericMode(): void; From b127436cc4f004964b252404605c66843ebe131c Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Thu, 5 Nov 2020 08:51:01 +0100 Subject: [PATCH 03/16] Fix TODO comment --- src/common/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 46350a14..1b235abf 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2732,7 +2732,7 @@ export class InputHandler extends Disposable implements IInputHandler { parseInt(match[4], 16) ]; - //TODO: remove debug + // TODO: remove debug console.log(`Setting ANSI color ${colorIndex} to RGB value ${color}`); this._onAnsiColorChange.fire(colorIndex, color); } From b898e016f3e252f4e8ca65a520ef6a65803cc9d1 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sat, 7 Nov 2020 21:33:42 +0100 Subject: [PATCH 04/16] Add IAnsiColorChangeEvent type --- src/browser/Terminal.ts | 10 ++++---- src/common/InputHandler.test.ts | 2 +- src/common/InputHandler.ts | 42 +++++++++++++++++++-------------- src/common/Types.d.ts | 10 ++++++++ 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 216b541a..9452833e 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, IColorRGB } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, IAnsiColorChangeEvent } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -149,9 +149,9 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); + this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(this._inputHandler.onAnsiColorChange((index, color) => this._changeAnsiColor(index, color))); this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); @@ -159,10 +159,10 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); } - private _changeAnsiColor(colorIndex: number, colorRGB: IColorRGB): void { - const color = rgba.toColor(colorRGB[0], colorRGB[1], colorRGB[2]); + private _changeAnsiColor(event: IAnsiColorChangeEvent): void { + const color = rgba.toColor(event.red, event.green, event.blue); - this._colorManager!.colors.ansi[colorIndex] = color; + this._colorManager!.colors.ansi[event.colorIndex] = color; this._renderService?.setColors(this._colorManager!.colors); this.viewport?.onThemeChange(this._colorManager!.colors); } diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 46f8eacc..6ff0fd57 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from 'common/InputHandler'; -import { IBufferLine, IAttributeData } from 'common/Types'; +import { IBufferLine, IAttributeData, IAnsiColorChangeEvent } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 1b235abf..0bd1b3a6 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorRGB } from 'common/Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -250,8 +250,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 _onAnsiColorChange = new EventEmitter(); + public get onAnsiColorChange(): IEvent { return this._onAnsiColorChange.event; } constructor( private readonly _bufferService: IBufferService, @@ -2714,27 +2714,33 @@ export class InputHandler extends Disposable implements IInputHandler { this._iconName = data; } + // This is really an internal method and not part of IInputHandler implementation. + // Making it public so that I can test it. + public parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { + // example data: 5;rgb:aa/bb/cc + const regex = /(\d+);rgb:([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})/; + const match = data.match(regex); + + if (match) { + return { + colorIndex: parseInt(match[1]), + red: parseInt(match[2], 16), + green: parseInt(match[3], 16), + blue: parseInt(match[4], 16) + }; + } + return null; + } + /** * OSC 4; ; ST (set ANSI color to ) * * The expected content of data is: ;rgb:// where rr, gg, bb are hex numbers. */ public setAnsiColor(data: string): void { - // example data: 5;rgb:aa/bb/cc - const regex = /(\d+);rgb:([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})/; - const match = data.match(regex); - - if (match) { - const colorIndex = parseInt(match[1]); - const color: IColorRGB = [ - parseInt(match[2], 16), - parseInt(match[3], 16), - parseInt(match[4], 16) - ]; - - // TODO: remove debug - console.log(`Setting ANSI color ${colorIndex} to RGB value ${color}`); - this._onAnsiColorChange.fire(colorIndex, color); + const event = this.parseAnsiColorChange(data); + if (event) { + this._onAnsiColorChange.fire(event); } else { this._logService.warn(`Expected format ;rgb:// but got data: ${data}`); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 4967da12..cbb11b3f 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -328,6 +328,16 @@ export interface IWindowOptions { setWinLines?: boolean; } +/** + * Event fired for OSC 4 command - to change ANSI color based on its index. + */ +export interface IAnsiColorChangeEvent { + colorIndex: number; + red: number; + green: number; + blue: number; +} + /** * Calls the parser and handles actions generated by the parser. */ From d5f4ae53c902ed972cc0bf7d57b083ad61160b03 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sat, 7 Nov 2020 21:34:21 +0100 Subject: [PATCH 05/16] Add IAnsiColorChangeEvent related tests --- src/common/InputHandler.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 6ff0fd57..3b8d1387 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1692,4 +1692,25 @@ describe('InputHandler', () => { assert.equal(coreService.decPrivateModes.origin, false); }); }); + describe('OSC', () => { + it('should ignore incorrect Ansi color change data', () => { + assert.deepEqual(inputHandler.parseAnsiColorChange('17;rgb:1a/2b/3c'), { + colorIndex: 17, + red: 0x1a, + green: 0x2b, + blue: 0x3c + }); + 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')); + }); + it('should fire event on Ansi color change', (done) => { + inputHandler.onAnsiColorChange(e => { + assert.deepEqual(e, { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c }); + done(); + }); + inputHandler.parse('\x1b]4;17;rgb:1a/2b/3c\x1b\\'); + }); + }); }); From bc86b7804c40f5036b8fc97c93b6dfccb5e5ea07 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sat, 7 Nov 2020 21:35:36 +0100 Subject: [PATCH 06/16] Use all and not only 16 Ansi colors in CharAtlas config --- src/browser/renderer/atlas/CharAtlasUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index 346b35f8..2b876f2b 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -18,7 +18,9 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number selection: undefined, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. - ansi: colors.ansi.slice(0, 16) + // ansi: colors.ansi.slice(0, 16) + // TODO: Using entire array to support OSC 4; can this break anything? + ansi: colors.ansi }; return { devicePixelRatio: window.devicePixelRatio, From 77ed5ba7edf6445655c5a4efe61d37315f34500b Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sun, 8 Nov 2020 10:19:12 +0100 Subject: [PATCH 07/16] Add terminal sequence doc --- src/common/InputHandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 0bd1b3a6..aec5c7a9 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2735,7 +2735,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * OSC 4; ; ST (set ANSI color to ) * - * The expected content of data is: ;rgb:// where rr, gg, bb are hex numbers. + * @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; Ps ; Pt BEL" "Set ANSI color `Ps` to `Pt`." + * `Ps` is the color index between 0 and 255. `Pt` color format is 'rgb:rr/gg/bb' where r, g, b are hexadecimal digits. */ public setAnsiColor(data: string): void { const event = this.parseAnsiColorChange(data); From 653599575359c382eac3670525a1ec4cd911b742 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sun, 8 Nov 2020 22:05:25 +0100 Subject: [PATCH 08/16] Update OSC 4 description to match #3036 --- src/common/InputHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index aec5c7a9..235515b1 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2735,8 +2735,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * OSC 4; ; ST (set ANSI color to ) * - * @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; Ps ; Pt BEL" "Set ANSI color `Ps` to `Pt`." - * `Ps` is the color index between 0 and 255. `Pt` color format is 'rgb:rr/gg/bb' where r, g, b are hexadecimal digits. + * @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:rr/gg/bb' where r, g, b are hexadecimal digits. */ public setAnsiColor(data: string): void { const event = this.parseAnsiColorChange(data); From e6917d19330364e4eb3d274644ad08b442cc8cf0 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sun, 8 Nov 2020 22:07:24 +0100 Subject: [PATCH 09/16] Clarify color spec description --- src/common/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 235515b1..7867a3fe 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2736,7 +2736,7 @@ 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:rr/gg/bb' where r, g, b are hexadecimal digits. + * `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits. */ public setAnsiColor(data: string): void { const event = this.parseAnsiColorChange(data); From 52b64beefce87e48f0b3d81ee795b643f6faa7cd Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sun, 29 Nov 2020 16:30:05 +0100 Subject: [PATCH 10/16] Make InputHandler parseAnsiColorChange private --- src/common/InputHandler.test.ts | 17 +++++++---------- src/common/InputHandler.ts | 6 ++---- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 3b8d1387..8cc1bc97 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1694,16 +1694,13 @@ describe('InputHandler', () => { }); describe('OSC', () => { it('should ignore incorrect Ansi color change data', () => { - assert.deepEqual(inputHandler.parseAnsiColorChange('17;rgb:1a/2b/3c'), { - colorIndex: 17, - red: 0x1a, - green: 0x2b, - blue: 0x3c - }); - 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')); + // this is testing a private method + const parseAnsiColorChange = inputHandler["_parseAnsiColorChange"]; + + assert.isNull(parseAnsiColorChange('17;rgb:a/b/c')); + assert.isNull(parseAnsiColorChange('17;rgb:#aabbcc')); + assert.isNull(parseAnsiColorChange('17;rgba:aa/bb/cc')); + assert.isNull(parseAnsiColorChange('rgb:aa/bb/cc')); }); it('should fire event on Ansi color change', (done) => { inputHandler.onAnsiColorChange(e => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 7867a3fe..3560ee6a 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2714,9 +2714,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._iconName = data; } - // This is really an internal method and not part of IInputHandler implementation. - // Making it public so that I can test it. - public parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { + private _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { // example data: 5;rgb:aa/bb/cc const regex = /(\d+);rgb:([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})/; const match = data.match(regex); @@ -2739,7 +2737,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. */ public setAnsiColor(data: string): void { - const event = this.parseAnsiColorChange(data); + const event = this._parseAnsiColorChange(data); if (event) { this._onAnsiColorChange.fire(event); } From f4a56389cdabeea2919125eb63fa07d5410b6954 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sun, 29 Nov 2020 23:22:31 +0100 Subject: [PATCH 11/16] Fix quotes --- 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 8cc1bc97..eff8c76d 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1695,7 +1695,7 @@ describe('InputHandler', () => { describe('OSC', () => { it('should ignore incorrect Ansi color change data', () => { // this is testing a private method - const parseAnsiColorChange = inputHandler["_parseAnsiColorChange"]; + const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; assert.isNull(parseAnsiColorChange('17;rgb:a/b/c')); assert.isNull(parseAnsiColorChange('17;rgb:#aabbcc')); From 5a74ce2e596e085e390c31d79286069ef8a0ee0d Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Mon, 30 Nov 2020 22:00:04 +0100 Subject: [PATCH 12/16] Add Ansi color change positive test --- src/common/InputHandler.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index eff8c76d..57680052 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1693,6 +1693,15 @@ describe('InputHandler', () => { }); }); describe('OSC', () => { + it('should parse correct Ansi color change data', () => { + // this is testing a private method + const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; + + assert.deepEqual( + parseAnsiColorChange('19;rgb:a1/b2/c3'), + { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 } + ); + }), it('should ignore incorrect Ansi color change data', () => { // this is testing a private method const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; From 2804bfa8b7403aa515ce14ae78fdd8a2110d96d1 Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Mon, 7 Dec 2020 23:20:10 +0100 Subject: [PATCH 13/16] Make safe references to colorManager in _changeAnsiColor --- src/browser/Terminal.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9452833e..1c65559a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -160,11 +160,13 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _changeAnsiColor(event: IAnsiColorChangeEvent): void { + if (!this._colorManager) { return; } + const color = rgba.toColor(event.red, event.green, event.blue); - this._colorManager!.colors.ansi[event.colorIndex] = color; - this._renderService?.setColors(this._colorManager!.colors); - this.viewport?.onThemeChange(this._colorManager!.colors); + this._colorManager.colors.ansi[event.colorIndex] = color; + this._renderService?.setColors(this._colorManager.colors); + this.viewport?.onThemeChange(this._colorManager.colors); } public dispose(): void { From 7d3f5375a81d8efda13fa0e93e738a0f911a1afd Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Mon, 7 Dec 2020 23:20:52 +0100 Subject: [PATCH 14/16] Implement OSC 4 for list of colors --- src/browser/Terminal.ts | 11 +++++--- src/common/InputHandler.test.ts | 48 ++++++++++++++++++++++++++------- src/common/InputHandler.ts | 19 ++++++++----- src/common/Types.d.ts | 12 ++++++--- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 1c65559a..06ac57f3 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -162,11 +162,14 @@ export class Terminal extends CoreTerminal implements ITerminal { private _changeAnsiColor(event: IAnsiColorChangeEvent): void { if (!this._colorManager) { return; } - const color = rgba.toColor(event.red, event.green, event.blue); + event.colors.forEach(ansiColor => { + const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue); - this._colorManager.colors.ansi[event.colorIndex] = color; - this._renderService?.setColors(this._colorManager.colors); - this.viewport?.onThemeChange(this._colorManager.colors); + this._colorManager!.colors.ansi[ansiColor.colorIndex] = color; + }); + + 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 57680052..05b6ea2f 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1693,16 +1693,15 @@ describe('InputHandler', () => { }); }); describe('OSC', () => { - it('should parse correct Ansi color change data', () => { + it('4: should parse correct Ansi color change data', () => { // this is testing a private method const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; + const event = parseAnsiColorChange('19;rgb:a1/b2/c3'); - assert.deepEqual( - parseAnsiColorChange('19;rgb:a1/b2/c3'), - { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 } - ); + assert.isNotNull(event); + assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 }); }), - it('should ignore incorrect Ansi color change data', () => { + it('4: should ignore incorrect Ansi color change data', () => { // this is testing a private method const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; @@ -1711,12 +1710,43 @@ describe('InputHandler', () => { assert.isNull(parseAnsiColorChange('17;rgba:aa/bb/cc')); assert.isNull(parseAnsiColorChange('rgb:aa/bb/cc')); }); - it('should fire event on Ansi color change', (done) => { + it('4: should parse a list of Ansi color changes', () => { + // this is testing a private method + const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; + const event = 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 }); + }); + it('4: should ignore incorrect colors in a list of Ansi color changes', () => { + // this is testing a private method + const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; + const event = 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 }); + }); + it('4: should be case insensitive when parsing Ansi color changes', () => { + // this is testing a private method + const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; + const event = 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 }); + }); + it('4: should fire event on Ansi color change', (done) => { inputHandler.onAnsiColorChange(e => { - assert.deepEqual(e, { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c }); + 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 }); done(); }); - inputHandler.parse('\x1b]4;17;rgb:1a/2b/3c\x1b\\'); + inputHandler.parse('\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 3560ee6a..44520cb9 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2715,19 +2715,25 @@ export class InputHandler extends Disposable implements IInputHandler { } private _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { + const result: IAnsiColorChangeEvent = { colors: [] }; // example data: 5;rgb:aa/bb/cc - const regex = /(\d+);rgb:([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})\/([0-9a-fA-F]{2})/; - const match = data.match(regex); + const regex = /(\d+);rgb:([0-9a-f]{2})\/([0-9a-f]{2})\/([0-9a-f]{2})/gi; + let match; - if (match) { - return { + 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) - }; + }); } - return null; + + if (result.colors.length === 0) { + return null; + } + + return result; } /** @@ -2735,6 +2741,7 @@ export class InputHandler extends Disposable implements IInputHandler { * * @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. */ public setAnsiColor(data: string): void { const event = this._parseAnsiColorChange(data); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index cbb11b3f..1ea26c2a 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -328,16 +328,20 @@ export interface IWindowOptions { setWinLines?: boolean; } -/** - * Event fired for OSC 4 command - to change ANSI color based on its index. - */ -export interface IAnsiColorChangeEvent { +export interface IAnsiColorChangeEventColor { colorIndex: number; red: number; green: number; blue: number; } +/** + * Event fired for OSC 4 command - to change ANSI color based on its index. + */ +export interface IAnsiColorChangeEvent { + colors: IAnsiColorChangeEventColor[]; +} + /** * Calls the parser and handles actions generated by the parser. */ From 5c33e4008db483d54beec303c08bd01d821c190b Mon Sep 17 00:00:00 2001 From: Slawek Zachcial Date: Sat, 9 Jan 2021 14:30:24 +0100 Subject: [PATCH 15/16] Make _parseAnsiColorChange available for tests in TestInputHandler --- src/common/InputHandler.test.ts | 23 +++++++++-------------- src/common/InputHandler.ts | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 05b6ea2f..4c0d8559 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -40,6 +40,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); } } describe('InputHandler', () => { @@ -1695,25 +1696,21 @@ describe('InputHandler', () => { describe('OSC', () => { it('4: should parse correct Ansi color change data', () => { // this is testing a private method - const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; - const event = parseAnsiColorChange('19;rgb:a1/b2/c3'); + 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 }); }), it('4: should ignore incorrect Ansi color change data', () => { // this is testing a private method - const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; - - assert.isNull(parseAnsiColorChange('17;rgb:a/b/c')); - assert.isNull(parseAnsiColorChange('17;rgb:#aabbcc')); - assert.isNull(parseAnsiColorChange('17;rgba:aa/bb/cc')); - assert.isNull(parseAnsiColorChange('rgb:aa/bb/cc')); + 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')); }); it('4: should parse a list of Ansi color changes', () => { // this is testing a private method - const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; - const event = parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d'); + 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); @@ -1723,8 +1720,7 @@ describe('InputHandler', () => { }); it('4: should ignore incorrect colors in a list of Ansi color changes', () => { // this is testing a private method - const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; - const event = parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d'); + 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 }); @@ -1732,8 +1728,7 @@ describe('InputHandler', () => { }); it('4: should be case insensitive when parsing Ansi color changes', () => { // this is testing a private method - const parseAnsiColorChange = inputHandler['_parseAnsiColorChange']; - const event = parseAnsiColorChange('19;rGb:A1/b2/C3'); + 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 }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 44520cb9..626e1fe2 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2714,7 +2714,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._iconName = data; } - private _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { + protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { const result: IAnsiColorChangeEvent = { colors: [] }; // example data: 5;rgb:aa/bb/cc const regex = /(\d+);rgb:([0-9a-f]{2})\/([0-9a-f]{2})\/([0-9a-f]{2})/gi; From 19c14b076b018ac3faa86dff2f147757528a4b84 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Jan 2021 07:40:25 -0800 Subject: [PATCH 16/16] Remove resolved TODO --- src/browser/renderer/atlas/CharAtlasUtils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index 2b876f2b..20695d3c 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -16,10 +16,6 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: undefined, cursorAccent: undefined, selection: undefined, - // For the static char atlas, we only use the first 16 colors, but we need all 256 for the - // dynamic character atlas. - // ansi: colors.ansi.slice(0, 16) - // TODO: Using entire array to support OSC 4; can this break anything? ansi: colors.ansi }; return {