diff options
Diffstat (limited to 'node_modules/xterm/src/browser/services/RenderService.ts')
-rw-r--r-- | node_modules/xterm/src/browser/services/RenderService.ts | 222 |
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(); + } +} |