树视图 API
树视图 API 允许扩展在 Visual Studio Code 的侧边栏中显示内容。此内容以树状结构组织,并符合 VS Code 内置视图的样式。
例如,内置的“引用搜索视图”扩展将引用搜索结果显示为单独的视图。
查找所有引用结果显示在 引用: 结果 树视图中,该视图位于 引用 视图容器中。
本指南教您如何编写扩展,向 Visual Studio Code 贡献树视图和视图容器。
树视图 API 基础知识
为了解释树视图 API,我们将构建一个名为 Node Dependencies 的示例扩展。此扩展将使用树视图来显示当前文件夹中的所有 Node.js 依赖项。添加树视图的步骤是在 package.json
中贡献树视图,创建 TreeDataProvider
,并注册 TreeDataProvider
。您可以在 vscode-extension-samples GitHub 存储库中的 tree-view-sample
中找到此示例扩展的完整源代码。
package.json 贡献
首先,您必须使用 package.json
中的 contributes.views 贡献点,让 VS Code 知道您正在贡献一个视图。
这是我们扩展的第一个版本的 package.json
{
"name": "custom-view-samples",
"displayName": "Custom view Samples",
"description": "Samples for VS Code's view API",
"version": "0.0.1",
"publisher": "alexr00",
"engines": {
"vscode": "^1.74.0"
},
"activationEvents": [],
"main": "./out/extension.js",
"contributes": {
"views": {
"explorer": [
{
"id": "nodeDependencies",
"name": "Node Dependencies"
}
]
}
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./"
},
"devDependencies": {
"@types/node": "^10.12.21",
"@types/vscode": "^1.42.0",
"typescript": "^3.5.1",
"tslint": "^5.12.1"
}
}
注意:如果您的扩展面向 1.74 之前的 VS Code 版本,您必须在
activationEvents
中显式列出onView:nodeDependencies
。
您必须为视图指定标识符和名称,并且可以贡献到以下位置
explorer
:侧边栏中的资源管理器视图debug
:侧边栏中的运行和调试视图scm
:侧边栏中的源代码管理视图test
:侧边栏中的测试资源管理器视图- 自定义视图容器
树数据提供程序
第二步是为注册的视图提供数据,以便 VS Code 可以在视图中显示数据。为此,您应该首先实现 TreeDataProvider。我们的 TreeDataProvider
将提供节点依赖项数据,但您可以拥有提供其他类型数据的数据提供程序。
此 API 中有两个必要的方法需要您实现
getChildren(element?: T): ProviderResult<T[]>
- 实现此方法以返回给定element
或根的子项(如果未传递元素)。getTreeItem(element: T): TreeItem | Thenable<TreeItem>
- 实现此方法以返回元素的用户界面表示 (TreeItem),该元素将显示在视图中。
当用户打开树视图时,将调用不带 element
的 getChildren
方法。从那里,您的 TreeDataProvider
应返回您的顶级树项。在我们的示例中,顶级树项的 collapsibleState
为 TreeItemCollapsibleState.Collapsed
,这意味着顶级树项将显示为折叠状态。将 collapsibleState
设置为 TreeItemCollapsibleState.Expanded
将导致树项显示为展开状态。将 collapsibleState
保留为其默认值 TreeItemCollapsibleState.None
表示树项没有子项。对于 collapsibleState
为 TreeItemCollapsibleState.None
的树项,不会调用 getChildren
。
这是一个提供节点依赖项数据的 TreeDataProvider
实现示例
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
export class NodeDependenciesProvider implements vscode.TreeDataProvider<Dependency> {
constructor(private workspaceRoot: string) {}
getTreeItem(element: Dependency): vscode.TreeItem {
return element;
}
getChildren(element?: Dependency): Thenable<Dependency[]> {
if (!this.workspaceRoot) {
vscode.window.showInformationMessage('No dependency in empty workspace');
return Promise.resolve([]);
}
if (element) {
return Promise.resolve(
this.getDepsInPackageJson(
path.join(this.workspaceRoot, 'node_modules', element.label, 'package.json')
)
);
} else {
const packageJsonPath = path.join(this.workspaceRoot, 'package.json');
if (this.pathExists(packageJsonPath)) {
return Promise.resolve(this.getDepsInPackageJson(packageJsonPath));
} else {
vscode.window.showInformationMessage('Workspace has no package.json');
return Promise.resolve([]);
}
}
}
/**
* Given the path to package.json, read all its dependencies and devDependencies.
*/
private getDepsInPackageJson(packageJsonPath: string): Dependency[] {
if (this.pathExists(packageJsonPath)) {
const toDep = (moduleName: string, version: string): Dependency => {
if (this.pathExists(path.join(this.workspaceRoot, 'node_modules', moduleName))) {
return new Dependency(
moduleName,
version,
vscode.TreeItemCollapsibleState.Collapsed
);
} else {
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.None);
}
};
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const deps = packageJson.dependencies
? Object.keys(packageJson.dependencies).map(dep =>
toDep(dep, packageJson.dependencies[dep])
)
: [];
const devDeps = packageJson.devDependencies
? Object.keys(packageJson.devDependencies).map(dep =>
toDep(dep, packageJson.devDependencies[dep])
)
: [];
return deps.concat(devDeps);
} else {
return [];
}
}
private pathExists(p: string): boolean {
try {
fs.accessSync(p);
} catch (err) {
return false;
}
return true;
}
}
class Dependency extends vscode.TreeItem {
constructor(
public readonly label: string,
private version: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.tooltip = `${this.label}-${this.version}`;
this.description = this.version;
}
iconPath = {
light: path.join(__filename, '..', '..', 'resources', 'light', 'dependency.svg'),
dark: path.join(__filename, '..', '..', 'resources', 'dark', 'dependency.svg')
};
}
注册 TreeDataProvider
第三步是将上述数据提供程序注册到您的视图。
可以通过以下两种方式完成
-
vscode.window.registerTreeDataProvider
- 通过提供注册的视图 ID 和上述数据提供程序来注册树数据提供程序。const rootPath = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined; vscode.window.registerTreeDataProvider( 'nodeDependencies', new NodeDependenciesProvider(rootPath) );
-
vscode.window.createTreeView
- 通过提供注册的视图 ID 和上述数据提供程序来创建树视图。这将提供对 TreeView 的访问权限,您可以使用它来执行其他视图操作。如果您需要TreeView
API,请使用createTreeView
。vscode.window.createTreeView('nodeDependencies', { treeDataProvider: new NodeDependenciesProvider(rootPath) });
这是扩展的运行效果
更新树视图内容
我们的节点依赖项视图很简单,一旦显示数据,就不会更新。但是,在视图中添加一个刷新按钮并使用 package.json
的当前内容更新节点依赖项视图将很有用。为此,我们可以使用 onDidChangeTreeData
事件。
onDidChangeTreeData?: Event<T | undefined | null | void>
- 如果您的树数据可以更改并且您想要更新树视图,请实现此方法。
将以下内容添加到您的 NodeDependenciesProvider
。
private _onDidChangeTreeData: vscode.EventEmitter<Dependency | undefined | null | void> = new vscode.EventEmitter<Dependency | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<Dependency | undefined | null | void> = this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire();
}
现在我们有了一个刷新方法,但没有人调用它。我们可以添加一个命令来调用刷新。
在 package.json
的 contributes
部分中,添加
"commands": [
{
"command": "nodeDependencies.refreshEntry",
"title": "Refresh",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
},
]
并在您的扩展激活中注册该命令
import * as vscode from 'vscode';
import { NodeDependenciesProvider } from './nodeDependencies';
export function activate(context: vscode.ExtensionContext) {
const rootPath =
vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0
? vscode.workspace.workspaceFolders[0].uri.fsPath
: undefined;
const nodeDependenciesProvider = new NodeDependenciesProvider(rootPath);
vscode.window.registerTreeDataProvider('nodeDependencies', nodeDependenciesProvider);
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () =>
nodeDependenciesProvider.refresh()
);
}
现在我们有了一个命令来刷新节点依赖项视图,但是视图上的一个按钮会更好。我们已经为命令添加了一个 icon
,因此当我们将它添加到视图时,它将显示该图标。
在 package.json
的 contributes
部分中,添加
"menus": {
"view/title": [
{
"command": "nodeDependencies.refreshEntry",
"when": "view == nodeDependencies",
"group": "navigation"
},
]
}
激活
重要的是,您的扩展仅在用户需要您的扩展提供的功能时才激活。在这种情况下,您应该考虑仅在用户开始使用视图时才激活您的扩展。当您的扩展声明视图贡献时,VS Code 会自动为您执行此操作。当用户打开视图时,VS Code 会发出 activationEvent onView:${viewId}(对于上面的示例,为 onView:nodeDependencies
)。
注意:对于 1.74.0 之前的 VS Code 版本,您必须在
package.json
中显式注册此激活事件,以便 VS Code 在此视图上激活您的扩展"activationEvents": [ "onView:nodeDependencies", ],
视图容器
视图容器包含在活动栏或面板中与内置视图容器一起显示的视图列表。内置视图容器的示例包括源代码管理和资源管理器。
要贡献视图容器,您应该首先使用 package.json
中的 contributes.viewsContainers 贡献点注册它。
您必须指定以下必填字段
id
- 您正在创建的新视图容器的 ID。title
- 将显示在视图容器顶部的名称。icon
- 当在活动栏中时,将为视图容器显示的图像。
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "package-explorer",
"title": "Package Explorer",
"icon": "media/dep.svg"
}
]
}
}
或者,您可以通过将其放置在 panel
节点下,将此视图贡献到面板。
"contributes": {
"viewsContainers": {
"panel": [
{
"id": "package-explorer",
"title": "Package Explorer",
"icon": "media/dep.svg"
}
]
}
}
向视图容器贡献视图
创建视图容器后,您可以使用 package.json
中的 contributes.views 贡献点。
"contributes": {
"views": {
"package-explorer": [
{
"id": "nodeDependencies",
"name": "Node Dependencies",
"icon": "media/dep.svg",
"contextualTitle": "Package Explorer"
}
]
}
}
视图还可以具有可选的 visibility
属性,可以将其设置为 visible
、collapsed
或 hidden
。此属性仅在首次使用此视图打开工作区时才被 VS Code 遵守。之后,可见性将设置为用户选择的任何值。如果您有一个包含许多视图的视图容器,或者如果您的视图对扩展的每个用户都无用,请考虑将视图设置为 collapsed
或 hidden
。hidden
视图将出现在视图容器的“视图”菜单中
视图操作
操作在您的各个树项上以内联图标形式提供,在树项上下文菜单中以及视图标题的顶部提供。操作是您设置为在这些位置显示的命令,方法是将贡献添加到您的 package.json
。
要贡献到这三个位置,您可以使用 package.json 中的以下菜单贡献点
view/title
- 用于在视图标题中显示操作的位置。主要或内联操作使用"group": "navigation"
,其余为辅助操作,位于...
菜单中。view/item/context
- 用于显示树项操作的位置。内联操作使用"group": "inline"
,其余为辅助操作,位于...
菜单中。
您可以使用 when 子句控制这些操作的可见性。
示例
"contributes": {
"commands": [
{
"command": "nodeDependencies.refreshEntry",
"title": "Refresh",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
},
{
"command": "nodeDependencies.addEntry",
"title": "Add"
},
{
"command": "nodeDependencies.editEntry",
"title": "Edit",
"icon": {
"light": "resources/light/edit.svg",
"dark": "resources/dark/edit.svg"
}
},
{
"command": "nodeDependencies.deleteEntry",
"title": "Delete"
}
],
"menus": {
"view/title": [
{
"command": "nodeDependencies.refreshEntry",
"when": "view == nodeDependencies",
"group": "navigation"
},
{
"command": "nodeDependencies.addEntry",
"when": "view == nodeDependencies"
}
],
"view/item/context": [
{
"command": "nodeDependencies.editEntry",
"when": "view == nodeDependencies && viewItem == dependency",
"group": "inline"
},
{
"command": "nodeDependencies.deleteEntry",
"when": "view == nodeDependencies && viewItem == dependency"
}
]
}
}
默认情况下,操作按字母顺序排序。要指定不同的排序,请添加 @
,后跟您想要的顺序到组。例如,navigation@3
将使操作在 navigation
组中显示为第 3 个。
您可以通过创建不同的组来进一步分隔 ...
菜单中的项目。这些组名称是任意的,并按组名称字母顺序排序。
注意: 如果您想为特定的树项显示操作,您可以通过使用 TreeItem.contextValue
定义树项的上下文来实现,并且您可以在 when
表达式中为键 viewItem
指定上下文值。
示例
"contributes": {
"menus": {
"view/item/context": [
{
"command": "nodeDependencies.deleteEntry",
"when": "view == nodeDependencies && viewItem == dependency"
}
]
}
}
欢迎内容
如果您的视图可以为空,或者如果您想向另一个扩展的空视图添加欢迎内容,您可以贡献 viewsWelcome
内容。空视图是没有 TreeView.message
和空树的视图。
"contributes": {
"viewsWelcome": [
{
"view": "nodeDependencies",
"contents": "No node dependencies found [learn more](https://npmjs.net.cn/).\n[Add Dependency](command:nodeDependencies.addEntry)"
}
]
}
欢迎内容中支持链接。按照惯例,单独一行上的链接是一个按钮。每个欢迎内容也可以包含一个 when
子句。有关更多示例,请参阅 内置 Git 扩展。
TreeDataProvider
扩展编写者应以编程方式注册 TreeDataProvider,以在视图中填充数据。
vscode.window.registerTreeDataProvider('nodeDependencies', new DepNodeProvider());
有关实现,请参阅 tree-view-sample
中的 nodeDependencies.ts。
TreeView
如果您想以编程方式对视图执行某些 UI 操作,您可以使用 window.createTreeView
而不是 window.registerTreeDataProvider
。这将提供对视图的访问权限,您可以使用它来执行视图操作。
vscode.window.createTreeView('ftpExplorer', {
treeDataProvider: new FtpTreeDataProvider()
});
有关实现,请参阅 tree-view-sample
中的 ftpExplorer.ts。