aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/xterm/src/browser/services/RenderService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/xterm/src/browser/services/RenderService.ts')
-rw-r--r--node_modules/xterm/src/browser/services/RenderService.ts222
1 files changed, 222 insertions, 0 deletions
diff --git a/node_modules/xterm/src/browser/services/RenderService.ts b/node_modules/xterm/src/browser/services/RenderService.ts
new file mode 100644
index 0000000..b8283e0
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/RenderService.ts
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderer, IRenderDimensions } from 'browser/renderer/Types';
+import { RenderDebouncer } from 'browser/RenderDebouncer';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { Disposable } from 'common/Lifecycle';
+import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
+import { addDisposableDomListener } from 'browser/Lifecycle';
+import { IColorSet, IRenderDebouncer } from 'browser/Types';
+import { IOptionsService, IBufferService } from 'common/services/Services';
+import { ICharSizeService, IRenderService } from 'browser/services/Services';
+
+interface ISelectionState {
+ start: [number, number] | undefined;
+ end: [number, number] | undefined;
+ columnSelectMode: boolean;
+}
+
+export class RenderService extends Disposable implements IRenderService {
+ public serviceBrand: undefined;
+
+ private _renderDebouncer: IRenderDebouncer;
+ private _screenDprMonitor: ScreenDprMonitor;
+
+ private _isPaused: boolean = false;
+ private _needsFullRefresh: boolean = false;
+ private _isNextRenderRedrawOnly: boolean = true;
+ private _needsSelectionRefresh: boolean = false;
+ private _canvasWidth: number = 0;
+ private _canvasHeight: number = 0;
+ private _selectionState: ISelectionState = {
+ start: undefined,
+ end: undefined,
+ columnSelectMode: false
+ };
+
+ private _onDimensionsChange = new EventEmitter<IRenderDimensions>();
+ public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }
+ private _onRender = new EventEmitter<{ start: number, end: number }>();
+ public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
+ private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
+ public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }
+
+ public get dimensions(): IRenderDimensions { return this._renderer.dimensions; }
+
+ constructor(
+ private _renderer: IRenderer,
+ private _rowCount: number,
+ screenElement: HTMLElement,
+ @IOptionsService optionsService: IOptionsService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
+ @IBufferService bufferService: IBufferService
+ ) {
+ super();
+
+ this.register({ dispose: () => this._renderer.dispose() });
+
+ this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
+ this.register(this._renderDebouncer);
+
+ this._screenDprMonitor = new ScreenDprMonitor();
+ this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
+ this.register(this._screenDprMonitor);
+
+ this.register(bufferService.onResize(e => this._fullRefresh()));
+ this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
+ this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
+
+ // No need to register this as renderer is explicitly disposed in RenderService.dispose
+ 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.
+ this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
+
+ // Detect whether IntersectionObserver is detected and enable renderer pause
+ // and resume based on terminal visibility if so
+ if ('IntersectionObserver' in window) {
+ const observer = new IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 });
+ observer.observe(screenElement);
+ this.register({ dispose: () => observer.disconnect() });
+ }
+ }
+
+ private _onIntersectionChange(entry: IntersectionObserverEntry): void {
+ this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
+
+ // Terminal was hidden on open
+ if (!this._isPaused && !this._charSizeService.hasValidSize) {
+ this._charSizeService.measure();
+ }
+
+ if (!this._isPaused && this._needsFullRefresh) {
+ this.refreshRows(0, this._rowCount - 1);
+ this._needsFullRefresh = false;
+ }
+ }
+
+ public refreshRows(start: number, end: number, isRedrawOnly: boolean = false): void {
+ if (this._isPaused) {
+ this._needsFullRefresh = true;
+ return;
+ }
+ if (!isRedrawOnly) {
+ this._isNextRenderRedrawOnly = false;
+ }
+ this._renderDebouncer.refresh(start, end, this._rowCount);
+ }
+
+ private _renderRows(start: number, end: number): void {
+ this._renderer.renderRows(start, end);
+
+ // Update selection if needed
+ if (this._needsSelectionRefresh) {
+ this._renderer.onSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode);
+ this._needsSelectionRefresh = false;
+ }
+
+ // Fire render event only if it was not a redraw
+ if (!this._isNextRenderRedrawOnly) {
+ this._onRender.fire({ start, end });
+ }
+ this._isNextRenderRedrawOnly = true;
+ }
+
+ public resize(cols: number, rows: number): void {
+ this._rowCount = rows;
+ this._fireOnCanvasResize();
+ }
+
+ public changeOptions(): void {
+ this._renderer.onOptionsChanged();
+ this.refreshRows(0, this._rowCount - 1);
+ this._fireOnCanvasResize();
+ }
+
+ private _fireOnCanvasResize(): void {
+ // 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;
+ }
+ this._onDimensionsChange.fire(this._renderer.dimensions);
+ }
+
+ public dispose(): void {
+ super.dispose();
+ }
+
+ public setRenderer(renderer: IRenderer): void {
+ // TODO: RenderService should be the only one to dispose the renderer
+ this._renderer.dispose();
+ this._renderer = renderer;
+ this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
+
+ // Force a refresh
+ this._needsSelectionRefresh = true;
+ this._fullRefresh();
+ }
+
+ private _fullRefresh(): void {
+ if (this._isPaused) {
+ this._needsFullRefresh = true;
+ } else {
+ this.refreshRows(0, this._rowCount - 1);
+ }
+ }
+
+ public clearTextureAtlas(): void {
+ this._renderer?.clearTextureAtlas?.();
+ this._fullRefresh();
+ }
+
+ public setColors(colors: IColorSet): void {
+ this._renderer.setColors(colors);
+ this._fullRefresh();
+ }
+
+ public onDevicePixelRatioChange(): void {
+ // Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable
+ // when devicePixelRatio changes
+ this._charSizeService.measure();
+
+ this._renderer.onDevicePixelRatioChange();
+ this.refreshRows(0, this._rowCount - 1);
+ }
+
+ public onResize(cols: number, rows: number): void {
+ this._renderer.onResize(cols, rows);
+ this._fullRefresh();
+ }
+
+ // TODO: Is this useful when we have onResize?
+ public onCharSizeChanged(): void {
+ this._renderer.onCharSizeChanged();
+ }
+
+ public onBlur(): void {
+ this._renderer.onBlur();
+ }
+
+ public onFocus(): void {
+ 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);
+ }
+
+ public onCursorMove(): void {
+ this._renderer.onCursorMove();
+ }
+
+ public clear(): void {
+ this._renderer.clear();
+ }
+}