Skip to content

Commit

Permalink
Merge pull request #4151 from Tyriar/renderer_before_open
Browse files Browse the repository at this point in the history
Allow renderer to be set before Terminal.open is called
  • Loading branch information
Tyriar committed Oct 7, 2022
2 parents a060c64 + ffa4d92 commit eb3de3e
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 53 deletions.
26 changes: 14 additions & 12 deletions addons/xterm-addon-canvas/src/CanvasAddon.ts
Expand Up @@ -18,21 +18,23 @@ export class CanvasAddon implements ITerminalAddon {
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;

public activate(terminal: Terminal): void {
const core = (terminal as any)._core;
if (!terminal.element) {
throw new Error('Cannot activate CanvasAddon before Terminal.open');
core.onWillOpen(() => this.activate(terminal));
return;
}
this._terminal = terminal;
const bufferService: IBufferService = (terminal as any)._core._bufferService;
const renderService: IRenderService = (terminal as any)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
const charSizeService: ICharSizeService = (terminal as any)._core._charSizeService;
const coreService: ICoreService = (terminal as any)._core.coreService;
const coreBrowserService: ICoreBrowserService = (terminal as any)._core._coreBrowserService;
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
const optionsService: IOptionsService = (terminal as any)._core.optionsService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
const screenElement: HTMLElement = (terminal as any)._core.screenElement;
const linkifier = (terminal as any)._core.linkifier2;
const bufferService: IBufferService = core._bufferService;
const renderService: IRenderService = core._renderService;
const characterJoinerService: ICharacterJoinerService = core._characterJoinerService;
const charSizeService: ICharSizeService = core._charSizeService;
const coreService: ICoreService = core.coreService;
const coreBrowserService: ICoreBrowserService = core._coreBrowserService;
const decorationService: IDecorationService = core._decorationService;
const optionsService: IOptionsService = core.optionsService;
const colors: IColorSet = core._colorManager.colors;
const screenElement: HTMLElement = core.screenElement;
const linkifier = core.linkifier2;
this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
renderService.setRenderer(this._renderer);
Expand Down
16 changes: 9 additions & 7 deletions addons/xterm-addon-webgl/src/WebglAddon.ts
Expand Up @@ -25,19 +25,21 @@ export class WebglAddon implements ITerminalAddon {
) {}

public activate(terminal: Terminal): void {
const core = (terminal as any)._core;
if (!terminal.element) {
throw new Error('Cannot activate WebglAddon before Terminal.open');
core.onWillOpen(() => this.activate(terminal));
return;
}
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
this._terminal = terminal;
const renderService: IRenderService = (terminal as any)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
const coreBrowserService: ICoreBrowserService = (terminal as any)._core._coreBrowserService;
const coreService: ICoreService = (terminal as any)._core.coreService;
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
const renderService: IRenderService = core._renderService;
const characterJoinerService: ICharacterJoinerService = core._characterJoinerService;
const coreBrowserService: ICoreBrowserService = core._coreBrowserService;
const coreService: ICoreService = core.coreService;
const decorationService: IDecorationService = core._decorationService;
const colors: IColorSet = core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer);
forwardEvent(this._renderer.onContextLoss, this._onContextLoss);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
Expand Down
18 changes: 7 additions & 11 deletions demo/client.ts
Expand Up @@ -270,18 +270,14 @@ function createTerminal(): void {
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';

term.open(terminalContainer);
addons.fit.instance!.fit();
try {
typedTerm.loadAddon(addons.webgl.instance);
setTimeout(() => {
addTextureAtlas(addons.webgl.instance.textureAtlas);
addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e));
}, 0);
}
catch {
addons.webgl.instance = undefined;
}
typedTerm.loadAddon(addons.webgl.instance);
setTimeout(() => {
addTextureAtlas(addons.webgl.instance.textureAtlas);
addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e));
}, 0);

term.open(terminalContainer);
term.focus();

addDomListener(paddingElement, 'change', setPadding);
Expand Down
26 changes: 16 additions & 10 deletions src/browser/Terminal.ts
Expand Up @@ -135,14 +135,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
private readonly _onBell = new EventEmitter<void>();
public readonly onBell = this._onBell.event;

