From 120527cb41814acd85daae353819efbce24cefa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 8 Mar 2020 22:56:58 +0100 Subject: [PATCH] underline color support, test cases --- src/InputHandler.test.ts | 105 ++++++++++++++++++++++++++- src/InputHandler.ts | 55 ++++++++------ src/common/Types.d.ts | 5 ++ src/common/buffer/AttributeData.ts | 42 +++++++++-- src/common/buffer/BufferLine.test.ts | 53 +++++++++++++- 5 files changed, 228 insertions(+), 32 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 3aeb4f8e..38fccf24 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1445,7 +1445,7 @@ describe('InputHandler', () => { assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); }); }); - describe('extended SGR 4 support', () => { + describe('extended underline style support (SGR 4)', () => { let term: TestTerminal; beforeEach(() => { term = new TestTerminal({cols: 10, rows: 5}); @@ -1519,4 +1519,107 @@ describe('InputHandler', () => { assert.equal(term.curAttrData.getUnderlineStyle(), UnderlineStyle.SINGLE); }); }); + describe('underline colors (SGR 58 & SGR 59)', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 10, rows: 5}); + }); + it('defaults to FG color', () => { + for (const s of ['', '\x1b[30m', '\x1b[38;510m', '\x1b[38;2;1;2;3m']) { + term.writeSync(s); + assert.equal(term.curAttrData.getUnderlineColor(), term.curAttrData.getFgColor()); + assert.equal(term.curAttrData.getUnderlineColorMode(), term.curAttrData.getFgColorMode()); + assert.equal(term.curAttrData.isUnderlineColorRGB(), term.curAttrData.isFgRGB()); + assert.equal(term.curAttrData.isUnderlineColorPalette(), term.curAttrData.isFgPalette()); + assert.equal(term.curAttrData.isUnderlineColorDefault(), term.curAttrData.isFgDefault()); + } + }); + it('correctly sets P256/RGB colors', () => { + term.writeSync('\x1b[4m'); + term.writeSync('\x1b[58;5;123m'); + assert.equal(term.curAttrData.getUnderlineColor(), 123); + assert.equal(term.curAttrData.getUnderlineColorMode(), Attributes.CM_P256); + assert.equal(term.curAttrData.isUnderlineColorRGB(), false); + assert.equal(term.curAttrData.isUnderlineColorPalette(), true); + assert.equal(term.curAttrData.isUnderlineColorDefault(), false); + term.writeSync('\x1b[58;2::1:2:3m'); + assert.equal(term.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3); + assert.equal(term.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB); + assert.equal(term.curAttrData.isUnderlineColorRGB(), true); + assert.equal(term.curAttrData.isUnderlineColorPalette(), false); + assert.equal(term.curAttrData.isUnderlineColorDefault(), false); + }); + it('P256/RGB persistence', () => { + const cell = new CellData(); + term.writeSync('\x1b[4m'); + term.writeSync('\x1b[58;5;123m'); + assert.equal(term.curAttrData.getUnderlineColor(), 123); + assert.equal(term.curAttrData.getUnderlineColorMode(), Attributes.CM_P256); + assert.equal(term.curAttrData.isUnderlineColorRGB(), false); + assert.equal(term.curAttrData.isUnderlineColorPalette(), true); + assert.equal(term.curAttrData.isUnderlineColorDefault(), false); + term.writeSync('ab'); + (term as any)._bufferService.buffers.active.lines.get(0).loadCell(1, cell); + assert.equal(cell.getUnderlineColor(), 123); + assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256); + assert.equal(cell.isUnderlineColorRGB(), false); + assert.equal(cell.isUnderlineColorPalette(), true); + assert.equal(cell.isUnderlineColorDefault(), false); + + term.writeSync('\x1b[4:0m'); + assert.equal(term.curAttrData.getUnderlineColor(), term.curAttrData.getFgColor()); + assert.equal(term.curAttrData.getUnderlineColorMode(), term.curAttrData.getFgColorMode()); + assert.equal(term.curAttrData.isUnderlineColorRGB(), term.curAttrData.isFgRGB()); + assert.equal(term.curAttrData.isUnderlineColorPalette(), term.curAttrData.isFgPalette()); + assert.equal(term.curAttrData.isUnderlineColorDefault(), term.curAttrData.isFgDefault()); + term.writeSync('a'); + (term as any)._bufferService.buffers.active.lines.get(0).loadCell(1, cell); + assert.equal(cell.getUnderlineColor(), 123); + assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256); + assert.equal(cell.isUnderlineColorRGB(), false); + assert.equal(cell.isUnderlineColorPalette(), true); + assert.equal(cell.isUnderlineColorDefault(), false); + (term as any)._bufferService.buffers.active.lines.get(0).loadCell(2, cell); + assert.equal(cell.getUnderlineColor(), term.curAttrData.getFgColor()); + assert.equal(cell.getUnderlineColorMode(), term.curAttrData.getFgColorMode()); + assert.equal(cell.isUnderlineColorRGB(), term.curAttrData.isFgRGB()); + assert.equal(cell.isUnderlineColorPalette(), term.curAttrData.isFgPalette()); + assert.equal(cell.isUnderlineColorDefault(), term.curAttrData.isFgDefault()); + + term.writeSync('\x1b[4m'); + term.writeSync('\x1b[58;2::1:2:3m'); + assert.equal(term.curAttrData.getUnderlineColor(), (1 << 16) | (2 << 8) | 3); + assert.equal(term.curAttrData.getUnderlineColorMode(), Attributes.CM_RGB); + assert.equal(term.curAttrData.isUnderlineColorRGB(), true); + assert.equal(term.curAttrData.isUnderlineColorPalette(), false); + assert.equal(term.curAttrData.isUnderlineColorDefault(), false); + term.writeSync('a'); + term.writeSync('\x1b[24m'); + (term as any)._bufferService.buffers.active.lines.get(0).loadCell(1, cell); + assert.equal(cell.getUnderlineColor(), 123); + assert.equal(cell.getUnderlineColorMode(), Attributes.CM_P256); + assert.equal(cell.isUnderlineColorRGB(), false); + assert.equal(cell.isUnderlineColorPalette(), true); + assert.equal(cell.isUnderlineColorDefault(), false); + (term as any)._bufferService.buffers.active.lines.get(0).loadCell(3, cell); + assert.equal(cell.getUnderlineColor(), (1 << 16) | (2 << 8) | 3); + assert.equal(cell.getUnderlineColorMode(), Attributes.CM_RGB); + assert.equal(cell.isUnderlineColorRGB(), true); + assert.equal(cell.isUnderlineColorPalette(), false); + assert.equal(cell.isUnderlineColorDefault(), false); + + // eAttrs in buffer pos 0 and 1 should be the same object + assert.equal( + (term as any)._bufferService.buffers.active.lines.get(0)._extendedAttrs[0], + (term as any)._bufferService.buffers.active.lines.get(0)._extendedAttrs[1] + ); + // should not have written eAttr for pos 2 in the buffer + assert.equal((term as any)._bufferService.buffers.active.lines.get(0)._extendedAttrs[2], undefined); + // eAttrs in buffer pos 1 and pos 3 must be different objs + assert.notEqual( + (term as any)._bufferService.buffers.active.lines.get(0)._extendedAttrs[1], + (term as any)._bufferService.buffers.active.lines.get(0)._extendedAttrs[3] + ); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index af8e6b24..3c55cec8 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2026,6 +2026,21 @@ export class InputHandler extends Disposable implements IInputHandler { } } + /** + * Helper to write color information packed with color mode. + */ + private _updateColor(color: number, mode: number, c1: number, c2: number, c3: number): number { + if (mode === 2) { + color |= Attributes.CM_RGB; + color &= ~Attributes.RGB_MASK; + color |= AttributeData.fromColorRGB([c1, c2, c3]); + } else if (mode === 5) { + color &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + color |= Attributes.CM_P256 | (c1 & 0xff); + } + return color; + } + /** * Helper to extract and apply color params/subparams. * Returns advance for params index. @@ -2075,24 +2090,16 @@ export class InputHandler extends Disposable implements IInputHandler { } // apply colors - if (accu[0] === 38) { - if (accu[1] === 2) { - attr.fg |= Attributes.CM_RGB; - attr.fg &= ~Attributes.RGB_MASK; - attr.fg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]); - } else if (accu[1] === 5) { - attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); - attr.fg |= Attributes.CM_P256 | (accu[3] & 0xff); - } - } else if (accu[0] === 48) { - if (accu[1] === 2) { - attr.bg |= Attributes.CM_RGB; - attr.bg &= ~Attributes.RGB_MASK; - attr.bg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]); - } else if (accu[1] === 5) { - attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); - attr.bg |= Attributes.CM_P256 | (accu[3] & 0xff); - } + switch (accu[0]) { + case 38: + attr.fg = this._updateColor(attr.fg, accu[1], accu[3], accu[4], accu[5]); + break; + case 48: + attr.bg = this._updateColor(attr.bg, accu[1], accu[3], accu[4], accu[5]); + break; + case 58: + attr.extended = attr.extended.clone(); + attr.extended.underlineColor = this._updateColor(attr.extended.underlineColor, accu[1], accu[3], accu[4], accu[5]); } return advance; @@ -2125,11 +2132,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // update HAS_EXTENDED in BG - if (attr.extended.isEmpty()) { - attr.bg &= ~BgFlags.HAS_EXTENDED; - } else { - attr.bg |= BgFlags.HAS_EXTENDED; - } + attr.updateExtended(); } /** @@ -2300,9 +2303,13 @@ export class InputHandler extends Disposable implements IInputHandler { // reset bg attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); - } else if (p === 38 || p === 48) { + } else if (p === 38 || p === 48 || p === 58) { // fg color 256 and RGB i += this._extractColor(params, i, attr); + } else if (p === 59) { + attr.extended = attr.extended.clone(); + attr.extended.underlineColor = -1; + attr.updateExtended(); } else if (p === 100) { // FIXME: dead branch, p=100 already handled above! // reset fg/bg attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index bc76efda..e8ad3f2d 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -109,7 +109,12 @@ export interface IAttributeData { // extended attrs hasExtendedAttrs(): number; + updateExtended(): void; getUnderlineColor(): number; + getUnderlineColorMode(): number; + isUnderlineColorRGB(): boolean; + isUnderlineColorPalette(): boolean; + isUnderlineColorDefault(): boolean; getUnderlineStyle(): number; } diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index b8b64169..0236c69b 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -73,12 +73,45 @@ export class AttributeData implements IAttributeData { public hasExtendedAttrs(): number { return this.bg & BgFlags.HAS_EXTENDED; } + public updateExtended(): void { + if (this.extended.isEmpty()) { + this.bg &= ~BgFlags.HAS_EXTENDED; + } else { + this.bg |= BgFlags.HAS_EXTENDED; + } + } public getUnderlineColor(): number { if ((this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor) { - return this.extended.underlineColor; + switch (this.extended.underlineColor & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: return this.extended.underlineColor & Attributes.PCOLOR_MASK; + case Attributes.CM_RGB: return this.extended.underlineColor & Attributes.RGB_MASK; + default: return this.getFgColor(); + } } return this.getFgColor(); } + public getUnderlineColorMode(): number { + return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor + ? this.extended.underlineColor & Attributes.CM_MASK + : this.getFgColorMode(); + } + public isUnderlineColorRGB(): boolean { + return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor + ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_RGB + : this.isFgRGB(); + } + public isUnderlineColorPalette(): boolean { + return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor + ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P16 + || (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P256 + : this.isFgPalette(); + } + public isUnderlineColorDefault(): boolean { + return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor + ? (this.extended.underlineColor & Attributes.CM_MASK) === 0 + : this.isFgDefault(); + } public getUnderlineStyle(): UnderlineStyle { return this.fg & FgFlags.UNDERLINE ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE) @@ -104,11 +137,10 @@ export class ExtendedAttrs implements IExtendedAttrs { } /** - * Convenient method to indicate whether the object holds no additional information - * and can be removed from the global attr object. + * Convenient method to indicate whether the object holds no additional information, + * that needs to be persistant in the buffer. */ public isEmpty(): boolean { - // needs to test for every single attribute stored - return !(this.underlineStyle !== UnderlineStyle.NONE || ~this.underlineColor); + return this.underlineStyle === UnderlineStyle.NONE; } } diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index 1b456107..4c5fc3a4 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -32,9 +32,27 @@ describe('AttributeData', () => { attrs.bg |= BgFlags.HAS_EXTENDED; assert.equal(!!attrs.hasExtendedAttrs(), true); }); - it('getUnderlineColor', () => { + it('getUnderlineColor - P256', () => { const attrs = new AttributeData(); - attrs.extended.underlineColor = (1 << 16) | (2 << 8) | 3; + // set a P256 color + attrs.extended.underlineColor = Attributes.CM_P256 | 45; + + // should use FG color if BgFlags.HAS_EXTENDED is not set + assert.equal(attrs.getUnderlineColor(), -1); + + // should use underlineColor if BgFlags.HAS_EXTENDED is set and underlineColor holds a value + attrs.bg |= BgFlags.HAS_EXTENDED; + assert.equal(attrs.getUnderlineColor(), 45); + + // should use FG color if underlineColor holds no value + attrs.extended.underlineColor = -1; + attrs.fg |= Attributes.CM_P256 | 123; + assert.equal(attrs.getUnderlineColor(), 123); + }); + it('getUnderlineColor - RGB', () => { + const attrs = new AttributeData(); + // set a P256 color + attrs.extended.underlineColor = Attributes.CM_RGB | (1 << 16) | (2 << 8) | 3; // should use FG color if BgFlags.HAS_EXTENDED is not set assert.equal(attrs.getUnderlineColor(), -1); @@ -48,6 +66,37 @@ describe('AttributeData', () => { attrs.fg |= Attributes.CM_P256 | 123; assert.equal(attrs.getUnderlineColor(), 123); }); + it('getUnderlineColorMode / isUnderlineColorRGB / isUnderlineColorPalette / isUnderlineColorDefault', () => { + const attrs = new AttributeData(); + + // should always return color mode of fg + for (const mode of [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]) { + attrs.extended.underlineColor = mode; + assert.equal(attrs.getUnderlineColorMode(), attrs.getFgColorMode()); + assert.equal(attrs.isUnderlineColorDefault(), true); + } + attrs.fg = Attributes.CM_RGB; + for (const mode of [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]) { + attrs.extended.underlineColor = mode; + assert.equal(attrs.getUnderlineColorMode(), attrs.getFgColorMode()); + assert.equal(attrs.isUnderlineColorDefault(), false); + assert.equal(attrs.isUnderlineColorRGB(), true); + } + + // should return own mode + attrs.bg |= BgFlags.HAS_EXTENDED; + attrs.extended.underlineColor = Attributes.CM_DEFAULT; + assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_DEFAULT); + attrs.extended.underlineColor = Attributes.CM_P16; + assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_P16); + assert.equal(attrs.isUnderlineColorPalette(), true); + attrs.extended.underlineColor = Attributes.CM_P256; + assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_P256); + assert.equal(attrs.isUnderlineColorPalette(), true); + attrs.extended.underlineColor = Attributes.CM_RGB; + assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_RGB); + assert.equal(attrs.isUnderlineColorRGB(), true); + }); it('getUnderlineStyle', () => { const attrs = new AttributeData();