From 03caa411b5511460a6417a8c0ffe8b53f336d56b Mon Sep 17 00:00:00 2001 From: Julien Roncaglia Date: Wed, 21 Feb 2018 23:21:34 +0100 Subject: [PATCH 1/3] Allow extensions to specify custom tree view resoure type The FileKind was previously guessed from the collapsability of the item, extensions now have the capability to set it manually. Fixes #43216 --- src/vs/vscode.d.ts | 27 ++++++++++++++++--- src/vs/workbench/api/node/extHost.api.impl.ts | 1 + src/vs/workbench/api/node/extHostTreeViews.ts | 17 +++++++----- src/vs/workbench/api/node/extHostTypes.ts | 12 +++++++++ .../browser/parts/views/customView.ts | 19 ++++++++++--- src/vs/workbench/common/views.ts | 11 ++++++-- 6 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 3ca7322116d..dde953700ed 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5063,6 +5063,25 @@ declare module 'vscode' { getChildren(element?: T): ProviderResult; } + /** + * A category in a File Icon Theme, either [file](#ThemeIconCategory.file) or [folder](#ThemeIconCategory.folder) + */ + export class ThemeIconCategory { + /** + * Use the File Icon Theme for files + */ + static readonly File: ThemeIconCategory; + + /** + * Use the File Icon Theme for files + */ + static readonly Folder: ThemeIconCategory; + + readonly id: string; + + private constructor(id: string); + } + export class TreeItem { /** * A human-readable string describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). @@ -5077,15 +5096,17 @@ declare module 'vscode' { id?: string; /** - * The icon path for the tree item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). + * The icon path for the tree item. + * When `falsy` it is derived from [resourceUri](#TreeItem.resourceUri) and the current theme (As a file if the node isn't collapsible or for folders otherwise) + * When a [ThemeIconCategory](#ThemeIconCategory) is specified it is derived from [resourceUri](#TreeItem.resourceUri) and the current theme for the specified category. */ - iconPath?: string | Uri | { light: string | Uri; dark: string | Uri }; + iconPath?: string | Uri | { light: string | Uri | ThemeIconCategory; dark: string | Uri | ThemeIconCategory } | ThemeIconCategory; /** * The [uri](#Uri) of the resource representing this item. * * Will be used to derive the [label](#TreeItem.label), when it is not provided. - * Will be used to derive the icon from current file icon theme, when [iconPath](#TreeItem.iconPath) is not provided. + * Will be used to derive the icon from current icon theme, when [iconPath](#TreeItem.iconPath) is not provided or is a [ThemeIconCategory](#ThemeIconCategory). */ resourceUri?: Uri; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 466456f5bc3..6ebb507e700 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -620,6 +620,7 @@ export function createApiFactory( WorkspaceEdit: extHostTypes.WorkspaceEdit, ProgressLocation: extHostTypes.ProgressLocation, TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, + ThemeIconCategory: extHostTypes.ThemeIconCategory, TreeItem: extHostTypes.TreeItem, ThemeColor: extHostTypes.ThemeColor, // functions diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 0ab16383768..f5c1dc92ba3 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -12,11 +12,11 @@ import { debounceEvent } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; -import { ITreeItem, TreeViewItemHandleArg } from 'vs/workbench/common/views'; +import { ITreeItem, TreeViewItemHandleArg, IThemeIconCategory } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { asWinJsPromise } from 'vs/base/common/async'; import { coalesce } from 'vs/base/common/arrays'; -import { TreeItemCollapsibleState } from 'vs/workbench/api/node/extHostTypes'; +import { TreeItemCollapsibleState, ThemeIconCategory as ExtHostThemeIconCategory } from 'vs/workbench/api/node/extHostTypes'; import { isUndefinedOrNull } from 'vs/base/common/types'; type TreeItemHandle = string; @@ -214,9 +214,11 @@ class ExtHostTreeView extends Disposable { throw new Error('This should not be reached'); } - private getLightIconPath(extensionTreeItem: vscode.TreeItem): string { + private getLightIconPath(extensionTreeItem: vscode.TreeItem): string | IThemeIconCategory { if (extensionTreeItem.iconPath) { - if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI) { + if (typeof extensionTreeItem.iconPath === 'string' + || extensionTreeItem.iconPath instanceof URI + || extensionTreeItem.iconPath instanceof ExtHostThemeIconCategory) { return this.getIconPath(extensionTreeItem.iconPath); } return this.getIconPath(extensionTreeItem.iconPath['light']); @@ -224,17 +226,20 @@ class ExtHostTreeView extends Disposable { return void 0; } - private getDarkIconPath(extensionTreeItem: vscode.TreeItem): string { + private getDarkIconPath(extensionTreeItem: vscode.TreeItem): string | IThemeIconCategory { if (extensionTreeItem.iconPath && extensionTreeItem.iconPath['dark']) { return this.getIconPath(extensionTreeItem.iconPath['dark']); } return void 0; } - private getIconPath(iconPath: string | URI): string { + private getIconPath(iconPath: string | URI | ExtHostThemeIconCategory): string | IThemeIconCategory { if (iconPath instanceof URI) { return iconPath.toString(); } + if (iconPath instanceof ExtHostThemeIconCategory) { + return { id: iconPath.id }; + } return URI.file(iconPath).toString(); } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 2cac1f11910..f2894951a96 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1544,6 +1544,18 @@ export enum TreeItemCollapsibleState { Expanded = 2 } +export class ThemeIconCategory { + static readonly File = new ThemeIconCategory('file'); + + static readonly Folder = new ThemeIconCategory('folder'); + + readonly id: string; + + private constructor(id: string) { + this.id = id; + } +} + export class ThemeColor { id: string; constructor(id: string) { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 5381405294e..28aaa5181b9 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -13,7 +13,7 @@ import * as DOM from 'vs/base/browser/dom'; import { $ } from 'vs/base/browser/builder'; import { LIGHT } from 'vs/platform/theme/common/themeService'; import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; -import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor } from 'vs/workbench/common/views'; +import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor, FileThemeIconCategoryId, FolderThemeIconCategoryId } from 'vs/workbench/common/views'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -414,8 +414,19 @@ class TreeRenderer implements IRenderer { DOM.removeClass(templateData.label, 'custom-view-tree-node-item-label'); DOM.removeClass(templateData.resourceLabel.element, 'custom-view-tree-node-item-resourceLabel'); - if (resource && !icon) { - templateData.resourceLabel.setLabel({ name: label, resource }, { fileKind: node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE, title: node.tooltip }); + if (resource && (typeof icon !== 'string')) { + let fileKind = node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE; + if (icon && icon.id) { + switch (icon.id) { + case FileThemeIconCategoryId: + fileKind = FileKind.FILE; + break; + case FolderThemeIconCategoryId: + fileKind = FileKind.FOLDER; + break; + } + } + templateData.resourceLabel.setLabel({ name: label, resource }, { fileKind, title: node.tooltip }); DOM.addClass(templateData.resourceLabel.element, 'custom-view-tree-node-item-resourceLabel'); } else { templateData.label.textContent = label; @@ -460,7 +471,7 @@ class TreeItemIcon extends Disposable { const fileIconTheme = this.themeService.getFileIconTheme(); const contributedIcon = this.themeService.getTheme().type === LIGHT ? this._treeItem.icon : this._treeItem.iconDark; - const hasContributedIcon = !!contributedIcon; + const hasContributedIcon = typeof contributedIcon === 'string'; const hasChildren = this._treeItem.collapsibleState !== TreeItemCollapsibleState.None; const hasResource = !!this._treeItem.resourceUri; const isFolder = hasResource && hasChildren; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 453e722abd9..99869b43d8d 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -198,6 +198,13 @@ export enum TreeItemCollapsibleState { Expanded = 2 } +export const FileThemeIconCategoryId = 'file'; +export const FolderThemeIconCategoryId = 'folder'; + +export interface IThemeIconCategory { + readonly id: string; +} + export interface ITreeItem { handle: string; @@ -208,9 +215,9 @@ export interface ITreeItem { label?: string; - icon?: string; + icon?: string | IThemeIconCategory; - iconDark?: string; + iconDark?: string | IThemeIconCategory; resourceUri?: UriComponents; From 29f6d542b8005eff902e8042088a20b0bd8dbe7e Mon Sep 17 00:00:00 2001 From: Julien Roncaglia Date: Fri, 23 Feb 2018 13:45:22 +0100 Subject: [PATCH 2/3] Don't alias ThemeIconCategory --- src/vs/workbench/api/node/extHostTreeViews.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index f5c1dc92ba3..927d6c52a78 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -16,7 +16,7 @@ import { ITreeItem, TreeViewItemHandleArg, IThemeIconCategory } from 'vs/workben import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { asWinJsPromise } from 'vs/base/common/async'; import { coalesce } from 'vs/base/common/arrays'; -import { TreeItemCollapsibleState, ThemeIconCategory as ExtHostThemeIconCategory } from 'vs/workbench/api/node/extHostTypes'; +import { TreeItemCollapsibleState, ThemeIconCategory } from 'vs/workbench/api/node/extHostTypes'; import { isUndefinedOrNull } from 'vs/base/common/types'; type TreeItemHandle = string; @@ -218,7 +218,7 @@ class ExtHostTreeView extends Disposable { if (extensionTreeItem.iconPath) { if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI - || extensionTreeItem.iconPath instanceof ExtHostThemeIconCategory) { + || extensionTreeItem.iconPath instanceof ThemeIconCategory) { return this.getIconPath(extensionTreeItem.iconPath); } return this.getIconPath(extensionTreeItem.iconPath['light']); @@ -233,11 +233,11 @@ class ExtHostTreeView extends Disposable { return void 0; } - private getIconPath(iconPath: string | URI | ExtHostThemeIconCategory): string | IThemeIconCategory { + private getIconPath(iconPath: string | URI | ThemeIconCategory): string | IThemeIconCategory { if (iconPath instanceof URI) { return iconPath.toString(); } - if (iconPath instanceof ExtHostThemeIconCategory) { + if (iconPath instanceof ThemeIconCategory) { return { id: iconPath.id }; } return URI.file(iconPath).toString(); From 46e9101dc1e8054dcb6715cd034a619de10fe98a Mon Sep 17 00:00:00 2001 From: Julien Roncaglia Date: Fri, 23 Feb 2018 22:00:11 +0100 Subject: [PATCH 3/3] ThemeIconCategory -> ThemeIcon --- src/vs/vscode.d.ts | 14 +++++++------- src/vs/workbench/api/node/extHost.api.impl.ts | 2 +- src/vs/workbench/api/node/extHostTreeViews.ts | 14 +++++++------- src/vs/workbench/api/node/extHostTypes.ts | 6 +++--- src/vs/workbench/browser/parts/views/customView.ts | 6 +++--- src/vs/workbench/common/views.ts | 10 +++++----- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index dde953700ed..e73c915cae0 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5064,18 +5064,18 @@ declare module 'vscode' { } /** - * A category in a File Icon Theme, either [file](#ThemeIconCategory.file) or [folder](#ThemeIconCategory.folder) + * A category in a File Icon Theme, either [file](#ThemeIcon.file) or [folder](#ThemeIcon.folder) */ - export class ThemeIconCategory { + export class ThemeIcon { /** * Use the File Icon Theme for files */ - static readonly File: ThemeIconCategory; + static readonly File: ThemeIcon; /** * Use the File Icon Theme for files */ - static readonly Folder: ThemeIconCategory; + static readonly Folder: ThemeIcon; readonly id: string; @@ -5098,15 +5098,15 @@ declare module 'vscode' { /** * The icon path for the tree item. * When `falsy` it is derived from [resourceUri](#TreeItem.resourceUri) and the current theme (As a file if the node isn't collapsible or for folders otherwise) - * When a [ThemeIconCategory](#ThemeIconCategory) is specified it is derived from [resourceUri](#TreeItem.resourceUri) and the current theme for the specified category. + * When a [ThemeIcon](#ThemeIcon) is specified it is derived from [resourceUri](#TreeItem.resourceUri) and the current theme for the specified category. */ - iconPath?: string | Uri | { light: string | Uri | ThemeIconCategory; dark: string | Uri | ThemeIconCategory } | ThemeIconCategory; + iconPath?: string | Uri | { light: string | Uri | ThemeIcon; dark: string | Uri | ThemeIcon } | ThemeIcon; /** * The [uri](#Uri) of the resource representing this item. * * Will be used to derive the [label](#TreeItem.label), when it is not provided. - * Will be used to derive the icon from current icon theme, when [iconPath](#TreeItem.iconPath) is not provided or is a [ThemeIconCategory](#ThemeIconCategory). + * Will be used to derive the icon from current icon theme, when [iconPath](#TreeItem.iconPath) is not provided or is a [ThemeIcon](#ThemeIcon). */ resourceUri?: Uri; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 6ebb507e700..66bdc0bb72e 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -620,7 +620,7 @@ export function createApiFactory( WorkspaceEdit: extHostTypes.WorkspaceEdit, ProgressLocation: extHostTypes.ProgressLocation, TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, - ThemeIconCategory: extHostTypes.ThemeIconCategory, + ThemeIcon: extHostTypes.ThemeIcon, TreeItem: extHostTypes.TreeItem, ThemeColor: extHostTypes.ThemeColor, // functions diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 927d6c52a78..07b53d5be71 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -12,11 +12,11 @@ import { debounceEvent } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; -import { ITreeItem, TreeViewItemHandleArg, IThemeIconCategory } from 'vs/workbench/common/views'; +import { ITreeItem, TreeViewItemHandleArg, IThemeIcon } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { asWinJsPromise } from 'vs/base/common/async'; import { coalesce } from 'vs/base/common/arrays'; -import { TreeItemCollapsibleState, ThemeIconCategory } from 'vs/workbench/api/node/extHostTypes'; +import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes'; import { isUndefinedOrNull } from 'vs/base/common/types'; type TreeItemHandle = string; @@ -214,11 +214,11 @@ class ExtHostTreeView extends Disposable { throw new Error('This should not be reached'); } - private getLightIconPath(extensionTreeItem: vscode.TreeItem): string | IThemeIconCategory { + private getLightIconPath(extensionTreeItem: vscode.TreeItem): string | IThemeIcon { if (extensionTreeItem.iconPath) { if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI - || extensionTreeItem.iconPath instanceof ThemeIconCategory) { + || extensionTreeItem.iconPath instanceof ThemeIcon) { return this.getIconPath(extensionTreeItem.iconPath); } return this.getIconPath(extensionTreeItem.iconPath['light']); @@ -226,18 +226,18 @@ class ExtHostTreeView extends Disposable { return void 0; } - private getDarkIconPath(extensionTreeItem: vscode.TreeItem): string | IThemeIconCategory { + private getDarkIconPath(extensionTreeItem: vscode.TreeItem): string | IThemeIcon { if (extensionTreeItem.iconPath && extensionTreeItem.iconPath['dark']) { return this.getIconPath(extensionTreeItem.iconPath['dark']); } return void 0; } - private getIconPath(iconPath: string | URI | ThemeIconCategory): string | IThemeIconCategory { + private getIconPath(iconPath: string | URI | ThemeIcon): string | IThemeIcon { if (iconPath instanceof URI) { return iconPath.toString(); } - if (iconPath instanceof ThemeIconCategory) { + if (iconPath instanceof ThemeIcon) { return { id: iconPath.id }; } return URI.file(iconPath).toString(); diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index f2894951a96..a3cdf3bbb2d 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1544,10 +1544,10 @@ export enum TreeItemCollapsibleState { Expanded = 2 } -export class ThemeIconCategory { - static readonly File = new ThemeIconCategory('file'); +export class ThemeIcon { + static readonly File = new ThemeIcon('file'); - static readonly Folder = new ThemeIconCategory('folder'); + static readonly Folder = new ThemeIcon('folder'); readonly id: string; diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 28aaa5181b9..2a4ff5e6540 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -13,7 +13,7 @@ import * as DOM from 'vs/base/browser/dom'; import { $ } from 'vs/base/browser/builder'; import { LIGHT } from 'vs/platform/theme/common/themeService'; import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; -import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor, FileThemeIconCategoryId, FolderThemeIconCategoryId } from 'vs/workbench/common/views'; +import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor, FileThemeIconId, FolderThemeIconId } from 'vs/workbench/common/views'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -418,10 +418,10 @@ class TreeRenderer implements IRenderer { let fileKind = node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE; if (icon && icon.id) { switch (icon.id) { - case FileThemeIconCategoryId: + case FileThemeIconId: fileKind = FileKind.FILE; break; - case FolderThemeIconCategoryId: + case FolderThemeIconId: fileKind = FileKind.FOLDER; break; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 99869b43d8d..0f3038d2892 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -198,10 +198,10 @@ export enum TreeItemCollapsibleState { Expanded = 2 } -export const FileThemeIconCategoryId = 'file'; -export const FolderThemeIconCategoryId = 'folder'; +export const FileThemeIconId = 'file'; +export const FolderThemeIconId = 'folder'; -export interface IThemeIconCategory { +export interface IThemeIcon { readonly id: string; } @@ -215,9 +215,9 @@ export interface ITreeItem { label?: string; - icon?: string | IThemeIconCategory; + icon?: string | IThemeIcon; - iconDark?: string | IThemeIconCategory; + iconDark?: string | IThemeIcon; resourceUri?: UriComponents;