mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #3775 from Tyriar/bg_decorations
Support for decorations to change cell foreground and background
This commit is contained in:
@@ -653,7 +653,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
* @param result The result to select.
|
||||
* @return Whether a result was selected.
|
||||
*/
|
||||
private _selectResult(result: ISearchResult | undefined, decorations?: ISearchDecorationOptions, noScroll?: boolean): boolean {
|
||||
private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean {
|
||||
const terminal = this._terminal!;
|
||||
this._selectedDecoration?.dispose();
|
||||
if (!result) {
|
||||
@@ -661,18 +661,19 @@ export class SearchAddon implements ITerminalAddon {
|
||||
return false;
|
||||
}
|
||||
terminal.select(result.col, result.row, result.size);
|
||||
if (decorations?.activeMatchColorOverviewRuler) {
|
||||
if (options) {
|
||||
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
|
||||
if (marker) {
|
||||
this._selectedDecoration = terminal.registerDecoration({
|
||||
marker,
|
||||
x: result.col,
|
||||
width: result.size,
|
||||
backgroundColor: options.activeMatchBackground,
|
||||
overviewRulerOptions: {
|
||||
color: decorations.activeMatchColorOverviewRuler
|
||||
color: options.activeMatchColorOverviewRuler
|
||||
}
|
||||
});
|
||||
this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.activeMatchBackground, decorations.activeMatchBorder));
|
||||
this._selectedDecoration?.onRender((e) => this._applyStyles(e, options.activeMatchBorder));
|
||||
this._selectedDecoration?.onDispose(() => marker.dispose());
|
||||
}
|
||||
}
|
||||
@@ -695,15 +696,12 @@ export class SearchAddon implements ITerminalAddon {
|
||||
* @param borderColor the border color to apply
|
||||
* @returns
|
||||
*/
|
||||
private _applyStyles(element: HTMLElement, backgroundColor: string | undefined, borderColor: string | undefined): void {
|
||||
private _applyStyles(element: HTMLElement, borderColor: string | undefined): void {
|
||||
if (element.clientWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!element.classList.contains('xterm-find-result-decoration')) {
|
||||
element.classList.add('xterm-find-result-decoration');
|
||||
if (backgroundColor) {
|
||||
element.style.backgroundColor = backgroundColor;
|
||||
}
|
||||
if (borderColor) {
|
||||
element.style.outline = `1px solid ${borderColor}`;
|
||||
}
|
||||
@@ -719,18 +717,20 @@ export class SearchAddon implements ITerminalAddon {
|
||||
private _createResultDecoration(result: ISearchResult, options: ISearchDecorationOptions): IDecoration | undefined {
|
||||
const terminal = this._terminal!;
|
||||
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
|
||||
if (!marker || !options?.matchOverviewRuler) {
|
||||
if (!marker) {
|
||||
return undefined;
|
||||
}
|
||||
const findResultDecoration = terminal.registerDecoration({
|
||||
marker,
|
||||
x: result.col,
|
||||
width: result.size,
|
||||
backgroundColor: options.matchBackground,
|
||||
overviewRulerOptions: this._resultDecorations?.get(marker.line) ? undefined : {
|
||||
color: options.matchOverviewRuler, position: 'center'
|
||||
color: options.matchOverviewRuler,
|
||||
position: 'center'
|
||||
}
|
||||
});
|
||||
findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBackground, options.matchBorder));
|
||||
findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBorder));
|
||||
findResultDecoration?.onDispose(() => marker.dispose());
|
||||
return findResultDecoration;
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ declare module 'xterm-addon-search' {
|
||||
*/
|
||||
interface ISearchDecorationOptions {
|
||||
/**
|
||||
* The background color of a match.
|
||||
* The background color of a match, this must use #RRGGBB format.
|
||||
*/
|
||||
matchBackground?: string;
|
||||
|
||||
/**
|
||||
* The border color of a match
|
||||
* The border color of a match.
|
||||
*/
|
||||
matchBorder?: string;
|
||||
|
||||
@@ -60,7 +60,7 @@ declare module 'xterm-addon-search' {
|
||||
matchOverviewRuler: string;
|
||||
|
||||
/**
|
||||
* The background color for the currently active match.
|
||||
* The background color for the currently active match, this must use #RRGGBB format.
|
||||
*/
|
||||
activeMatchBackground?: string;
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRaster
|
||||
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, FgFlags } from 'common/buffer/Constants';
|
||||
import { NULL_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal, IBufferLine } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
@@ -187,6 +188,8 @@ export class GlyphRenderer {
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the glyph
|
||||
if (chars && chars.length > 1) {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,8 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelect
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ICharacterJoinerService, IRenderService } from 'browser/services/Servic
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { isSafari } from 'common/Platform';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
@@ -30,8 +31,9 @@ export class WebglAddon implements ITerminalAddon {
|
||||
this._terminal = terminal;
|
||||
const renderService: IRenderService = (terminal as any)._core._renderService;
|
||||
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
|
||||
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
|
||||
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
|
||||
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer);
|
||||
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, decorationService, this._preserveDrawingBuffer);
|
||||
this._renderer.onContextLoss(() => this._onContextLoss.fire());
|
||||
renderService.setRenderer(this._renderer);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { RectangleRenderer } from './RectangleRenderer';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IEvent } from 'xterm';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
@@ -23,6 +23,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { CharData, ICellData } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
@@ -31,6 +32,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
private _model: RenderModel = new RenderModel();
|
||||
private _workCell: CellData = new CellData();
|
||||
private _workColors: { fg: number, bg: number } = { fg: 0, bg: 0 };
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _gl: IWebGL2RenderingContext;
|
||||
@@ -52,6 +54,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _terminal: Terminal,
|
||||
private _colors: IColorSet,
|
||||
private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
private readonly _decorationService: IDecorationService,
|
||||
preserveDrawingBuffer?: boolean
|
||||
) {
|
||||
super();
|
||||
@@ -331,14 +334,17 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
let code = cell.getCode();
|
||||
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
|
||||
// Load colors/resolve overrides into work colors
|
||||
this._loadColorsForCell(x, row);
|
||||
|
||||
if (code !== NULL_CELL_CODE) {
|
||||
this._model.lineLengths[y] = x + 1;
|
||||
}
|
||||
|
||||
// Nothing has changed, no updates needed
|
||||
if (this._model.cells[i] === code &&
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) {
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -349,10 +355,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Cache the results in the model
|
||||
this._model.cells[i] = code;
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = cell.bg;
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg;
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
|
||||
|
||||
this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars);
|
||||
this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, chars);
|
||||
|
||||
if (isJoined) {
|
||||
// Restore work cell
|
||||
@@ -363,8 +369,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR);
|
||||
this._model.cells[j] = NULL_CELL_CODE;
|
||||
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
|
||||
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
|
||||
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
|
||||
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,6 +382,64 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads colors for the cell into the work colors object. This resolves overrides/inverse if
|
||||
* necessary which is why the work cell object is not used.
|
||||
*/
|
||||
private _loadColorsForCell(x: number, y: number): void {
|
||||
this._workColors.bg = this._workCell.bg;
|
||||
this._workColors.fg = this._workCell.fg;
|
||||
|
||||
// Get any decoration foreground/background overrides, this happens on the model to avoid
|
||||
// spreading decoration override logic throughout the different sub-renderers
|
||||
let bgOverride: number | undefined;
|
||||
let fgOverride: number | undefined;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.backgroundColorRGB) {
|
||||
bgOverride = (d.backgroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgOverride = (d.foregroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
|
||||
// ahead of time in order to use the correct cache key
|
||||
if (bgOverride !== undefined) {
|
||||
// Non-RGB attributes from model + override + force RGB color mode
|
||||
bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB;
|
||||
}
|
||||
if (fgOverride !== undefined) {
|
||||
// Non-RGB attributes from model + force disable inverse + override + force RGB color mode
|
||||
fgOverride = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride | Attributes.CM_RGB;
|
||||
}
|
||||
|
||||
// Handle case where inverse was specified by only one of bgOverride or fgOverride was set,
|
||||
// resolving the other inverse color and setting the inverse flag if needed.
|
||||
if (this._workColors.fg & FgFlags.INVERSE) {
|
||||
if (bgOverride !== undefined && fgOverride === undefined) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
||||
fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
} else {
|
||||
fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
}
|
||||
}
|
||||
if (bgOverride === undefined && fgOverride !== undefined) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
||||
bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
} else {
|
||||
bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the override if it exists
|
||||
this._workColors.bg = bgOverride ?? this._workColors.bg;
|
||||
this._workColors.fg = fgOverride ?? this._workColors.fg;
|
||||
}
|
||||
|
||||
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { Terminal, FontWeight } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
|
||||
const NULL_COLOR: IColor = {
|
||||
css: '',
|
||||
|
||||
@@ -8,10 +8,10 @@ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
|
||||
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
|
||||
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
|
||||
import { throwIfFalsy } from '../WebglUtils';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { channels, rgba } from 'browser/Color';
|
||||
import { channels, rgba } from 'common/Color';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
import { isPowerlineGlyph } from 'browser/renderer/RendererUtils';
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { ITheme } from 'xterm';
|
||||
import { getBrowserType, launchBrowser, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils';
|
||||
import { getBrowserType, launchBrowser, openTerminal, pollFor, timeout, writeSync } from '../../../out-test/api/TestUtils';
|
||||
import { ITerminalOptions } from '../../../src/common/Types';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
@@ -745,18 +745,18 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [152, 198, 110, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [208, 179, 49, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [161, 183, 215, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [191, 174, 194, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [154, 186, 221, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [203, 173, 199, 255]);
|
||||
// Unchanged
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
@@ -813,18 +813,18 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [36, 72, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [72, 59, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [32, 64, 106, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [75, 51, 80, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [40, 67, 13, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [67, 63, 19, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [45, 65, 87, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [81, 57, 78, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
|
||||
});
|
||||
@@ -874,6 +874,95 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decoration color overrides', async () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser({ rendererType: 'dom' }));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('foregroundColor', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = `█`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
itWebgl('foregroundColor should ignore inverse', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = `\\x1b[7m█\\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
itWebgl('foregroundColor should ignore inverse (only fg on decoration)', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
width: 2,
|
||||
foregroundColor: '#ff0000'
|
||||
});
|
||||
`);
|
||||
const data = `\\x1b[7m█ \\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground
|
||||
});
|
||||
itWebgl('backgroundColor', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = ` `;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
});
|
||||
itWebgl('backgroundColor should ignore inverse', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = `\\x1b[7m \\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
});
|
||||
itWebgl('backgroundColor should ignore inverse (only bg on decoration)', async () => {
|
||||
const data = `\\x1b[7m█ \\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
width: 2,
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default
|
||||
await pollFor(page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
|
||||
+13
-5
@@ -110,11 +110,11 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions {
|
||||
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked,
|
||||
incremental: e.key !== `Enter`,
|
||||
decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? {
|
||||
matchBackground: '#55575380',
|
||||
matchBackground: '#232422',
|
||||
matchBorder: '#555753',
|
||||
matchOverviewRuler: '#555753',
|
||||
activeMatchBackground: '#ef292980',
|
||||
activeMatchBorder: '#ef2929',
|
||||
activeMatchBackground: '#ef2929',
|
||||
activeMatchBorder: '#ffffff',
|
||||
activeMatchColorOverviewRuler: '#ef2929'
|
||||
} : undefined
|
||||
};
|
||||
@@ -556,8 +556,16 @@ function loadTest() {
|
||||
function addDecoration() {
|
||||
term.options['overviewRulerWidth'] = 15;
|
||||
const marker = term.addMarker(1);
|
||||
const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef292980', position: 'left' } });
|
||||
decoration.onRender((e) => e.style.backgroundColor = '#ef292980');
|
||||
const decoration = term.registerDecoration({
|
||||
marker,
|
||||
backgroundColor: '#00FF00',
|
||||
foregroundColor: '#00FE00',
|
||||
overviewRulerOptions: { color: '#ef292980', position: 'left' }
|
||||
});
|
||||
decoration.onRender((e: HTMLElement) => {
|
||||
e.style.right = '100%';
|
||||
e.style.backgroundColor = '#ef292980';
|
||||
});
|
||||
}
|
||||
|
||||
function addOverviewRuler() {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
|
||||
export class ColorContrastCache implements IColorContrastCache {
|
||||
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { ITheme } from 'common/services/Services';
|
||||
import { channels, color, css } from 'browser/Color';
|
||||
import { channels, color, css } from 'common/Color';
|
||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
||||
import { ColorIndex } from 'common/Types';
|
||||
import { ColorIndex, IColor } from 'common/Types';
|
||||
|
||||
|
||||
interface IRestoreColorSet {
|
||||
|
||||
@@ -52,7 +52,7 @@ import { MouseService } from 'browser/services/MouseService';
|
||||
import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { color, rgba } from 'common/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
import { toRgbString } from 'common/input/XParseColor';
|
||||
import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer';
|
||||
@@ -1358,6 +1358,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._setup();
|
||||
super.reset();
|
||||
this._selectionService?.reset();
|
||||
this._decorationService.reset();
|
||||
|
||||
// reattach
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
|
||||
Vendored
+1
-7
@@ -5,11 +5,10 @@
|
||||
|
||||
import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
|
||||
import { ICoreTerminal, CharData, ITerminalOptions, IColor } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
|
||||
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
|
||||
element: HTMLElement | undefined;
|
||||
@@ -113,11 +112,6 @@ export interface IColorManager {
|
||||
onOptionsChange(key: string): void;
|
||||
}
|
||||
|
||||
export interface IColor {
|
||||
css: string;
|
||||
rgba: number; // 32-bit int with rgba in each byte
|
||||
}
|
||||
|
||||
export interface IColorSet {
|
||||
foreground: IColor;
|
||||
background: IColor;
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
*/
|
||||
|
||||
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { ICellData, IColor } from 'common/Types';
|
||||
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
|
||||
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
|
||||
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
|
||||
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { channels, color, rgba } from 'browser/Color';
|
||||
import { channels, color, rgba } from 'common/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
|
||||
@@ -52,7 +52,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _colors: IColorSet,
|
||||
private _rendererId: number,
|
||||
protected readonly _bufferService: IBufferService,
|
||||
protected readonly _optionsService: IOptionsService
|
||||
protected readonly _optionsService: IOptionsService,
|
||||
protected readonly _decorationService: IDecorationService
|
||||
) {
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
@@ -294,7 +295,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param bold Whether the text is bold.
|
||||
*/
|
||||
protected _drawChars(cell: ICellData, x: number, y: number): void {
|
||||
const contrastColor = this._getContrastColor(cell);
|
||||
const contrastColor = this._getContrastColor(cell, x, y);
|
||||
|
||||
// skip cache right away if we draw in RGB
|
||||
// Note: to avoid bad runtime JoinedCellData will be skipped
|
||||
@@ -325,7 +326,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._currentGlyphIdentifier.bold = !!cell.isBold();
|
||||
this._currentGlyphIdentifier.dim = !!cell.isDim();
|
||||
this._currentGlyphIdentifier.italic = !!cell.isItalic();
|
||||
const atlasDidDraw = this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
|
||||
|
||||
// Don't try cache the glyph if it uses any decoration foreground/background override.
|
||||
let hasOverrides = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.backgroundColorRGB || d.foregroundColorRGB) {
|
||||
hasOverrides = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
|
||||
|
||||
if (!atlasDidDraw) {
|
||||
this._drawUncachedChars(cell, x, y);
|
||||
@@ -427,15 +438,30 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`;
|
||||
}
|
||||
|
||||
private _getContrastColor(cell: CellData): IColor | undefined {
|
||||
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) {
|
||||
private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined {
|
||||
// Get any decoration foreground/background overrides, this must be fetched before the early
|
||||
// exist but applied after inverse
|
||||
let bgOverride: number | undefined;
|
||||
let fgOverride: number | undefined;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.backgroundColorRGB) {
|
||||
bgOverride = d.backgroundColorRGB.rgba;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgOverride = d.foregroundColorRGB.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
if (!bgOverride && !fgOverride) {
|
||||
// Try get from cache
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let fgColor = cell.getFgColor();
|
||||
@@ -453,13 +479,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
|
||||
const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
if (!fgOverride) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
}
|
||||
// If it was an override and there was no contrast change, set as the result
|
||||
result = fgOverride;
|
||||
}
|
||||
|
||||
const color: IColor = {
|
||||
@@ -470,7 +500,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
),
|
||||
rgba: result
|
||||
};
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
if (!bgOverride && !fgOverride) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { ICoreBrowserService } from 'browser/services/Services';
|
||||
|
||||
@@ -40,9 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkifierEvent | undefined;
|
||||
@@ -21,9 +21,10 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
linkifier: ILinkifier,
|
||||
linkifier2: ILinkifier2,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
|
||||
linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
|
||||
let nextRendererId = 1;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
interface ISelectionState {
|
||||
start?: [number, number];
|
||||
@@ -24,9 +24,10 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
colors: IColorSet,
|
||||
rendererId: number,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._clearState();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user