Skip to content

Commit 6a152ca

Browse files
EhabYEhab Younesalexr00
authored
Expose the focused element and change event in the TreeView API (microsoft#184268)
* Expose the focused element and event in the TreeView API * Exposed active TreeItem through extension proposal * Add proposal to test extension * Merge change selection and focus events * Finish selection+focus change in treeview * Clean up * Clean up * Add checkProposedApiEnabled back in --------- Co-authored-by: Ehab Younes <[email protected]> Co-authored-by: Alex Ross <[email protected]>
1 parent a0f904d commit 6a152ca

File tree

8 files changed

+77
-32
lines changed

8 files changed

+77
-32
lines changed

extensions/vscode-api-tests/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"timeline",
4545
"tokenInformation",
4646
"treeItemCheckbox",
47+
"treeViewActiveItem",
4748
"treeViewReveal",
4849
"testInvalidateResults",
4950
"workspaceTrust",

src/vs/workbench/api/browser/mainThreadTreeViews.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
177177
private registerListeners(treeViewId: string, treeView: ITreeView): void {
178178
this._register(treeView.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true)));
179179
this._register(treeView.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false)));
180-
this._register(treeView.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle))));
181-
this._register(treeView.onDidChangeFocus(item => this._proxy.$setFocus(treeViewId, item.handle)));
180+
this._register(treeView.onDidChangeSelectionAndFocus(items => this._proxy.$setSelectionAndFocus(treeViewId, items.selection.map(({ handle }) => handle), items.focus.handle)));
182181
this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
183182
this._register(treeView.onDidChangeCheckboxState(items => {
184183
this._proxy.$changeCheckboxState(treeViewId, <CheckboxUpdate[]>items.map(item => {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,8 +1563,7 @@ export interface ExtHostTreeViewsShape {
15631563
$handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
15641564
$handleDrag(sourceViewId: string, sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise<DataTransferDTO | undefined>;
15651565
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
1566-
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
1567-
$setFocus(treeViewId: string, treeItemHandle: string): void;
1566+
$setSelectionAndFocus(treeViewId: string, selectionHandles: string[], focusHandle: string): void;
15681567
$setVisible(treeViewId: string, visible: boolean): void;
15691568
$changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void;
15701569
$hasResolve(treeViewId: string): Promise<boolean>;

src/vs/workbench/api/common/extHostTreeViews.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
2424
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
2525
import { ITreeViewsDnDService, TreeViewsDnDService } from 'vs/editor/common/services/treeViewsDnd';
2626
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
27+
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
2728

2829
type TreeItemHandle = string;
2930

@@ -99,6 +100,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
99100
get onDidExpandElement() { return treeView.onDidExpandElement; },
100101
get selection() { return treeView.selectedElements; },
101102
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
103+
get activeItem() {
104+
checkProposedApiEnabled(extension, 'treeViewActiveItem');
105+
return treeView.focusedElement;
106+
},
107+
get onDidChangeActiveItem() {
108+
checkProposedApiEnabled(extension, 'treeViewActiveItem');
109+
return treeView.onDidChangeActiveItem;
110+
},
102111
get visible() { return treeView.visible; },
103112
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
104113
get onDidChangeCheckboxState() {
@@ -222,20 +231,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
222231
treeView.setExpanded(treeItemHandle, expanded);
223232
}
224233

225-
$setSelection(treeViewId: string, treeItemHandles: string[]): void {
234+
$setSelectionAndFocus(treeViewId: string, selectedHandles: string[], focusedHandle: string) {
226235
const treeView = this.treeViews.get(treeViewId);
227236
if (!treeView) {
228237
throw new NoTreeViewError(treeViewId);
229238
}
230-
treeView.setSelection(treeItemHandles);
231-
}
232-
233-
$setFocus(treeViewId: string, treeItemHandles: string) {
234-
const treeView = this.treeViews.get(treeViewId);
235-
if (!treeView) {
236-
throw new NoTreeViewError(treeViewId);
237-
}
238-
treeView.setFocus(treeItemHandles);
239+
treeView.setSelectionAndFocus(selectedHandles, focusedHandle);
239240
}
240241

241242
$setVisible(treeViewId: string, isVisible: boolean): void {
@@ -313,6 +314,9 @@ class ExtHostTreeView<T> extends Disposable {
313314
private _onDidChangeSelection: Emitter<vscode.TreeViewSelectionChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewSelectionChangeEvent<T>>());
314315
readonly onDidChangeSelection: Event<vscode.TreeViewSelectionChangeEvent<T>> = this._onDidChangeSelection.event;
315316

317+
private _onDidChangeActiveItem: Emitter<vscode.TreeViewActiveItemChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewActiveItemChangeEvent<T>>());
318+
readonly onDidChangeActiveItem: Event<vscode.TreeViewActiveItemChangeEvent<T>> = this._onDidChangeActiveItem.event;
319+
316320
private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
317321
readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;
318322

@@ -479,15 +483,20 @@ class ExtHostTreeView<T> extends Disposable {
479483
}
480484
}
481485

482-
setSelection(treeItemHandles: TreeItemHandle[]): void {
483-
if (!equals(this._selectedHandles, treeItemHandles)) {
484-
this._selectedHandles = treeItemHandles;
486+
setSelectionAndFocus(selectedHandles: TreeItemHandle[], focusedHandle: string): void {
487+
const changedSelection = !equals(this._selectedHandles, selectedHandles);
488+
this._selectedHandles = selectedHandles;
489+
490+
const changedFocus = this._focusedHandle !== focusedHandle;
491+
this._focusedHandle = focusedHandle;
492+
493+
if (changedSelection) {
485494
this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements }));
486495
}
487-
}
488496

489-
setFocus(treeItemHandle: TreeItemHandle) {
490-
this._focusedHandle = treeItemHandle;
497+
if (changedFocus) {
498+
this._onDidChangeActiveItem.fire(Object.freeze({ activeItem: this.focusedElement }));
499+
}
491500
}
492501

493502
setVisible(visible: boolean): void {

src/vs/workbench/browser/parts/views/treeView.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,18 +215,17 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
215215

216216
private root: ITreeItem;
217217
private elementsToRefresh: ITreeItem[] = [];
218+
private lastSelection: readonly ITreeItem[] = [];
219+
private lastActive: ITreeItem;
218220

219221
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
220222
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
221223

222224
private readonly _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
223225
readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;
224226

225-
private _onDidChangeSelection: Emitter<readonly ITreeItem[]> = this._register(new Emitter<readonly ITreeItem[]>());
226-
readonly onDidChangeSelection: Event<readonly ITreeItem[]> = this._onDidChangeSelection.event;
227-
228-
private _onDidChangeFocus: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
229-
readonly onDidChangeFocus: Event<ITreeItem> = this._onDidChangeFocus.event;
227+
private _onDidChangeSelectionAndFocus: Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._register(new Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }>());
228+
readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._onDidChangeSelectionAndFocus.event;
230229

231230
private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
232231
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
@@ -267,6 +266,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
267266
) {
268267
super();
269268
this.root = new Root();
269+
this.lastActive = this.root;
270270
// Try not to add anything that could be costly to this constructor. It gets called once per tree view
271271
// during startup, and anything added here can affect performance.
272272
}
@@ -702,10 +702,17 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
702702
const customTreeKey = RawCustomTreeViewContextKey.bindTo(this.tree.contextKeyService);
703703
customTreeKey.set(true);
704704
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
705-
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
705+
706+
this._register(this.tree.onDidChangeSelection(e => {
707+
this.lastSelection = e.elements;
708+
this.lastActive = this.tree?.getFocus()[0] ?? this.lastActive;
709+
this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive });
710+
}));
706711
this._register(this.tree.onDidChangeFocus(e => {
707-
if (e.elements.length) {
708-
this._onDidChangeFocus.fire(e.elements[0]);
712+
if (e.elements.length && (e.elements[0] !== this.lastActive)) {
713+
this.lastActive = e.elements[0];
714+
this.lastSelection = this.tree?.getSelection() ?? this.lastSelection;
715+
this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive });
709716
}
710717
}));
711718
this._register(this.tree.onDidChangeCollapseState(e => {
@@ -957,7 +964,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
957964
}
958965
const newSelection = tree.getSelection();
959966
if (oldSelection.length !== newSelection.length || oldSelection.some((value, index) => value.handle !== newSelection[index].handle)) {
960-
this._onDidChangeSelection.fire(newSelection);
967+
this.lastSelection = newSelection;
968+
this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive });
961969
}
962970
this.refreshing = false;
963971
this._onDidCompleteRefresh.fire();

src/vs/workbench/common/views.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -667,9 +667,7 @@ export interface ITreeView extends IDisposable {
667667

668668
readonly onDidCollapseItem: Event<ITreeItem>;
669669

670-
readonly onDidChangeSelection: Event<readonly ITreeItem[]>;
671-
672-
readonly onDidChangeFocus: Event<ITreeItem>;
670+
readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }>;
673671

674672
readonly onDidChangeVisibility: Event<boolean>;
675673

src/vs/workbench/services/extensions/common/extensionsApiProposals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export const allApiProposals = Object.freeze({
9191
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
9292
timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts',
9393
tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts',
94+
treeViewActiveItem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts',
9495
treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts',
9596
tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts',
9697
windowActivity: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.windowActivity.d.ts',
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
declare module 'vscode' {
7+
8+
// https://github.com/microsoft/vscode/issues/170248
9+
10+
export interface TreeView<T> extends Disposable {
11+
/**
12+
* Currently active item.
13+
*/
14+
readonly activeItem: T | undefined;
15+
/**
16+
* Event that is fired when the {@link TreeView.activeItem active item} has changed
17+
*/
18+
readonly onDidChangeActiveItem: Event<TreeViewActiveItemChangeEvent<T>>;
19+
}
20+
21+
/**
22+
* The event that is fired when there is a change in {@link TreeView.activeItem tree view's active item}
23+
*/
24+
export interface TreeViewActiveItemChangeEvent<T> {
25+
/**
26+
* Active item.
27+
*/
28+
readonly activeItem: T | undefined;
29+
}
30+
}

0 commit comments

Comments
 (0)