Optional TreeItem Checkbox (#158250)

This commit is contained in:
Benjamin Simmonds
2022-09-06 12:41:57 +02:00
committed by GitHub
parent daf5eb2262
commit f51258b93b
12 changed files with 281 additions and 12 deletions

View File

@@ -42,6 +42,7 @@
"textSearchProvider",
"timeline",
"tokenInformation",
"treeItemCheckbox",
"treeViewReveal",
"workspaceTrust",
"telemetry"

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, CheckboxUpdate } from 'vs/workbench/api/common/extHost.protocol';
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge } from 'vs/workbench/common/views';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { distinct } from 'vs/base/common/arrays';
@@ -170,6 +170,11 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
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.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
this._register(treeView.onDidChangeCheckboxState(items => {
this._proxy.$changeCheckboxState(treeViewId, <CheckboxUpdate[]>items.map(item => {
return { treeItemHandle: item.handle, newState: item.checkboxChecked ?? false };
}));
}));
}
private getTreeView(treeViewId: string): ITreeView | null {

View File

@@ -1295,6 +1295,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
ThemeColor: extHostTypes.ThemeColor,
ThemeIcon: extHostTypes.ThemeIcon,
TreeItem: extHostTypes.TreeItem,
TreeItem2: extHostTypes.TreeItem,
TreeItemCheckboxState: extHostTypes.TreeItemCheckboxState,
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
TypeHierarchyItem: extHostTypes.TypeHierarchyItem,
UIKind: UIKind,

View File

@@ -1399,6 +1399,11 @@ export interface DataTransferDTO {
readonly items: Array<[/* type */string, DataTransferItemDTO]>;
}
export interface CheckboxUpdate {
treeItemHandle: string;
newState: boolean;
}
export interface ExtHostTreeViewsShape {
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[] | undefined>;
$handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
@@ -1407,6 +1412,7 @@ export interface ExtHostTreeViewsShape {
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
$setFocus(treeViewId: string, treeItemHandle: string): void;
$setVisible(treeViewId: string, visible: boolean): void;
$changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void;
$hasResolve(treeViewId: string): Promise<boolean>;
$resolve(treeViewId: string, treeItemHandle: string, token: CancellationToken): Promise<ITreeItem | undefined>;
}

View File

@@ -10,11 +10,11 @@ import { basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
import { CheckboxUpdate, DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TreeCommand, TreeViewPaneHandleArg } from 'vs/workbench/common/views';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { asPromise } from 'vs/base/common/async';
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType, TreeItem } from 'vs/workbench/api/common/extHostTypes';
import { TreeItemCollapsibleState, TreeItemCheckboxState, ThemeIcon, MarkdownString as MarkdownStringType, TreeItem } from 'vs/workbench/api/common/extHostTypes';
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
import { equals, coalesce } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
@@ -23,6 +23,7 @@ import { MarkdownString, ViewBadge, DataTransfer } from 'vs/workbench/api/common
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
type TreeItemHandle = string;
@@ -99,6 +100,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
get visible() { return treeView.visible; },
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
get onDidChangeTreeCheckbox() { checkProposedApiEnabled(extension, 'treeItemCheckbox'); return treeView.onDidChangeTreeCheckbox; },
get message() { return treeView.message; },
set message(message: string) {
treeView.message = message;
@@ -234,6 +236,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
treeView.setVisible(isVisible);
}
$changeCheckboxState(treeViewId: string, checkboxUpdate: CheckboxUpdate[]): void {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
}
treeView.setCheckboxState(checkboxUpdate);
}
private createExtHostTreeView<T>(id: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): ExtHostTreeView<T> {
const treeView = new ExtHostTreeView<T>(id, options, this._proxy, this.commands.converter, this.logService, extension);
this.treeViews.set(id, treeView);
@@ -296,6 +306,9 @@ class ExtHostTreeView<T> extends Disposable {
private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;
private _onDidChangeTreeCheckbox = this._register(new Emitter<vscode.TreeCheckboxChangeEvent<T>>());
readonly onDidChangeTreeCheckbox: Event<vscode.TreeCheckboxChangeEvent<T>> = this._onDidChangeTreeCheckbox.event;
private _onDidChangeData: Emitter<TreeData<T>> = this._register(new Emitter<TreeData<T>>());
private refreshPromise: Promise<void> = Promise.resolve();
@@ -474,6 +487,26 @@ class ExtHostTreeView<T> extends Disposable {
}
}
async setCheckboxState(checkboxUpdates: CheckboxUpdate[]) {
const items = (await Promise.all(checkboxUpdates.map(async checkboxUpdate => {
const extensionItem = this.getExtensionElement(checkboxUpdate.treeItemHandle);
if (extensionItem) {
return {
extensionItem: extensionItem,
treeItem: await this.dataProvider.getTreeItem(extensionItem),
newState: checkboxUpdate.newState ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked
};
}
return Promise.resolve(undefined);
}))).filter((item) => item !== undefined) as { extensionItem: T; treeItem: vscode.TreeItem2; newState: TreeItemCheckboxState }[];
items.forEach(item => {
item.treeItem.checkboxState = item.newState ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked;
});
this._onDidChangeTreeCheckbox.fire({ items: items.map(item => [item.extensionItem, item.newState]) });
}
async handleDrag(sourceTreeItemHandles: TreeItemHandle[], treeDataTransfer: vscode.DataTransfer, token: CancellationToken): Promise<vscode.DataTransfer | undefined> {
const extensionTreeItems: T[] = [];
for (const sourceHandle of sourceTreeItemHandles) {
@@ -717,8 +750,13 @@ class ExtHostTreeView<T> extends Disposable {
return command ? { ...this.commands.toInternal(command, disposable), originalId: command.command } : undefined;
}
private getCheckbox(extensionTreeItem: vscode.TreeItem2): boolean | undefined {
return (extensionTreeItem.checkboxState !== undefined) ?
extensionTreeItem.checkboxState === TreeItemCheckboxState.Checked : undefined;
}
private validateTreeItem(extensionTreeItem: vscode.TreeItem) {
if (!TreeItem.isTreeItem(extensionTreeItem)) {
if (!TreeItem.isTreeItem(extensionTreeItem, this.extension)) {
throw new Error(`Extension ${this.extension.identifier.value} has provided an invalid tree item.`);
}
}
@@ -741,7 +779,8 @@ class ExtHostTreeView<T> extends Disposable {
iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
themeIcon: this.getThemeIcon(extensionTreeItem),
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
accessibilityInformation: extensionTreeItem.accessibilityInformation
accessibilityInformation: extensionTreeItem.accessibilityInformation,
checkboxChecked: this.getCheckbox(extensionTreeItem)
};
return {

View File

@@ -13,10 +13,12 @@ import { nextCharLength } from 'vs/base/common/strings';
import { isString, isStringArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';
/**
@@ -2413,12 +2415,13 @@ export class TreeItem {
command?: vscode.Command;
contextValue?: string;
tooltip?: string | vscode.MarkdownString;
checkboxState?: vscode.TreeItemCheckboxState;
static isTreeItem(thing: any): thing is TreeItem {
static isTreeItem(thing: any, extension: IExtensionDescription): thing is TreeItem {
if (thing instanceof TreeItem) {
return true;
}
const treeItemThing = thing as vscode.TreeItem;
const treeItemThing = thing as vscode.TreeItem2;
if (treeItemThing.label !== undefined && !isString(treeItemThing.label) && !(treeItemThing.label?.label)) {
console.log('INVALID tree item, invalid label', treeItemThing.label);
return false;
@@ -2462,6 +2465,13 @@ export class TreeItem {
console.log('INVALID tree item, invalid accessibilityInformation', treeItemThing.accessibilityInformation);
return false;
}
if (treeItemThing.checkboxState !== undefined) {
checkProposedApiEnabled(extension, 'treeItemCheckbox');
if (treeItemThing.checkboxState !== TreeItemCheckboxState.Checked && treeItemThing.checkboxState !== TreeItemCheckboxState.Unchecked) {
console.log('INVALID tree item, invalid checkboxState', treeItemThing.checkboxState);
return false;
}
}
return true;
}
@@ -2484,6 +2494,11 @@ export enum TreeItemCollapsibleState {
Expanded = 2
}
export enum TreeItemCheckboxState {
Unchecked = 0,
Checked = 1
}
@es5ClassCompat
export class DataTransferItem {

View File

@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { attachToggleStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITreeItem } from 'vs/workbench/common/views';
export class CheckboxStateHandler extends Disposable {
private readonly _onDidChangeCheckboxState = this._register(new Emitter<ITreeItem[]>());
readonly onDidChangeCheckboxState: Event<ITreeItem[]> = this._onDidChangeCheckboxState.event;
public setCheckboxState(node: ITreeItem) {
this._onDidChangeCheckboxState.fire([node]);
}
}
export class TreeItemCheckbox extends Disposable {
public toggle: Toggle | undefined;
private checkboxContainer: HTMLDivElement;
public isDisposed = false;
public static readonly checkboxClass = 'custom-view-tree-node-item-checkbox';
private readonly _onDidChangeState = new Emitter<boolean>();
readonly onDidChangeState: Event<boolean> = this._onDidChangeState.event;
constructor(container: HTMLElement, private checkboxStateHandler: CheckboxStateHandler, private themeService: IThemeService) {
super();
this.checkboxContainer = <HTMLDivElement>container;
}
public render(node: ITreeItem) {
if (node.checkboxChecked !== undefined) {
if (!this.toggle) {
this.createCheckbox(node);
}
else {
this.toggle.checked = node.checkboxChecked;
this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined);
}
}
}
private createCheckbox(node: ITreeItem) {
if (node.checkboxChecked !== undefined) {
this.toggle = new Toggle({
isChecked: node.checkboxChecked,
title: localize('check', "Check"),
icon: node.checkboxChecked ? Codicon.check : undefined
});
this.toggle.domNode.classList.add(TreeItemCheckbox.checkboxClass);
DOM.append(this.checkboxContainer, this.toggle.domNode);
this.registerListener(node);
}
}
private registerListener(node: ITreeItem) {
if (this.toggle) {
this._register({ dispose: () => this.removeCheckbox() });
this._register(this.toggle);
this._register(this.toggle.onChange(() => {
this.setCheckbox(node);
}));
this._register(attachToggleStyler(this.toggle, this.themeService));
}
}
private setCheckbox(node: ITreeItem) {
if (this.toggle && node.checkboxChecked !== undefined) {
node.checkboxChecked = this.toggle.checked;
this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined);
this.toggle.checked = this.toggle.checked;
this.checkboxStateHandler.setCheckboxState(node);
}
}
private removeCheckbox() {
const children = this.checkboxContainer.children;
for (const child of children) {
this.checkboxContainer.removeChild(child);
}
}
}

View File

@@ -120,6 +120,20 @@
padding-left: 3px;
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-checkbox {
width: 16px;
height: 16px;
margin: 3px 6px 3px 0px;
padding: 0px;
border: 1px solid var(--vscode-checkbox-border);
opacity: 1;
background-color: var(--vscode-checkbox-background);
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-checkbox.codicon {
font-size: 13px;
line-height: 15px;
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-inputbox {
line-height: normal;
flex: 1;

View File

@@ -66,6 +66,7 @@ import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService';
import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd';
import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd';
import { CheckboxStateHandler, TreeItemCheckbox } from 'vs/workbench/browser/checkbox';
export class TreeViewPane extends ViewPane {
@@ -236,6 +237,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
private readonly _onDidChangeDescription: Emitter<string | undefined> = this._register(new Emitter<string | undefined>());
readonly onDidChangeDescription: Event<string | undefined> = this._onDidChangeDescription.event;
private readonly _onDidChangeCheckboxState: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
readonly onDidChangeCheckboxState: Event<ITreeItem[]> = this._onDidChangeCheckboxState.event;
private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());
constructor(
@@ -598,7 +602,12 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
const aligner = new Aligner(this.themeService);
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);
const checkboxStateHandler = this._register(new CheckboxStateHandler());
this._register(checkboxStateHandler.onDidChangeCheckboxState(items => {
items.forEach(item => this.tree?.rerender(item));
this._onDidChangeCheckboxState.fire(items);
}));
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler);
const widgetAriaLabel = this._title;
this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer!, new TreeViewDelegate(), [renderer],
@@ -641,7 +650,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
}
},
expandOnlyOnTwistieClick: (e: ITreeItem) => {
return !!e.command || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick';
return !!e.command || e.checkboxChecked !== undefined || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick';
},
collapseByDefault: (e: ITreeItem): boolean => {
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
@@ -685,6 +694,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
if (!e.browserEvent) {
return;
}
if ((e.browserEvent.target as HTMLElement).classList.contains(TreeItemCheckbox.checkboxClass)) {
return;
}
const selection = this.tree!.getSelection();
const command = await this.resolveCommand(selection.length === 1 ? selection[0] : undefined);
@@ -1009,6 +1021,8 @@ interface ITreeExplorerTemplateData {
container: HTMLElement;
resourceLabel: IResourceLabel;
icon: HTMLElement;
checkboxContainer: HTMLElement;
checkbox?: TreeItemCheckbox;
actionBar: ActionBar;
}
@@ -1025,6 +1039,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
private labels: ResourceLabels,
private actionViewItemProvider: IActionViewItemProvider,
private aligner: Aligner,
private checkboxStateHandler: CheckboxStateHandler,
@IThemeService private readonly themeService: IThemeService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ILabelService private readonly labelService: ILabelService,
@@ -1050,6 +1065,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
container.classList.add('custom-view-tree-node-item');
const checkboxContainer = DOM.append(container, DOM.$(''));
const resourceLabel = this.labels.create(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate });
const icon = DOM.prepend(resourceLabel.element, DOM.$('.custom-view-tree-node-item-icon'));
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
@@ -1057,7 +1073,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
actionViewItemProvider: this.actionViewItemProvider
});
return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None };
return { resourceLabel, icon, checkboxContainer, actionBar, container, elementDisposable: Disposable.None };
}
private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | ITooltipMarkdownString | undefined {
@@ -1123,6 +1139,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
commandEnabled = isTreeCommandEnabled(node.command, this.contextKeyService);
}
const disposableStore = new DisposableStore();
templateData.elementDisposable = disposableStore;
this.renderCheckbox(node, templateData, disposableStore);
if (resource) {
const fileDecorations = this.configurationService.getValue<{ colors: boolean; badges: boolean }>('explorer.decorations');
const labelResource = resource ? resource : URI.parse('missing:_icon_resource');
@@ -1171,8 +1192,6 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
const disposableStore = new DisposableStore();
templateData.elementDisposable = disposableStore;
const menuActions = this.menus.getResourceActions(node);
if (menuActions.menu) {
disposableStore.add(menuActions.menu);
@@ -1187,6 +1206,21 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
disposableStore.add(toDisposable(() => this.treeViewsService.removeRenderedTreeItemElement(node)));
}
private renderCheckbox(node: ITreeItem, templateData: ITreeExplorerTemplateData, disposableStore: DisposableStore) {
if (node.checkboxChecked !== undefined) {
if (!templateData.checkbox) {
const checkbox = new TreeItemCheckbox(templateData.checkboxContainer, this.checkboxStateHandler, this.themeService);
templateData.checkbox = checkbox;
disposableStore.add({ dispose: () => { checkbox.dispose(); templateData.checkbox = undefined; } });
}
templateData.checkbox.render(node);
}
else if (templateData.checkbox) {
templateData.checkbox.dispose();
templateData.checkbox = undefined;
}
}
private setAlignment(container: HTMLElement, treeItem: ITreeItem) {
container.parentElement!.classList.toggle('align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
}
@@ -1244,9 +1278,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
class Aligner extends Disposable {
private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> | undefined;
private hasCheckboxes: boolean;
constructor(private themeService: IThemeService) {
super();
this.hasCheckboxes = false;
}
set tree(tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>) {
@@ -1254,6 +1290,9 @@ class Aligner extends Disposable {
}
public alignIconWithTwisty(treeItem: ITreeItem): boolean {
if (this.hasCheckboxes) {
return false;
}
if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
return false;
}
@@ -1262,6 +1301,12 @@ class Aligner extends Disposable {
}
if (this._tree) {
if (!this.hasCheckboxes && treeItem.checkboxChecked !== undefined) {
this.hasCheckboxes = true;
// TODO: rerender already rendered elements
this._tree.rerender();
return false;
}
const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
if (this.hasIcon(parent)) {
return !!parent.children && parent.children.some(c => c.collapsibleState !== TreeItemCollapsibleState.None && !this.hasIcon(c));

View File

@@ -675,6 +675,8 @@ export interface ITreeView extends IDisposable {
readonly onDidChangeWelcomeState: Event<void>;
readonly onDidChangeCheckboxState: Event<ITreeItem[]>;
readonly container: any | undefined;
refresh(treeItems?: ITreeItem[]): Promise<void>;
@@ -772,6 +774,8 @@ export interface ITreeItem {
children?: ITreeItem[];
accessibilityInformation?: IAccessibilityInformation;
checkboxChecked?: boolean;
}
export class ResolvableTreeItem implements ITreeItem {

View File

@@ -65,6 +65,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',
treeItemCheckbox: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeItemCheckbox.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',
workspaceTrust: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts'

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* 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' {
export class TreeItem2 extends TreeItem {
/**
* [TreeItemCheckboxState](#TreeItemCheckboxState) of the tree item.
*/
checkboxState?: TreeItemCheckboxState;
}
/**
* Checkbox state of the tree item
*/
export enum TreeItemCheckboxState {
/**
* Determines an item is unchecked
*/
Unchecked = 0,
/**
* Determines an item is checked
*/
Checked = 1
}
/**
* A data provider that provides tree data
*/
export interface TreeView<T> {
/**
* An event to signal that an element or root has either been checked or unchecked.
*/
onDidChangeTreeCheckbox: Event<TreeCheckboxChangeEvent<T>>;
}
export interface TreeCheckboxChangeEvent<T> {
/**
* The item that was checked or unchecked.
*/
readonly items: [T, TreeItemCheckboxState][];
}
}