Skip to content

Commit

Permalink
Implements global vertical scrolling
Browse files Browse the repository at this point in the history
  • Loading branch information
hediet committed Nov 15, 2023
1 parent f404d11 commit b315102
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,28 @@ export class TemplateData implements IObjectData {

export class DiffEditorItemTemplate extends Disposable implements IPooledObject<TemplateData> {
private readonly _contentHeight = observableValue<number>(this, 500);
private readonly _collapsed = observableValue<boolean>(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<number>(this, 0);
private readonly _modifiedWidth = observableValue<number>(this, 0);
private readonly _originalContentWidth = observableValue<number>(this, 0);
private readonly _originalWidth = observableValue<number>(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<boolean>(this, false);

private readonly _elements = h('div', {
style: {
display: 'flex',
Expand Down Expand Up @@ -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) {
Expand All @@ -128,19 +164,14 @@ 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`;
this._elements.root.style.height = `${verticalRange.length}px`;
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 => {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down Expand Up @@ -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<DiffEditorItem[]>(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<number>;

constructor(
private readonly _element: HTMLElement,
private readonly _dimension: IObservable<Dimension | undefined>,
Expand All @@ -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();
}));
Expand Down Expand Up @@ -163,25 +171,36 @@ 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<IReference<DiffEditorItemTemplate> | 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<TemplateData, DiffEditorItemTemplate>,
private readonly _entry: LazyPromise<IDiffEntry>,
baseDiffEditorWidget: DiffEditorWidget,
private readonly _scrollLeft: IObservable<number>,
) {
super();

this._vm = this._register(baseDiffEditorWidget.createViewModel({
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 {
Expand All @@ -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);
Expand Down

0 comments on commit b315102

Please sign in to comment.