diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 60c1bfb74fd6f..aaa832d38049c 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -31,12 +31,28 @@ export class TemplateData implements IObjectData { export class DiffEditorItemTemplate extends Disposable implements IPooledObject { private readonly _contentHeight = observableValue(this, 500); - private readonly _collapsed = observableValue(this, false); public readonly height = derived(this, reader => { const h = this._collapsed.read(reader) ? 0 : this._contentHeight.read(reader); return h + this._outerEditorHeight; }); + private readonly _modifiedContentWidth = observableValue(this, 0); + private readonly _modifiedWidth = observableValue(this, 0); + private readonly _originalContentWidth = observableValue(this, 0); + private readonly _originalWidth = observableValue(this, 0); + + public readonly maxScroll = derived(this, reader => { + const scroll1 = this._modifiedContentWidth.read(reader) - this._modifiedWidth.read(reader); + const scroll2 = this._originalContentWidth.read(reader) - this._originalWidth.read(reader); + if (scroll1 > scroll2) { + return { maxScroll: scroll1, width: this._modifiedWidth.read(reader) }; + } else { + return { maxScroll: scroll2, width: this._originalWidth.read(reader) }; + } + }); + + private readonly _collapsed = observableValue(this, false); + private readonly _elements = h('div', { style: { display: 'flex', @@ -109,16 +125,36 @@ 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._modifiedWidth.set(width, undefined); + }); + + this._editor.getOriginalEditor().onDidLayoutChange(e => { + const width = this._editor.getOriginalEditor().getLayoutInfo().contentWidth; + this._originalWidth.set(width, undefined); + }); + 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._container.appendChild(this._elements.root); - this._outerEditorHeight = 38; //this._elements.header.clientHeight; //this._elements.root.clientHeight - this._elements.editor.clientHeight; - //console.log('outerEditorHeight', this._outerEditorHeight); + this._outerEditorHeight = 38; + } + + public setScrollLeft(left: number): void { + if (this._modifiedContentWidth.get() - this._modifiedWidth.get() > this._originalContentWidth.get() - this._originalWidth.get()) { + this._editor.getModifiedEditor().setScrollLeft(left); + } else { + this._editor.getOriginalEditor().setScrollLeft(left); + } } public setData(data: TemplateData) { @@ -128,11 +164,6 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); } - public hide(): void { - this._elements.root.style.top = `-100000px`; - this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible - } - public render(verticalRange: OffsetRange, width: number, editorScroll: number, viewPort: OffsetRange): void { this._elements.root.style.visibility = 'visible'; this._elements.root.style.top = `${verticalRange.start}px`; @@ -140,7 +171,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.root.style.width = `${width}px`; this._elements.root.style.position = 'absolute'; - + // For sticky scroll this._elements.header.style.transform = `translateY(${Math.max(0, Math.min(verticalRange.length - this._elements.header.clientHeight, viewPort.start - verticalRange.start))}px)`; globalTransaction(tx => { @@ -151,4 +182,9 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); this._editor.getOriginalEditor().setScrollTop(editorScroll); } + + public hide(): void { + this._elements.root.style.top = `-100000px`; + this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible + } } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index f372ba1ba7191..c8e0db5060e5d 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -19,6 +19,7 @@ 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'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div', { @@ -58,14 +59,28 @@ export class MultiDiffEditorWidgetImpl extends Disposable { }, }, {})); + private readonly _scrollable = this._register(new Scrollable({ + forceIntegerValues: false, + scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(this._element), cb), + smoothScrollDuration: 100, + })); + + private readonly _scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, { + vertical: ScrollbarVisibility.Auto, + horizontal: ScrollbarVisibility.Auto, + className: 'monaco-component', + useShadows: false, + }, this._scrollable)); + + private readonly _scrollTop = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollTop */ this._scrollableElement.getScrollPosition().scrollTop); + private readonly _scrollLeft = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollLeft */ this._scrollableElement.getScrollPosition().scrollLeft); + private readonly _viewItems = derivedWithStore(this, - (reader, store) => this._documents.read(reader).map(d => store.add(new DiffEditorItem(this._objectPool, d, this._editor))) + (reader, store) => this._documents.read(reader).map(d => store.add(new DiffEditorItem(this._objectPool, d, this._editor, this._scrollLeft))) ); private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0)); - private readonly _scrollTop: IObservable; - constructor( private readonly _element: HTMLElement, private readonly _dimension: IObservable, @@ -75,45 +90,38 @@ export class MultiDiffEditorWidgetImpl extends Disposable { ) { super(); - //this._sizeObserver.setAutomaticLayout(true); - this._register(autorun((reader) => { /** @description Update widget dimension */ const dimension = this._dimension.read(reader); this._sizeObserver.observe(dimension); })); - const scrollable = this._register(new Scrollable({ - forceIntegerValues: false, - scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(_element), cb), - smoothScrollDuration: 100, - })); - this._elements.content.style.position = 'relative'; - const scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, { - vertical: ScrollbarVisibility.Auto, - className: 'monaco-component', - useShadows: false, - }, scrollable)); - - this._scrollTop = observableFromEvent(scrollableElement.onScroll, () => /** @description onScroll */ scrollableElement.getScrollPosition().scrollTop); - this._register(autorun((reader) => { /** @description Update scroll dimensions */ const height = this._sizeObserver.height.read(reader); this._elements.root.style.height = `${height}px`; const totalHeight = this._totalHeight.read(reader); this._elements.content.style.height = `${totalHeight}px`; - scrollableElement.setScrollDimensions({ + + let scrollWidth = _element.clientWidth; + const viewItems = this._viewItems.read(reader); + const max = findFirstMaxBy(viewItems, i => i.maxScroll.read(reader).maxScroll); + if (max) { + const maxScroll = max.maxScroll.read(reader); + scrollWidth = _element.clientWidth + maxScroll.maxScroll; + } + + this._scrollableElement.setScrollDimensions({ width: _element.clientWidth, height: height, scrollHeight: totalHeight, - scrollWidth: _element.clientWidth, + scrollWidth, }); })); - _element.replaceChildren(scrollableElement.getDomNode()); + _element.replaceChildren(this._scrollableElement.getDomNode()); this._register(toDisposable(() => { _element.replaceChildren(); })); @@ -163,18 +171,24 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } class DiffEditorItem extends Disposable { - private readonly _lastTemplateHeight = observableValue(this, 500); + private readonly _lastTemplateData = observableValue<{ contentHeight: number; maxScroll: { maxScroll: number; width: number } }>( + this, + { contentHeight: 500, maxScroll: { maxScroll: 0, width: 0 }, } + ); private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); private _vm: IDiffEditorViewModel | undefined; public readonly contentHeight = derived(this, reader => - this._templateRef.read(reader)?.object.height?.read(reader) ?? this._lastTemplateHeight.read(reader) + this._templateRef.read(reader)?.object.height?.read(reader) ?? this._lastTemplateData.read(reader).contentHeight ); + public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? this._lastTemplateData.read(reader).maxScroll); + constructor( private readonly _objectPool: ObjectPool, private readonly _entry: LazyPromise, baseDiffEditorWidget: DiffEditorWidget, + private readonly _scrollLeft: IObservable, ) { super(); @@ -182,6 +196,11 @@ class DiffEditorItem extends Disposable { original: _entry.value!.original!, modified: _entry.value!.modified!, })); + + this._register(autorun((reader) => { + const scrollLeft = this._scrollLeft.read(reader); + this._templateRef.read(reader)?.object.setScrollLeft(scrollLeft); + })); } override dispose(): void { @@ -197,7 +216,10 @@ class DiffEditorItem extends Disposable { const ref = this._templateRef.get(); transaction(tx => { if (ref) { - this._lastTemplateHeight.set(ref.object.height.get(), tx); + this._lastTemplateData.set({ + contentHeight: ref.object.height.get(), + maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll + }, tx); ref.object.hide(); } this._templateRef.set(undefined, tx);