diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 7b35949d..332abeb8 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,13 +6,14 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel'; +import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal, IBufferLine } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; +import { AttributeData } from 'common/buffer/AttributeData'; interface IVertices { attributes: Float32Array; @@ -254,18 +255,49 @@ export class GlyphRenderer { for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; const code = model.cells[offset]; + let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET]; + if (fg & FgFlags.INVERSE) { + const workCell = new AttributeData(); + workCell.fg = fg; + workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET]; + // Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors + // from bg. This is needed since the inverse fg color should be based on the original bg + // color, not on the selection color + fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE)); + switch (workCell.getBgColorMode()) { + case Attributes.CM_P16: + case Attributes.CM_P256: + const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba; + fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK; + case Attributes.CM_RGB: + const arr = AttributeData.toColorRGB(workCell.getBgColor()); + fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT; + case Attributes.CM_DEFAULT: + default: + const c2 = this._colors.background.rgba; + fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK; + } + fg |= Attributes.CM_RGB; + } if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { line = terminal.buffer.getLine(row); } const chars = line!.getCell(x)!.char; - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); } else { - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]); + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); } } } + private _getColorFromAnsiIndex(idx: number): IColor { + if (idx >= this._colors.ansi.length) { + throw new Error('No color found for idx ' + idx); + } + return this._colors.ansi[idx]; + } + public onResize(): void { const terminal = this._terminal; const gl = this._gl; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 1cd198d9..8b9c84bf 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -825,6 +825,30 @@ describe('WebGL Renderer Integration Tests', function(): void { }); }); + describe('selection', async () => { + before(async () => setupBrowser()); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + + it('should resolve the inverse foreground color based on the original background color, not the selection', async () => { + const theme: ITheme = { + foreground: '#FF0000', + background: '#00FF00', + selection: '#0000FF' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await writeSync(` â–ˆ\\x1b[7mâ–ˆ\\x1b[0m`); + await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]); + await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]); + await page.evaluate(`window.term.selectAll()`); + // Selection only cell needs to be first to ensure renderer has kicked in + await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); + await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); + await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]); + }); + }); + describe('allowTransparency', async () => { before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true})); after(async () => browser.close()); diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index ee0dd4c7..c8224a92 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -184,34 +184,22 @@ export class ColorManager implements IColorManager { ); return fallback; } - let r: number; - let g: number; - let b: number; - let a: number; - let rgba: number; - if (css.length === 5) { - const num = parseInt(css.substr(1), 16); - r = ((num >> 12) & 0xF) * 16; - g = ((num >> 8) & 0xF) * 16; - b = ((num >> 4) & 0xF) * 16; - a = (num & 0xF) * 16; - rgba = channels.toRgba(r, g, b, a); - } else { - rgba = parseInt(css.substr(1), 16); - r = (rgba >> 24) & 0xFF; - g = (rgba >> 16) & 0xFF; - b = (rgba >> 8) & 0xFF; - a = (rgba ) & 0xFF; - } + // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color + // the color value has alpha less than 1.0, and the string is the color value in the CSS rgba() + const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component)); + const alpha = Math.round(a * 255); + const rgba: number = channels.toRgba(r, g, b, alpha); return { rgba, - css: channels.toCss(r, g, b, a) + css: channels.toCss(r, g, b, alpha) }; } return { - css, + // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color + // if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character + css: this._ctx.fillStyle, rgba: channels.toRgba(data[0], data[1], data[2], data[3]) }; }