Files
vscode-extension-samples/tree-view-sample/USAGE.md
Sandeep Somavarapu 899f2966f5 Rename to tree view
2017-06-02 14:27:05 +02:00

7.6 KiB

Example - Tree Explorer

Note:

This is a proposed API. It's experimental in nature and subject to change or removal without notice.

Currently, this API is only available when developing extensions using Insiders version of VS Code. Packaged extensions using this API won't run. Please refrain from publishing any extensions using this API.

The Tree Explorer API allows you to create customized Tree Explorers, similar to the File Explorer that comes with VS Code. A Tree Explorer extension adds an icon to the Activity Bar and creates a custom View when the icon is clicked.

From a high level, extension defines an explorer contribution, where it specifies a View label, an icon and a provider id. During the activation, extension calls vscode.window.registerTreeExplorerNodeProvider with the specified provider id. VS Code then asks the registered provider to provide necessary information for rendering the Tree Explorer View.

The remaining document assumes that you are familiar with normal extension development for VS Code.

Example - Dependency Tree Explorer

We'll show you an example of a Dependency Tree Explorer:

tree-explorer

To get it running, do the following:

  • Clone this repository
  • cd tree-explorer-sample
  • Install dependencies with npm install
  • Run Launch Extension in the Debug View

By default the Explorer wouldn't be displayed on Activity Bar. Find View: Toggle Custom Explorer in Command Palette and choose your Explorer to enable it.

The Structure of Tree Explorer extension

First, let's take a look at package.json. There are a few interesting bits:

{
  "enableProposedApi": true,
  "contributes": {
    "explorer": {
      "treeLabel": "Dependencies",
      "icon": "media/dep.svg",
      "treeExplorerNodeProviderId": "depTree"
    }
  }
}
  • enableProposedApi tells VS Code we acknowledge its experimental nature and would like to play with it.
  • contributes.explorer specifies
    • A label that will appear at the top of the View
    • The path to a svg icon that will appear at the Activity Bar
    • A provider id which we'll use in vscode.window.registerTreeExplorerNodeProvider

typings/vscode.proposed.d.ts defines the preview API, and is pulled from VS Code's GitHub repository: https://github.com/Microsoft/vscode/blob/master/src/vs/vscode.proposed.d.ts

Let's jump to src/extension.ts and take a look at the code.

The Implementation of a TreeExplorerNodeProvider

A TreeExplorerNodeProvider implements two required methods and three optional methods:

interface TreeExplorerNodeProvider<T> {
  provideRootNode(): T | Thenable<T>;
  resolveChildren(node: T): T[] | Thenable<T[]>;

  getLabel?(node: T): string;
  getHasChildren?(node: T): boolean;
  getClickCommand?(node: T): string;
}

The type T is the class of each node. provideRootNode provides the invisible node that represents the root of the tree. resolveChildren will be invoked when an expandable node is clicked upon, or invoked with the root node as argument when the Activity Bar icon is clicked.

The three optional methods are used for extracting relevant information used for rendering the tree. getLabel returns the label for each node, getHasChildren determines if each node is expandable, and getClickCommand returns a string that represents the command that should be executed when a node is clicked upon. When these functions are not provided, VS Code will use these default values:

  • node.toString() as label
  • true as hasChildren
  • null as clickCommand

In our dependency example, the node is defined as an union type:

type DepNode = Root // Root node
             | Node // A dependency installed to `node_modules`
             | Leaf // A dependency not present in `node_modules`
             ;

A Node is a dependency that still has dependencies or devDependencies in its package.json. A Leaf is a dependency that depends on no other packages.

In our DepNodeProvider:

provideRootNode(): DepNode {
  return new Root();
}

resolveChildren(node: DepNode): Thenable<DepNode[]> {
  if (!this.workspaceRoot) {
    vscode.window.showInformationMessage('No dependency in empty workspace');
    return Promise.resolve([]);
  }

  return new Promise((resolve) => {
    switch(node.kind) {
      case 'root':
        const packageJsonPath = path.join(this.workspaceRoot, 'package.json');
        if (this.pathExists(packageJsonPath)) {
          resolve(this.getDepsInPackageJson(packageJsonPath));
        } else {
          vscode.window.showInformationMessage('Workspace has no package.json');
          resolve([]);
        }
        break;
      case 'node':
        resolve(this.getDepsInPackageJson(path.join(this.workspaceRoot, 'node_modules', node.moduleName, 'package.json')));
        break;
      case 'leaf':
        resolve([]);
    }
  });
}

provideRootNode simply provides a Root node. resolveChildren behaves differently accordingly to the type of the node to resolves children for:

  • Root: read package.json in ${workspaceRoot}, and return its dependencies and devDependencies.
  • Node: read package.json in ${workspaceRoot}/node_modules/${node.moduleName}, and return its dependencies and devDependencies.
  • Leaf: Leaf isn't expandable and this case shouldn't happen. We return an empty array.

We also implement getLabel, getHasChildren, getClickCommand to:

  • Let each Node and Leaf display its moduleName as the label on the tree node.

      getLabel(node: DepNode): string {
        return node.kind === 'root' ? '' : node.moduleName;
      }
    
  • Make Leaf unexpandable.

    getHasChildren(node: DepNode): boolean {
      return node.kind !== 'leaf';
    }
    
  • Invoke extension.openPackageOnNpm when a Leaf is clicked.

    getClickCommand(node: DepNode): string {
      return node.kind === 'leaf' ? 'extension.openPackageOnNpm' : null;
    }
    

Implementation Considerations

Icon

To ensure UI consistency with other Views, you should provide a 32x32 SVG icon with transparent background as explorer.icon in package.json.

Long running resolveChildren

If resolveChildren could take very long, you should let it return a promise, and VS Code will show a progress bar as the promise gets resolved.

Error handling

We suggest that you handle errors promptly and notify the user using vscode.window.showInformationMessage if needed. In our Dependency example, we check for package.json in the root of the workspace. If it doesn't exist, we'll show an information message Workspace has no package.json. In empty workspace, we show a message No dependency in empty workspace.

Common Questions

Q: Will it be possible to access the DOM to add arbitrary UI using this API?

A: No, you can read about our approach to extensibility here.

Q: Can I add an Outline View/Symbol Tree View using this API?

A: We plan to include an API where language services could register a provider for Outline View items, similar to the existing DocumentSymbolProvider. Please refrain from using this API to make Outline Views.

Q: Can I have functionality X?

A: We realize this API in its current form is limiting, and we are planning to evolve it. You can track our progress in this GitHub issue. If you have ideas about how we could improve this API, please feel free to chime in.