From 4119c7d1d77c8ce1772c99a9e314111c517909ec Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 18:10:04 -0700 Subject: [PATCH] Move ColorManager ownership to Terminal --- src/Terminal.ts | 25 ++++++++++-------- src/TestUtils.test.ts | 8 +++--- src/Types.ts | 2 +- src/Viewport.ts | 2 +- src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/Renderer.ts | 29 ++++++++------------- src/renderer/Types.ts | 9 +++---- src/renderer/dom/DomRenderer.ts | 45 +++++++++++++++------------------ src/ui/ColorManager.ts | 2 +- 9 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 8f30cf05..60409859 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -53,6 +53,7 @@ import { clone } from './common/Clone'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; +import { ColorManager } from './ui/ColorManager'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -109,7 +110,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { useFlowControl: false, allowTransparency: false, tabStopWidth: 8, - theme: null, + theme: undefined, rightClickSelectsWord: Browser.isMac, rendererType: 'canvas', windowsMode: false @@ -212,6 +213,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; + private _colorManager: ColorManager; private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; private _windowsMode: IDisposable | undefined; @@ -528,9 +530,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } this._setupRenderer(); this.renderer.onCharSizeChanged(); - if (this._theme) { - this.renderer.setTheme(this._theme); - } this.mouseHelper.setRenderer(this.renderer); break; case 'scrollback': @@ -765,11 +764,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); - this._setupRenderer(); this._theme = this.options.theme; this.options.theme = null; + this._colorManager = new ColorManager(document, this.options.allowTransparency); + this._colorManager.setTheme(this._theme); + this._setupRenderer(); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); - this.viewport.onThemeChanged(this.renderer.colorManager.colors); + this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); this.register(this.onCursorMove(() => this.renderer.onCursorMove())); @@ -833,8 +835,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _setupRenderer(): void { switch (this.options.rendererType) { - case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; - case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; + case 'canvas': this.renderer = new Renderer(this, this._colorManager.colors); break; + case 'dom': this.renderer = new DomRenderer(this, this._colorManager.colors); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } this.renderer.onRender(e => this._onRender.fire(e)); @@ -847,9 +849,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II */ private _setTheme(theme: ITheme): void { this._theme = theme; - const colors = this.renderer.setTheme(theme); + this._colorManager.setTheme(theme); + if (this.renderer) { + this.renderer.onThemeChange(this._colorManager.colors); + } if (this.viewport) { - this.viewport.onThemeChanged(colors); + this.viewport.onThemeChange(this._colorManager.colors); } } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index fecb3a94..d3ed93a3 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -9,7 +9,7 @@ import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { ICircularList, XtermListener } from './common/Types'; import { Buffer } from './Buffer'; import * as Browser from './common/Platform'; -import { ITheme, IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; +import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from './core/buffer/BufferLine'; import { IColorManager, IColorSet } from './ui/Types'; @@ -384,7 +384,9 @@ export class MockRenderer implements IRenderer { throw new Error('Method not implemented.'); } dimensions: IRenderDimensions; - setTheme(theme: ITheme): IColorSet { return {}; } + onThemeChange(colors: IColorSet): void { + throw new Error('Method not implemented.'); + } onResize(cols: number, rows: number): void {} onCharSizeChanged(): void {} onBlur(): void {} @@ -404,7 +406,7 @@ export class MockViewport implements IViewport { throw new Error('Method not implemented.'); } scrollBarWidth: number = 0; - onThemeChanged(colors: IColorSet): void { + onThemeChange(colors: IColorSet): void { throw new Error('Method not implemented.'); } onWheel(ev: WheelEvent): void { diff --git a/src/Types.ts b/src/Types.ts index ea80383f..30f943fe 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -88,7 +88,7 @@ export interface IViewport extends IDisposable { onWheel(ev: WheelEvent): void; onTouchStart(ev: TouchEvent): void; onTouchMove(ev: TouchEvent): void; - onThemeChanged(colors: IColorSet): void; + onThemeChange(colors: IColorSet): void; } export interface ICompositionHelper { diff --git a/src/Viewport.ts b/src/Viewport.ts index b7e2b5f6..e32113e1 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -57,7 +57,7 @@ export class Viewport extends Disposable implements IViewport { setTimeout(() => this.syncScrollArea(), 0); } - public onThemeChanged(colors: IColorSet): void { + public onThemeChange(colors: IColorSet): void { this._viewportElement.style.backgroundColor = colors.background.css; } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 69129419..3901a860 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -74,7 +74,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {} public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} - public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void { + public onThemeChange(terminal: ITerminal, colorSet: IColorSet): void { this._refreshCharAtlas(terminal, colorSet); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index f5e022a3..527c5966 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -6,13 +6,11 @@ import { TextRenderLayer } from './TextRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; -import { ColorManager } from '../ui/ColorManager'; import { IRenderLayer, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { RenderDebouncer } from '../ui/RenderDebouncer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; -import { ITheme } from 'xterm'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; import { Disposable } from '../common/Lifecycle'; @@ -28,7 +26,6 @@ export class Renderer extends Disposable implements IRenderer { private _needsFullRefresh: boolean = false; private _characterJoinerRegistry: ICharacterJoinerRegistry; - public colorManager: ColorManager; public dimensions: IRenderDimensions; private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); @@ -36,20 +33,19 @@ export class Renderer extends Disposable implements IRenderer { private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - constructor(private _terminal: ITerminal, theme: ITheme) { + constructor( + private _terminal: ITerminal, + private _colors: IColorSet + ) { super(); const allowTransparency = this._terminal.options.allowTransparency; - this.colorManager = new ColorManager(document, allowTransparency); this._characterJoinerRegistry = new CharacterJoinerRegistry(_terminal); - if (theme) { - this.colorManager.setTheme(theme); - } this._renderLayers = [ - new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._characterJoinerRegistry, allowTransparency), - new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors), - new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), - new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors) + new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency), + new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors), + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal), + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -105,12 +101,10 @@ export class Renderer extends Disposable implements IRenderer { } } - public setTheme(theme: ITheme): IColorSet { - this.colorManager.setTheme(theme); - + public onThemeChange(colors: IColorSet): void { // Clear layers and force a full render this._renderLayers.forEach(l => { - l.onThemeChanged(this._terminal, this.colorManager.colors); + l.onThemeChange(this._terminal, this._colors); l.reset(this._terminal); }); @@ -119,8 +113,6 @@ export class Renderer extends Disposable implements IRenderer { } else { this._terminal.refresh(0, this._terminal.rows - 1); } - - return this.colorManager.colors; } public onResize(cols: number, rows: number): void { @@ -168,7 +160,6 @@ export class Renderer extends Disposable implements IRenderer { } public onOptionsChanged(): void { - this.colorManager.allowTransparency = this._terminal.options.allowTransparency; this._runOperation(l => l.onOptionsChanged(this._terminal)); } diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index df3de528..590df0e0 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -4,9 +4,9 @@ */ import { ITerminal, CharacterJoinerHandler } from '../Types'; -import { ITheme, IDisposable } from 'xterm'; +import { IDisposable } from 'xterm'; import { IEvent } from '../common/EventEmitter2'; -import { IColorManager, IColorSet } from '../ui/Types'; +import { IColorSet } from '../ui/Types'; /** * Flags used to render terminal text properly. @@ -27,13 +27,12 @@ export const enum FLAGS { */ export interface IRenderer extends IDisposable { dimensions: IRenderDimensions; - colorManager: IColorManager; onCanvasResize: IEvent<{ width: number, height: number }>; onRender: IEvent<{ start: number, end: number }>; dispose(): void; - setTheme(theme: ITheme): IColorSet; + onThemeChange(colors: IColorSet): void; onWindowResize(devicePixelRatio: number): void; onResize(cols: number, rows: number): void; onCharSizeChanged(): void; @@ -87,7 +86,7 @@ export interface IRenderLayer extends IDisposable { /** * Called when the theme changes. */ - onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void; + onThemeChange(terminal: ITerminal, colorSet: IColorSet): void; /** * Called when the data in the grid has changed (or needs to be rendered diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 8c582ec3..6f46f0ec 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -5,8 +5,6 @@ import { IRenderer, IRenderDimensions } from '../Types'; import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types'; -import { ITheme } from 'xterm'; -import { ColorManager } from '../../ui/ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; @@ -43,18 +41,17 @@ export class DomRenderer extends Disposable implements IRenderer { private _selectionContainer: HTMLElement; public dimensions: IRenderDimensions; - public colorManager: ColorManager; private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - constructor(private _terminal: ITerminal, theme: ITheme | undefined) { + constructor( + private _terminal: ITerminal, + private _colors: IColorSet + ) { super(); - const allowTransparency = this._terminal.options.allowTransparency; - this.colorManager = new ColorManager(document, allowTransparency); - this.setTheme(theme); this._rowContainer = document.createElement('div'); this._rowContainer.classList.add(ROW_CONTAINER_CLASS); @@ -143,11 +140,12 @@ export class DomRenderer extends Disposable implements IRenderer { this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; } - public setTheme(theme: ITheme | undefined): IColorSet { - if (theme) { - this.colorManager.setTheme(theme); - } + public onThemeChange(colors: IColorSet): void { + this._colors = colors; + this._injectCss(); + } + private _injectCss(): void { if (!this._themeStyleElement) { this._themeStyleElement = document.createElement('style'); this._terminal.screenElement.appendChild(this._themeStyleElement); @@ -156,8 +154,8 @@ export class DomRenderer extends Disposable implements IRenderer { // Base CSS let styles = `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + - ` color: ${this.colorManager.colors.foreground.css};` + - ` background-color: ${this.colorManager.colors.background.css};` + + ` color: ${this._colors.foreground.css};` + + ` background-color: ${this._colors.background.css};` + ` font-family: ${this._terminal.options.fontFamily};` + ` font-size: ${this._terminal.options.fontSize}px;` + `}`; @@ -182,21 +180,21 @@ export class DomRenderer extends Disposable implements IRenderer { // Cursor styles += `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` + - ` outline: 1px solid ${this.colorManager.colors.cursor.css};` + + ` outline: 1px solid ${this._colors.cursor.css};` + ` outline-offset: -1px;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` + ` animation: blink 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + - ` background-color: ${this.colorManager.colors.cursor.css};` + - ` color: ${this.colorManager.colors.cursorAccent.css};` + + ` background-color: ${this._colors.cursor.css};` + + ` color: ${this._colors.cursorAccent.css};` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` + - ` box-shadow: 1px 0 0 ${this.colorManager.colors.cursor.css} inset;` + + ` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` + - ` box-shadow: 0 -1px 0 ${this.colorManager.colors.cursor.css} inset;` + + ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` + `}`; // Selection styles += @@ -209,20 +207,19 @@ export class DomRenderer extends Disposable implements IRenderer { `}` + `${this._terminalSelector} .${SELECTION_CLASS} div {` + ` position: absolute;` + - ` background-color: ${this.colorManager.colors.selection.css};` + + ` background-color: ${this._colors.selection.css};` + `}`; // Colors - this.colorManager.colors.ansi.forEach((c, i) => { + this._colors.ansi.forEach((c, i) => { styles += `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; }); styles += - `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this.colorManager.colors.background.css}; }` + - `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this.colorManager.colors.foreground.css}; }`; + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this._colors.background.css}; }` + + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`; this._themeStyleElement.innerHTML = styles; - return this.colorManager.colors; } public onWindowResize(devicePixelRatio: number): void { @@ -332,7 +329,7 @@ export class DomRenderer extends Disposable implements IRenderer { public onOptionsChanged(): void { // Force a refresh this._updateDimensions(); - this.setTheme(undefined); + this._injectCss(); this._terminal.refresh(0, this._terminal.rows - 1); } diff --git a/src/ui/ColorManager.ts b/src/ui/ColorManager.ts index 8a463670..6d68c58d 100644 --- a/src/ui/ColorManager.ts +++ b/src/ui/ColorManager.ts @@ -108,7 +108,7 @@ export class ColorManager implements IColorManager { * @param theme The theme to use. If a partial theme is provided then default * colors will be used where colors are not defined. */ - public setTheme(theme: ITheme): void { + public setTheme(theme: ITheme = {}): void { this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);