Skip to content

Commit 2043283

Browse files
committed
List View top padding.
1 parent a9c43b5 commit 2043283

File tree

5 files changed

+130
-19
lines changed

5 files changed

+130
-19
lines changed

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface IListViewOptionsUpdate {
6262
readonly scrollByPage?: boolean;
6363
readonly mouseWheelScrollSensitivity?: number;
6464
readonly fastScrollSensitivity?: number;
65+
readonly topPadding?: number;
6566
}
6667

6768
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
@@ -278,6 +279,7 @@ export class ListView<T> implements IListView<T> {
278279
private items: IItem<T>[];
279280
private itemId: number;
280281
private rangeMap: RangeMap;
282+
private topPadding: number;
281283
private cache: RowCache<T>;
282284
private renderers = new Map<string, IListRenderer<any /* TODO@joao */, any>>();
283285
private lastRenderTop: number;
@@ -359,7 +361,8 @@ export class ListView<T> implements IListView<T> {
359361

360362
this.items = [];
361363
this.itemId = 0;
362-
this.rangeMap = new RangeMap();
364+
this.topPadding = options.topPadding ?? 0;
365+
this.rangeMap = new RangeMap(this.topPadding);
363366

364367
for (const renderer of renderers) {
365368
this.renderers.set(renderer.templateId, renderer);
@@ -466,6 +469,23 @@ export class ListView<T> implements IListView<T> {
466469
if (scrollableOptions) {
467470
this.scrollableElement.updateOptions(scrollableOptions);
468471
}
472+
473+
if (options.topPadding !== undefined && options.topPadding !== this.topPadding) {
474+
// trigger a rerender
475+
this.topPadding = options.topPadding;
476+
const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
477+
const offset = options.topPadding - this.rangeMap.topPadding;
478+
this.rangeMap.topPadding = options.topPadding;
479+
480+
this.render(lastRenderRange, Math.max(0, this.lastRenderTop + offset), this.lastRenderHeight, undefined, undefined, true);
481+
this.setScrollTop(this.lastRenderTop);
482+
483+
this.eventuallyUpdateScrollDimensions();
484+
485+
if (this.supportDynamicHeights) {
486+
this._rerender(this.lastRenderTop, this.lastRenderHeight);
487+
}
488+
}
469489
}
470490

471491
delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
@@ -597,7 +617,7 @@ export class ListView<T> implements IListView<T> {
597617

598618
// TODO@joao: improve this optimization to catch even more cases
599619
if (start === 0 && deleteCount >= this.items.length) {
600-
this.rangeMap = new RangeMap();
620+
this.rangeMap = new RangeMap(this.topPadding);
601621
this.rangeMap.splice(0, 0, inserted);
602622
deleted = this.items;
603623
this.items = inserted;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
972972
readonly scrollableElementChangeOptions?: ScrollableElementChangeOptions;
973973
readonly alwaysConsumeMouseWheel?: boolean;
974974
readonly initialSize?: Dimension;
975+
readonly topPadding?: number;
975976
}
976977

977978
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 _topPadding = 0;
95+
96+
get topPadding() {
97+
return this._topPadding;
98+
}
99+
100+
set topPadding(topPadding: number) {
101+
this._topPadding = topPadding;
102+
this._size = this._topPadding + this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
103+
}
104+
105+
constructor(topPadding?: number) {
106+
this._topPadding = topPadding || 0;
107+
this._size = this._topPadding;
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._topPadding + 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._topPadding) {
154+
return 0;
155+
}
156+
138157
let index = 0;
139-
let size = 0;
158+
let size = this._topPadding;
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._topPadding + 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/notebookEditorWidget.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -749,15 +749,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
749749
}`);
750750
}
751751

752-
// top insert toolbar
753-
const topInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
754-
styleSheets.push(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${topInsertToolbarHeight - 3}px }`);
755-
styleSheets.push(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element,
756-
.notebookOverlay > .cell-list-container > .notebook-gutter > .monaco-list > .monaco-scrollable-element {
757-
padding-top: ${topInsertToolbarHeight}px !important;
758-
box-sizing: border-box;
759-
}`);
760-
761752
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; }`);
762753
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`);
763754
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`);
@@ -869,6 +860,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
869860
additionalScrollHeight: 0,
870861
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
871862
initialSize: this._dimension,
863+
topPadding: this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType),
872864
styleController: (_suffix: string) => { return this._list; },
873865
overrideStyles: {
874866
listBackground: notebookEditorBackground,
@@ -1462,8 +1454,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
14621454
}));
14631455

14641456
if (this._dimension) {
1465-
const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
1466-
this._list.layout(this._dimension.height - topInserToolbarHeight, this._dimension.width);
1457+
this._list.layout(this._dimension.height, this._dimension.width);
14671458
} else {
14681459
this._list.layout();
14691460
}
@@ -1757,15 +1748,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
17571748
DOM.size(this._body, dimension.width, newBodyHeight);
17581749

17591750
const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
1760-
const newCellListHeight = Math.max(newBodyHeight - topInserToolbarHeight, 0);
1751+
const newCellListHeight = newBodyHeight;
17611752
if (this._list.getRenderHeight() < newCellListHeight) {
17621753
// 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)
1763-
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : topInserToolbarHeight });
1754+
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, topPadding: topInserToolbarHeight });
17641755
this._list.layout(newCellListHeight, dimension.width);
17651756
} else {
17661757
// 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.
17671758
this._list.layout(newCellListHeight, dimension.width);
1768-
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : topInserToolbarHeight });
1759+
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, topPadding: topInserToolbarHeight });
17691760
}
17701761

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

0 commit comments

Comments
 (0)