Tree View API
容许插件在sidebar
中渲染内容,这些内容以树的形状来展现node
咱们经过一个示例来介绍Tree View API
相关用法,这个示例利用树视图来展现当前文件夹中全部的Node.js
依赖。你能够在 tree-view-sample 查阅此示例的完整代码git
首先你要经过 contributes.views 让VS Code
知道你要“贡献出”一个视图,下面是package.json
的一个初步配置:github
{
"name": "helloworld",
"displayName": "HelloWorld",
"description": "",
"version": "0.0.1",
"engines": {
"vscode": "^1.56.0"
},
"categories": [
"Other"
],
"activationEvents": ["onView:nodeDependencies"],
"main": "./extension.js",
"contributes": {
"views": {
"explorer": [{
"id": "nodeDependencies",
"name": "Node Dependencies"
}]
}
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "node ./test/runTest.js"
},
"devDependencies": {
"@types/vscode": "^1.56.0",
"@types/glob": "^7.1.3",
"@types/mocha": "^8.0.4",
"@types/node": "14.x",
"eslint": "^7.19.0",
"glob": "^7.1.6",
"mocha": "^8.2.1",
"typescript": "^4.1.3",
"vscode-test": "^1.5.0"
}
}
复制代码
仅当用户须要时再去激活插件是十分重要的,例如在本文的示例中,咱们可让插件在用户使用插件视图的时候再去激活。VS Code
提供了 onView:${viewId} 事件来告知程序当前用户打开的视图,咱们能够在package.json
注册一个激活事件"activationEvents": ["onView:nodeDependencies"]
typescript
第二步是利用 TreeDataProvider 生成树视图所需的Node.js
依赖的数据,其中须要实现两个方法:npm
getChildren(element?: T): ProviderResult<T[]>
:返回指定节点(若是没有指定就是根节点)的子节点getTreeItem(element: T): TreeItem | Thenable<TreeItem>
:返回用于在视图里展现的UI节点每当用户打开树视图,getChildren
会被自动调用(没有参数),你能够在这里返回树视图的第一层级内容。在示例中,咱们用TreeItemCollapsibleState.Collapsed
(折叠)、TreeItemCollapsibleState.Expanded
(展开)、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 */
private getDepsInPackageJson(packageJsonPath: string): Dependency[] {
if (this.pathExists(packageJsonPath)) {
const toDep = (moduleName: string, version: string): Dependency => {
const depPackageJsonPath = path.join(this.workspaceRoot, 'node_modules', moduleName, 'package.json');
let collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
if (this.pathExists(depPackageJsonPath)) {
const depPackageJson = JSON.parse(fs.readFileSync(depPackageJsonPath, 'utf-8'));
// 若是依赖的代码包已经安装(node_modules有内容),且这个安装包自己有dependencies或devDependencies,才设置为可展开的
if ((!depPackageJson.dependencies || Object.keys(depPackageJson.dependencies).length === 0) &&
(!depPackageJson.devDependencies || Object.keys(depPackageJson.devDependencies).length === 0)) {
collapsibleState = vscode.TreeItemCollapsibleState.None;
}
}
return new Dependency(moduleName, version, collapsibleState);
};
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')
};
}
复制代码
第三步是将生成的依赖数据提供给视图,能够经过两种方式实现:json
vscode.window.registerTreeDataProvider
:注册树数据的provider
,须要提供视图ID和数据provider
对象api
vscode.window.registerTreeDataProvider(
'nodeDependencies',
new NodeDependenciesProvider(vscode.workspace.rootPath)
);
复制代码
vscode.window.createTreeView
:经过视图ID和数据provider
来建立视树视图,这会提供访问 树视图 的能力,若是你须要使用TreeView API
,可使用createTreeView
的方式服务器
vscode.window.createTreeView('nodeDependencies', {
treeDataProvider: new NodeDependenciesProvider(vscode.workspace.rootPath)
});
复制代码
至此一个具有基本目标功能的插件就已经完成,能够看到实际效果以下:markdown
上述代码的完整示例参见 tree-view-test v1
目前完成的这个插件仅具有最基本的功能,数依赖数据一经展现便没法更新。若是在视图中有一个刷新按钮将会是很是方便的,为了实现这个目标,咱们须要利用 onDidChangeTreeData 事件:
onDidChangeTreeData?: Event<T | undefined | null | void>
:当依赖数据变动而且你但愿更新树视图的时候执行在provider
中添加以下代码:
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
中定义一条更新命令:
"commands": [
{
"command": "nodeDependencies.refreshEntry",
"title": "Refresh Dependence",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
}
]
复制代码
而后注册该命令:
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () =>
nodeDependenciesProvider.refresh()
);
复制代码
此时咱们会看到,当执行了Refresh Dependence
命令后,Node.js
依赖的树视图会被更新:
在前文的基础上,若是在视图中添加一个按钮或许操做的时候有会更加直观、友好。咱们在package.json
中添加:
"menus": {
"view/title": [
{
"command": "nodeDependencies.refreshEntry",
"when": "view == nodeDependencies",
"group": "navigation"
},
]
}
复制代码
此时当咱们将鼠标浮在视图上时就会看到刷新按钮,点击效果同执行Refresh Dependence
命令:
group
属性用于菜单项的排序和分类,其中值为navigation
的group
是用来将置顶的,若是不设置,则刷新按钮将会被隐藏在“...”里,效果以下所示:
上述代码的完整示例参见 tree-view-test v2
视图容器包含了一系列展现在Activity Bar
或Panel
中的视图,若是但愿本身的插件自定义一个视图容器,咱们能够用 contributes.viewsContainers 在package.json
中注册:
"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
,这三个值仅在首次打开工做台的时候起做用,以后其取值取决于用户的控制。若是你的视图容器里有不少的视图,则能够利用该属性让你的界面更加简洁
如今咱们能够看到左侧的视图容器和树视图了:
上述代码的完整示例参见 tree-view-test v3
视图的行为附着在视图的内联图标上,这些图标能够在树视图中的每个节点上、还能够在树视图顶端的标题栏上,咱们能够在package.json
中对其进行配置:
view/title
:位置在视图标题栏上,能够用"group": "navigation"
来保证其优先级view/item/context
:位置在树节点上,能够用"group": "inline"
让其内联显示上述都可用 when clause 控制其生效条件
若是咱们想实现上图的效果,能够用以下代码实现:
{
"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"
}
]
}
}
}
复制代码
咱们能够在
when
字段中使用 TreeItem.contextValue 的数据,来控制相应行为的显示
上述代码的完整示例参见 tree-view-test v4
咱们能够添加一个欢迎内容,以便当视图内容初始化或为空的时候显示:
"contributes": {
"viewsWelcome": [{
"view": "nodeDependencies",
"contents": "没有发现依赖内容, [了解更多](https://www.npmjs.com/).\n[添加依赖](command:nodeDependencies.addEntry)"
}]
复制代码
contributes.viewsWelcome.contents
支持连接,若是连接单起一行,会被渲染为按钮。每一个viewsWelcome
支持 when clause
上述代码的完整示例参见 tree-view-test v5