private readonly _onFocus = new EventEmitter<void>();
public readonly onFocus = this._onFocus.event;
private readonly _onBlur = new EventEmitter<void>();
public readonly onBlur = this._onBlur.event;
private readonly _onA11yCharEmitter = new EventEmitter<string>();
public readonly onA11yChar = this._onA11yCharEmitter.event;
private readonly _onA11yTabEmitter = new EventEmitter<number>();
public readonly onA11yTab = this._onA11yTabEmitter.event;
private _onFocus = new EventEmitter<void>();
public get onFocus(): IEvent<void> { return this._onFocus.event; }
private _onBlur = new EventEmitter<void>();
public get onBlur(): IEvent<void> { return this._onBlur.event; }
private _onA11yCharEmitter = new EventEmitter<string>();
public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
private _onA11yTabEmitter = new EventEmitter<number>();
public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
private _onWillOpen = new EventEmitter<HTMLElement>();
public get onWillOpen(): IEvent<HTMLElement> { return this._onWillOpen.event; }

/**
* Creates a new `Terminal` object.
Expand Down Expand Up @@ -509,8 +511,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);
this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);

const renderer = this._createRenderer();
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
this._renderService = this.register(this._instantiationService.createInstance(RenderService, this.rows, this.screenElement));
this._instantiationService.setService(IRenderService, this._renderService);
this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
Expand All @@ -523,6 +524,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Performance: Add viewport and helper elements from the fragment
this.element.appendChild(fragment);

this._onWillOpen.fire(this.element);
if (!this._renderService.hasRenderer()) {
this._renderService.setRenderer(this._createRenderer());
}

this._mouseService = this._instantiationService.createInstance(MouseService);
this._instantiationService.setService(IMouseService, this._mouseService);

Expand Down
4 changes: 4 additions & 0 deletions src/browser/TestUtils.test.ts
Expand Up @@ -41,6 +41,7 @@ export class MockTerminal implements ITerminal {
public onTitleChange!: IEvent<string>;
public onBell!: IEvent<void>;
public onScroll!: IEvent<number>;
public onWillOpen!: IEvent<HTMLElement>;
public onKey!: IEvent<{ key: string, domEvent: KeyboardEvent }>;
public onRender!: IEvent<{ start: number, end: number }>;
public onResize!: IEvent<{ cols: number, rows: number }>;
Expand Down Expand Up @@ -400,6 +401,9 @@ export class MockRenderService implements IRenderService {
public resize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
public hasRenderer(): boolean {
throw new Error('Method not implemented.');
}
public setRenderer(renderer: IRenderer): void {
throw new Error('Method not implemented.');
}
Expand Down
1 change: 1 addition & 0 deletions src/browser/Types.d.ts
Expand Up @@ -23,6 +23,7 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal {
onFocus: IEvent<void>;
onA11yChar: IEvent<string>;
onA11yTab: IEvent<number>;
onWillOpen: IEvent<HTMLElement>;

cancel(ev: Event, force?: boolean): boolean | void;
}
Expand Down
51 changes: 38 additions & 13 deletions src/browser/services/RenderService.ts
Expand Up @@ -23,6 +23,7 @@ interface ISelectionState {
export class RenderService extends Disposable implements IRenderService {
public serviceBrand: undefined;

private _renderer: IRenderer | undefined;
private _renderDebouncer: IRenderDebouncerWithCallback;
private _screenDprMonitor: ScreenDprMonitor;
private _pausedResizeTask = new DebouncedIdleTask();
Expand All @@ -48,10 +49,9 @@ export class RenderService extends Disposable implements IRenderService {
private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
public readonly onRefreshRequest = this._onRefreshRequest.event;

public get dimensions(): IRenderDimensions { return this._renderer.dimensions; }
public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; }

constructor(
private _renderer: IRenderer,
private _rowCount: number,
screenElement: HTMLElement,
@IOptionsService optionsService: IOptionsService,
Expand All @@ -62,7 +62,7 @@ export class RenderService extends Disposable implements IRenderService {
) {
super();

this.register({ dispose: () => this._renderer.dispose() });
this.register({ dispose: () => this._renderer?.dispose() });

this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end));
this.register(this._renderDebouncer);
Expand All @@ -83,7 +83,7 @@ export class RenderService extends Disposable implements IRenderService {
this.register(decorationService.onDecorationRemoved(() => this._fullRefresh()));

// No need to register this as renderer is explicitly disposed in RenderService.dispose
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
// this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));

// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
Expand Down Expand Up @@ -125,6 +125,9 @@ export class RenderService extends Disposable implements IRenderService {
}

private _renderRows(start: number, end: number): void {
if (!this._renderer) {
return;
}
this._renderer.renderRows(start, end);

// Update selection if needed
Expand All @@ -147,12 +150,18 @@ export class RenderService extends Disposable implements IRenderService {
}

private _handleOptionsChanged(): void {
if (!this._renderer) {
return;
}
this._renderer.onOptionsChanged();
this.refreshRows(0, this._rowCount - 1);
this._fireOnCanvasResize();
}

private _fireOnCanvasResize(): void {
if (!this._renderer) {
return;
}
// Don't fire the event if the dimensions haven't changed
if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) {
return;
Expand All @@ -164,9 +173,13 @@ export class RenderService extends Disposable implements IRenderService {
super.dispose();
}

public hasRenderer(): boolean {
return !!this._renderer;
}

public setRenderer(renderer: IRenderer): void {
// TODO: RenderService should be the only one to dispose the renderer
this._renderer.dispose();
this._renderer?.dispose();
this._renderer = renderer;
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));

Expand All @@ -188,11 +201,17 @@ export class RenderService extends Disposable implements IRenderService {
}

public clearTextureAtlas(): void {
this._renderer?.clearTextureAtlas?.();
if (!this._renderer) {
return;
}
this._renderer.clearTextureAtlas?.();
this._fullRefresh();
}

public setColors(colors: IColorSet): void {
if (!this._renderer) {
return;
}
this._renderer.setColors(colors);
this._fullRefresh();
}
Expand All @@ -202,13 +221,19 @@ export class RenderService extends Disposable implements IRenderService {
// when devicePixelRatio changes
this._charSizeService.measure();

if (!this._renderer) {
return;
}
this._renderer.onDevicePixelRatioChange();
this.refreshRows(0, this._rowCount - 1);
}

public onResize(cols: number, rows: number): void {
if (!this._renderer) {
return;
}
if (this._isPaused) {
this._pausedResizeTask.set(() => this._renderer.onResize(cols, rows));
this._pausedResizeTask.set(() => this._renderer!.onResize(cols, rows));
} else {
this._renderer.onResize(cols, rows);
}
Expand All @@ -217,29 +242,29 @@ export class RenderService extends Disposable implements IRenderService {

// TODO: Is this useful when we have onResize?
public onCharSizeChanged(): void {
this._renderer.onCharSizeChanged();
this._renderer?.onCharSizeChanged();
}

public onBlur(): void {
this._renderer.onBlur();
this._renderer?.onBlur();
}

public onFocus(): void {
this._renderer.onFocus();
this._renderer?.onFocus();
}

public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
this._selectionState.start = start;
this._selectionState.end = end;
this._selectionState.columnSelectMode = columnSelectMode;
this._renderer.onSelectionChanged(start, end, columnSelectMode);
this._renderer?.onSelectionChanged(start, end, columnSelectMode);
}

public onCursorMove(): void {
this._renderer.onCursorMove();
this._renderer?.onCursorMove();
}

public clear(): void {
this._renderer.clear();
this._renderer?.clear();
}
}
1 change: 1 addition & 0 deletions src/browser/services/Services.ts
Expand Up @@ -71,6 +71,7 @@ export interface IRenderService extends IDisposable {
refreshRows(start: number, end: number): void;
clearTextureAtlas(): void;
resize(cols: number, rows: number): void;
hasRenderer(): boolean;
setRenderer(renderer: IRenderer): void;
setColors(colors: IColorSet): void;
onDevicePixelRatioChange(): void;
Expand Down

0 comments on commit eb3de3e

Please sign in to comment.