Skip to content

Commit 419102e

Browse files
dmitrivMSCopilot
andcommitted
Defer breadcrumbs reveal layout measurement
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7edc21d commit 419102e

2 files changed

Lines changed: 81 additions & 4 deletions

File tree

src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { DomScrollableElement } from '../scrollbar/scrollableElement.js';
1010
import { commonPrefixLength } from '../../../common/arrays.js';
1111
import { ThemeIcon } from '../../../common/themables.js';
1212
import { Emitter, Event } from '../../../common/event.js';
13-
import { DisposableStore, dispose, IDisposable } from '../../../common/lifecycle.js';
13+
import { DisposableStore, dispose, IDisposable, MutableDisposable } from '../../../common/lifecycle.js';
1414
import { ScrollbarVisibility } from '../../../common/scrollable.js';
1515
import './breadcrumbsWidget.css';
1616

@@ -60,14 +60,16 @@ export class BreadcrumbsWidget {
6060

6161
private _pendingDimLayout: IDisposable | undefined;
6262
private _pendingLayout: IDisposable | undefined;
63+
private readonly _pendingReveal = this._disposables.add(new MutableDisposable<IDisposable>());
6364
private _dimension: dom.Dimension | undefined;
6465

6566
constructor(
6667
container: HTMLElement,
6768
horizontalScrollbarSize: number,
6869
horizontalScrollbarVisibility: ScrollbarVisibility = ScrollbarVisibility.Auto,
6970
separatorIcon: ThemeIcon,
70-
styles: IBreadcrumbsWidgetStyles
71+
styles: IBreadcrumbsWidgetStyles,
72+
private readonly _measure: typeof dom.measure = dom.measure,
7173
) {
7274
this._domNode = document.createElement('div');
7375
this._domNode.className = 'monaco-breadcrumbs';
@@ -241,18 +243,33 @@ export class BreadcrumbsWidget {
241243
}
242244

243245
private _reveal(nth: number, minimal: boolean): void {
246+
this._pendingReveal.clear();
244247
if (nth < 0 || nth >= this._nodes.length) {
245248
return;
246249
}
247250
const node = this._nodes[nth];
248251
if (!node) {
249252
return;
250253
}
254+
if (!minimal) {
255+
this._pendingReveal.value = this._measure(dom.getWindow(this._domNode), () => {
256+
if (this._nodes[nth] === node) {
257+
this._revealNode(node, false);
258+
}
259+
});
260+
return;
261+
}
262+
263+
this._revealNode(node, true);
264+
}
265+
266+
private _revealNode(node: HTMLElement, minimal: boolean): void {
251267
const { width } = this._scrollable.getScrollDimensions();
252268
const { scrollLeft } = this._scrollable.getScrollPosition();
253-
if (!minimal || node.offsetLeft > scrollLeft + width || node.offsetLeft < scrollLeft) {
269+
const nodeOffsetLeft = node.offsetLeft;
270+
if (!minimal || nodeOffsetLeft > scrollLeft + width || nodeOffsetLeft < scrollLeft) {
254271
this._scrollable.setRevealOnScroll(false);
255-
this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft });
272+
this._scrollable.setScrollPosition({ scrollLeft: nodeOffsetLeft });
256273
this._scrollable.setRevealOnScroll(true);
257274
}
258275
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import assert from 'assert';
7+
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsWidgetStyles } from '../../../../browser/ui/breadcrumbs/breadcrumbsWidget.js';
8+
import { Codicon } from '../../../../common/codicons.js';
9+
import { IDisposable, toDisposable } from '../../../../common/lifecycle.js';
10+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../common/utils.js';
11+
12+
class TestBreadcrumbsItem extends BreadcrumbsItem {
13+
14+
constructor(private readonly label: string) {
15+
super();
16+
}
17+
18+
override dispose(): void { }
19+
20+
override equals(other: BreadcrumbsItem): boolean {
21+
return other instanceof TestBreadcrumbsItem && other.label === this.label;
22+
}
23+
24+
override render(container: HTMLElement): void {
25+
container.textContent = this.label;
26+
}
27+
}
28+
29+
const styles: IBreadcrumbsWidgetStyles = {
30+
breadcrumbsBackground: undefined,
31+
breadcrumbsForeground: undefined,
32+
breadcrumbsFocusForeground: undefined,
33+
breadcrumbsFocusAndSelectionForeground: undefined,
34+
breadcrumbsHoverForeground: undefined,
35+
};
36+
37+
suite('BreadcrumbsWidget', () => {
38+
const store = ensureNoDisposablesAreLeakedInTestSuite();
39+
40+
test('defers non-focus reveals and cancels them when focus changes', () => {
41+
const operations: string[] = [];
42+
const measure = (_targetWindow: Window, _callback: () => void): IDisposable => {
43+
operations.push('schedule');
44+
return toDisposable(() => operations.push('cancel'));
45+
};
46+
const container = document.createElement('div');
47+
document.body.appendChild(container);
48+
store.add(toDisposable(() => container.remove()));
49+
const widget = store.add(new BreadcrumbsWidget(container, 3, undefined, Codicon.chevronRight, styles, measure));
50+
const first = new TestBreadcrumbsItem('first');
51+
const last = new TestBreadcrumbsItem('last');
52+
widget.setItems([first, last]);
53+
54+
widget.revealLast();
55+
widget.reveal(first);
56+
widget.domFocus();
57+
58+
assert.deepStrictEqual(operations, ['schedule', 'cancel', 'schedule', 'cancel']);
59+
});
60+
});

0 commit comments

Comments
 (0)