diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 7d1b145c..e7ece483 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -115,6 +115,11 @@ export class SearchAddon implements ITerminalAddon { } } + public clearActiveDecoration(): void { + this._selectedDecoration?.dispose(); + this._selectedDecoration = undefined; + } + /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -655,7 +660,7 @@ export class SearchAddon implements ITerminalAddon { */ private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; - this._selectedDecoration?.dispose(); + this.clearActiveDecoration(); if (!result) { terminal.clearSelection(); return false; @@ -669,6 +674,7 @@ export class SearchAddon implements ITerminalAddon { x: result.col, width: result.size, backgroundColor: options.activeMatchBackground, + layer: 'top', overviewRulerOptions: { color: options.activeMatchColorOverviewRuler } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 9ed1da62..300e5063 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -111,6 +111,13 @@ declare module 'xterm-addon-search' { */ public clearDecorations(): void; + /** + * Clears the active result decoration, this decoration is applied on top of the selection so + * removing it will reveal the selection underneath. This is intended to be called on the search + * textarea's `blur` event. + */ + public clearActiveDecoration(): void; + /** * When decorations are enabled, fires when * the search results change. diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1b45ae3a..d060c4d1 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -380,17 +380,28 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.bg = this._workCell.bg; this._workColors.fg = this._workCell.fg; + // Get any foreground/background overrides, this happens on the model to avoid spreading + // override logic throughout the different sub-renderers let bgOverride: number | undefined; let fgOverride: number | undefined; + // Apply decorations on the bottom layer + for (const d of this._decorationService.getDecorationsAtCell(x, y, 'bottom')) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + } + } + // Apply the selection color if needed if (this._isCellSelected(x, y)) { bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF; } - // Get any decoration foreground/background overrides, this happens on the model to avoid - // spreading decoration override logic throughout the different sub-renderers - for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + // Apply decorations on the top layer + for (const d of this._decorationService.getDecorationsAtCell(x, y, 'top')) { if (d.backgroundColorRGB) { bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; } diff --git a/demo/client.ts b/demo/client.ts index a63864a2..b9e52d7b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -212,10 +212,15 @@ function createTerminal(): void { addDomListener(actionElements.findNext, 'keyup', (e) => { addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e)); }); - addDomListener(actionElements.findPrevious, 'keyup', (e) => { addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e)); }); + addDomListener(actionElements.findNext, 'blur', (e) => { + addons.search.instance.clearActiveDecoration(); + }); + addDomListener(actionElements.findPrevious, 'blur', (e) => { + addons.search.instance.clearActiveDecoration(); + }); // fit is called within a setTimeout, cols and rows need this. setTimeout(() => { diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index e0f3566c..696b793f 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -443,13 +443,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { // exist but applied after inverse let bgOverride: number | undefined; let fgOverride: number | undefined; + let isTop = false; for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + if (d.options.layer !== 'top' && isTop) { + continue; + } if (d.backgroundColorRGB) { bgOverride = d.backgroundColorRGB.rgba; } if (d.foregroundColorRGB) { fgOverride = d.foregroundColorRGB.rgba; } + isTop = d.options.layer === 'top'; } if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) { diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 193d891d..ef5a9b62 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -179,10 +179,15 @@ export class TextRenderLayer extends BaseRenderLayer { // Get any decoration foreground/background overrides, this must be fetched before the early // exist but applied after inverse + let isTop = false; for (const d of this._decorationService.getDecorationsAtCell(x, this._bufferService.buffer.ydisp + y)) { + if (d.options.layer !== 'top' && isTop) { + continue; + } if (d.backgroundColorRGB) { nextFillStyle = d.backgroundColorRGB.css; } + isTop = d.options.layer === 'top'; } if (prevFillStyle === null) { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 71dc782a..bf3939e8 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -177,7 +177,11 @@ export class DomRendererRowFactory { // been applied let bgOverride: IColor | undefined; let fgOverride: IColor | undefined; + let isTop = false; for (const d of this._decorationService.getDecorationsAtCell(x, row)) { + if (d.options.layer !== 'top' && isTop) { + continue; + } if (d.backgroundColorRGB) { bgColorMode = Attributes.CM_RGB; bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; @@ -188,6 +192,7 @@ export class DomRendererRowFactory { fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; fgOverride = d.foregroundColorRGB; } + isTop = d.options.layer === 'top'; } // Foreground diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e32abdce..755f13b3 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -64,13 +64,13 @@ export class DecorationService extends Disposable implements IDecorationService return this._decorations.getKeyIterator(line); } - public *getDecorationsAtCell(x: number, line: number): IterableIterator { + public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator { let xmin = 0; let xmax = 0; for (const d of this._decorations.getKeyIterator(line)) { xmin = d.options.x ?? 0; xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { + if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { yield d; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 82492eb4..c3190210 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -312,7 +312,7 @@ export interface IDecorationService extends IDisposable { /** Iterates over the decorations at a line (in no particular order). */ getDecorationsAtLine(line: number): IterableIterator; /** Iterates over the decorations at a cell (in no particular order). */ - getDecorationsAtCell(x: number, line: number): IterableIterator; + getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator; } export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0c421dc0..15ee4650 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -500,6 +500,16 @@ declare module 'xterm' { */ readonly foregroundColor?: string; + /** + * What layer to render the decoration at when {@link backgroundColor} or + * {@link foregroundColor} are used. `'bottom'` will render under the selection, `'top`' will + * render above the selection\*. + * + * *\* The selection will render on top regardless of layer on the canvas renderer due to how + * it renders selection separately.* + */ + readonly layer?: 'bottom' | 'top'; + /** * When defined, renders the decoration in the overview ruler to the right * of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set