Path: blob/main/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as DOM from '../../../../../base/browser/dom.js';6import { createFastDomNode, FastDomNode } from '../../../../../base/browser/fastDomNode.js';7import { PixelRatio } from '../../../../../base/browser/pixelRatio.js';8import { Color } from '../../../../../base/common/color.js';9import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';10import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved } from '../../../../../platform/theme/common/colorRegistry.js';11import { IColorTheme, IThemeService, Themable } from '../../../../../platform/theme/common/themeService.js';12import { IDiffElementViewModelBase } from './diffElementViewModel.js';13import { NotebookDiffEditorEventDispatcher } from './eventDispatcher.js';14import { INotebookTextDiffEditor } from './notebookDiffEditorBrowser.js';1516const MINIMUM_SLIDER_SIZE = 20;1718export class NotebookDiffOverviewRuler extends Themable {19private readonly _domNode: FastDomNode<HTMLCanvasElement>;20private readonly _overviewViewportDomElement: FastDomNode<HTMLElement>;2122private _diffElementViewModels: readonly IDiffElementViewModelBase[] = [];23private _lanes = 2;2425private _insertColor: Color | null;26private _insertColorHex: string | null;27private _removeColor: Color | null;28private _removeColorHex: string | null;2930private readonly _disposables: DisposableStore;31private _renderAnimationFrame: IDisposable | null;3233constructor(readonly notebookEditor: INotebookTextDiffEditor, readonly width: number, container: HTMLElement, @IThemeService themeService: IThemeService) {34super(themeService);35this._insertColor = null;36this._removeColor = null;37this._insertColorHex = null;38this._removeColorHex = null;39this._disposables = this._register(new DisposableStore());40this._renderAnimationFrame = null;41this._domNode = createFastDomNode(document.createElement('canvas'));42this._domNode.setPosition('relative');43this._domNode.setLayerHinting(true);44this._domNode.setContain('strict');4546container.appendChild(this._domNode.domNode);4748this._overviewViewportDomElement = createFastDomNode(document.createElement('div'));49this._overviewViewportDomElement.setClassName('diffViewport');50this._overviewViewportDomElement.setPosition('absolute');51this._overviewViewportDomElement.setWidth(width);52container.appendChild(this._overviewViewportDomElement.domNode);5354this._register(PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).onDidChange(() => {55this._scheduleRender();56}));5758this._register(this.themeService.onDidColorThemeChange(e => {59const colorChanged = this.applyColors(e);60if (colorChanged) {61this._scheduleRender();62}63}));64this.applyColors(this.themeService.getColorTheme());6566this._register(this.notebookEditor.onDidScroll(() => {67this._renderOverviewViewport();68}));6970this._register(DOM.addStandardDisposableListener(container, DOM.EventType.POINTER_DOWN, (e) => {71this.notebookEditor.delegateVerticalScrollbarPointerDown(e);72}));73}7475private applyColors(theme: IColorTheme): boolean {76const newInsertColor = theme.getColor(diffOverviewRulerInserted) || (theme.getColor(diffInserted) || defaultInsertColor).transparent(2);77const newRemoveColor = theme.getColor(diffOverviewRulerRemoved) || (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2);78const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor);79this._insertColor = newInsertColor;80this._removeColor = newRemoveColor;81if (this._insertColor) {82this._insertColorHex = Color.Format.CSS.formatHexA(this._insertColor);83}8485if (this._removeColor) {86this._removeColorHex = Color.Format.CSS.formatHexA(this._removeColor);87}8889return hasChanges;90}9192layout() {93this._layoutNow();94}9596updateViewModels(elements: readonly IDiffElementViewModelBase[], eventDispatcher: NotebookDiffEditorEventDispatcher | undefined) {97this._disposables.clear();9899this._diffElementViewModels = elements;100101if (eventDispatcher) {102this._disposables.add(eventDispatcher.onDidChangeLayout(() => {103this._scheduleRender();104}));105106this._disposables.add(eventDispatcher.onDidChangeCellLayout(() => {107this._scheduleRender();108}));109}110111this._scheduleRender();112}113114private _scheduleRender(): void {115if (this._renderAnimationFrame === null) {116this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(DOM.getWindow(this._domNode.domNode), this._onRenderScheduled.bind(this), 16);117}118}119120private _onRenderScheduled(): void {121this._renderAnimationFrame = null;122this._layoutNow();123}124125private _layoutNow() {126const layoutInfo = this.notebookEditor.getLayoutInfo();127const height = layoutInfo.height;128const contentHeight = this._diffElementViewModels.map(view => view.totalHeight).reduce((a, b) => a + b, 0);129const ratio = PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).value;130this._domNode.setWidth(this.width);131this._domNode.setHeight(height);132this._domNode.domNode.width = this.width * ratio;133this._domNode.domNode.height = height * ratio;134const ctx = this._domNode.domNode.getContext('2d')!;135ctx.clearRect(0, 0, this.width * ratio, height * ratio);136this._renderCanvas(ctx, this.width * ratio, height * ratio, contentHeight * ratio, ratio);137this._renderOverviewViewport();138}139140private _renderOverviewViewport(): void {141const layout = this._computeOverviewViewport();142if (!layout) {143this._overviewViewportDomElement.setTop(0);144this._overviewViewportDomElement.setHeight(0);145} else {146this._overviewViewportDomElement.setTop(layout.top);147this._overviewViewportDomElement.setHeight(layout.height);148}149}150151private _computeOverviewViewport(): { height: number; top: number } | null {152const layoutInfo = this.notebookEditor.getLayoutInfo();153if (!layoutInfo) {154return null;155}156157const scrollTop = this.notebookEditor.getScrollTop();158const scrollHeight = this.notebookEditor.getScrollHeight();159160const computedAvailableSize = Math.max(0, layoutInfo.height);161const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0);162const visibleSize = layoutInfo.height;163const computedSliderSize = Math.round(Math.max(MINIMUM_SLIDER_SIZE, Math.floor(visibleSize * computedRepresentableSize / scrollHeight)));164const computedSliderRatio = (computedRepresentableSize - computedSliderSize) / (scrollHeight - visibleSize);165const computedSliderPosition = Math.round(scrollTop * computedSliderRatio);166167return {168height: computedSliderSize,169top: computedSliderPosition170};171}172173private _renderCanvas(ctx: CanvasRenderingContext2D, width: number, height: number, scrollHeight: number, ratio: number) {174if (!this._insertColorHex || !this._removeColorHex) {175// no op when colors are not yet known176return;177}178179const laneWidth = width / this._lanes;180let currentFrom = 0;181for (let i = 0; i < this._diffElementViewModels.length; i++) {182const element = this._diffElementViewModels[i];183184const cellHeight = Math.round((element.totalHeight / scrollHeight) * ratio * height);185switch (element.type) {186case 'insert':187ctx.fillStyle = this._insertColorHex;188ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight);189break;190case 'delete':191ctx.fillStyle = this._removeColorHex;192ctx.fillRect(0, currentFrom, laneWidth, cellHeight);193break;194case 'unchanged':195case 'unchangedMetadata':196break;197case 'modified':198case 'modifiedMetadata':199ctx.fillStyle = this._removeColorHex;200ctx.fillRect(0, currentFrom, laneWidth, cellHeight);201ctx.fillStyle = this._insertColorHex;202ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight);203break;204}205206207currentFrom += cellHeight;208}209}210211override dispose() {212if (this._renderAnimationFrame !== null) {213this._renderAnimationFrame.dispose();214this._renderAnimationFrame = null;215}216217super.dispose();218}219}220221222