Add cell color override API for text recoloring

Add setCellColorOverrides/clearCellColorOverrides to the terminal API,
backed by a per-line sparse override store consumed by the DOM renderer's
row factory. Covered cells render their original text in the given color,
with override equality participating in span merging so runs break only at
color boundaries. Overrides are keyed by absolute buffer line of the active
buffer; refresh is limited to the affected viewport rows.

This gives embedders a cheap way to recolor terminal text (e.g. keyword
highlighting) without decoration objects, which fragment text spans and
carry per-cell lookup cost in the DOM renderer, and which are force-hidden
while the alternate buffer is active.
This commit is contained in:
ys-ll
2026-09-20 19:20:47 +08:00
parent f447274f43
commit d07813da37
10 changed files with 195 additions and 5 deletions
+3 -1
View File
@@ -10,7 +10,7 @@ import type { IRenderService } from 'browser/services/Services';
import type { ICoreTerminal, IDisposable, IMarker } from 'common/Types';
import * as playwright from '@playwright/test';
import { PageFunction } from 'playwright-core/types/structs';
import { IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from '@xterm/xterm';
import { ICellColorSpan, IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from '@xterm/xterm';
export interface ITestContext {
browser: Browser;
@@ -279,6 +279,8 @@ export class TerminalProxy implements ITerminalProxyCustomMethods, PlaywrightApi
public async resize(cols: number, rows: number): Promise<void> { return this._page.evaluate(([term, cols, rows]) => term.resize(cols, rows), [await this.getHandle(), cols, rows] as const); }
public async registerMarker(y?: number | undefined): Promise<IMarker> { return this._page.evaluate(([term, y]) => term.registerMarker(y), [await this.getHandle(), y] as const); }
public async registerDecoration(decorationOptions: IDecorationOptions): Promise<IDecoration | undefined> { return this._page.evaluate(([term, decorationOptions]) => term.registerDecoration(decorationOptions), [await this.getHandle(), decorationOptions] as const); }
public setCellColorOverrides(lineY: number, spans: ICellColorSpan[] | null): void { throw new Error('not supported via proxy'); }
public async clearCellColorOverrides(): Promise<void> { return this._page.evaluate((term) => term.clearCellColorOverrides(), await this.getHandle()); }
public async clearTextureAtlas(): Promise<void> { return this.evaluate(([term]) => term.clearTextureAtlas()); }
// #endregion