Expose the focused element and change event in the TreeView API (#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 <ehab.younes@sigasi.com>
Co-authored-by: Alex Ross <alros@microsoft.com>
This commit is contained in:
Ehab Younes 2023-07-14 08:49:15 +00:00 committed by GitHub
parent a0f904dd54
commit 6a152ca523
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 32 deletions

View file

@ -44,6 +44,7 @@
"timeline",
"tokenInformation",
"treeItemCheckbox",
"treeViewActiveItem",
"treeViewReveal",
"testInvalidateResults",
"workspaceTrust",

View file

@ -177,8 +177,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
private registerListeners(treeViewId: string, treeView: ITreeView): void {
this._register(treeView.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true)));
this._register(treeView.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false)));
this._register(treeView.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle))));
this._register(treeView.onDidChangeFocus(item => this._proxy.$setFocus(treeViewId, item.handle)));
this._register(treeView.onDidChangeSelectionAndFocus(items => this._proxy.$setSelectionAndFocus(treeViewId, items.selection.map(({ handle }) => handle), items.focus.handle)));
this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
this._register(treeView.onDidChangeCheckboxState(items => {
this._proxy.$changeCheckboxState(treeViewId, <CheckboxUpdate[]>items.map(item => {

View file

@ -1563,8 +1563,7 @@ export interface ExtHostTreeViewsShape {
$handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
$handleDrag(sourceViewId: string, sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise<DataTransferDTO | undefined>;
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
$setFocus(treeViewId: string, treeItemHandle: string): void;
$setSelectionAndFocus(treeViewId: string, selectionHandles: string[], focusHandle: string): void;
$setVisible(treeViewId: string, visible: boolean): void;
$changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void;
$hasResolve(treeViewId: string): Promise<boolean>;

View file

@ -24,6 +24,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { ITreeViewsDnDService, TreeViewsDnDService } from 'vs/editor/common/services/treeViewsDnd';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
type TreeItemHandle = string;
@ -99,6 +100,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
get onDidExpandElement() { return treeView.onDidExpandElement; },
get selection() { return treeView.selectedElements; },
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
get activeItem() {
checkProposedApiEnabled(extension, 'treeViewActiveItem');
return treeView.focusedElement;
},
get onDidChangeActiveItem() {
checkProposedApiEnabled(extension, 'treeViewActiveItem');
return treeView.onDidChangeActiveItem;
},
get visible() { return treeView.visible; },
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
get onDidChangeCheckboxState() {
@ -222,20 +231,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
treeView.setExpanded(treeItemHandle, expanded);
}
$setSelection(treeViewId: string, treeItemHandles: string[]): void {
$setSelectionAndFocus(treeViewId: string, selectedHandles: string[], focusedHandle: string) {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new NoTreeViewError(treeViewId);
}
treeView.setSelection(treeItemHandles);
}
$setFocus(treeViewId: string, treeItemHandles: string) {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new NoTreeViewError(treeViewId);
}
treeView.setFocus(treeItemHandles);
treeView.setSelectionAndFocus(selectedHandles, focusedHandle);
}
$setVisible(treeViewId: string, isVisible: boolean): void {
@ -313,6 +314,9 @@ class ExtHostTreeView<T> extends Disposable {
private _onDidChangeSelection: Emitter<vscode.TreeViewSelectionChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewSelectionChangeEvent<T>>());
readonly onDidChangeSelection: Event<vscode.TreeViewSelectionChangeEvent<T>> = this._onDidChangeSelection.event;
private _onDidChangeActiveItem: Emitter<vscode.TreeViewActiveItemChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewActiveItemChangeEvent<T>>());
readonly onDidChangeActiveItem: Event<vscode.TreeViewActiveItemChangeEvent<T>> = this._onDidChangeActiveItem.event;
private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;
@ -479,15 +483,20 @@ class ExtHostTreeView<T> extends Disposable {
}
}
setSelection(treeItemHandles: TreeItemHandle[]): void {
if (!equals(this._selectedHandles, treeItemHandles)) {
this._selectedHandles = treeItemHandles;
setSelectionAndFocus(selectedHandles: TreeItemHandle[], focusedHandle: string): void {
const changedSelection = !equals(this._selectedHandles, selectedHandles);
this._selectedHandles = selectedHandles;
const changedFocus = this._focusedHandle !== focusedHandle;
this._focusedHandle = focusedHandle;
if (changedSelection) {
this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements }));
}
}
setFocus(treeItemHandle: TreeItemHandle) {
this._focusedHandle = treeItemHandle;
if (changedFocus) {
this._onDidChangeActiveItem.fire(Object.freeze({ activeItem: this.focusedElement }));
}
}
setVisible(visible: boolean): void {

View file

@ -215,6 +215,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
private root: ITreeItem;
private elementsToRefresh: ITreeItem[] = [];
private lastSelection: readonly ITreeItem[] = [];
private lastActive: ITreeItem;
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
@ -222,11 +224,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
private readonly _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;
private _onDidChangeSelection: Emitter<readonly ITreeItem[]> = this._register(new Emitter<readonly ITreeItem[]>());
readonly onDidChangeSelection: Event<readonly ITreeItem[]> = this._onDidChangeSelection.event;
private _onDidChangeFocus: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
readonly onDidChangeFocus: Event<ITreeItem> = this._onDidChangeFocus.event;
private _onDidChangeSelectionAndFocus: Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._register(new Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }>());
readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._onDidChangeSelectionAndFocus.event;
private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
@ -267,6 +266,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
) {
super();
this.root = new Root();
this.lastActive = this.root;
// Try not to add anything that could be costly to this constructor. It gets called once per tree view
// during startup, and anything added here can affect performance.
}
@ -702,10 +702,17 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
const customTreeKey = RawCustomTreeViewContextKey.bindTo(this.tree.contextKeyService);
customTreeKey.set(true);
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
this._register(this.tree.onDidChangeSelection(e => {
this.lastSelection = e.elements;
this.lastActive = this.tree?.getFocus()[0] ?? this.lastActive;
this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive });
}));
this._register(this.tree.onDidChangeFocus(e => {
if (e.elements.length) {
this._onDidChangeFocus.fire(e.elements[0]);
if (e.elements.length && (e.elements[0] !== this.lastActive)) {
this.lastActive = e.elements[0];
this.lastSelection = this.tree?.getSelection() ?? this.lastSelection;
this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive });
}
}));
this._register(this.tree.onDidChangeCollapseState(e => {
@ -957,7 +964,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
}
const newSelection = tree.getSelection();
if (oldSelection.length !== newSelection.length || oldSelection.some((value, index) => value.handle !== newSelection[index].handle)) {
this._onDidChangeSelection.fire(newSelection);
this.lastSelection = newSelection;
this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive });
}
this.refreshing = false;
this._onDidCompleteRefresh.fire();

View file

@ -667,9 +667,7 @@ export interface ITreeView extends IDisposable {
readonly onDidCollapseItem: Event<ITreeItem>;
readonly onDidChangeSelection: Event<readonly ITreeItem[]>;
readonly onDidChangeFocus: Event<ITreeItem>;
readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }>;
readonly onDidChangeVisibility: Event<boolean>;

View file

@ -91,6 +91,7 @@ export const allApiProposals = Object.freeze({
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts',
tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts',
treeViewActiveItem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts',
treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts',
tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts',
windowActivity: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.windowActivity.d.ts',

View file

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/170248
export interface TreeView<T> extends Disposable {
/**
* Currently active item.
*/
readonly activeItem: T | undefined;
/**
* Event that is fired when the {@link TreeView.activeItem active item} has changed
*/
readonly onDidChangeActiveItem: Event<TreeViewActiveItemChangeEvent<T>>;
}
/**
* The event that is fired when there is a change in {@link TreeView.activeItem tree view's active item}
*/
export interface TreeViewActiveItemChangeEvent<T> {
/**
* Active item.
*/
readonly activeItem: T | undefined;
}
}