Skip to content

Commit b315102

Browse files
committed
Implements global vertical scrolling
1 parent f404d11 commit b315102

2 files changed

Lines changed: 92 additions & 34 deletions

File tree

src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,28 @@ export class TemplateData implements IObjectData {
3131

3232
export class DiffEditorItemTemplate extends Disposable implements IPooledObject<TemplateData> {
3333
private readonly _contentHeight = observableValue<number>(this, 500);
34-
private readonly _collapsed = observableValue<boolean>(this, false);
3534
public readonly height = derived(this, reader => {
3635
const h = this._collapsed.read(reader) ? 0 : this._contentHeight.read(reader);
3736
return h + this._outerEditorHeight;
3837
});
3938

39+
private readonly _modifiedContentWidth = observableValue<number>(this, 0);
40+
private readonly _modifiedWidth = observableValue<number>(this, 0);
41+
private readonly _originalContentWidth = observableValue<number>(this, 0);
42+
private readonly _originalWidth = observableValue<number>(this, 0);
43+
44+
public readonly maxScroll = derived(this, reader => {
45+
const scroll1 = this._modifiedContentWidth.read(reader) - this._modifiedWidth.read(reader);
46+
const scroll2 = this._originalContentWidth.read(reader) - this._originalWidth.read(reader);
47+
if (scroll1 > scroll2) {
48+
return { maxScroll: scroll1, width: this._modifiedWidth.read(reader) };
49+
} else {
50+
return { maxScroll: scroll2, width: this._originalWidth.read(reader) };
51+
}
52+
});
53+
54+
private readonly _collapsed = observableValue<boolean>(this, false);
55+
4056
private readonly _elements = h('div', {
4157
style: {
4258
display: 'flex',
@@ -109,16 +125,36 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
109125
this._elements.editor.style.display = this._collapsed.get() ? 'none' : 'block';
110126
}));
111127

128+
this._editor.getModifiedEditor().onDidLayoutChange(e => {
129+
const width = this._editor.getModifiedEditor().getLayoutInfo().contentWidth;
130+
this._modifiedWidth.set(width, undefined);
131+
});
132+
133+
this._editor.getOriginalEditor().onDidLayoutChange(e => {
134+
const width = this._editor.getOriginalEditor().getLayoutInfo().contentWidth;
135+
this._originalWidth.set(width, undefined);
136+
});
137+
112138
this._register(this._editor.onDidContentSizeChange(e => {
113139
globalTransaction(tx => {
114140
this._contentHeight.set(e.contentHeight, tx);
141+
this._modifiedContentWidth.set(this._editor.getModifiedEditor().getContentWidth(), tx);
142+
this._originalContentWidth.set(this._editor.getOriginalEditor().getContentWidth(), tx);
115143
});
116144
}));
117145

146+
118147
this._container.appendChild(this._elements.root);
119148

120-
this._outerEditorHeight = 38; //this._elements.header.clientHeight; //this._elements.root.clientHeight - this._elements.editor.clientHeight;
121-
//console.log('outerEditorHeight', this._outerEditorHeight);
149+
this._outerEditorHeight = 38;
150+
}
151+
152+
public setScrollLeft(left: number): void {
153+
if (this._modifiedContentWidth.get() - this._modifiedWidth.get() > this._originalContentWidth.get() - this._originalWidth.get()) {
154+
this._editor.getModifiedEditor().setScrollLeft(left);
155+
} else {
156+
this._editor.getOriginalEditor().setScrollLeft(left);
157+
}
122158
}
123159

124160
public setData(data: TemplateData) {
@@ -128,19 +164,14 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
128164
});
129165
}
130166

131-
public hide(): void {
132-
this._elements.root.style.top = `-100000px`;
133-
this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible
134-
}
135-
136167
public render(verticalRange: OffsetRange, width: number, editorScroll: number, viewPort: OffsetRange): void {
137168
this._elements.root.style.visibility = 'visible';
138169
this._elements.root.style.top = `${verticalRange.start}px`;
139170
this._elements.root.style.height = `${verticalRange.length}px`;
140171
this._elements.root.style.width = `${width}px`;
141172
this._elements.root.style.position = 'absolute';
142173

143-
174+
// For sticky scroll
144175
this._elements.header.style.transform = `translateY(${Math.max(0, Math.min(verticalRange.length - this._elements.header.clientHeight, viewPort.start - verticalRange.start))}px)`;
145176

146177
globalTransaction(tx => {
@@ -151,4 +182,9 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
151182
});
152183
this._editor.getOriginalEditor().setScrollTop(editorScroll);
153184
}
185+
186+
public hide(): void {
187+
this._elements.root.style.top = `-100000px`;
188+
this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible
189+
}
154190
}

src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate';
1919
import { ObjectPool } from './objectPool';
2020
import { disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base';
2121
import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory';
22+
import { findFirstMaxBy } from 'vs/base/common/arraysFind';
2223

2324
export class MultiDiffEditorWidgetImpl extends Disposable {
2425
private readonly _elements = h('div', {
@@ -58,14 +59,28 @@ export class MultiDiffEditorWidgetImpl extends Disposable {
5859
},
5960
}, {}));
6061

62+
private readonly _scrollable = this._register(new Scrollable({
63+
forceIntegerValues: false,
64+
scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(this._element), cb),
65+
smoothScrollDuration: 100,
66+
}));
67+
68+
private readonly _scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, {
69+
vertical: ScrollbarVisibility.Auto,
70+
horizontal: ScrollbarVisibility.Auto,
71+
className: 'monaco-component',
72+
useShadows: false,
73+
}, this._scrollable));
74+
75+
private readonly _scrollTop = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollTop */ this._scrollableElement.getScrollPosition().scrollTop);
76+
private readonly _scrollLeft = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollLeft */ this._scrollableElement.getScrollPosition().scrollLeft);
77+
6178
private readonly _viewItems = derivedWithStore<DiffEditorItem[]>(this,
62-
(reader, store) => this._documents.read(reader).map(d => store.add(new DiffEditorItem(this._objectPool, d, this._editor)))
79+
(reader, store) => this._documents.read(reader).map(d => store.add(new DiffEditorItem(this._objectPool, d, this._editor, this._scrollLeft)))
6380
);
6481

