Skip to content

Commit 9760f34

Browse files
authored
Merge pull request microsoft#178627 from microsoft/rebornix/awake-lion
List View top padding
2 parents 01ff455 + fa823ba commit 9760f34

File tree

9 files changed

+140
-32
lines changed

9 files changed

+140
-32
lines changed

src/vs/base/browser/ui/list/listPaging.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export interface IPagedListOptions<T> {
108108
readonly mouseSupport?: boolean;
109109
readonly horizontalScrolling?: boolean;
110110
readonly scrollByPage?: boolean;
111-
readonly additionalScrollHeight?: number;
111+
readonly paddingBottom?: number;
112112
}
113113

114114
function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: IPagedListOptions<T>): IListOptions<number> {

src/vs/base/browser/ui/list/listView.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ export interface IListViewAccessibilityProvider<T> {
5656
}
5757

5858
export interface IListViewOptionsUpdate {
59-
readonly additionalScrollHeight?: number;
6059
readonly smoothScrolling?: boolean;
6160
readonly horizontalScrolling?: boolean;
6261
readonly scrollByPage?: boolean;
6362
readonly mouseWheelScrollSensitivity?: number;
6463
readonly fastScrollSensitivity?: number;
64+
readonly paddingTop?: number;
65+
readonly paddingBottom?: number;
6566
}
6667

6768
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
@@ -298,7 +299,7 @@ export class ListView<T> implements IListView<T> {
298299
private setRowLineHeight: boolean;
299300
private setRowHeight: boolean;
300301
private supportDynamicHeights: boolean;
301-
private additionalScrollHeight: number;
302+
private paddingBottom: number;
302303
private accessibilityProvider: ListViewAccessibilityProvider<T>;
303304
private scrollWidth: number | undefined;
304305

@@ -364,7 +365,7 @@ export class ListView<T> implements IListView<T> {
364365

365366
this.items = [];
366367
this.itemId = 0;
367-
this.rangeMap = new RangeMap();
368+
this.rangeMap = new RangeMap(options.paddingTop ?? 0);
368369

369370
for (const renderer of renderers) {
370371
this.renderers.set(renderer.templateId, renderer);
@@ -386,7 +387,7 @@ export class ListView<T> implements IListView<T> {
386387
this._horizontalScrolling = options.horizontalScrolling ?? DefaultOptions.horizontalScrolling;
387388
this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling);
388389

389-
this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;
390+
this.paddingBottom = typeof options.paddingBottom === 'undefined' ? 0 : options.paddingBottom;
390391

391392
this.accessibilityProvider = new ListViewAccessibilityProvider(options.accessibilityProvider);
392393

@@ -441,8 +442,8 @@ export class ListView<T> implements IListView<T> {
441442
}
442443

443444
updateOptions(options: IListViewOptionsUpdate) {
444-
if (options.additionalScrollHeight !== undefined) {
445-
this.additionalScrollHeight = options.additionalScrollHeight;
445+
if (options.paddingBottom !== undefined) {
446+
this.paddingBottom = options.paddingBottom;
446447
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
447448
}
448449

@@ -471,6 +472,22 @@ export class ListView<T> implements IListView<T> {
471472
if (scrollableOptions) {
472473
this.scrollableElement.updateOptions(scrollableOptions);
473474
}
475+
476+
if (options.paddingTop !== undefined && options.paddingTop !== this.rangeMap.paddingTop) {
477+
// trigger a rerender
478+
const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
479+
const offset = options.paddingTop - this.rangeMap.paddingTop;
480+
this.rangeMap.paddingTop = options.paddingTop;
481+
482+
this.render(lastRenderRange, Math.max(0, this.lastRenderTop + offset), this.lastRenderHeight, undefined, undefined, true);
483+
this.setScrollTop(this.lastRenderTop);
484+
485+
this.eventuallyUpdateScrollDimensions();
486+
487+
if (this.supportDynamicHeights) {
488+
this._rerender(this.lastRenderTop, this.lastRenderHeight);
489+
}
490+
}
474491
}
475492

476493
delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
@@ -602,7 +619,7 @@ export class ListView<T> implements IListView<T> {
602619

603620
// TODO@joao: improve this optimization to catch even more cases
604621
if (start === 0 && deleteCount >= this.items.length) {
605-
this.rangeMap = new RangeMap();
622+
this.rangeMap = new RangeMap(this.rangeMap.paddingTop);
606623
this.rangeMap.splice(0, 0, inserted);
607624
deleted = this.items;
608625
this.items = inserted;
@@ -1017,7 +1034,7 @@ export class ListView<T> implements IListView<T> {
10171034
}
10181035

10191036
get scrollHeight(): number {
1020-
return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.additionalScrollHeight;
1037+
return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.paddingBottom;
10211038
}
10221039

10231040
// Events

src/vs/base/browser/ui/list/listWidget.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,12 +991,13 @@ export interface IListOptions<T> extends IListOptionsUpdate {
991991
readonly mouseSupport?: boolean;
992992
readonly horizontalScrolling?: boolean;
993993
readonly scrollByPage?: boolean;
994-
readonly additionalScrollHeight?: number;
995994
readonly transformOptimization?: boolean;
996995
readonly smoothScrolling?: boolean;
997996
readonly scrollableElementChangeOptions?: ScrollableElementChangeOptions;
998997
readonly alwaysConsumeMouseWheel?: boolean;
999998
readonly initialSize?: Dimension;
999+
readonly paddingTop?: number;
1000+
readonly paddingBottom?: number;
10001001
}
10011002

10021003
export interface IListStyles {

src/vs/base/browser/ui/list/rangeMap.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ export class RangeMap {
9191

9292
private groups: IRangedGroup[] = [];
9393
private _size = 0;
94+
private _paddingTop = 0;
95+
96+
get paddingTop() {
97+
return this._paddingTop;
98+
}
99+
100+
set paddingTop(paddingTop: number) {
101+
this._size = this._size + paddingTop - this._paddingTop;
102+
this._paddingTop = paddingTop;
103+
}
104+
105+
constructor(topPadding?: number) {
106+
this._paddingTop = topPadding ?? 0;
107+
this._size = this._paddingTop;
108+
}
94109

95110
splice(index: number, deleteCount: number, items: IItem[] = []): void {
96111
const diff = items.length - deleteCount;
@@ -104,7 +119,7 @@ export class RangeMap {
104119
}));
105120

106121
this.groups = concat(before, middle, after);
107-
this._size = this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
122+
this._size = this._paddingTop + this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
108123
}
109124

110125
/**
@@ -135,8 +150,12 @@ export class RangeMap {
135150
return -1;
136151
}
137152

153+
if (position < this._paddingTop) {
154+
return 0;
155+
}
156+
138157
let index = 0;
139-
let size = 0;
158+
let size = this._paddingTop;
140159

141160
for (const group of this.groups) {
142161
const count = group.range.end - group.range.start;
@@ -177,7 +196,7 @@ export class RangeMap {
177196
const newCount = count + groupCount;
178197

179198
if (index < newCount) {
180-
return position + ((index - count) * group.size);
199+
return this._paddingTop + position + ((index - count) * group.size);
181200
}
182201

183202
position += groupCount * group.size;

src/vs/base/test/browser/ui/list/rangeMap.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,83 @@ suite('RangeMap', () => {
343343
});
344344
});
345345
});
346+
347+
suite('RangeMap with top padding', () => {
348+
let rangeMap: RangeMap;
349+
350+
setup(() => {
351+
rangeMap = new RangeMap(10);
352+
});
353+
354+
test('empty', () => {
355+
assert.strictEqual(rangeMap.size, 10);
356+
assert.strictEqual(rangeMap.count, 0);
357+
});
358+
359+
const one = { size: 1 };
360+
const five = { size: 5 };
361+
const ten = { size: 10 };
362+
363+
test('length & count', () => {
364+
rangeMap.splice(0, 0, [one]);
365+
assert.strictEqual(rangeMap.size, 11);
366+
assert.strictEqual(rangeMap.count, 1);
367+
});
368+
369+
test('length & count #2', () => {
370+
rangeMap.splice(0, 0, [one, one, one, one, one]);
371+
assert.strictEqual(rangeMap.size, 15);
372+
assert.strictEqual(rangeMap.count, 5);
373+
});
374+
375+
test('length & count #3', () => {
376+
rangeMap.splice(0, 0, [five]);
377+
assert.strictEqual(rangeMap.size, 15);
378+
assert.strictEqual(rangeMap.count, 1);
379+
});
380+
381+
test('length & count #4', () => {
382+
rangeMap.splice(0, 0, [five, five, five, five, five]);
383+
assert.strictEqual(rangeMap.size, 35);
384+
assert.strictEqual(rangeMap.count, 5);
385+
});
386+
387+
test('insert', () => {
388+
rangeMap.splice(0, 0, [five, five, five, five, five]);
389+
assert.strictEqual(rangeMap.size, 35);
390+
assert.strictEqual(rangeMap.count, 5);
391+
392+
rangeMap.splice(0, 0, [five, five, five, five, five]);
393+
assert.strictEqual(rangeMap.size, 60);
394+
assert.strictEqual(rangeMap.count, 10);
395+
396+
rangeMap.splice(5, 0, [ten, ten]);
397+
assert.strictEqual(rangeMap.size, 80);
398+
assert.strictEqual(rangeMap.count, 12);
399+
400+
rangeMap.splice(12, 0, [{ size: 200 }]);
401+
assert.strictEqual(rangeMap.size, 280);
402+
assert.strictEqual(rangeMap.count, 13);
403+
});
404+
405+
suite('indexAt, positionAt', () => {
406+
test('empty', () => {
407+
assert.strictEqual(rangeMap.indexAt(0), 0);
408+
assert.strictEqual(rangeMap.indexAt(10), 0);
409+
assert.strictEqual(rangeMap.indexAt(-1), -1);
410+
assert.strictEqual(rangeMap.positionAt(0), -1);
411+
assert.strictEqual(rangeMap.positionAt(10), -1);
412+
assert.strictEqual(rangeMap.positionAt(-1), -1);
413+
});
414+
415+
test('simple', () => {
416+
rangeMap.splice(0, 0, [one]);
417+
assert.strictEqual(rangeMap.indexAt(0), 0);
418+
assert.strictEqual(rangeMap.indexAt(1), 0);
419+
assert.strictEqual(rangeMap.indexAt(10), 0);
420+
assert.strictEqual(rangeMap.indexAt(11), 1);
421+
assert.strictEqual(rangeMap.positionAt(0), 10);
422+
assert.strictEqual(rangeMap.positionAt(1), -1);
423+
});
424+
});
425+
});

src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
268268
mouseSupport: true,
269269
multipleSelectionSupport: false,
270270
typeNavigationEnabled: true,
271-
additionalScrollHeight: 0,
271+
paddingBottom: 0,
272272
// transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
273273
styleController: (_suffix: string) => { return this._list!; },
274274
overrideStyles: {

src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -754,15 +754,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
754754
}`);
755755
}
756756

757-
// top insert toolbar
758-
const topInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
759-
styleSheets.push(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${topInsertToolbarHeight - 3}px }`);
760-
styleSheets.push(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element,
761-
.notebookOverlay > .cell-list-container > .notebook-gutter > .monaco-list > .monaco-scrollable-element {
762-
padding-top: ${topInsertToolbarHeight}px !important;
763-
box-sizing: border-box;
764-
}`);
765-
766757
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${codeCellLeftMargin + cellRunGutter}px; }`);
767758
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`);
768759
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`);
@@ -878,7 +869,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
878869
multipleSelectionSupport: true,
879870
selectionNavigation: true,
880871
typeNavigationEnabled: true,
881-
additionalScrollHeight: 0,
872+
paddingTop: this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType),
873+
paddingBottom: 0,
882874
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
883875
initialSize: this._dimension,
884876
styleController: (_suffix: string) => { return this._list; },
@@ -1477,8 +1469,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
14771469
}));
14781470

14791471
if (this._dimension) {
1480-
const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
1481-
this._list.layout(this._dimension.height - topInserToolbarHeight, this._dimension.width);
1472+
this._list.layout(this._dimension.height, this._dimension.width);
14821473
} else {
14831474
this._list.layout();
14841475
}
@@ -1772,15 +1763,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
17721763
DOM.size(this._body, dimension.width, newBodyHeight);
17731764

17741765
const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
1775-
const newCellListHeight = Math.max(newBodyHeight - topInserToolbarHeight, 0);
1766+
const newCellListHeight = newBodyHeight;
17761767
if (this._list.getRenderHeight() < newCellListHeight) {
17771768
// the new dimension is larger than the list viewport, update its additional height first, otherwise the list view will move down a bit (as the `scrollBottom` will move down)
1778-
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : topInserToolbarHeight });
1769+
this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: topInserToolbarHeight });
17791770
this._list.layout(newCellListHeight, dimension.width);
17801771
} else {
17811772
// the new dimension is smaller than the list viewport, if we update the additional height, the `scrollBottom` will move up, which moves the whole list view upwards a bit. So we run a layout first.
17821773
this._list.layout(newCellListHeight, dimension.width);
1783-
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : topInserToolbarHeight });
1774+
this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: topInserToolbarHeight });
17841775
}
17851776

17861777
this._overlayContainer.style.visibility = 'visible';

src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ suite('NotebookCellList', () => {
139139
});
140140

141141
const cellList = createNotebookCellList(instantiationService);
142-
// without additionalscrollheight, the last 20 px will always be hidden due to `topInsertToolbarHeight`
143-
cellList.updateOptions({ additionalScrollHeight: 100 });
142+
// without paddingBottom, the last 20 px will always be hidden due to `topInsertToolbarHeight`
143+
cellList.updateOptions({ paddingBottom: 100 });
144144
cellList.attachViewModel(viewModel);
145145

146146
// render height 210, it can render 3 full cells and 1 partial cell

src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
9797
accessibilityProvider: instantiationService.createInstance(TerminalTabsAccessibilityProvider),
9898
smoothScrolling: _configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
9999
multipleSelectionSupport: true,
100-
additionalScrollHeight: TerminalTabsListSizes.TabHeight,
100+
paddingBottom: TerminalTabsListSizes.TabHeight,
101101
dnd: instantiationService.createInstance(TerminalTabsDragAndDrop),
102102
openOnSingleClick: true
103103
},

0 commit comments

Comments
 (0)