From 9e7eb0d8be8a47ed95cd43aeb4db2a9212b38ca5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 23 Sep 2022 11:12:25 -0700 Subject: [PATCH 1/4] Expose scrollable element delegatePointerDown in List View --- src/vs/base/browser/ui/list/listView.ts | 8 ++++++-- src/vs/base/browser/ui/scrollbar/scrollableElement.ts | 2 +- .../contrib/notebook/browser/diff/notebookTextDiffList.ts | 6 +++++- .../contrib/notebook/browser/view/notebookCellList.ts | 6 +++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 7e9f2aecba640..d8d980a9cfc20 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -405,8 +405,12 @@ export class ListView implements ISpliceable, IDisposable { } } - triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { - this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); + delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this.scrollableElement.delegateScrollFromMouseWheelEvent(browserEvent); + } + + delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) { + this.scrollableElement.delegateVerticalScrollbarPointerDown(browserEvent); } updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null): void { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index c7bffd0c8d624..b6b6c32a1dce0 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -334,7 +334,7 @@ export abstract class AbstractScrollableElement extends Widget { this._revealOnScroll = value; } - public triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + public delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { this._onMouseWheel(new StandardWheelEvent(browserEvent)); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 0ec024ae116eb..3504ac2a58d95 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -336,7 +336,11 @@ export class NotebookTextDiffList extends WorkbenchList implements ID } triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { - this.view.triggerScrollFromMouseWheelEvent(browserEvent); + this.view.delegateScrollFromMouseWheelEvent(browserEvent); + } + + delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) { + this.view.delegateVerticalScrollbarPointerDown(browserEvent); } isElementAboveViewport(index: number) { From bf3cbdff018c839866cff04299de400b3ca64c32 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 23 Sep 2022 11:13:09 -0700 Subject: [PATCH 2/4] hook up overview ruler with notebook diff editor --- .../notebook/browser/diff/notebookDiff.css | 29 ++- .../browser/diff/notebookDiffEditorBrowser.ts | 4 + .../browser/diff/notebookDiffOverviewRuler.ts | 192 ++++++++++++++++++ .../browser/diff/notebookTextDiffEditor.ts | 53 ++++- 4 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index 801d217ba0882..cdd889d3afabf 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -16,6 +16,10 @@ width: 50%; } */ +.notebook-text-diff-editor { + position: relative; +}; + .notebook-text-diff-editor .cell-body { display: flex; flex-direction: row; @@ -61,7 +65,7 @@ /* overflow: hidden; */ } -.notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { +.notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { cursor: default; } @@ -142,13 +146,13 @@ overflow: hidden; } -.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { +.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { overflow: visible !important; } -.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row, -.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover, -.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { +.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row, +.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover, +.monaco-workbench .notebook-text-diff-editor > .notebook-diff-list-view > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: none !important; background-color: transparent !important; } @@ -287,3 +291,18 @@ left: 4px !important; width: 15px !important; } + +.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .scrollbar.visible { + z-index: var(--z-index-notebook-scrollbar); + cursor: default; +} + +.notebook-text-diff-editor .notebook-overview-ruler-container { + position: absolute; + top: 0; + right: 0; +} + +.notebook-text-diff-editor .notebook-overview-ruler-container .diffViewport { + z-index: var(--notebook-diff-view-viewport-slider); +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index ebb303246b8b9..13559d512e425 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -30,9 +30,12 @@ export interface INotebookTextDiffEditor { notebookOptions: NotebookOptions; readonly textModel?: NotebookTextModel; onMouseUp: Event<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase }>; + onDidScroll: Event; onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel; output: ICellOutputViewModel }>; getOverflowContainerDomNode(): HTMLElement; getLayoutInfo(): NotebookLayoutInfo; + getScrollTop(): number; + getScrollHeight(): number; layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void; createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void; @@ -42,6 +45,7 @@ export interface INotebookTextDiffEditor { * Trigger the editor to scroll from scroll event programmatically */ triggerScroll(event: IMouseWheelEvent): void; + delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent): void; getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): Promise; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): Promise; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts new file mode 100644 index 0000000000000..08e81c40f0f7e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as browser from 'vs/base/browser/browser'; +import * as DOM from 'vs/base/browser/dom'; +import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; +import { Color } from 'vs/base/common/color'; +import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; +import { DiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; + +export class NotebookDiffOverviewRuler extends Themable { + private readonly _domNode: FastDomNode; + private readonly _overviewViewportDomElement: FastDomNode; + + private _diffElementViewModels: DiffElementViewModelBase[] = []; + private _lanes = 2; + + protected _insertColor: Color | null; + protected _removeColor: Color | null; + + constructor(readonly notebookEditor: INotebookTextDiffEditor, readonly width: number, container: HTMLElement, @IThemeService themeService: IThemeService) { + super(themeService); + this._insertColor = null; + this._removeColor = null; + this._domNode = createFastDomNode(document.createElement('canvas')); + this._domNode.setPosition('relative'); + this._domNode.setLayerHinting(true); + this._domNode.setContain('strict'); + + container.appendChild(this._domNode.domNode); + + this._overviewViewportDomElement = createFastDomNode(document.createElement('div')); + this._overviewViewportDomElement.setClassName('diffViewport'); + this._overviewViewportDomElement.setPosition('absolute'); + this._overviewViewportDomElement.setWidth(width); + container.appendChild(this._overviewViewportDomElement.domNode); + + this._register(browser.PixelRatio.onDidChange(() => { + this.layout(); + })); + + this._register(this.themeService.onDidColorThemeChange(e => { + const colorChanged = this.applyColors(e); + if (colorChanged) { + this.layout(); + } + })); + this.applyColors(this.themeService.getColorTheme()); + + this._register(this.notebookEditor.onDidScroll(() => { + this._layoutOverviewViewport(); + })); + + this._register(DOM.addStandardDisposableListener(this._overviewViewportDomElement.domNode, DOM.EventType.POINTER_DOWN, (e) => { + this.notebookEditor.delegateVerticalScrollbarPointerDown(e); + })); + } + + private applyColors(theme: IColorTheme): boolean { + const newInsertColor = theme.getColor(diffOverviewRulerInserted) || (theme.getColor(diffInserted) || defaultInsertColor).transparent(2); + const newRemoveColor = theme.getColor(diffOverviewRulerRemoved) || (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2); + const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor); + this._insertColor = newInsertColor; + this._removeColor = newRemoveColor; + return hasChanges; + } + + layout() { + const layoutInfo = this.notebookEditor.getLayoutInfo(); + const scrollHeight = layoutInfo.scrollHeight; + const height = layoutInfo.height; + const ratio = browser.PixelRatio.value; + this._domNode.setWidth(this.width); + this._domNode.setHeight(height); + this._domNode.domNode.width = this.width * ratio; + this._domNode.domNode.height = height * ratio; + const ctx = this._domNode.domNode.getContext('2d')!; + ctx.clearRect(0, 0, this.width * ratio, height * ratio); + this._render(ctx, this.width * ratio, height * ratio, scrollHeight * ratio, ratio); + this._layoutOverviewViewport(); + } + + updateViewModels(elements: DiffElementViewModelBase[]) { + this._diffElementViewModels = elements; + this.layout(); + } + + + private _layoutOverviewViewport(): void { + const layout = this._computeOverviewViewport(); + if (!layout) { + this._overviewViewportDomElement.setTop(0); + this._overviewViewportDomElement.setHeight(0); + } else { + this._overviewViewportDomElement.setTop(layout.top); + this._overviewViewportDomElement.setHeight(layout.height); + } + } + + private _computeOverviewViewport(): { height: number; top: number } | null { + const layoutInfo = this.notebookEditor.getLayoutInfo(); + if (!layoutInfo) { + return null; + } + + const scrollTop = this.notebookEditor.getScrollTop(); + const scrollHeight = this.notebookEditor.getScrollHeight(); + + const computedAvailableSize = Math.max(0, layoutInfo.height); + const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0); + const computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0; + + const computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio)); + const computedSliderPosition = Math.floor(scrollTop * computedRatio); + + return { + height: computedSliderSize, + top: computedSliderPosition + }; + } + + private _render(ctx: CanvasRenderingContext2D, width: number, height: number, scrollHeight: number, ratio: number) { + if (!this._insertColor || !this._removeColor) { + // no op when colors are not yet known + return; + } + + const insertColorHex = Color.Format.CSS.formatHexA(this._insertColor); + const removeColorHex = Color.Format.CSS.formatHexA(this._removeColor); + + const laneWidth = width / this._lanes; + let currentFrom = 0; + for (let i = 0; i < this._diffElementViewModels.length; i++) { + const element = this._diffElementViewModels[i]; + + const cellHeight = element.layoutInfo.totalHeight * ratio; + switch (element.type) { + case 'insert': + ctx.fillStyle = insertColorHex; + ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight); + break; + case 'delete': + ctx.fillStyle = removeColorHex; + ctx.fillRect(0, currentFrom, laneWidth, cellHeight); + break; + case 'unchanged': + break; + case 'modified': + ctx.fillStyle = removeColorHex; + ctx.fillRect(0, currentFrom, laneWidth, cellHeight); + ctx.fillStyle = insertColorHex; + ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight); + break; + } + + currentFrom += cellHeight; + } + } +} + +registerThemingParticipant((theme, collector) => { + const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); + if (scrollbarSliderBackgroundColor) { + collector.addRule(` + .notebook-text-diff-editor .diffViewport { + background: ${scrollbarSliderBackgroundColor}; + } + `); + } + + const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); + if (scrollbarSliderHoverBackgroundColor) { + collector.addRule(` + .notebook-text-diff-editor .diffViewport:hover { + background: ${scrollbarSliderHoverBackgroundColor}; + } + `); + } + + const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); + if (scrollbarSliderActiveBackgroundColor) { + collector.addRule(` + .notebook-text-diff-editor .diffViewport:active { + background: ${scrollbarSliderActiveBackgroundColor}; + } + `); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 53f2626ba2453..7e9ee0bc76c06 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -44,6 +44,8 @@ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/co import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { NotebookDiffOverviewRuler } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler'; +import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; const $ = DOM.$; @@ -82,11 +84,15 @@ class NotebookDiffEditorSelection implements IEditorPaneSelection { } export class NotebookTextDiffEditor extends EditorPane implements INotebookTextDiffEditor, INotebookDelegateForWebview, IEditorPaneWithSelection { + public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = 30; creationOptions: INotebookEditorCreationOptions = getDefaultNotebookCreationOptions(); static readonly ID: string = NOTEBOOK_DIFF_EDITOR_ID; private _rootElement!: HTMLElement; + private _listViewContainer!: HTMLElement; private _overflowContainer!: HTMLElement; + private _overviewRulerContainer!: HTMLElement; + private _overviewRuler!: NotebookDiffOverviewRuler; private _dimension: DOM.Dimension | null = null; private _diffElementViewModels: DiffElementViewModelBase[] = []; private _list!: NotebookTextDiffList; @@ -97,6 +103,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase }>()); public readonly onMouseUp = this._onMouseUp.event; + private readonly _onDidScroll = this._register(new Emitter()); + readonly onDidScroll: Event = this._onDidScroll.event; private _eventDispatcher: NotebookDiffEditorEventDispatcher | undefined; protected _scopeContextKeyService!: IContextKeyService; private _model: INotebookDiffEditorModel | null = null; @@ -166,6 +174,18 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD // throw new Error('Method not implemented.'); } + getScrollTop() { + return this._list?.scrollTop ?? 0; + } + + getScrollHeight() { + return this._list?.scrollHeight ?? 0; + } + + delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) { + this._list?.delegateVerticalScrollbarPointerDown(browserEvent); + } + updateOutputHeight(cellInfo: IDiffCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean): void { const diffElement = cellInfo.diffElement; const cell = this.getCellByInfo(cellInfo); @@ -217,10 +237,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this.instantiationService.createInstance(CellDiffSideBySideRenderer, this), ]; + this._listViewContainer = DOM.append(this._rootElement, DOM.$('.notebook-diff-list-view')); + this._list = this.instantiationService.createInstance( NotebookTextDiffList, 'NotebookTextDiff', - this._rootElement, + this._listViewContainer, this.instantiationService.createInstance(NotebookCellTextDiffListDelegate), renderers, this.contextKeyService, @@ -274,8 +296,17 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } })); + this._register(this._list.onDidScroll(() => { + this._onDidScroll.fire(); + })); + this._register(this._list.onDidChangeFocus(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.USER }))); + this._overviewRulerContainer = document.createElement('div'); + this._overviewRulerContainer.classList.add('notebook-overview-ruler-container'); + this._rootElement.appendChild(this._overviewRulerContainer); + this._registerOverviewRuler(); + // transparent cover this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover')); this._webviewTransparentCover.style.display = 'none'; @@ -296,8 +327,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._register(this._list.onDidScroll(e => { this._webviewTransparentCover!.style.top = `${e.scrollTop}px`; })); + } - + private _registerOverviewRuler() { + this._overviewRuler = this._register(this.instantiationService.createInstance(NotebookDiffOverviewRuler, this, NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH, this._overviewRulerContainer!)); } private _updateOutputsOffsetsInWebview(scrollTop: number, scrollHeight: number, activeWebview: BackLayerWebView, getActiveNestedCell: (diffElement: DiffElementViewModelBase) => DiffNestedCellViewModel | undefined, diffSide: DiffSide) { @@ -526,6 +559,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _setViewModel(viewModels: DiffElementViewModelBase[]) { this._diffElementViewModels = viewModels; this._list.splice(0, this._list.length, this._diffElementViewModels); + this._overviewRuler.updateViewModels(this._diffElementViewModels); } /** @@ -942,11 +976,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD layout(dimension: DOM.Dimension): void { this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600); this._rootElement.classList.toggle('narrow-width', dimension.width < 600); - this._dimension = dimension; - this._rootElement.style.height = `${dimension.height}px`; + this._dimension = dimension.with(dimension.width - NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH); - this._list?.layout(this._dimension.height, this._dimension.width); + this._listViewContainer.style.height = `${dimension.height}px`; + this._listViewContainer.style.width = `${this._dimension.width}px`; + this._list?.layout(this._dimension.height, this._dimension.width); if (this._modifiedWebview) { this._modifiedWebview.element.style.width = `calc(50% - 16px)`; @@ -959,10 +994,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } if (this._webviewTransparentCover) { - this._webviewTransparentCover.style.height = `${dimension.height}px`; - this._webviewTransparentCover.style.width = `${dimension.width}px`; + this._webviewTransparentCover.style.height = `${this._dimension.height}px`; + this._webviewTransparentCover.style.width = `${this._dimension.width}px`; } + this._overviewRuler.layout(); + this._eventDispatcher?.emit([new NotebookDiffLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); } @@ -974,6 +1011,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } +registerZIndex(ZIndex.Base, 10, 'notebook-diff-view-viewport-slider'); + registerThemingParticipant((theme, collector) => { const cellBorderColor = theme.getColor(notebookCellBorder); if (cellBorderColor) { From b4cf032804b3b12569a1b9e03c3dd50c67b2964b Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 23 Sep 2022 12:02:49 -0700 Subject: [PATCH 3/4] Update overview once per frame --- .../browser/diff/notebookDiffOverviewRuler.ts | 99 ++++++++++++++----- .../browser/diff/notebookTextDiffEditor.ts | 2 +- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts index 08e81c40f0f7e..c446f07e32b3f 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts @@ -7,9 +7,11 @@ import * as browser from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; import { Color } from 'vs/base/common/color'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { DiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; import { INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; export class NotebookDiffOverviewRuler extends Themable { @@ -19,13 +21,22 @@ export class NotebookDiffOverviewRuler extends Themable { private _diffElementViewModels: DiffElementViewModelBase[] = []; private _lanes = 2; - protected _insertColor: Color | null; - protected _removeColor: Color | null; + private _insertColor: Color | null; + private _insertColorHex: string | null; + private _removeColor: Color | null; + private _removeColorHex: string | null; + + private _disposables: DisposableStore; + private _renderAnimationFrame: IDisposable | null; constructor(readonly notebookEditor: INotebookTextDiffEditor, readonly width: number, container: HTMLElement, @IThemeService themeService: IThemeService) { super(themeService); this._insertColor = null; this._removeColor = null; + this._insertColorHex = null; + this._removeColorHex = null; + this._disposables = this._register(new DisposableStore()); + this._renderAnimationFrame = null; this._domNode = createFastDomNode(document.createElement('canvas')); this._domNode.setPosition('relative'); this._domNode.setLayerHinting(true); @@ -40,19 +51,19 @@ export class NotebookDiffOverviewRuler extends Themable { container.appendChild(this._overviewViewportDomElement.domNode); this._register(browser.PixelRatio.onDidChange(() => { - this.layout(); + this._scheduleRender(); })); this._register(this.themeService.onDidColorThemeChange(e => { const colorChanged = this.applyColors(e); if (colorChanged) { - this.layout(); + this._scheduleRender(); } })); this.applyColors(this.themeService.getColorTheme()); this._register(this.notebookEditor.onDidScroll(() => { - this._layoutOverviewViewport(); + this._renderOverviewViewport(); })); this._register(DOM.addStandardDisposableListener(this._overviewViewportDomElement.domNode, DOM.EventType.POINTER_DOWN, (e) => { @@ -66,12 +77,52 @@ export class NotebookDiffOverviewRuler extends Themable { const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor); this._insertColor = newInsertColor; this._removeColor = newRemoveColor; + if (this._insertColor) { + this._insertColorHex = Color.Format.CSS.formatHexA(this._insertColor); + } + + if (this._removeColor) { + this._removeColorHex = Color.Format.CSS.formatHexA(this._removeColor); + } + return hasChanges; } layout() { + this._layoutNow(); + } + + updateViewModels(elements: DiffElementViewModelBase[], eventDispatcher: NotebookDiffEditorEventDispatcher | undefined) { + this._disposables.clear(); + + this._diffElementViewModels = elements; + + if (eventDispatcher) { + this._disposables.add(eventDispatcher.onDidChangeLayout(() => { + this._scheduleRender(); + })); + + this._disposables.add(eventDispatcher.onDidChangeCellLayout(() => { + this._scheduleRender(); + })); + } + + this._scheduleRender(); + } + + private _scheduleRender(): void { + if (this._renderAnimationFrame === null) { + this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 100); + } + } + + private _onRenderScheduled(): void { + this._renderAnimationFrame = null; + this._layoutNow(); + } + + private _layoutNow() { const layoutInfo = this.notebookEditor.getLayoutInfo(); - const scrollHeight = layoutInfo.scrollHeight; const height = layoutInfo.height; const ratio = browser.PixelRatio.value; this._domNode.setWidth(this.width); @@ -80,17 +131,11 @@ export class NotebookDiffOverviewRuler extends Themable { this._domNode.domNode.height = height * ratio; const ctx = this._domNode.domNode.getContext('2d')!; ctx.clearRect(0, 0, this.width * ratio, height * ratio); - this._render(ctx, this.width * ratio, height * ratio, scrollHeight * ratio, ratio); - this._layoutOverviewViewport(); - } - - updateViewModels(elements: DiffElementViewModelBase[]) { - this._diffElementViewModels = elements; - this.layout(); + this._renderCanvas(ctx, this.width * ratio, ratio); + this._renderOverviewViewport(); } - - private _layoutOverviewViewport(): void { + private _renderOverviewViewport(): void { const layout = this._computeOverviewViewport(); if (!layout) { this._overviewViewportDomElement.setTop(0); @@ -123,15 +168,12 @@ export class NotebookDiffOverviewRuler extends Themable { }; } - private _render(ctx: CanvasRenderingContext2D, width: number, height: number, scrollHeight: number, ratio: number) { - if (!this._insertColor || !this._removeColor) { + private _renderCanvas(ctx: CanvasRenderingContext2D, width: number, ratio: number) { + if (!this._insertColorHex || !this._removeColorHex) { // no op when colors are not yet known return; } - const insertColorHex = Color.Format.CSS.formatHexA(this._insertColor); - const removeColorHex = Color.Format.CSS.formatHexA(this._removeColor); - const laneWidth = width / this._lanes; let currentFrom = 0; for (let i = 0; i < this._diffElementViewModels.length; i++) { @@ -140,19 +182,19 @@ export class NotebookDiffOverviewRuler extends Themable { const cellHeight = element.layoutInfo.totalHeight * ratio; switch (element.type) { case 'insert': - ctx.fillStyle = insertColorHex; + ctx.fillStyle = this._insertColorHex; ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight); break; case 'delete': - ctx.fillStyle = removeColorHex; + ctx.fillStyle = this._removeColorHex; ctx.fillRect(0, currentFrom, laneWidth, cellHeight); break; case 'unchanged': break; case 'modified': - ctx.fillStyle = removeColorHex; + ctx.fillStyle = this._removeColorHex; ctx.fillRect(0, currentFrom, laneWidth, cellHeight); - ctx.fillStyle = insertColorHex; + ctx.fillStyle = this._insertColorHex; ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight); break; } @@ -160,6 +202,15 @@ export class NotebookDiffOverviewRuler extends Themable { currentFrom += cellHeight; } } + + override dispose() { + if (this._renderAnimationFrame !== null) { + this._renderAnimationFrame.dispose(); + this._renderAnimationFrame = null; + } + + super.dispose(); + } } registerThemingParticipant((theme, collector) => { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 7e9ee0bc76c06..977573bf87d5c 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -559,7 +559,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _setViewModel(viewModels: DiffElementViewModelBase[]) { this._diffElementViewModels = viewModels; this._list.splice(0, this._list.length, this._diffElementViewModels); - this._overviewRuler.updateViewModels(this._diffElementViewModels); + this._overviewRuler.updateViewModels(this._diffElementViewModels, this._eventDispatcher); } /** From a609f1edcde374e9b4b10b076f99bd62b3d97f11 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 23 Sep 2022 13:14:07 -0700 Subject: [PATCH 4/4] fix diff css --- src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index cdd889d3afabf..bb6786c48c20d 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -18,7 +18,7 @@ .notebook-text-diff-editor { position: relative; -}; +} .notebook-text-diff-editor .cell-body { display: flex;