Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manage checkbox state by default #182750

Merged
merged 1 commit into from May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/vs/workbench/api/browser/mainThreadTreeViews.ts
Expand Up @@ -37,7 +37,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}

async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise<void> {
async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; manuallyManageCheckboxes: boolean }): Promise<void> {
this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options);

this.extensionService.whenInstalledExtensionsRegistered().then(() => {
Expand All @@ -49,8 +49,9 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
if (viewer) {
// Order is important here. The internal tree isn't created until the dataProvider is set.
// Set all other properties first!
viewer.showCollapseAllAction = !!options.showCollapseAll;
viewer.canSelectMany = !!options.canSelectMany;
viewer.showCollapseAllAction = options.showCollapseAll;
viewer.canSelectMany = options.canSelectMany;
viewer.manuallyManageCheckboxes = options.manuallyManageCheckboxes;
viewer.dragAndDropController = dndController;
if (dndController) {
this._dndControllers.set(treeViewId, dndController);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Expand Up @@ -276,7 +276,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
}

export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise<void>;
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; manuallyManageCheckboxes: boolean }): Promise<void>;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise<void>;
$reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void>;
$setMessage(treeViewId: string, message: string): void;
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHostTreeViews.ts
Expand Up @@ -92,7 +92,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
const hasHandleDrag = !!options.dragAndDropController?.handleDrag;
const hasHandleDrop = !!options.dragAndDropController?.handleDrop;
const treeView = this.createExtHostTreeView(viewId, options, extension);
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop });
const proxyOptions = { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop, manuallyManageCheckboxes: !!options.manuallyManageCheckboxSelection };
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, proxyOptions);
return {
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
get onDidExpandElement() { return treeView.onDidExpandElement; },
Expand Down
Expand Up @@ -75,7 +75,7 @@ suite('MainThreadHostTreeView', function () {
}
drain(): any { return null; }
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService());
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false });
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false, manuallyManageCheckboxes: false });
await testExtensionService.whenInstalledExtensionsRegistered();
});

Expand Down
75 changes: 73 additions & 2 deletions src/vs/workbench/browser/parts/views/treeView.ts
Expand Up @@ -206,6 +206,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
private treeContainer: HTMLElement | undefined;
private _messageValue: string | undefined;
private _canSelectMany: boolean = false;
private _manuallyManageCheckboxes: boolean = false;
private messageElement: HTMLElement | undefined;
private tree: Tree | undefined;
private treeLabels: ResourceLabels | undefined;
Expand Down Expand Up @@ -349,6 +350,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
node = node ?? self.root;
node.children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node));
children = node.children ?? [];
children.forEach(child => child.parent = node);
}
if (node instanceof Root) {
const oldEmpty = this._isEmpty;
Expand Down Expand Up @@ -447,6 +449,14 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
}
}

get manuallyManageCheckboxes(): boolean {
return this._manuallyManageCheckboxes;
}

set manuallyManageCheckboxes(manuallyManageCheckboxes: boolean) {
this._manuallyManageCheckboxes = manuallyManageCheckboxes;
}

get hasIconForParentNode(): boolean {
return this._hasIconForParentNode;
}
Expand Down Expand Up @@ -610,6 +620,68 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
this._register(focusTracker.onDidBlur(() => this.focused = false));
}

private updateCheckboxes(items: ITreeItem[]) {
const additionalItems: ITreeItem[] = [];

if (!this.manuallyManageCheckboxes) {
for (const item of items) {
if (item.checkbox !== undefined) {

function checkChildren(currentItem: ITreeItem) {
for (const child of (currentItem.children ?? [])) {
if (child.checkbox !== undefined && currentItem.checkbox !== undefined) {
child.checkbox.isChecked = currentItem.checkbox.isChecked;
additionalItems.push(child);
checkChildren(child);
}
}
}
checkChildren(item);

const visitedParents: Set<ITreeItem> = new Set();
function checkParents(currentItem: ITreeItem) {
if (currentItem.parent && (currentItem.parent.checkbox !== undefined) && currentItem.parent.children) {
if (visitedParents.has(currentItem.parent)) {
return;
} else {
visitedParents.add(currentItem.parent);
}

let someUnchecked = false;
let someChecked = false;
for (const child of currentItem.parent.children) {
if (someUnchecked && someChecked) {
break;
}
if (child.checkbox !== undefined) {
if (child.checkbox.isChecked) {
someChecked = true;
} else {
someUnchecked = true;
}
}
}
if (someChecked && !someUnchecked) {
currentItem.parent.checkbox.isChecked = true;
additionalItems.push(currentItem.parent);
checkParents(currentItem.parent);
} else if (someUnchecked && !someChecked) {
currentItem.parent.checkbox.isChecked = false;
additionalItems.push(currentItem.parent);
checkParents(currentItem.parent);
}
}
}
checkParents(item);
}
}
}
items = items.concat(additionalItems);
items.forEach(item => this.tree?.rerender(item));
this._onDidChangeCheckboxState.fire(items);
}


protected createTree() {
const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService);
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
Expand All @@ -618,8 +690,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
const aligner = new Aligner(this.themeService);
const checkboxStateHandler = this._register(new CheckboxStateHandler());
this._register(checkboxStateHandler.onDidChangeCheckboxState(items => {
items.forEach(item => this.tree?.rerender(item));
this._onDidChangeCheckboxState.fire(items);
this.updateCheckboxes(items);
}));
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler);
const widgetAriaLabel = this._title;
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/common/views.ts
Expand Up @@ -651,6 +651,8 @@ export interface ITreeView extends IDisposable {

canSelectMany: boolean;

manuallyManageCheckboxes: boolean;

message?: string;

title: string;
Expand Down Expand Up @@ -784,6 +786,8 @@ export interface ITreeItem {

children?: ITreeItem[];

parent?: ITreeItem;

accessibilityInformation?: IAccessibilityInformation;

checkbox?: ITreeItemCheckboxState;
Expand Down
17 changes: 15 additions & 2 deletions src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts
Expand Up @@ -7,9 +7,10 @@ declare module 'vscode' {

export class TreeItem2 extends TreeItem {
/**
* [TreeItemCheckboxState](#TreeItemCheckboxState) of the tree item.
* {@link TreeItemCheckboxState TreeItemCheckboxState} of the tree item.
* {@link TreeDataProvider.onDidChangeTreeData onDidChangeTreeData} should be fired when {@link TreeItem2.checkboxState checkboxState} changes.
*/
checkboxState?: TreeItemCheckboxState | { readonly state: TreeItemCheckboxState; readonly tooltip?: string };
checkboxState?: TreeItemCheckboxState | { readonly state: TreeItemCheckboxState; readonly tooltip?: string; readonly accessibilityInformation?: AccessibilityInformation };
}

/**
Expand Down Expand Up @@ -42,4 +43,16 @@ declare module 'vscode' {
*/
readonly items: ReadonlyArray<[T, TreeItemCheckboxState]>;
}

/**
* Options for creating a {@link TreeView}
*/
export interface TreeViewOptions<T> {
/**
* By default, when the children of a tree item have already been fetched, child checkboxes are automatically managed based on the checked state of the parent tree item.
* If the tree item is collapsed by default (meaning that the children haven't yet been fetched) then child checkboxes will not be updated.
* To override this behavior and manage child and parent checkbox state in the extension, set this to `true`.
*/
manuallyManageCheckboxSelection?: boolean;
}
}