From 2f0ec17854869196c8f1ded5738d6d60a81cfdc7 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 17 Nov 2023 18:40:04 +0100 Subject: [PATCH] Fixes #198432 --- .../diffEditorItemTemplate.ts | 51 +++++++++++------ .../multiDiffEditorViewModel.ts | 43 +++++++++++++++ .../multiDiffEditorWidget.ts | 14 ++++- .../multiDiffEditorWidgetImpl.ts | 55 ++++++------------- .../browser/multiDiffEditor.ts | 13 ++++- .../browser/multiDiffEditorInput.ts | 14 ++++- 6 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 7021e1a01471f..4587abb6d44c1 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -6,8 +6,8 @@ import { h } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { autorun, derived } from 'vs/base/common/observable'; -import { globalTransaction, observableValue } from 'vs/base/common/observableInternal/base'; +import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { IObservable, globalTransaction, observableValue } from 'vs/base/common/observableInternal/base'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IDocumentDiffItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; @@ -16,6 +16,7 @@ import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IObjectData, IPooledObject } from './objectPool'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; export class TemplateData implements IObjectData { constructor( @@ -85,10 +86,14 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< ]) ]); - private readonly _editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, { + public readonly editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, { overflowWidgetsDomNode: this._overflowWidgetsDomNode, }, {})); + private readonly isModifedFocused = isFocused(this.editor.getModifiedEditor()); + private readonly isOriginalFocused = isFocused(this.editor.getOriginalEditor()); + public readonly isFocused = derived(this, reader => this.isModifedFocused.read(reader) || this.isOriginalFocused.read(reader)); + private readonly _resourceLabel = this._workbenchUIElementFactory.createResourceLabel ? this._register(this._workbenchUIElementFactory.createResourceLabel(this._elements.title)) : undefined; @@ -114,21 +119,21 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.editor.style.display = this._collapsed.get() ? 'none' : 'block'; })); - this._editor.getModifiedEditor().onDidLayoutChange(e => { - const width = this._editor.getModifiedEditor().getLayoutInfo().contentWidth; + this.editor.getModifiedEditor().onDidLayoutChange(e => { + const width = this.editor.getModifiedEditor().getLayoutInfo().contentWidth; this._modifiedWidth.set(width, undefined); }); - this._editor.getOriginalEditor().onDidLayoutChange(e => { - const width = this._editor.getOriginalEditor().getLayoutInfo().contentWidth; + this.editor.getOriginalEditor().onDidLayoutChange(e => { + const width = this.editor.getOriginalEditor().getLayoutInfo().contentWidth; this._originalWidth.set(width, undefined); }); - this._register(this._editor.onDidContentSizeChange(e => { + this._register(this.editor.onDidContentSizeChange(e => { globalTransaction(tx => { this._contentHeight.set(e.contentHeight, tx); - this._modifiedContentWidth.set(this._editor.getModifiedEditor().getContentWidth(), tx); - this._originalContentWidth.set(this._editor.getOriginalEditor().getContentWidth(), tx); + this._modifiedContentWidth.set(this.editor.getModifiedEditor().getContentWidth(), tx); + this._originalContentWidth.set(this.editor.getOriginalEditor().getContentWidth(), tx); }); })); @@ -138,9 +143,9 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< public setScrollLeft(left: number): void { if (this._modifiedContentWidth.get() - this._modifiedWidth.get() > this._originalContentWidth.get() - this._originalWidth.get()) { - this._editor.getModifiedEditor().setScrollLeft(left); + this.editor.getModifiedEditor().setScrollLeft(left); } else { - this._editor.getOriginalEditor().setScrollLeft(left); + this.editor.getOriginalEditor().setScrollLeft(left); } } @@ -169,12 +174,12 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< if (data.entry.onOptionsDidChange) { this._dataStore.add(data.entry.onOptionsDidChange(() => { - this._editor.updateOptions(updateOptions(data.entry.options ?? {})); + this.editor.updateOptions(updateOptions(data.entry.options ?? {})); })); } globalTransaction(tx => { - this._editor.setModel(data.viewModel, tx); - this._editor.updateOptions(updateOptions(data.entry.options ?? {})); + this.editor.setModel(data.viewModel, tx); + this.editor.updateOptions(updateOptions(data.entry.options ?? {})); }); } @@ -189,12 +194,12 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.header.style.transform = `translateY(${Math.max(0, Math.min(verticalRange.length - this._elements.header.clientHeight, viewPort.start - verticalRange.start))}px)`; globalTransaction(tx => { - this._editor.layout({ + this.editor.layout({ width: width, height: verticalRange.length - this._outerEditorHeight, }); }); - this._editor.getOriginalEditor().setScrollTop(editorScroll); + this.editor.getOriginalEditor().setScrollTop(editorScroll); } public hide(): void { @@ -202,3 +207,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible } } + +function isFocused(editor: ICodeEditor): IObservable { + return observableFromEvent( + h => { + const store = new DisposableStore(); + store.add(editor.onDidFocusEditorWidget(() => h(true))); + store.add(editor.onDidBlurEditorWidget(() => h(false))); + return store; + }, + () => editor.hasWidgetFocus() + ); +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts new file mode 100644 index 0000000000000..5bd8541e52a08 --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; + +export class MultiDiffEditorViewModel extends Disposable { + private readonly _documents = observableFromEvent(this._model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this._model.documents); + + public readonly items = derivedWithStore(this, + (reader, store) => this._documents.read(reader).map(d => store.add(new DocumentDiffItemViewModel(d, this._diffEditorViewModelFactory))) + ).recomputeInitiallyAndOnChange(this._store); + + public readonly activeDiffItem = observableValue(this, undefined); + + constructor( + private readonly _model: IMultiDiffEditorModel, + private readonly _diffEditorViewModelFactory: DiffEditorWidget + ) { + super(); + } +} + +export class DocumentDiffItemViewModel extends Disposable { + public readonly diffEditorViewModel: IDiffEditorViewModel; + + constructor( + public readonly entry: LazyPromise, + diffEditorViewModelFactory: DiffEditorWidget + ) { + super(); + + this.diffEditorViewModel = this._register(diffEditorViewModelFactory.createViewModel({ + original: entry.value!.original!, + modified: entry.value!.modified!, + })); + } +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 69b47dfb5f518..97376e36d1924 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -5,14 +5,16 @@ import { Dimension } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; -import { derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; +import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { MultiDiffEditorViewModel, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { Event } from 'vs/base/common/event'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -50,4 +52,12 @@ export class MultiDiffEditorWidget extends Disposable { public layout(dimension: Dimension): void { this._dimension.set(dimension, undefined); } + + private readonly _activeControl = derived(this, (reader) => this._widgetImpl.read(reader).activeControl.read(reader)); + + public getActiveControl(): any | undefined { + return this._activeControl.get(); + } + + public readonly onDidChangeActiveControl = Event.fromObservableLight(this._activeControl); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 2261883a5560d..ffe1aa9eb8cca 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -6,20 +6,20 @@ import { Dimension, getWindow, h, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, autorun, derived, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { IObservable, IReader, autorun, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./style'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; -import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; -import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate'; import { ObjectPool } from './objectPool'; import { disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; import { findFirstMaxBy } from 'vs/base/common/arraysFind'; +import { MultiDiffEditorViewModel, DocumentDiffItemViewModel } from './multiDiffEditorViewModel'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div', { @@ -85,6 +85,9 @@ export class MultiDiffEditorWidgetImpl extends Disposable { ); private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0)); + public readonly activeDiffItem = derived(this, reader => this._viewItems.read(reader).find(i => i.template.read(reader)?.isFocused.read(reader))); + public readonly lastActiveDiffItem = derivedObservableWithCache((reader, lastValue) => this.activeDiffItem.read(reader) ?? lastValue); + public readonly activeControl = derived(this, reader => this.lastActiveDiffItem.read(reader)?.template.read(reader)?.editor); constructor( private readonly _element: HTMLElement, @@ -95,6 +98,13 @@ export class MultiDiffEditorWidgetImpl extends Disposable { ) { super(); + this._register(autorun((reader) => { + const lastActiveDiffItem = this.lastActiveDiffItem.read(reader); + transaction(tx => { + this._viewModel.read(reader)?.activeDiffItem.set(lastActiveDiffItem?.viewModel, tx); + }); + })); + this._register(autorun((reader) => { /** @description Update widget dimension */ const dimension = this._dimension.read(reader); @@ -179,37 +189,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } } -export class MultiDiffEditorViewModel extends Disposable { - private readonly _documents = observableFromEvent(this._model.onDidChange, /** @description MultiDiffEditorViewModel.documents */() => this._model.documents); - - public readonly items = derivedWithStore(this, - (reader, store) => this._documents.read(reader).map(d => store.add(new DocumentDiffItemViewModel(d, this._diffEditorViewModelFactory))) - ).recomputeInitiallyAndOnChange(this._store); - - constructor( - private readonly _model: IMultiDiffEditorModel, - private readonly _diffEditorViewModelFactory: DiffEditorWidget, - ) { - super(); - } -} - -class DocumentDiffItemViewModel extends Disposable { - public readonly diffEditorViewModel: IDiffEditorViewModel; - - constructor( - public readonly entry: LazyPromise, - diffEditorViewModelFactory: DiffEditorWidget, - ) { - super(); - - this.diffEditorViewModel = this._register(diffEditorViewModelFactory.createViewModel({ - original: entry.value!.original!, - modified: entry.value!.modified!, - })); - } -} - class VirtualizedViewItem extends Disposable { // TODO this should be in the view model private readonly _lastTemplateData = observableValue<{ contentHeight: number; maxScroll: { maxScroll: number; width: number } }>( @@ -224,8 +203,10 @@ class VirtualizedViewItem extends Disposable { public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? this._lastTemplateData.read(reader).maxScroll); + public readonly template = derived(this, reader => this._templateRef.read(reader)?.object); + constructor( - private readonly _viewModel: DocumentDiffItemViewModel, + public readonly viewModel: DocumentDiffItemViewModel, private readonly _objectPool: ObjectPool, private readonly _scrollLeft: IObservable, ) { @@ -243,7 +224,7 @@ class VirtualizedViewItem extends Disposable { } public override toString(): string { - return `VirtualViewItem(${this._viewModel.entry.value!.title})`; + return `VirtualViewItem(${this.viewModel.entry.value!.title})`; } public hide(): void { @@ -263,7 +244,7 @@ class VirtualizedViewItem extends Disposable { public render(verticalSpace: OffsetRange, offset: number, width: number, viewPort: OffsetRange): void { let ref = this._templateRef.get(); if (!ref) { - ref = this._objectPool.getUnusedObj(new TemplateData(this._viewModel.diffEditorViewModel, this._viewModel.entry.value!)); + ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel.diffEditorViewModel, this.viewModel.entry.value!)); this._templateRef.set(ref, undefined); } ref.object.render(verticalSpace, width, offset, viewPort); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 4c74c8e748474..4fddc9ac6b002 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -15,6 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { ICompositeControl } from 'vs/workbench/common/composite'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; @@ -38,6 +39,10 @@ export class MultiDiffEditor extends EditorPane { parent, this.instantiationService.createInstance(WorkbenchUIElementFactory), )); + + this._register(this._multiDiffEditorWidget.onDidChangeActiveControl(() => { + this._onDidChangeControl.fire(); + })); } override async setInput(input: MultiDiffEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { @@ -51,11 +56,15 @@ export class MultiDiffEditor extends EditorPane { override async clearInput(): Promise { await super.clearInput(); - this._multiDiffEditorWidget?.setViewModel(undefined); + this._multiDiffEditorWidget!.setViewModel(undefined); } layout(dimension: DOM.Dimension): void { - this._multiDiffEditorWidget?.layout(dimension); + this._multiDiffEditorWidget!.layout(dimension); + } + + override getControl(): ICompositeControl | undefined { + return this._multiDiffEditorWidget!.getActiveControl(); } } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 29e3eaebf5311..d97b3424f0ca2 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -8,7 +8,7 @@ import { deepClone } from 'vs/base/common/objects'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; @@ -16,8 +16,9 @@ import { localize } from 'vs/nls'; import { IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { ILanguageSupport } from 'vs/workbench/services/textfile/common/textfiles'; -export class MultiDiffEditorInput extends EditorInput { +export class MultiDiffEditorInput extends EditorInput implements ILanguageSupport { static readonly ID: string = 'workbench.input.multiDiffEditor'; get resource(): URI | undefined { @@ -54,6 +55,15 @@ export class MultiDiffEditorInput extends EditorInput { super(); } + setLanguageId(languageId: string, source?: string | undefined): void { + const activeDiffItem = this.viewModel?.activeDiffItem.get(); + const value = activeDiffItem?.entry?.value; + if (!value) { return; } + const target = value.modified ?? value.original; + if (!target) { return; } + target.setLanguage(languageId, source); + } + // TODO this should return the view model async getModel(): Promise { if (!this._model) {