The Tree View API allows plug-ins to render content in sidebar, which is displayed in the shape of a Tree

Tree View API basics

We introduced the Tree View API with an example that uses a Tree View to show all node.js dependencies in the current folder. You can see the complete code for this example at tree-view-sample

Configuration package. Json

First you contribute to VS Code to know that you “contribute” a view by contributing. Here is a preliminary configuration of package.json:

{
    "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." "}}Copy the code

It is important to deactivate the plug-in only when the user needs it, as in the example in this article, we can deactivate the plug-in while the user is using the plug-in view. VS Code provides an onView:${viewId} event to tell the application which view the user is currently opening. In package.json, we can register an activation event “activationEvents”: [“onView:nodeDependencies”].

Generate the data

The second step is to use the TreeDataProvider to generate node.js dependent data for the tree view. Two methods need to be implemented:

  • getChildren(element? : T): ProviderResult<T[]>: Returns the child node of the specified node (root if not specified)
  • getTreeItem(element: T): TreeItem | Thenable<TreeItem>: Returns the UI node used for display in the view

Whenever the user opens the tree view, getChildren is automatically called (with no arguments) and you can return the first level of the tree view from there. In the example, we use TreeItemCollapsibleState. Collapsed (folding), TreeItemCollapsibleState. Expanded (expand), TreeItemCollapsibleState. None (no child node, Does not trigger the getChildren method) controls the collapse state of the node. Here is an example implementation of the 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 {
        returnelement; } 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'));
                    // If the dependencies package is already installed (node_modules has content) and the package itself has dependencies or devDependencies, set it to expandable
                    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 = `The ${this.label}-The ${this.version}`;
        this.description = this.version;
    }

    iconPath = {
        light: path.join(__filename, '.. '.'.. '.'resources'.'light'.'dependency.svg'),
        dark: path.join(__filename, '.. '.'.. '.'resources'.'dark'.'dependency.svg')}; }Copy the code

Registered TreeDataProvider

The third step is to feed the generated dependency data to the view, which can be done in two ways:

  • Vscode. Window. RegisterTreeDataProvider: tree register data provider, you need to provide the view ID and data provider object

    vscode.window.registerTreeDataProvider(
        'nodeDependencies'.new NodeDependenciesProvider(vscode.workspace.rootPath)
    );
    Copy the code
  • Vscode. Window. CreateTreeView: through the view ID and data provider to create visual tree view, it’s ability to provide access to the tree view, if you need to use the TreeView apis, you can use createTreeView way

    vscode.window.createTreeView('nodeDependencies', {
        treeDataProvider: new NodeDependenciesProvider(vscode.workspace.rootPath)
    });
    Copy the code

Now that a plug-in with basic target functionality is complete, you can see the following in action:

See tree-view-test v1 for a complete example of the above code

Update view content

On the command line

Currently completed, the plugin has only the most basic functions, and the dependency data cannot be updated once displayed. It would be handy to have a refresh button in the view. To do this, we need to use the onDidChangeTreeData event:

  • onDidChangeTreeData? : Event<T | undefined | null | void>: executed when the dependency data changes and you want to update the tree view

Add the following code to the 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();
    }
Copy the code

Now that we have the update function, but have not called it, we can define an update command in package.json:

    "commands": [{"command": "nodeDependencies.refreshEntry"."title": "Refresh Dependence"."icon": {
                    "light": "resources/light/refresh.svg"."dark": "resources/dark/refresh.svg"}}]Copy the code

Then register the command:

  vscode.commands.registerCommand('nodeDependencies.refreshEntry'.() = >
      nodeDependenciesProvider.refresh()
  );
Copy the code

At this point we’ll see when we executeRefresh DependenceAfter the command,Node.jsThe dependent tree view is updated:

In button mode

On the basis of the above, it would be more intuitive and friendly to add a button to the view. In package.json we add:

"menus": {
    "view/title": [{"command": "nodeDependencies.refreshEntry"."when": "view == nodeDependencies"."group": "navigation"]}},Copy the code

When we hover the mouse over the view, we see the Refresh button. It works the same way as the Refresh Dependence command:

The group attribute is used to sort and categorize menu items. The group with the value of navigation is used to place the top. If not set, the refresh button will be hidden in the “… In, the effect is as follows:

See tree-view-test v2 for a complete example of the above code

Add to View Container

Creating a View container

View the container contains a series of features in view of the Activity Bar or the Panel, if you want to own a view custom plug-in container, we can use contributes. ViewsContainers in package. Json in registration:

    "contributes": {
        "viewsContainers": {
            "activitybar": [{
                "id": "package-explorer"."title": "Package Explorer"."icon": "media/dep.svg"}}}]Copy the code

Or you can do the configuration under the Panel field

    "contributes": {
        "viewsContainers": {
           "panel": [{
                "id": "package-explorer"."title": "Package Explorer"."icon": "media/dep.svg"}}}]Copy the code

Bind the view to the view container

This can be done in package.json using contributes. Views

    "contributes": {
        "views": {
            "package-explorer": [{
                "id": "nodeDependencies"."name": "Node Dependencies"."icon": "media/dep.svg"."contextualTitle": "Package Explorer"}}}]Copy the code

Note that a view can be set to the visibility property, which has three values: Visible, collapsed, and hidden. These values only take effect when the workbench is opened for the first time, and then depend on user control. If you have many views in your view container, you can use this property to make your interface cleaner


Now we can see the view container and tree view on the left:

See tree-view-test V3 for a complete example of the above code

View behavior interpretation

The view’s behavior is attached to the view’s inline ICONS, which can be found on each node in the tree view, as well as on the title bar at the top of the tree view, which we can configure in package.json:

  • view/title: in the view title bar, available"group": "navigation"To ensure its priority
  • view/item/context: is on the tree node"group": "inline"Make it appear inline

All of the above can be controlled by the when clause

If we wanted to achieve the above effect, we could do it with the following code:

{
    "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"}]}}}Copy the code

We can use treeItem. contextValue data in the WHEN field to control the display of the corresponding behavior

See tree-view-test V4 for a complete example of the above code

View welcome content

We can add a welcome content to display when the view content is initialized or empty:

    "contributes": {
        "viewsWelcome": [{
            "view": "nodeDependencies"."contents": "Found no reliance on the content, [more] (https://www.npmjs.com/). \ n [add dependent on] (command: nodeDependencies addEntry)"
        }]
Copy the code

Contributes. ViewsWelcome. Contents support link, if the link list on a line, will be rendered as buttons. Each viewsWelcome supports the when clause

See tree-view-test V5 for a complete example of the above code

Related articles

  • VS Code Plug-in Development Tutorial (1) Overview

  • VS Code plug-in Development Tutorial (2

  • VS Code Plug-in Development Tutorial (3

  • VS Code Plug-in Development Tutorial (4

  • VS Code plug-in development tutorial (5) Using Command

  • VS Code Plugin development Tutorial (6) Color Theme overview

  • VS Code plug-in development tutorial (7) Tree View

  • VS Code Plug-in Development Tutorial (8) Webview

  • Build a Custom Editor

  • VS Code Plug-in Development Tutorial (10

  • Language Server Extension Guide Language Server Extension Guide