From 88a7d66e28f09085d4a486fc1e634601acb97991 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 20 Aug 2020 16:52:19 -0700 Subject: [PATCH] build text diff editor --- .../editor/browser/widget/diffEditorWidget.ts | 19 + .../celllDiffViewModel.ts} | 20 +- .../contrib/notebook/browser/diff/common.ts | 12 + .../notebook/browser/diff/notebookDiff.css | 73 +++ .../browser/diff/notebookTextDiffEditor.ts | 267 +++++++++ .../browser/diff/notebookTextDiffList.ts | 521 ++++++++++++++++++ .../notebook/browser/notebook.contribution.ts | 6 +- 7 files changed, 904 insertions(+), 14 deletions(-) rename src/vs/workbench/contrib/notebook/browser/{media/notebookDiff.css => diff/celllDiffViewModel.ts} (51%) create mode 100644 src/vs/workbench/contrib/notebook/browser/diff/common.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css create mode 100644 src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 9411b1e544b..4925dfcd46c 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -175,6 +175,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly _onDidUpdateDiff: Emitter = this._register(new Emitter()); public readonly onDidUpdateDiff: Event = this._onDidUpdateDiff.event; + private readonly _onDidContentSizeChange: Emitter = this._register(new Emitter()); + public readonly onDidContentSizeChange: Event = this._onDidContentSizeChange.event; + private readonly id: number; private _state: editorBrowser.DiffEditorState; private _updatingDiffProgress: IProgressRunner | null; @@ -421,6 +424,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return this._renderIndicators; } + public getContentHeight(): number { + return this.modifiedEditor.getContentHeight(); + } + private _setState(newState: editorBrowser.DiffEditorState): void { if (this._state === newState) { return; @@ -555,6 +562,18 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } })); + this._register(editor.onDidContentSizeChange(e => { + const width = this.originalEditor.getContentWidth() + this.modifiedEditor.getContentWidth(); + const height = this.modifiedEditor.getContentHeight(); + + this._onDidContentSizeChange.fire({ + contentHeight: height, + contentWidth: width, + contentHeightChanged: e.contentHeightChanged, + contentWidthChanged: e.contentWidthChanged + }); + })); + return editor; } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel.ts similarity index 51% rename from src/vs/workbench/contrib/notebook/browser/media/notebookDiff.css rename to src/vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel.ts index 80404d79179..67392c463ae 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel.ts @@ -3,15 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.notebook-diff-editor { - display: flex; - flex-direction: row; - height: 100%; - width: 100%; -} -.notebook-diff-editor-modified, -.notebook-diff-editor-original { - display: flex; - height: 100%; - width: 50%; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; + +export class CellDiffViewModel { + constructor( + readonly original: NotebookCellTextModel | undefined, + readonly modified: NotebookCellTextModel | undefined, + readonly type: 'unchanged' | 'insert' | 'delete' | 'modified' + ) { + } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/common.ts b/src/vs/workbench/contrib/notebook/browser/diff/common.ts new file mode 100644 index 00000000000..9e769d6eda7 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/common.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; + +export interface INotebookTextDiffEditor { + getLayoutInfo(): NotebookLayoutInfo; + layoutNotebookCell(cell: CellDiffViewModel, height: number): void; +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css new file mode 100644 index 00000000000..6f4897cf5ea --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* .notebook-diff-editor { + display: flex; + flex-direction: row; + height: 100%; + width: 100%; +} +.notebook-diff-editor-modified, +.notebook-diff-editor-original { + display: flex; + height: 100%; + width: 50%; +} */ + +.notebook-text-diff-editor .cell-diff-editor-container { + margin: 8px; +} + +.notebook-text-diff-editor .cell-diff-editor-container .metadata-container { + display: flex; + height: 24px; + align-items: center; + cursor: default; +} + +.notebook-text-diff-editor .cell-diff-editor-container .metadata-container .metadata-folding-indicator .codicon { + visibility: visible; + padding: 4px 0 0 10px; + cursor: pointer; +} + +.notebook-text-diff-editor .cell-diff-editor-container .metadata-container .metadata-status { + font-size: 12px; +} + +.notebook-text-diff-editor .cell-diff-editor-container .metadata-container .metadata-status span { + margin: 0 8px; + line-height: 21px; +} + +.notebook-text-diff-editor .cell-diff-editor-container.delete .editor-container { + display: inline-block; + width: calc(50% - 18px); +} + +.notebook-text-diff-editor .cell-diff-editor-container.delete .diagonal-fill { + display: inline-block; + width: calc(50% + 18px); +} + +.notebook-text-diff-editor .cell-diff-editor-container.insert .editor-container { + display: inline-block; + width: calc(50% + 18px); +} + +.notebook-text-diff-editor .cell-diff-editor-container.insert .diagonal-fill { + display: inline-block; + width: calc(50% - 18px); +} + +.notebook-text-diff-editor { + 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 > .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 { + outline: none !important; +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts new file mode 100644 index 00000000000..574a8338367 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -0,0 +1,267 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as DOM from 'vs/base/browser/dom'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { notebookCellBorder, NotebookEditorWidget, notebookOutputContainerColor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { NotebookDiffEditorInput } from '../notebookDiffEditorInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { CellDiffRenderer, NotebookCellTextDiffListDelegate, NotebookTextDiffList } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { diffDiagonalFill, editorBackground, focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { getZoomLevel } from 'vs/base/browser/browser'; +import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/common'; + +export class NotebookTextDiffEditor extends BaseEditor implements INotebookTextDiffEditor { + static readonly ID: string = 'workbench.editor.notebookTextDiffEditor'; + + private _rootElement!: HTMLElement; + private _dimension: DOM.Dimension | null = null; + private _list!: WorkbenchList; + private _fontInfo: BareFontInfo | undefined; + + constructor( + @IInstantiationService readonly instantiationService: IInstantiationService, + @IThemeService readonly themeService: IThemeService, + @IContextKeyService readonly contextKeyService: IContextKeyService, + @INotebookEditorWorkerService readonly notebookEditorWorkerService: INotebookEditorWorkerService, + @IConfigurationService private readonly configurationService: IConfigurationService, + + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + ) { + super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); + const editorOptions = this.configurationService.getValue('editor'); + this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()); + } + + protected createEditor(parent: HTMLElement): void { + this._rootElement = DOM.append(parent, DOM.$('.notebook-text-diff-editor')); + + const renderer = this.instantiationService.createInstance(CellDiffRenderer, this); + + this._list = this.instantiationService.createInstance( + NotebookTextDiffList, + 'NotebookTextDiff', + this._rootElement, + this.instantiationService.createInstance(NotebookCellTextDiffListDelegate), + [ + renderer + ], + this.contextKeyService, + { + setRowLineHeight: false, + setRowHeight: false, + supportDynamicHeights: true, + horizontalScrolling: false, + keyboardSupport: false, + mouseSupport: true, + multipleSelectionSupport: false, + enableKeyboardNavigation: true, + additionalScrollHeight: 0, + // transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', + styleController: (_suffix: string) => { return this._list!; }, + overrideStyles: { + listBackground: editorBackground, + listActiveSelectionBackground: editorBackground, + listActiveSelectionForeground: foreground, + listFocusAndSelectionBackground: editorBackground, + listFocusAndSelectionForeground: foreground, + listFocusBackground: editorBackground, + listFocusForeground: foreground, + listHoverForeground: foreground, + listHoverBackground: editorBackground, + listHoverOutline: focusBorder, + listFocusOutline: focusBorder, + listInactiveSelectionBackground: editorBackground, + listInactiveSelectionForeground: foreground, + listInactiveFocusBackground: editorBackground, + listInactiveFocusOutline: editorBackground, + }, + accessibilityProvider: { + getAriaLabel() { return null; }, + getWidgetAriaLabel() { + return nls.localize('notebookTreeAriaLabel', "Notebook Text Diff"); + } + }, + // focusNextPreviousDelegate: { + // onFocusNext: (applyFocusNext: () => void) => this._updateForCursorNavigationMode(applyFocusNext), + // onFocusPrevious: (applyFocusPrevious: () => void) => this._updateForCursorNavigationMode(applyFocusPrevious), + // } + } + ); + } + + async setInput(input: NotebookDiffEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + // const group = this.group!; + + await super.setInput(input, options, token); + + const model = await input.resolve(); + if (model === null) { + return; + } + + const diffResult = await this.notebookEditorWorkerService.computeDiff(model.original.resource, model.modified.resource); + const cellChanges = diffResult.cellsDiff.changes; + + const cellDiffViewModels: CellDiffViewModel[] = []; + const originalModel = model.original.notebook; + const modifiedModel = model.modified.notebook; + let originalCellIndex = 0; + let modifiedCellIndex = 0; + + for (let i = 0; i < cellChanges.length; i++) { + const change = cellChanges[i]; + // common cells + + cellDiffViewModels.push(...originalModel.cells.slice(originalCellIndex, change.originalStart).map(cell => { + return new CellDiffViewModel( + cell, + undefined, + 'unchanged' + ); + })); + + // modified cells + const modifiedLen = Math.min(change.originalLength, change.modifiedLength); + + for (let j = 0; j < modifiedLen; j++) { + cellDiffViewModels.push(new CellDiffViewModel( + originalModel.cells[change.originalStart + j], + modifiedModel.cells[change.modifiedStart + j], + 'modified' + )); + } + + for (let j = modifiedLen; j < change.originalLength; j++) { + // deletion + cellDiffViewModels.push(new CellDiffViewModel( + originalModel.cells[change.originalStart + j], + undefined, + 'delete' + )); + } + + for (let j = modifiedLen; j < change.modifiedLength; j++) { + // insertion + cellDiffViewModels.push(new CellDiffViewModel( + undefined, + modifiedModel.cells[change.modifiedStart + j], + 'insert' + )); + } + + originalCellIndex = change.originalStart + change.originalLength; + modifiedCellIndex = change.modifiedStart + change.modifiedLength; + } + + for (let i = originalCellIndex; i < originalModel.cells.length; i++) { + cellDiffViewModels.push(new CellDiffViewModel( + originalModel.cells[i], + undefined, + 'delete' + )); + } + + for (let i = modifiedCellIndex; i < modifiedModel.cells.length; i++) { + cellDiffViewModels.push(new CellDiffViewModel( + undefined, + modifiedModel.cells[i], + 'insert' + )); + } + + this._list.splice(0, this._list.length, cellDiffViewModels); + } + + layoutNotebookCell(cell: CellDiffViewModel, height: number) { + const index = this._list!.indexOf(cell); + if (index >= 0) { + this._list!.updateElementHeight(index, height); + } + } + + getDomNode() { + return this._rootElement; + } + + getControl(): NotebookEditorWidget | undefined { + return undefined; + } + + setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + super.setEditorVisible(visible, group); + } + + focus() { + super.focus(); + } + + clearInput(): void { + super.clearInput(); + } + + getLayoutInfo(): NotebookLayoutInfo { + if (!this._list) { + throw new Error('Editor is not initalized successfully'); + } + + return { + width: this._dimension!.width, + height: this._dimension!.height, + fontInfo: this._fontInfo! + }; + } + + 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._list?.layout(this._dimension.height, this._dimension.width); + } +} + +registerThemingParticipant((theme, collector) => { + const cellBorderColor = theme.getColor(notebookCellBorder); + if (cellBorderColor) { + collector.addRule(`.notebook-text-diff-editor .editor-container { border: 1px solid ${cellBorderColor};}`); + } + + const diffDiagonalFillColor = theme.getColor(diffDiagonalFill); + collector.addRule(` + .notebook-text-diff-editor .diagonal-fill { + background-image: linear-gradient( + -45deg, + ${diffDiagonalFillColor} 12.5%, + #0000 12.5%, #0000 50%, + ${diffDiagonalFillColor} 50%, ${diffDiagonalFillColor} 62.5%, + #0000 62.5%, #0000 100% + ); + background-size: 8px 8px; + } + `); + + const containerBackground = theme.getColor(notebookOutputContainerColor); + if (containerBackground) { + collector.addRule(`.notebook-text-diff-editor .cell-diff-editor-container .metadata-container { background-color: ${containerBackground}; }`); + } + +}); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts new file mode 100644 index 00000000000..387b8306414 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -0,0 +1,521 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/notebookDiff'; +import { getZoomLevel } from 'vs/base/browser/browser'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import * as DOM from 'vs/base/browser/dom'; +import { IListStyles, IStyleController } from 'vs/base/browser/ui/list/listWidget'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; +import { INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/common'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { isMacintosh } from 'vs/base/common/platform'; +import { renderCodicons } from 'vs/base/common/codicons'; + +export interface CellDiffRenderTemplate { + readonly container: HTMLElement; + +} + +const fixedDiffEditorOptions: IEditorOptions = { + padding: { + top: 12, + bottom: 12 + }, + scrollBeyondLastLine: false, + scrollbar: { + verticalScrollbarSize: 14, + horizontal: 'auto', + useShadows: true, + verticalHasArrows: false, + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false + }, + renderLineHighlightOnlyWhenFocus: true, + overviewRulerLanes: 0, + selectOnLineNumbers: false, + wordWrap: 'off', + lineNumbers: 'off', + lineDecorationsWidth: 0, + glyphMargin: true, + fixedOverflowWidgets: true, + minimap: { enabled: false }, + renderValidationDecorations: 'on' +}; + +const fixedEditorOptions: IEditorOptions = { + padding: { + top: 12, + bottom: 12 + }, + scrollBeyondLastLine: false, + scrollbar: { + verticalScrollbarSize: 14, + horizontal: 'auto', + useShadows: true, + verticalHasArrows: false, + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false + }, + renderLineHighlightOnlyWhenFocus: true, + overviewRulerLanes: 0, + selectOnLineNumbers: false, + wordWrap: 'off', + lineNumbers: 'off', + lineDecorationsWidth: 0, + glyphMargin: false, + fixedOverflowWidgets: true, + minimap: { enabled: false }, + renderValidationDecorations: 'on' +}; + +export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { + private readonly lineHeight: number; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + const editorOptions = this.configurationService.getValue('editor'); + this.lineHeight = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()).lineHeight; + } + + getHeight(element: CellDiffViewModel): number { + return 100; + } + + hasDynamicHeight(element: CellDiffViewModel): boolean { + return false; + } + + getTemplateId(element: CellDiffViewModel): string { + return CellDiffRenderer.TEMPLATE_ID; + } +} +export class CellDiffRenderer implements IListRenderer { + static readonly TEMPLATE_ID = 'cell_diff'; + + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + @IInstantiationService protected readonly instantiationService: IInstantiationService + ) { } + + get templateId() { + return CellDiffRenderer.TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): CellDiffRenderTemplate { + return { + container + }; + } + + renderElement(element: CellDiffViewModel, index: number, templateData: CellDiffRenderTemplate, height: number | undefined): void { + templateData.container.innerText = ''; + switch (element.type) { + case 'unchanged': + this.instantiationService.createInstance(UnchangedCell, this.notebookEditor, element, templateData); + return; + case 'delete': + this.instantiationService.createInstance(DeletedCell, this.notebookEditor, element, templateData); + return; + case 'insert': + this.instantiationService.createInstance(InsertCell, this.notebookEditor, element, templateData); + return; + case 'modified': + this.instantiationService.createInstance(ModifiedCell, this.notebookEditor, element, templateData); + return; + default: + break; + } + } + + disposeTemplate(templateData: CellDiffRenderTemplate): void { + templateData.container.innerText = ''; + } +} + +class UnchangedCell extends Disposable { + private _editor!: CodeEditorWidget; + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: CellDiffViewModel, + readonly templateData: CellDiffRenderTemplate, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { + super(); + const diffEditorContainer = DOM.$('.cell-diff-editor-container'); + DOM.append(templateData.container, diffEditorContainer); + + const originalCell = cell.original!; + const lineCount = originalCell.textBuffer.getLineCount(); + const lineHeight = notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + const editorContainer = DOM.append(diffEditorContainer, DOM.$('.editor-container')); + const metadataContainer = DOM.append(diffEditorContainer, DOM.$('.metadata-container')); + this.buildMetadata(metadataContainer); + + this._editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { + ...fixedEditorOptions, + dimension: { + width: notebookEditor.getLayoutInfo().width - 20, + height: editorHeight + } + }, {}); + + this._register(this._editor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged) { + this._editor.layout({ + width: notebookEditor.getLayoutInfo().width - 20, + height: e.contentHeight + }); + this.notebookEditor.layoutNotebookCell(this.cell, e.contentHeight + 32 + 24); + } + })); + + originalCell.resolveTextModelRef().then(ref => { + this._register(ref); + + const textModel = ref.object.textEditorModel; + this._editor.setModel(textModel); + + this._editor.layout({ + width: notebookEditor.getLayoutInfo().width - 20, + height: this._editor.getContentHeight() + }); + this.notebookEditor.layoutNotebookCell(this.cell, this._editor.getContentHeight() + 32 + 24); + }); + } + + buildMetadata(metadataContainer: HTMLElement) { + const foldingIndicator = DOM.append(metadataContainer, DOM.$('.metadata-folding-indicator')); + foldingIndicator.innerHTML = renderCodicons('$(chevron-right)'); + const metadataStatus = DOM.append(metadataContainer, DOM.$('div.metadata-status')); + const metadataStatusSpan = DOM.append(metadataStatus, DOM.$('span')); + metadataStatusSpan.textContent = 'Metadata unchanged'; + + } +} + +class DeletedCell extends Disposable { + private _editor!: CodeEditorWidget; + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: CellDiffViewModel, + readonly templateData: CellDiffRenderTemplate, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { + super(); + const diffEditorContainer = DOM.$('.cell-diff-editor-container'); + DOM.append(templateData.container, diffEditorContainer); + DOM.addClass(diffEditorContainer, 'delete'); + + const originalCell = cell.original!; + const lineCount = originalCell.textBuffer.getLineCount(); + const lineHeight = notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + const editorContainer = DOM.append(diffEditorContainer, DOM.$('.editor-container')); + const diagonalFill = DOM.append(diffEditorContainer, DOM.$('.diagonal-fill')); + + this._editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { + ...fixedEditorOptions, + dimension: { + width: (notebookEditor.getLayoutInfo().width - 20) / 2 - 18, + height: editorHeight + } + }, {}); + + diagonalFill.style.height = `${editorHeight}px`; + + this._register(this._editor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged) { + this._editor.layout({ + width: (notebookEditor.getLayoutInfo().width - 20) / 2 - 18, + height: e.contentHeight + }); + diagonalFill.style.height = `${e.contentHeight}px`; + this.notebookEditor.layoutNotebookCell(this.cell, e.contentHeight + 32); + } + })); + + originalCell.resolveTextModelRef().then(ref => { + this._register(ref); + + const textModel = ref.object.textEditorModel; + this._editor.setModel(textModel); + + this.notebookEditor.layoutNotebookCell(this.cell, this._editor.getContentHeight() + 32); + diagonalFill.style.height = `${this._editor.getContentHeight()}px`; + + }); + } +} + +class InsertCell extends Disposable { + private _editor!: CodeEditorWidget; + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: CellDiffViewModel, + readonly templateData: CellDiffRenderTemplate, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { + super(); + const diffEditorContainer = DOM.$('.cell-diff-editor-container'); + DOM.append(templateData.container, diffEditorContainer); + DOM.addClass(diffEditorContainer, 'insert'); + + const modifiedCell = cell.modified!; + const lineCount = modifiedCell.textBuffer.getLineCount(); + const lineHeight = notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + const diagonalFill = DOM.append(diffEditorContainer, DOM.$('.diagonal-fill')); + const editorContainer = DOM.append(diffEditorContainer, DOM.$('.editor-container')); + + this._editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { + ...fixedEditorOptions, + dimension: { + width: (notebookEditor.getLayoutInfo().width - 20) / 2 - 18, + height: editorHeight + } + }, {}); + + diagonalFill.style.height = `${editorHeight}px`; + + this._register(this._editor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged) { + this._editor.layout({ + width: (notebookEditor.getLayoutInfo().width - 20) / 2 - 18, + height: e.contentHeight + }); + diagonalFill.style.height = `${e.contentHeight}px`; + this.notebookEditor.layoutNotebookCell(this.cell, e.contentHeight + 32); + } + })); + + modifiedCell.resolveTextModelRef().then(ref => { + this._register(ref); + + const textModel = ref.object.textEditorModel; + this._editor.setModel(textModel); + + this.notebookEditor.layoutNotebookCell(this.cell, this._editor.getContentHeight() + 32); + diagonalFill.style.height = `${this._editor.getContentHeight()}px`; + }); + } +} + +class ModifiedCell extends Disposable { + private _editor!: DiffEditorWidget; + private _editorContainer!: HTMLElement; + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + readonly cell: CellDiffViewModel, + readonly templateData: CellDiffRenderTemplate, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { + super(); + const diffEditorContainer = DOM.$('.cell-diff-editor-container'); + DOM.append(templateData.container, diffEditorContainer); + + const modifiedCell = cell.modified!; + const lineCount = modifiedCell.textBuffer.getLineCount(); + const lineHeight = notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const editorHeight = lineCount * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + this._editorContainer = DOM.append(diffEditorContainer, DOM.$('.editor-container')); + + this._editor = this.instantiationService.createInstance(DiffEditorWidget, this._editorContainer, { + ...fixedDiffEditorOptions + }); + + this._editor.layout({ + width: notebookEditor.getLayoutInfo().width - 20, + height: editorHeight + }); + + this._editorContainer.style.height = `${editorHeight}px`; + + this._register(this._editor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged) { + this._editorContainer.style.height = `${e.contentHeight}px`; + this.notebookEditor.layoutNotebookCell(this.cell, e.contentHeight + 32); + this._editor.layout(); + } + })); + + this.initialize(); + } + + async initialize() { + const originalCell = this.cell.original!; + const modifiedCell = this.cell.modified!; + + const originalRef = await originalCell.resolveTextModelRef(); + const modifiedRef = await modifiedCell.resolveTextModelRef(); + const textModel = originalRef.object.textEditorModel; + const modifiedTextModel = modifiedRef.object.textEditorModel; + this._register(originalRef); + this._register(modifiedRef); + + this._editor.setModel({ + original: textModel, + modified: modifiedTextModel + }); + + const contentHeight = this._editor.getContentHeight(); + + this._editorContainer.style.height = `${contentHeight}px`; + this._editor.layout(); + this.notebookEditor.layoutNotebookCell(this.cell, contentHeight + 32); + } +} + +export class NotebookTextDiffList extends WorkbenchList implements IDisposable, IStyleController { + private styleElement?: HTMLStyleElement; + + constructor( + listUser: string, + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: IListRenderer[], + contextKeyService: IContextKeyService, + options: IWorkbenchListOptions, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); + } + + style(styles: IListStyles) { + const selectorSuffix = this.view.domId; + if (!this.styleElement) { + this.styleElement = DOM.createStyleSheet(this.view.domNode); + } + const suffix = selectorSuffix && `.${selectorSuffix}`; + const content: string[] = []; + + if (styles.listBackground) { + if (styles.listBackground.isOpaque()) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows { background: ${styles.listBackground}; }`); + } else if (!isMacintosh) { // subpixel AA doesn't exist in macOS + console.warn(`List with id '${selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`); + } + } + + if (styles.listFocusBackground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`); + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listFocusForeground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); + } + + if (styles.listActiveSelectionBackground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listActiveSelectionBackground}; }`); + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listActiveSelectionForeground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listActiveSelectionForeground}; }`); + } + + if (styles.listFocusAndSelectionBackground) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; } + `); + } + + if (styles.listFocusAndSelectionForeground) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; } + `); + } + + if (styles.listInactiveFocusBackground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`); + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listInactiveSelectionBackground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listInactiveSelectionBackground}; }`); + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listInactiveSelectionForeground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listInactiveSelectionForeground}; }`); + } + + if (styles.listHoverBackground) { + content.push(`.monaco-list${suffix}:not(.drop-target) > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); + } + + if (styles.listHoverForeground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); + } + + if (styles.listSelectionOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`); + } + + if (styles.listFocusOutline) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + `); + } + + if (styles.listInactiveFocusOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`); + } + + if (styles.listHoverOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); + } + + if (styles.listDropBackground) { + content.push(` + .monaco-list${suffix}.drop-target, + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows.drop-target, + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + `); + } + + if (styles.listFilterWidgetBackground) { + content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`); + } + + if (styles.listFilterWidgetOutline) { + content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`); + } + + if (styles.listFilterWidgetNoMatchesOutline) { + content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`); + } + + if (styles.listMatchesShadow) { + content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this.styleElement.innerHTML) { + this.styleElement.innerHTML = newStyles; + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index f80967a2fb1..86382efd0c8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -43,7 +43,7 @@ import { INotebookEditorModelResolverService, NotebookModelResolverService } fro import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; -import { NotebookDiffEditor } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditor'; +import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { NotebookEditorWorkerServiceImpl } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; @@ -79,8 +79,8 @@ Registry.as(EditorExtensions.Editors).registerEditor( Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( - NotebookDiffEditor, - NotebookDiffEditor.ID, + NotebookTextDiffEditor, + NotebookTextDiffEditor.ID, 'Notebook Diff Editor' ), [