6582
private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0));
6683

67-
private readonly _scrollTop: IObservable<number>;
68-
6984
constructor(
7085
private readonly _element: HTMLElement,
7186
private readonly _dimension: IObservable<Dimension | undefined>,
@@ -75,45 +90,38 @@ export class MultiDiffEditorWidgetImpl extends Disposable {
7590
) {
7691
super();
7792

78-
//this._sizeObserver.setAutomaticLayout(true);
79-
8093
this._register(autorun((reader) => {
8194
/** @description Update widget dimension */
8295
const dimension = this._dimension.read(reader);
8396
this._sizeObserver.observe(dimension);
8497
}));
8598

86-
const scrollable = this._register(new Scrollable({
87-
forceIntegerValues: false,
88-
scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(_element), cb),
89-
smoothScrollDuration: 100,
90-
}));
91-
9299
this._elements.content.style.position = 'relative';
93100

94-
const scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, {
95-
vertical: ScrollbarVisibility.Auto,
96-
className: 'monaco-component',
97-
useShadows: false,
98-
}, scrollable));
99-
100-
this._scrollTop = observableFromEvent(scrollableElement.onScroll, () => /** @description onScroll */ scrollableElement.getScrollPosition().scrollTop);
101-
102101
this._register(autorun((reader) => {
103102
/** @description Update scroll dimensions */
104103
const height = this._sizeObserver.height.read(reader);
105104
this._elements.root.style.height = `${height}px`;
106105
const totalHeight = this._totalHeight.read(reader);
107106
this._elements.content.style.height = `${totalHeight}px`;
108-
scrollableElement.setScrollDimensions({
107+
108+
let scrollWidth = _element.clientWidth;
109+
const viewItems = this._viewItems.read(reader);
110+
const max = findFirstMaxBy(viewItems, i => i.maxScroll.read(reader).maxScroll);
111+
if (max) {
112+
const maxScroll = max.maxScroll.read(reader);
113+
scrollWidth = _element.clientWidth + maxScroll.maxScroll;
114+
}
115+
116+
this._scrollableElement.setScrollDimensions({
109117
width: _element.clientWidth,
110118
height: height,
111119
scrollHeight: totalHeight,
112-
scrollWidth: _element.clientWidth,
120+
scrollWidth,
113121
});
114122
}));
115123

116-
_element.replaceChildren(scrollableElement.getDomNode());
124+
_element.replaceChildren(this._scrollableElement.getDomNode());
117125
this._register(toDisposable(() => {
118126
_element.replaceChildren();
119127
}));
@@ -163,25 +171,36 @@ export class MultiDiffEditorWidgetImpl extends Disposable {
163171
}
164172

165173
class DiffEditorItem extends Disposable {
166-
private readonly _lastTemplateHeight = observableValue(this, 500);
174+
private readonly _lastTemplateData = observableValue<{ contentHeight: number; maxScroll: { maxScroll: number; width: number } }>(
175+
this,
176+
{ contentHeight: 500, maxScroll: { maxScroll: 0, width: 0 }, }
177+
);
167178
private readonly _templateRef = this._register(disposableObservableValue<IReference<DiffEditorItemTemplate> | undefined>(this, undefined));
168179
private _vm: IDiffEditorViewModel | undefined;
169180

170181
public readonly contentHeight = derived(this, reader =>
171-
this._templateRef.read(reader)?.object.height?.read(reader) ?? this._lastTemplateHeight.read(reader)
182+
this._templateRef.read(reader)?.object.height?.read(reader) ?? this._lastTemplateData.read(reader).contentHeight
172183
);
173184

185+
public readonly maxScroll = derived(this, reader => this._templateRef.read(reader)?.object.maxScroll.read(reader) ?? this._lastTemplateData.read(reader).maxScroll);
186+
174187
constructor(
175188
private readonly _objectPool: ObjectPool<TemplateData, DiffEditorItemTemplate>,
176189
private readonly _entry: LazyPromise<IDiffEntry>,
177190
baseDiffEditorWidget: DiffEditorWidget,
191+
private readonly _scrollLeft: IObservable<number>,
178192
) {
179193
super();
180194

181195
this._vm = this._register(baseDiffEditorWidget.createViewModel({
182196
original: _entry.value!.original!,
183197
modified: _entry.value!.modified!,
184198
}));
199+
200+
this._register(autorun((reader) => {
201+
const scrollLeft = this._scrollLeft.read(reader);
202+
this._templateRef.read(reader)?.object.setScrollLeft(scrollLeft);
203+
}));
185204
}
186205

187206
override dispose(): void {
@@ -197,7 +216,10 @@ class DiffEditorItem extends Disposable {
197216
const ref = this._templateRef.get();
198217
transaction(tx => {
199218
if (ref) {
200-
this._lastTemplateHeight.set(ref.object.height.get(), tx);
219+
this._lastTemplateData.set({
220+
contentHeight: ref.object.height.get(),
221+
maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll
222+
}, tx);
201223
ref.object.hide();
202224
}
203225
this._templateRef.set(undefined, tx);

0 commit comments

Comments
 (0)