Path: blob/main/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.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 { Emitter } from '../../../../../base/common/event.js';6import { hash } from '../../../../../base/common/hash.js';7import { Disposable } from '../../../../../base/common/lifecycle.js';8import { URI } from '../../../../../base/common/uri.js';9import { DiffEditorWidget } from '../../../../../editor/browser/widget/diffEditor/diffEditorWidget.js';10import { FontInfo } from '../../../../../editor/common/config/fontInfo.js';11import * as editorCommon from '../../../../../editor/common/editorCommon.js';12import { getEditorPadding } from './diffCellEditorOptions.js';13import { DiffNestedCellViewModel } from './diffNestedCellViewModel.js';14import { NotebookDiffEditorEventDispatcher, NotebookDiffViewEventType } from './eventDispatcher.js';15import { CellDiffViewModelLayoutChangeEvent, DIFF_CELL_MARGIN, DiffSide, IDiffElementLayoutInfo } from './notebookDiffEditorBrowser.js';16import { CellLayoutState, IGenericCellViewModel } from '../notebookBrowser.js';17import { NotebookLayoutInfo } from '../notebookViewEvents.js';18import { getFormattedMetadataJSON, NotebookCellTextModel } from '../../common/model/notebookCellTextModel.js';19import { NotebookTextModel } from '../../common/model/notebookTextModel.js';20import { CellUri, ICellOutput, INotebookTextModel, IOutputDto, IOutputItemDto } from '../../common/notebookCommon.js';21import { INotebookService } from '../../common/notebookService.js';22import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';23import { Schemas } from '../../../../../base/common/network.js';24import { IDiffEditorHeightCalculatorService } from './editorHeightCalculator.js';25import { NotebookDocumentMetadataTextModel } from '../../common/model/notebookMetadataTextModel.js';2627const PropertyHeaderHeight = 25;2829// From `.monaco-editor .diff-hidden-lines .center` in src/vs/editor/browser/widget/diffEditor/style.css30export const HeightOfHiddenLinesRegionInDiffEditor = 24;3132export const DefaultLineHeight = 17;3334export enum PropertyFoldingState {35Expanded,36Collapsed37}3839export const OUTPUT_EDITOR_HEIGHT_MAGIC = 1440;4041type ILayoutInfoDelta0 = { [K in keyof IDiffElementLayoutInfo]?: number; };42interface ILayoutInfoDelta extends ILayoutInfoDelta0 {43rawOutputHeight?: number;44recomputeOutput?: boolean;45}4647export type IDiffElementViewModelBase = DiffElementCellViewModelBase | DiffElementPlaceholderViewModel | NotebookDocumentMetadataViewModel;4849export abstract class DiffElementViewModelBase extends Disposable {50protected _layoutInfoEmitter = this._register(new Emitter<CellDiffViewModelLayoutChangeEvent>());51onDidLayoutChange = this._layoutInfoEmitter.event;52abstract renderOutput: boolean;53constructor(54public readonly mainDocumentTextModel: INotebookTextModel,55public readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher,56public readonly initData: {57metadataStatusHeight: number;58outputStatusHeight: number;59fontInfo: FontInfo | undefined;60}61) {62super();6364this._register(this.editorEventDispatcher.onDidChangeLayout(e => this._layoutInfoEmitter.fire({ outerWidth: true })));65}6667abstract layoutChange(): void;68abstract getHeight(lineHeight: number): number;69abstract get totalHeight(): number;70}7172export class DiffElementPlaceholderViewModel extends DiffElementViewModelBase {73readonly type: 'placeholder' = 'placeholder';74public hiddenCells: DiffElementCellViewModelBase[] = [];75protected _unfoldHiddenCells = this._register(new Emitter<void>());76onUnfoldHiddenCells = this._unfoldHiddenCells.event;7778public renderOutput: boolean = false;79constructor(80mainDocumentTextModel: INotebookTextModel,81editorEventDispatcher: NotebookDiffEditorEventDispatcher,82initData: {83metadataStatusHeight: number;84outputStatusHeight: number;85fontInfo: FontInfo | undefined;86}87) {88super(mainDocumentTextModel, editorEventDispatcher, initData);8990}91get totalHeight() {92return 24 + (2 * DIFF_CELL_MARGIN);93}94getHeight(_: number): number {95return this.totalHeight;96}97override layoutChange(): void {98//99}100showHiddenCells() {101this._unfoldHiddenCells.fire();102}103}104105106export class NotebookDocumentMetadataViewModel extends DiffElementViewModelBase {107public readonly originalMetadata: NotebookDocumentMetadataTextModel;108public readonly modifiedMetadata: NotebookDocumentMetadataTextModel;109public cellFoldingState: PropertyFoldingState;110protected _layoutInfo!: IDiffElementLayoutInfo;111public renderOutput: boolean = false;112set editorHeight(height: number) {113this._layout({ editorHeight: height });114}115116get editorHeight() {117throw new Error('Use Cell.layoutInfo.editorHeight');118}119120set editorMargin(margin: number) {121this._layout({ editorMargin: margin });122}123124get editorMargin() {125throw new Error('Use Cell.layoutInfo.editorMargin');126}127get layoutInfo(): IDiffElementLayoutInfo {128return this._layoutInfo;129}130131get totalHeight() {132return this.layoutInfo.totalHeight;133}134135private _sourceEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null;136constructor(137public readonly originalDocumentTextModel: INotebookTextModel,138public readonly modifiedDocumentTextModel: INotebookTextModel,139public readonly type: 'unchangedMetadata' | 'modifiedMetadata',140editorEventDispatcher: NotebookDiffEditorEventDispatcher,141initData: {142metadataStatusHeight: number;143outputStatusHeight: number;144fontInfo: FontInfo | undefined;145},146notebookService: INotebookService,147private readonly editorHeightCalculator: IDiffEditorHeightCalculatorService148) {149super(originalDocumentTextModel, editorEventDispatcher, initData);150151const cellStatusHeight = PropertyHeaderHeight;152this._layoutInfo = {153width: 0,154editorHeight: 0,155editorMargin: 0,156metadataHeight: 0,157cellStatusHeight,158metadataStatusHeight: 0,159rawOutputHeight: 0,160outputTotalHeight: 0,161outputStatusHeight: 0,162outputMetadataHeight: 0,163bodyMargin: 32,164totalHeight: 82 + cellStatusHeight + 0,165layoutState: CellLayoutState.Uninitialized166};167168this.cellFoldingState = type === 'modifiedMetadata' ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed;169this.originalMetadata = this._register(new NotebookDocumentMetadataTextModel(originalDocumentTextModel));170this.modifiedMetadata = this._register(new NotebookDocumentMetadataTextModel(modifiedDocumentTextModel));171}172173public async computeHeights() {174if (this.type === 'unchangedMetadata') {175this.editorHeight = this.editorHeightCalculator.computeHeightFromLines(this.originalMetadata.textBuffer.getLineCount());176} else {177const original = this.originalMetadata.uri;178const modified = this.modifiedMetadata.uri;179this.editorHeight = await this.editorHeightCalculator.diffAndComputeHeight(original, modified);180}181}182183layoutChange() {184this._layout({ recomputeOutput: true });185}186187protected _layout(delta: ILayoutInfoDelta) {188const width = delta.width !== undefined ? delta.width : this._layoutInfo.width;189const editorHeight = delta.editorHeight !== undefined ? delta.editorHeight : this._layoutInfo.editorHeight;190const editorMargin = delta.editorMargin !== undefined ? delta.editorMargin : this._layoutInfo.editorMargin;191const cellStatusHeight = delta.cellStatusHeight !== undefined ? delta.cellStatusHeight : this._layoutInfo.cellStatusHeight;192const bodyMargin = delta.bodyMargin !== undefined ? delta.bodyMargin : this._layoutInfo.bodyMargin;193194const totalHeight = editorHeight195+ editorMargin196+ cellStatusHeight197+ bodyMargin;198199const newLayout: IDiffElementLayoutInfo = {200width: width,201editorHeight: editorHeight,202editorMargin: editorMargin,203metadataHeight: 0,204cellStatusHeight,205metadataStatusHeight: 0,206outputTotalHeight: 0,207outputStatusHeight: 0,208bodyMargin: bodyMargin,209rawOutputHeight: 0,210outputMetadataHeight: 0,211totalHeight: totalHeight,212layoutState: CellLayoutState.Measured213};214215let somethingChanged = false;216217const changeEvent: CellDiffViewModelLayoutChangeEvent = {};218219if (newLayout.width !== this._layoutInfo.width) {220changeEvent.width = true;221somethingChanged = true;222}223224if (newLayout.editorHeight !== this._layoutInfo.editorHeight) {225changeEvent.editorHeight = true;226somethingChanged = true;227}228229if (newLayout.editorMargin !== this._layoutInfo.editorMargin) {230changeEvent.editorMargin = true;231somethingChanged = true;232}233234if (newLayout.cellStatusHeight !== this._layoutInfo.cellStatusHeight) {235changeEvent.cellStatusHeight = true;236somethingChanged = true;237}238239if (newLayout.bodyMargin !== this._layoutInfo.bodyMargin) {240changeEvent.bodyMargin = true;241somethingChanged = true;242}243244if (newLayout.totalHeight !== this._layoutInfo.totalHeight) {245changeEvent.totalHeight = true;246somethingChanged = true;247}248249if (somethingChanged) {250this._layoutInfo = newLayout;251this._fireLayoutChangeEvent(changeEvent);252}253}254255getHeight(lineHeight: number) {256if (this._layoutInfo.layoutState === CellLayoutState.Uninitialized) {257const editorHeight = this.cellFoldingState === PropertyFoldingState.Collapsed ? 0 : this.computeInputEditorHeight(lineHeight);258return this._computeTotalHeight(editorHeight);259} else {260return this._layoutInfo.totalHeight;261}262}263264private _computeTotalHeight(editorHeight: number) {265const totalHeight = editorHeight266+ this._layoutInfo.editorMargin267+ this._layoutInfo.metadataHeight268+ this._layoutInfo.cellStatusHeight269+ this._layoutInfo.metadataStatusHeight270+ this._layoutInfo.outputTotalHeight271+ this._layoutInfo.outputStatusHeight272+ this._layoutInfo.outputMetadataHeight273+ this._layoutInfo.bodyMargin;274275return totalHeight;276}277278public computeInputEditorHeight(_lineHeight: number): number {279return this.editorHeightCalculator.computeHeightFromLines(Math.max(this.originalMetadata.textBuffer.getLineCount(), this.modifiedMetadata.textBuffer.getLineCount()));280}281282private _fireLayoutChangeEvent(state: CellDiffViewModelLayoutChangeEvent) {283this._layoutInfoEmitter.fire(state);284this.editorEventDispatcher.emit([{ type: NotebookDiffViewEventType.CellLayoutChanged, source: this._layoutInfo }]);285}286287getComputedCellContainerWidth(layoutInfo: NotebookLayoutInfo, diffEditor: boolean, fullWidth: boolean) {288if (fullWidth) {289return layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0) - 2;290}291292return (layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0)) / 2 - 18 - 2;293}294295getSourceEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null {296return this._sourceEditorViewState;297}298299saveSpirceEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) {300this._sourceEditorViewState = viewState;301}302}303304305export abstract class DiffElementCellViewModelBase extends DiffElementViewModelBase {306public cellFoldingState: PropertyFoldingState;307public metadataFoldingState: PropertyFoldingState;308public outputFoldingState: PropertyFoldingState;309protected _stateChangeEmitter = this._register(new Emitter<{ renderOutput: boolean }>());310onDidStateChange = this._stateChangeEmitter.event;311protected _layoutInfo!: IDiffElementLayoutInfo;312313public displayIconToHideUnmodifiedCells?: boolean;314private _hideUnchangedCells = this._register(new Emitter<void>());315public onHideUnchangedCells = this._hideUnchangedCells.event;316317hideUnchangedCells() {318this._hideUnchangedCells.fire();319}320set rawOutputHeight(height: number) {321this._layout({ rawOutputHeight: Math.min(OUTPUT_EDITOR_HEIGHT_MAGIC, height) });322}323324get rawOutputHeight() {325throw new Error('Use Cell.layoutInfo.rawOutputHeight');326}327328set outputStatusHeight(height: number) {329this._layout({ outputStatusHeight: height });330}331332get outputStatusHeight() {333throw new Error('Use Cell.layoutInfo.outputStatusHeight');334}335336set outputMetadataHeight(height: number) {337this._layout({ outputMetadataHeight: height });338}339340get outputMetadataHeight() {341throw new Error('Use Cell.layoutInfo.outputStatusHeight');342}343344set editorHeight(height: number) {345this._layout({ editorHeight: height });346}347348get editorHeight() {349throw new Error('Use Cell.layoutInfo.editorHeight');350}351352set editorMargin(margin: number) {353this._layout({ editorMargin: margin });354}355356get editorMargin() {357throw new Error('Use Cell.layoutInfo.editorMargin');358}359360set metadataStatusHeight(height: number) {361this._layout({ metadataStatusHeight: height });362}363364get metadataStatusHeight() {365throw new Error('Use Cell.layoutInfo.outputStatusHeight');366}367368set metadataHeight(height: number) {369this._layout({ metadataHeight: height });370}371372get metadataHeight() {373throw new Error('Use Cell.layoutInfo.metadataHeight');374}375376private _renderOutput = true;377378set renderOutput(value: boolean) {379this._renderOutput = value;380this._layout({ recomputeOutput: true });381this._stateChangeEmitter.fire({ renderOutput: this._renderOutput });382}383384get renderOutput() {385return this._renderOutput;386}387388get layoutInfo(): IDiffElementLayoutInfo {389return this._layoutInfo;390}391392get totalHeight() {393return this.layoutInfo.totalHeight;394}395396protected get ignoreOutputs() {397return this.configurationService.getValue<boolean>('notebook.diff.ignoreOutputs') || !!(this.mainDocumentTextModel?.transientOptions.transientOutputs);398}399400protected get ignoreMetadata() {401return this.configurationService.getValue<boolean>('notebook.diff.ignoreMetadata');402}403404private _sourceEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null;405private _outputEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null;406private _metadataEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null;407public readonly original: DiffNestedCellViewModel | undefined;408409public readonly modified: DiffNestedCellViewModel | undefined;410constructor(411mainDocumentTextModel: INotebookTextModel,412original: NotebookCellTextModel | undefined,413modified: NotebookCellTextModel | undefined,414readonly type: 'unchanged' | 'insert' | 'delete' | 'modified',415editorEventDispatcher: NotebookDiffEditorEventDispatcher,416initData: {417metadataStatusHeight: number;418outputStatusHeight: number;419fontInfo: FontInfo | undefined;420},421notebookService: INotebookService,422public readonly index: number,423private readonly configurationService: IConfigurationService,424public readonly diffEditorHeightCalculator: IDiffEditorHeightCalculatorService425) {426super(mainDocumentTextModel, editorEventDispatcher, initData);427this.original = original ? this._register(new DiffNestedCellViewModel(original, notebookService)) : undefined;428this.modified = modified ? this._register(new DiffNestedCellViewModel(modified, notebookService)) : undefined;429const editorHeight = this._estimateEditorHeight(initData.fontInfo);430const cellStatusHeight = PropertyHeaderHeight;431this._layoutInfo = {432width: 0,433editorHeight: editorHeight,434editorMargin: 0,435metadataHeight: 0,436cellStatusHeight,437metadataStatusHeight: this.ignoreMetadata ? 0 : PropertyHeaderHeight,438rawOutputHeight: 0,439outputTotalHeight: 0,440outputStatusHeight: this.ignoreOutputs ? 0 : PropertyHeaderHeight,441outputMetadataHeight: 0,442bodyMargin: 32,443totalHeight: 82 + cellStatusHeight + editorHeight,444layoutState: CellLayoutState.Uninitialized445};446447this.cellFoldingState = modified?.getTextBufferHash() !== original?.getTextBufferHash() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed;448this.metadataFoldingState = PropertyFoldingState.Collapsed;449this.outputFoldingState = PropertyFoldingState.Collapsed;450}451452layoutChange() {453this._layout({ recomputeOutput: true });454}455456private _estimateEditorHeight(fontInfo: FontInfo | undefined) {457const lineHeight = fontInfo?.lineHeight ?? 17;458459switch (this.type) {460case 'unchanged':461case 'insert':462{463const lineCount = this.modified!.textModel.textBuffer.getLineCount();464const editorHeight = lineCount * lineHeight + getEditorPadding(lineCount).top + getEditorPadding(lineCount).bottom;465return editorHeight;466}467case 'delete':468case 'modified':469{470const lineCount = this.original!.textModel.textBuffer.getLineCount();471const editorHeight = lineCount * lineHeight + getEditorPadding(lineCount).top + getEditorPadding(lineCount).bottom;472return editorHeight;473}474}475}476477protected _layout(delta: ILayoutInfoDelta) {478const width = delta.width !== undefined ? delta.width : this._layoutInfo.width;479const editorHeight = delta.editorHeight !== undefined ? delta.editorHeight : this._layoutInfo.editorHeight;480const editorMargin = delta.editorMargin !== undefined ? delta.editorMargin : this._layoutInfo.editorMargin;481const metadataHeight = delta.metadataHeight !== undefined ? delta.metadataHeight : this._layoutInfo.metadataHeight;482const cellStatusHeight = delta.cellStatusHeight !== undefined ? delta.cellStatusHeight : this._layoutInfo.cellStatusHeight;483const metadataStatusHeight = delta.metadataStatusHeight !== undefined ? delta.metadataStatusHeight : this._layoutInfo.metadataStatusHeight;484const rawOutputHeight = delta.rawOutputHeight !== undefined ? delta.rawOutputHeight : this._layoutInfo.rawOutputHeight;485const outputStatusHeight = delta.outputStatusHeight !== undefined ? delta.outputStatusHeight : this._layoutInfo.outputStatusHeight;486const bodyMargin = delta.bodyMargin !== undefined ? delta.bodyMargin : this._layoutInfo.bodyMargin;487const outputMetadataHeight = delta.outputMetadataHeight !== undefined ? delta.outputMetadataHeight : this._layoutInfo.outputMetadataHeight;488const outputHeight = this.ignoreOutputs ? 0 : (delta.recomputeOutput || delta.rawOutputHeight !== undefined || delta.outputMetadataHeight !== undefined) ? this._getOutputTotalHeight(rawOutputHeight, outputMetadataHeight) : this._layoutInfo.outputTotalHeight;489490const totalHeight = editorHeight491+ editorMargin492+ cellStatusHeight493+ metadataHeight494+ metadataStatusHeight495+ outputHeight496+ outputStatusHeight497+ bodyMargin;498499const newLayout: IDiffElementLayoutInfo = {500width: width,501editorHeight: editorHeight,502editorMargin: editorMargin,503metadataHeight: metadataHeight,504cellStatusHeight,505metadataStatusHeight: metadataStatusHeight,506outputTotalHeight: outputHeight,507outputStatusHeight: outputStatusHeight,508bodyMargin: bodyMargin,509rawOutputHeight: rawOutputHeight,510outputMetadataHeight: outputMetadataHeight,511totalHeight: totalHeight,512layoutState: CellLayoutState.Measured513};514515let somethingChanged = false;516517const changeEvent: CellDiffViewModelLayoutChangeEvent = {};518519if (newLayout.width !== this._layoutInfo.width) {520changeEvent.width = true;521somethingChanged = true;522}523524if (newLayout.editorHeight !== this._layoutInfo.editorHeight) {525changeEvent.editorHeight = true;526somethingChanged = true;527}528529if (newLayout.editorMargin !== this._layoutInfo.editorMargin) {530changeEvent.editorMargin = true;531somethingChanged = true;532}533534if (newLayout.metadataHeight !== this._layoutInfo.metadataHeight) {535changeEvent.metadataHeight = true;536somethingChanged = true;537}538539if (newLayout.cellStatusHeight !== this._layoutInfo.cellStatusHeight) {540changeEvent.cellStatusHeight = true;541somethingChanged = true;542}543544if (newLayout.metadataStatusHeight !== this._layoutInfo.metadataStatusHeight) {545changeEvent.metadataStatusHeight = true;546somethingChanged = true;547}548549if (newLayout.outputTotalHeight !== this._layoutInfo.outputTotalHeight) {550changeEvent.outputTotalHeight = true;551somethingChanged = true;552}553554if (newLayout.outputStatusHeight !== this._layoutInfo.outputStatusHeight) {555changeEvent.outputStatusHeight = true;556somethingChanged = true;557}558559if (newLayout.bodyMargin !== this._layoutInfo.bodyMargin) {560changeEvent.bodyMargin = true;561somethingChanged = true;562}563564if (newLayout.outputMetadataHeight !== this._layoutInfo.outputMetadataHeight) {565changeEvent.outputMetadataHeight = true;566somethingChanged = true;567}568569if (newLayout.totalHeight !== this._layoutInfo.totalHeight) {570changeEvent.totalHeight = true;571somethingChanged = true;572}573574if (somethingChanged) {575this._layoutInfo = newLayout;576this._fireLayoutChangeEvent(changeEvent);577}578}579580getHeight(lineHeight: number) {581if (this._layoutInfo.layoutState === CellLayoutState.Uninitialized) {582const editorHeight = this.cellFoldingState === PropertyFoldingState.Collapsed ? 0 : this.computeInputEditorHeight(lineHeight);583return this._computeTotalHeight(editorHeight);584} else {585return this._layoutInfo.totalHeight;586}587}588589private _computeTotalHeight(editorHeight: number) {590const totalHeight = editorHeight591+ this._layoutInfo.editorMargin592+ this._layoutInfo.metadataHeight593+ this._layoutInfo.cellStatusHeight594+ this._layoutInfo.metadataStatusHeight595+ this._layoutInfo.outputTotalHeight596+ this._layoutInfo.outputStatusHeight597+ this._layoutInfo.outputMetadataHeight598+ this._layoutInfo.bodyMargin;599600return totalHeight;601}602603public computeInputEditorHeight(lineHeight: number): number {604const lineCount = Math.max(this.original?.textModel.textBuffer.getLineCount() ?? 1, this.modified?.textModel.textBuffer.getLineCount() ?? 1);605return this.diffEditorHeightCalculator.computeHeightFromLines(lineCount);606}607608private _getOutputTotalHeight(rawOutputHeight: number, metadataHeight: number) {609if (this.outputFoldingState === PropertyFoldingState.Collapsed) {610return 0;611}612613if (this.renderOutput) {614if (this.isOutputEmpty()) {615// single line;616return 24;617}618return this.getRichOutputTotalHeight() + metadataHeight;619} else {620return rawOutputHeight;621}622}623624private _fireLayoutChangeEvent(state: CellDiffViewModelLayoutChangeEvent) {625this._layoutInfoEmitter.fire(state);626this.editorEventDispatcher.emit([{ type: NotebookDiffViewEventType.CellLayoutChanged, source: this._layoutInfo }]);627}628629abstract checkIfInputModified(): false | { reason: string | undefined };630abstract checkIfOutputsModified(): false | { reason: string | undefined };631abstract checkMetadataIfModified(): false | { reason: string | undefined };632abstract isOutputEmpty(): boolean;633abstract getRichOutputTotalHeight(): number;634abstract getCellByUri(cellUri: URI): IGenericCellViewModel;635abstract getOutputOffsetInCell(diffSide: DiffSide, index: number): number;636abstract getOutputOffsetInContainer(diffSide: DiffSide, index: number): number;637abstract updateOutputHeight(diffSide: DiffSide, index: number, height: number): void;638abstract getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel;639640getComputedCellContainerWidth(layoutInfo: NotebookLayoutInfo, diffEditor: boolean, fullWidth: boolean) {641if (fullWidth) {642return layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0) - 2;643}644645return (layoutInfo.width - 2 * DIFF_CELL_MARGIN + (diffEditor ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0)) / 2 - 18 - 2;646}647648getOutputEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null {649return this._outputEditorViewState;650}651652saveOutputEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) {653this._outputEditorViewState = viewState;654}655656getMetadataEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null {657return this._metadataEditorViewState;658}659660saveMetadataEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) {661this._metadataEditorViewState = viewState;662}663664getSourceEditorViewState(): editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null {665return this._sourceEditorViewState;666}667668saveSpirceEditorViewState(viewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null) {669this._sourceEditorViewState = viewState;670}671}672673export class SideBySideDiffElementViewModel extends DiffElementCellViewModelBase {674get originalDocument() {675return this.otherDocumentTextModel;676}677678get modifiedDocument() {679return this.mainDocumentTextModel;680}681682declare readonly original: DiffNestedCellViewModel;683declare readonly modified: DiffNestedCellViewModel;684override readonly type: 'unchanged' | 'modified';685686/**687* The height of the editor when the unchanged lines are collapsed.688*/689private editorHeightWithUnchangedLinesCollapsed?: number;690constructor(691mainDocumentTextModel: NotebookTextModel,692readonly otherDocumentTextModel: NotebookTextModel,693original: NotebookCellTextModel,694modified: NotebookCellTextModel,695type: 'unchanged' | 'modified',696editorEventDispatcher: NotebookDiffEditorEventDispatcher,697initData: {698metadataStatusHeight: number;699outputStatusHeight: number;700fontInfo: FontInfo | undefined;701},702notebookService: INotebookService,703configurationService: IConfigurationService,704index: number,705diffEditorHeightCalculator: IDiffEditorHeightCalculatorService706) {707super(708mainDocumentTextModel,709original,710modified,711type,712editorEventDispatcher,713initData,714notebookService,715index,716configurationService,717diffEditorHeightCalculator);718719this.type = type;720721this.cellFoldingState = this.modified.textModel.getValue() !== this.original.textModel.getValue() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed;722this.metadataFoldingState = PropertyFoldingState.Collapsed;723this.outputFoldingState = PropertyFoldingState.Collapsed;724725if (this.checkMetadataIfModified()) {726this.metadataFoldingState = PropertyFoldingState.Expanded;727}728729if (this.checkIfOutputsModified()) {730this.outputFoldingState = PropertyFoldingState.Expanded;731}732733this._register(this.original.onDidChangeOutputLayout(() => {734this._layout({ recomputeOutput: true });735}));736737this._register(this.modified.onDidChangeOutputLayout(() => {738this._layout({ recomputeOutput: true });739}));740741this._register(this.modified.textModel.onDidChangeContent(() => {742if (mainDocumentTextModel.transientOptions.cellContentMetadata) {743const cellMetadataKeys = [...Object.keys(mainDocumentTextModel.transientOptions.cellContentMetadata)];744const modifiedMedataRaw = Object.assign({}, this.modified.metadata);745const originalCellMetadata = this.original.metadata;746for (const key of cellMetadataKeys) {747if (key in originalCellMetadata) {748modifiedMedataRaw[key] = originalCellMetadata[key];749}750}751752this.modified.textModel.metadata = modifiedMedataRaw;753}754}));755}756757override checkIfInputModified(): false | { reason: string | undefined } {758if (this.original.textModel.getTextBufferHash() === this.modified.textModel.getTextBufferHash()) {759return false;760}761return {762reason: 'Cell content has changed',763};764}765checkIfOutputsModified() {766if (this.mainDocumentTextModel.transientOptions.transientOutputs || this.ignoreOutputs) {767return false;768}769770const ret = outputsEqual(this.original?.outputs ?? [], this.modified?.outputs ?? []);771772if (ret === OutputComparison.Unchanged) {773return false;774}775776return {777reason: ret === OutputComparison.Metadata ? 'Output metadata has changed' : undefined,778kind: ret779};780}781782checkMetadataIfModified() {783if (this.ignoreMetadata) {784return false;785}786const modified = hash(getFormattedMetadataJSON(this.mainDocumentTextModel.transientOptions.transientCellMetadata, this.original?.metadata || {}, this.original?.language)) !== hash(getFormattedMetadataJSON(this.mainDocumentTextModel.transientOptions.transientCellMetadata, this.modified?.metadata ?? {}, this.modified?.language));787if (modified) {788return { reason: undefined };789} else {790return false;791}792}793794updateOutputHeight(diffSide: DiffSide, index: number, height: number) {795if (diffSide === DiffSide.Original) {796this.original.updateOutputHeight(index, height);797} else {798this.modified.updateOutputHeight(index, height);799}800}801802getOutputOffsetInContainer(diffSide: DiffSide, index: number) {803if (diffSide === DiffSide.Original) {804return this.original.getOutputOffset(index);805} else {806return this.modified.getOutputOffset(index);807}808}809810getOutputOffsetInCell(diffSide: DiffSide, index: number) {811const offsetInOutputsContainer = this.getOutputOffsetInContainer(diffSide, index);812813return this._layoutInfo.editorHeight814+ this._layoutInfo.editorMargin815+ this._layoutInfo.metadataHeight816+ this._layoutInfo.cellStatusHeight817+ this._layoutInfo.metadataStatusHeight818+ this._layoutInfo.outputStatusHeight819+ this._layoutInfo.bodyMargin / 2820+ offsetInOutputsContainer;821}822823isOutputEmpty() {824if (this.mainDocumentTextModel.transientOptions.transientOutputs) {825return true;826}827828if (this.checkIfOutputsModified()) {829return false;830}831832// outputs are not changed833834return (this.original?.outputs || []).length === 0;835}836837getRichOutputTotalHeight() {838return Math.max(this.original.getOutputTotalHeight(), this.modified.getOutputTotalHeight());839}840841getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel {842return diffSide === DiffSide.Original ? this.original : this.modified;843}844845getCellByUri(cellUri: URI): IGenericCellViewModel {846if (cellUri.toString() === this.original.uri.toString()) {847return this.original;848} else {849return this.modified;850}851}852853public override computeInputEditorHeight(lineHeight: number): number {854if (this.type === 'modified' &&855typeof this.editorHeightWithUnchangedLinesCollapsed === 'number' &&856this.checkIfInputModified()) {857return this.editorHeightWithUnchangedLinesCollapsed;858}859860return super.computeInputEditorHeight(lineHeight);861}862863private async computeModifiedInputEditorHeight() {864if (this.checkIfInputModified()) {865this.editorHeightWithUnchangedLinesCollapsed = this._layoutInfo.editorHeight = await this.diffEditorHeightCalculator.diffAndComputeHeight(this.original.uri, this.modified.uri);866}867}868869private async computeModifiedMetadataEditorHeight() {870if (this.checkMetadataIfModified()) {871const originalMetadataUri = CellUri.generateCellPropertyUri(this.originalDocument.uri, this.original.handle, Schemas.vscodeNotebookCellMetadata);872const modifiedMetadataUri = CellUri.generateCellPropertyUri(this.modifiedDocument.uri, this.modified.handle, Schemas.vscodeNotebookCellMetadata);873this._layoutInfo.metadataHeight = await this.diffEditorHeightCalculator.diffAndComputeHeight(originalMetadataUri, modifiedMetadataUri);874}875}876877public async computeEditorHeights() {878if (this.type === 'unchanged') {879return;880}881882await Promise.all([this.computeModifiedInputEditorHeight(), this.computeModifiedMetadataEditorHeight()]);883}884885}886887export class SingleSideDiffElementViewModel extends DiffElementCellViewModelBase {888get cellViewModel() {889return this.type === 'insert' ? this.modified! : this.original!;890}891892get originalDocument() {893if (this.type === 'insert') {894return this.otherDocumentTextModel;895} else {896return this.mainDocumentTextModel;897}898}899900get modifiedDocument() {901if (this.type === 'insert') {902return this.mainDocumentTextModel;903} else {904return this.otherDocumentTextModel;905}906}907908override readonly type: 'insert' | 'delete';909910constructor(911mainDocumentTextModel: NotebookTextModel,912readonly otherDocumentTextModel: NotebookTextModel,913original: NotebookCellTextModel | undefined,914modified: NotebookCellTextModel | undefined,915type: 'insert' | 'delete',916editorEventDispatcher: NotebookDiffEditorEventDispatcher,917initData: {918metadataStatusHeight: number;919outputStatusHeight: number;920fontInfo: FontInfo | undefined;921},922notebookService: INotebookService,923configurationService: IConfigurationService,924diffEditorHeightCalculator: IDiffEditorHeightCalculatorService,925index: number926) {927super(mainDocumentTextModel, original, modified, type, editorEventDispatcher, initData, notebookService, index, configurationService, diffEditorHeightCalculator);928this.type = type;929930this._register(this.cellViewModel.onDidChangeOutputLayout(() => {931this._layout({ recomputeOutput: true });932}));933}934935override checkIfInputModified(): false | { reason: string | undefined } {936return {937reason: 'Cell content has changed',938};939}940941getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel {942return this.type === 'insert' ? this.modified! : this.original!;943}944945946checkIfOutputsModified(): false | { reason: string | undefined } {947return false;948}949950checkMetadataIfModified(): false | { reason: string | undefined } {951return false;952}953954updateOutputHeight(diffSide: DiffSide, index: number, height: number) {955this.cellViewModel?.updateOutputHeight(index, height);956}957958getOutputOffsetInContainer(diffSide: DiffSide, index: number) {959return this.cellViewModel.getOutputOffset(index);960}961962getOutputOffsetInCell(diffSide: DiffSide, index: number) {963const offsetInOutputsContainer = this.cellViewModel.getOutputOffset(index);964965return this._layoutInfo.editorHeight966+ this._layoutInfo.editorMargin967+ this._layoutInfo.metadataHeight968+ this._layoutInfo.cellStatusHeight969+ this._layoutInfo.metadataStatusHeight970+ this._layoutInfo.outputStatusHeight971+ this._layoutInfo.bodyMargin / 2972+ offsetInOutputsContainer;973}974975isOutputEmpty() {976if (this.mainDocumentTextModel.transientOptions.transientOutputs) {977return true;978}979980// outputs are not changed981982return (this.original?.outputs || this.modified?.outputs || []).length === 0;983}984985getRichOutputTotalHeight() {986return this.cellViewModel?.getOutputTotalHeight() ?? 0;987}988989getCellByUri(cellUri: URI): IGenericCellViewModel {990return this.cellViewModel;991}992}993994export const enum OutputComparison {995Unchanged = 0,996Metadata = 1,997Other = 2998}9991000export function outputEqual(a: ICellOutput, b: ICellOutput): OutputComparison {1001if (hash(a.metadata) === hash(b.metadata)) {1002return OutputComparison.Other;1003}10041005// metadata not equal1006for (let j = 0; j < a.outputs.length; j++) {1007const aOutputItem = a.outputs[j];1008const bOutputItem = b.outputs[j];10091010if (aOutputItem.mime !== bOutputItem.mime) {1011return OutputComparison.Other;1012}10131014if (aOutputItem.data.buffer.length !== bOutputItem.data.buffer.length) {1015return OutputComparison.Other;1016}10171018for (let k = 0; k < aOutputItem.data.buffer.length; k++) {1019if (aOutputItem.data.buffer[k] !== bOutputItem.data.buffer[k]) {1020return OutputComparison.Other;1021}1022}1023}10241025return OutputComparison.Metadata;1026}10271028function outputsEqual(original: ICellOutput[], modified: ICellOutput[]) {1029if (original.length !== modified.length) {1030return OutputComparison.Other;1031}10321033const len = original.length;1034for (let i = 0; i < len; i++) {1035const a = original[i];1036const b = modified[i];10371038if (hash(a.metadata) !== hash(b.metadata)) {1039return OutputComparison.Metadata;1040}10411042if (a.outputs.length !== b.outputs.length) {1043return OutputComparison.Other;1044}10451046for (let j = 0; j < a.outputs.length; j++) {1047const aOutputItem = a.outputs[j];1048const bOutputItem = b.outputs[j];10491050if (aOutputItem.mime !== bOutputItem.mime) {1051return OutputComparison.Other;1052}10531054if (aOutputItem.data.buffer.length !== bOutputItem.data.buffer.length) {1055return OutputComparison.Other;1056}10571058for (let k = 0; k < aOutputItem.data.buffer.length; k++) {1059if (aOutputItem.data.buffer[k] !== bOutputItem.data.buffer[k]) {1060return OutputComparison.Other;1061}1062}1063}1064}10651066return OutputComparison.Unchanged;1067}10681069export function getStreamOutputData(outputs: IOutputItemDto[]) {1070if (!outputs.length) {1071return null;1072}10731074const first = outputs[0];1075const mime = first.mime;1076const sameStream = !outputs.find(op => op.mime !== mime);10771078if (sameStream) {1079return outputs.map(opit => opit.data.toString()).join('');1080} else {1081return null;1082}1083}10841085export function getFormattedOutputJSON(outputs: IOutputDto[]) {1086if (outputs.length === 1) {1087const streamOutputData = getStreamOutputData(outputs[0].outputs);1088if (streamOutputData) {1089return streamOutputData;1090}1091}10921093return JSON.stringify(outputs.map(output => {1094return ({1095metadata: output.metadata,1096outputItems: output.outputs.map(opit => ({1097mimeType: opit.mime,1098data: opit.data.toString()1099}))1100});1101}), undefined, '\t');1102}110311041105