Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow renderer to be set before Terminal.open is called #4151

Merged
merged 4 commits into from Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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