Create basic theme service

Part of #4184
This commit is contained in:
Daniel Imms
2022-10-08 11:43:19 -07:00
parent 8db2244f5d
commit c4750eaf77
16 changed files with 120 additions and 54 deletions
@@ -8,7 +8,7 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from '
import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { IColor } from 'common/Types';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { Disposable, toDisposable } from 'common/Lifecycle';
@@ -84,7 +84,7 @@ export class RectangleRenderer extends Disposable {
constructor(
private _terminal: Terminal,
private _colors: IColorSet,
private _colors: ReadonlyColorSet,
private _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions
) {
+4 -3
View File
@@ -5,7 +5,7 @@
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { ICharacterJoinerService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
@@ -42,8 +42,9 @@ export class WebglAddon extends Disposable implements ITerminalAddon {
const coreBrowserService: ICoreBrowserService = core._coreBrowserService;
const coreService: ICoreService = core.coreService;
const decorationService: IDecorationService = core._decorationService;
const colors: IColorSet = core._colorManager.colors;
this._renderer = this.register(new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer));
const themeService: IThemeService = core._themeService;
// TODO: Pass in theme service
this._renderer = this.register(new WebglRenderer(terminal, themeService.colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer));
this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss));
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
@@ -9,7 +9,7 @@ import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/s
import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types';
import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services';
import { IColorSet, ITerminal } from 'browser/Types';
import { IColorSet, ITerminal, ReadonlyColorSet } from 'browser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
@@ -55,7 +55,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
constructor(
private _terminal: Terminal,
private _colors: IColorSet,
private _colors: ReadonlyColorSet,
private readonly _characterJoinerService: ICharacterJoinerService,
private readonly _coreBrowserService: ICoreBrowserService,
coreService: ICoreService,
@@ -6,7 +6,7 @@
import { IRenderLayer } from './Types';
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
import { ICoreBrowserService } from 'browser/services/Services';
import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types';
@@ -31,7 +31,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
id: string,
zIndex: number,
private _alpha: boolean,
protected _colors: IColorSet,
protected _colors: ReadonlyColorSet,
protected readonly _coreBrowserService: ICoreBrowserService
) {
super();
@@ -61,7 +61,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
public handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void {}
public handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}
public setColors(terminal: Terminal, colorSet: IColorSet): void {
public setColors(terminal: Terminal, colorSet: ReadonlyColorSet): void {
this._refreshCharAtlas(terminal, colorSet);
}
@@ -89,7 +89,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
* @param terminal The terminal.
* @param colorSet The color set to use for the char atlas.
*/
private _refreshCharAtlas(terminal: Terminal, colorSet: IColorSet): void {
private _refreshCharAtlas(terminal: Terminal, colorSet: ReadonlyColorSet): void {
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
return;
}
@@ -7,7 +7,7 @@ import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService } from 'browser/services/Services';
@@ -37,7 +37,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
terminal: Terminal,
container: HTMLElement,
zIndex: number,
colors: IColorSet,
colors: ReadonlyColorSet,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRedrawEvent>,
coreBrowserService: ICoreBrowserService,
private readonly _coreService: ICoreService
@@ -6,7 +6,7 @@
import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types';
import { ITerminal, IColorSet, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService } from 'browser/services/Services';
import { is256Color } from 'browser/renderer/shared/CharAtlasUtils';
@@ -18,7 +18,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
constructor(
container: HTMLElement,
zIndex: number,
colors: IColorSet,
colors: ReadonlyColorSet,
terminal: ITerminal,
coreBrowserService: ICoreBrowserService
) {
@@ -4,7 +4,7 @@
*/
import { IDisposable, Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
export interface IRenderLayer extends IDisposable {
@@ -31,7 +31,7 @@ export interface IRenderLayer extends IDisposable {
/**
* Called when the theme changes.
*/
setColors(terminal: Terminal, colorSet: IColorSet): void;
setColors(terminal: Terminal, colorSet: ReadonlyColorSet): void;
/**
* Called when the data in the grid has changed (or needs to be rendered
+25 -23
View File
@@ -39,9 +39,8 @@ import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseActio
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ICoreBrowserService, ICharacterJoinerService, IThemeService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { IBuffer } from 'common/buffer/Types';
import { MouseService } from 'browser/services/MouseService';
@@ -57,6 +56,7 @@ import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
import { OscLinkProvider } from 'browser/OscLinkProvider';
import { toDisposable } from 'common/Lifecycle';
import { ThemeService } from 'browser/services/ThemeService';
// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
@@ -86,6 +86,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _coreBrowserService: ICoreBrowserService | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
private _themeService: IThemeService | undefined;
private _characterJoinerService: ICharacterJoinerService | undefined;
private _selectionService: ISelectionService | undefined;
@@ -120,8 +121,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
public viewport: IViewport | undefined;
private _compositionHelper: ICompositionHelper | undefined;
private _accessibilityManager: AccessibilityManager | undefined;
private _colorManager: ColorManager | undefined;
private _theme: ITheme | undefined;
private readonly _onCursorMove = this.register(new EventEmitter<void>());
public readonly onCursorMove = this._onCursorMove.event;
@@ -199,9 +198,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
* while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.
*/
private _handleColorEvent(event: IColorEvent): void {
if (!this._colorManager) return;
if (!this._themeService) return;
for (const req of event) {
let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined;
let acc: 'foreground' | 'background' | 'cursor' | 'ansi';
let ident = '';
switch (req.index) {
case ColorIndex.FOREGROUND: // OSC 10 | 110
@@ -224,21 +223,26 @@ export class Terminal extends CoreTerminal implements ITerminal {
switch (req.type) {
case ColorRequestType.REPORT:
const channels = color.toColorRGB(acc === 'ansi'
? this._colorManager.colors.ansi[req.index]
: this._colorManager.colors[acc]);
? this._themeService.colors.ansi[req.index]
: this._themeService.colors[acc]);
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`);
break;
case ColorRequestType.SET:
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
else this._colorManager.colors[acc] = rgba.toColor(...req.color);
if (acc === 'ansi') {
this._themeService.modifyColors(colors => colors.ansi[req.index] = rgba.toColor(...req.color));
} else {
const narrowedAcc = acc;
this._themeService.modifyColors(colors => colors[narrowedAcc] = rgba.toColor(...req.color));
}
break;
case ColorRequestType.RESTORE:
this._colorManager.restoreColor(req.index);
this._themeService.restoreColor(req.index);
break;
}
}
this._renderService?.setColors(this._colorManager.colors);
this.viewport?.handleThemeChange(this._colorManager.colors);
// TODO: Have these listen to theme change
this._renderService?.setColors(this._themeService.colors);
this.viewport?.handleThemeChange(this._themeService.colors);
}
protected _setup(): void {
@@ -498,10 +502,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
this._instantiationService.setService(ICharSizeService, this._charSizeService);
this._theme = this.options.theme || this._theme;
this._colorManager = new ColorManager();
this.register(this.optionsService.onOptionChange(e => this._colorManager!.handleOptionsChange(e, this.optionsService.rawOptions[e])));
this._colorManager.setTheme(this._theme);
this._themeService = this._instantiationService.createInstance(ThemeService);
this._instantiationService.setService(IThemeService, this._themeService);
this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);
this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);
@@ -533,7 +535,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._viewportScrollArea,
this.element
);
this.viewport.handleThemeChange(this._colorManager.colors);
// TODO: Listen to theme service event
this.viewport.handleThemeChange(this._themeService.colors);
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
this.register(this.viewport);
@@ -610,7 +613,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
private _createRenderer(): IRenderer {
return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2);
// TODO: Listen to theme service
return this._instantiationService.createInstance(DomRenderer, this._themeService!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2);
}
/**
@@ -618,10 +622,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @param theme The theme to set.
*/
private _setTheme(theme: ITheme): void {
this._theme = theme;
this._colorManager?.setTheme(theme);
this._renderService?.setColors(this._colorManager!.colors);
this.viewport?.handleThemeChange(this._colorManager!.colors);
// TODO: Listen to theme service events
this.viewport?.handleThemeChange(this._themeService!.colors);
}
/**
+3 -1
View File
@@ -126,6 +126,8 @@ export interface IColorSet {
contrastCache: IColorContrastCache;
}
export type ReadonlyColorSet = Readonly<Omit<IColorSet, 'ansi'>> & { ansi: Readonly<Pick<IColorSet, 'ansi'>['ansi']> };
export interface IColorContrastCache {
clear(): void;
setCss(bg: number, fg: number, value: string | null): void;
@@ -150,7 +152,7 @@ export interface IViewport extends IDisposable {
handleWheel(ev: WheelEvent): boolean;
handleTouchStart(ev: TouchEvent): void;
handleTouchMove(ev: TouchEvent): boolean;
handleThemeChange(colors: IColorSet): void;
handleThemeChange(colors: ReadonlyColorSet): void;
}
export interface ILinkifierEvent {
+2 -2
View File
@@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/rende
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types';
import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types';
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
@@ -43,7 +43,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
constructor(
private _colors: IColorSet,
private _colors: ReadonlyColorSet,
private readonly _element: HTMLElement,
private readonly _screenElement: HTMLElement,
private readonly _viewportElement: HTMLElement,
@@ -9,7 +9,7 @@ import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/
import { CellData } from 'common/buffer/CellData';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { color, rgba } from 'common/Color';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils';
@@ -35,7 +35,7 @@ export class DomRendererRowFactory {
constructor(
private readonly _document: Document,
private _colors: IColorSet,
private _colors: ReadonlyColorSet,
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
@@ -1,6 +1,6 @@
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICoreBrowserService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants';
import { IDecorationService } from 'common/services/Services';
import { ICellData } from 'common/Types';
@@ -26,7 +26,7 @@ export class CellColorResolver {
constructor(
private readonly _terminal: Terminal,
private _colors: IColorSet,
private _colors: ReadonlyColorSet,
private readonly _selectionRenderModel: ISelectionRenderModel,
private readonly _decorationService: IDecorationService,
private readonly _coreBrowserService: ICoreBrowserService
@@ -5,7 +5,7 @@
import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas';
import { Terminal } from 'xterm';
import { IColorSet, ITerminal } from 'browser/Types';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types';
import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils';
@@ -27,7 +27,7 @@ const charAtlasCache: ITextureAtlasCacheEntry[] = [];
*/
export function acquireTextureAtlas(
terminal: Terminal,
colors: IColorSet,
colors: ReadonlyColorSet,
scaledCellWidth: number,
scaledCellHeight: number,
scaledCharWidth: number,
@@ -6,10 +6,10 @@
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig {
export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: ReadonlyColorSet, devicePixelRatio: number): ICharAtlasConfig {
// null out some fields that don't matter
const clonedColors: IColorSet = {
foreground: colors.foreground,
+18 -3
View File
@@ -5,10 +5,11 @@
import { IEvent } from 'common/EventEmitter';
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
import { IColorSet } from 'browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDisposable } from 'common/Types';
import { ColorIndex, IDisposable } from 'common/Types';
import { ITheme } from 'common/services/Services';
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
export interface ICharSizeService {
@@ -73,7 +74,7 @@ export interface IRenderService extends IDisposable {
resize(cols: number, rows: number): void;
hasRenderer(): boolean;
setRenderer(renderer: IRenderer): void;
setColors(colors: IColorSet): void;
setColors(colors: ReadonlyColorSet): void;
handleDevicePixelRatioChange(): void;
handleResize(cols: number, rows: number): void;
handleCharSizeChanged(): void;
@@ -121,3 +122,17 @@ export interface ICharacterJoinerService {
deregister(joinerId: number): boolean;
getJoinedCharacters(row: number): [number, number][];
}
export const IThemeService = createDecorator<IThemeService>('ThemeService');
export interface IThemeService {
serviceBrand: undefined;
readonly colors: ReadonlyColorSet;
setTheme(theme?: ITheme): void;
restoreColor(slot?: ColorIndex): void;
/**
* Allows external modifying of colors in the theme, this is used instead of {@link colors} to
* prevent accidental writes.
*/
modifyColors(callback: (colors: IColorSet) => void): void;
}
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ColorManager } from 'browser/ColorManager';
import { IThemeService } from 'browser/services/Services';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { Disposable } from 'common/Lifecycle';
import { IOptionsService, ITheme } from 'common/services/Services';
import { ColorIndex } from 'common/Types';
export class ThemeService extends Disposable implements IThemeService {
public serviceBrand: undefined;
// TODO: Merge color manager into theme service
private _colorManager = new ColorManager();
public get colors(): ReadonlyColorSet { return this._colorManager.colors; }
constructor(
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
this.register(this._optionsService.onOptionChange(key => {
if (key === 'theme') {
this.setTheme(this._optionsService.rawOptions.theme);
}
this._colorManager.handleOptionsChange(key, this._optionsService.rawOptions[key]);
}));
this._colorManager.setTheme(this._optionsService.rawOptions.theme);
}
public setTheme(theme: ITheme = {}): void {
this._colorManager.setTheme(theme);
}
public restoreColor(slot?: ColorIndex): void {
this._colorManager.restoreColor(slot);
}
public modifyColors(callback: (colors: IColorSet) => void): void {
callback(this._colorManager.colors);
// TODO: Fire event
}
}