Tree explorer sample

This commit is contained in:
Pine Wu
2016-11-16 09:33:20 -08:00
parent a17867e2b8
commit c38439756a
14 changed files with 384 additions and 0 deletions

View File

@ -7,6 +7,7 @@ Sample code illustrating the VS Code extension APIs.
* [Preview Html](/previewhtml-sample/README.md)
* [Vim](/vim-sample/README.md)
* [Integrated Terminal](/terminal-example/README.md)
* Experimental: [Tree Explorer](/tree-explorer/README.md)
# How to run locally

2
tree-explorer-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
out
node_modules

View File

@ -0,0 +1,28 @@
// A launch configuration that compiles the extension and then opens it inside a new window
{
"version": "0.1.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["/Users/octref/Code/work/vscode", "--extensionDevelopmentPath=${workspaceRoot}"],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out/src",
"preLaunchTask": "npm"
},
{
"name": "Launch Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out/test",
"preLaunchTask": "npm"
}
]
}

View File

@ -0,0 +1,10 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
}

30
tree-explorer-sample/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,30 @@
// Available variables which can be used inside of strings.
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
// A task runner that calls a custom npm script that compiles the extension.
{
"version": "0.1.0",
// we want to run npm
"command": "npm",
// the command is a shell script
"isShellCommand": true,
// show the output window only if unrecognized errors occur.
"showOutput": "silent",
// we run the custom script "compile" as defined in package.json
"args": ["run", "compile", "--loglevel", "silent"],
// The tsc compiler is started in watching mode
"isWatching": true,
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
"problemMatcher": "$tsc-watch"
}

View File

@ -0,0 +1,9 @@
.vscode/**
.vscode-test/**
out/test/**
test/**
src/**
**/*.map
.gitignore
tsconfig.json
vsc-extension-quickstart.md

View File

@ -0,0 +1,14 @@
# Dependency Tree Explorer
A sample for the Tree Explorer API showing dependencies in a node project.
![tree-explorer](tree-explorer.png)
## Running the example
Currently, this API is only available in [Insiders](https://code.visualstudio.com/insiders).
- Download Insiders version of VS Code and open this sample
- `npm install`
- `npm run compile`
- `F5` to start debugging

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
<title>Slice</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="dep" transform="translate(4.000000, 6.000000)" fill="#000000">
<g id="Page-1">
<path d="M5.14285714,16.2778182 C5.14285714,16.9917576 4.89285714,17.5986061 4.39285714,18.0983636 C3.89285714,18.5981212 3.28571429,18.848 2.57142857,18.848 C1.85714286,18.848 1.25,18.5981212 0.75,18.0983636 C0.25,17.5986061 0,16.9917576 0,16.2778182 C0,15.5638788 0.25,14.9570303 0.75,14.4572727 C1.25,13.9575152 1.85714286,13.7076364 2.57142857,13.7076364 C3.28571429,13.7076364 3.89285714,13.9575152 4.39285714,14.4572727 C4.89285714,14.9570303 5.14285714,15.5638788 5.14285714,16.2778182 L5.14285714,16.2778182 L5.14285714,16.2778182 Z M5.14285714,9.424 C5.14285714,10.1379394 4.89285714,10.7447879 4.39285714,11.2445455 C3.89285714,11.744303 3.28571429,11.9941818 2.57142857,11.9941818 C1.85714286,11.9941818 1.25,11.744303 0.75,11.2445455 C0.25,10.7447879 0,10.1379394 0,9.424 C0,8.7100606 0.25,8.1032121 0.75,7.6034545 C1.25,7.103697 1.85714286,6.8538182 2.57142857,6.8538182 C3.28571429,6.8538182 3.89285714,7.103697 4.39285714,7.6034545 C4.89285714,8.1032121 5.14285714,8.7100606 5.14285714,9.424 L5.14285714,9.424 L5.14285714,9.424 Z M24,14.9927273 L24,17.5629091 C24,17.6789242 23.9575893,17.779322 23.8727679,17.8641023 C23.7879464,17.9488826 23.6875,17.9912727 23.5714286,17.9912727 L7.2857143,17.9912727 C7.1696429,17.9912727 7.0691964,17.9488826 6.984375,17.8641023 C6.8995536,17.779322 6.8571429,17.6789242 6.8571429,17.5629091 L6.8571429,14.9927273 C6.8571429,14.8767121 6.8995536,14.7763144 6.984375,14.6915341 C7.0691964,14.6067538 7.1696429,14.5643636 7.2857143,14.5643636 L23.5714286,14.5643636 C23.6875,14.5643636 23.7879464,14.6067538 23.8727679,14.6915341 C23.9575893,14.7763144 24,14.8767121 24,14.9927273 L24,14.9927273 L24,14.9927273 Z M5.14285714,2.57018182 C5.14285714,3.2841212 4.89285714,3.8909697 4.39285714,4.3907273 C3.89285714,4.8904848 3.28571429,5.1403636 2.57142857,5.1403636 C1.85714286,5.1403636 1.25,4.8904848 0.75,4.3907273 C0.25,3.8909697 0,3.2841212 0,2.57018182 C0,1.85624242 0.25,1.24939394 0.75,0.74963636 C1.25,0.24987879 1.85714286,0 2.57142857,0 C3.28571429,0 3.89285714,0.24987879 4.39285714,0.74963636 C4.89285714,1.24939394 5.14285714,1.85624242 5.14285714,2.57018182 L5.14285714,2.57018182 L5.14285714,2.57018182 Z M24,8.1389091 L24,10.7090909 C24,10.8251061 23.9575893,10.9255038 23.8727679,11.0102841 C23.7879464,11.0950644 23.6875,11.1374545 23.5714286,11.1374545 L7.2857143,11.1374545 C7.1696429,11.1374545 7.0691964,11.0950644 6.984375,11.0102841 C6.8995536,10.9255038 6.8571429,10.8251061 6.8571429,10.7090909 L6.8571429,8.1389091 C6.8571429,8.0228939 6.8995536,7.9224962 6.984375,7.8377159 C7.0691964,7.7529356 7.1696429,7.7105455 7.2857143,7.7105455 L23.5714286,7.7105455 C23.6875,7.7105455 23.7879464,7.7529356 23.8727679,7.8377159 C23.9575893,7.9224962 24,8.0228939 24,8.1389091 L24,8.1389091 L24,8.1389091 Z M24,1.28509091 L24,3.8552727 C24,3.9712879 23.9575893,4.0716856 23.8727679,4.1564659 C23.7879464,4.2412462 23.6875,4.2836364 23.5714286,4.2836364 L7.2857143,4.2836364 C7.1696429,4.2836364 7.0691964,4.2412462 6.984375,4.1564659 C6.8995536,4.0716856 6.8571429,3.9712879 6.8571429,3.8552727 L6.8571429,1.28509091 C6.8571429,1.16907576 6.8995536,1.06867803 6.984375,0.98389773 C7.0691964,0.89911742 7.1696429,0.85672727 7.2857143,0.85672727 L23.5714286,0.85672727 C23.6875,0.85672727 23.7879464,0.89911742 23.8727679,0.98389773 C23.9575893,1.06867803 24,1.16907576 24,1.28509091 L24,1.28509091 L24,1.28509091 Z" id="Shape"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,36 @@
{
"name": "dep",
"displayName": "Dependency Example",
"description": "Dependency example for VSCode's explorer API",
"version": "0.0.1",
"publisher": "octref",
"engines": {
"vscode": "^1.7.0"
},
"enableProposedApi": true,
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/src/extension",
"icon": "media/dep.png",
"contributes": {
"explorer": {
"treeLabel": "Dependencies",
"icon": "media/dep.svg",
"treeExplorerNodeProviderId": "depTree"
}
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"devDependencies": {
"typescript": "^2.0.3",
"vscode": "^1.0.0",
"@types/node": "^6.0.40"
}
}

View File

@ -0,0 +1,136 @@
'use strict';
import * as vscode from 'vscode';
import { TreeExplorerNodeProvider } from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
export function activate(context: vscode.ExtensionContext) {
const rootPath = vscode.workspace.rootPath;
// The `providerId` here must be identical to `contributes.explorer.treeExplorerNodeProviderId` in package.json.
vscode.window.registerTreeExplorerNodeProvider('depTree', new DepNodeProvider(rootPath));
// This command will be invoked using exactly the node you provided in `resolveChildren`.
vscode.commands.registerCommand('extension.openPackageOnNpm', (node: DepNode) => {
if (node.kind === 'leaf') {
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`https://www.npmjs.com/package/${node.moduleName}`));
}
});
}
class DepNodeProvider implements TreeExplorerNodeProvider<DepNode> {
constructor(private workspaceRoot: string) {
}
/**
* As root node is invisible, its label doesn't matter.
*/
getLabel(node: DepNode): string {
return node.kind === 'root' ? '' : node.moduleName;
}
/**
* Leaf is unexpandable.
*/
getHasChildren(node: DepNode): boolean {
return node.kind !== 'leaf';
}
/**
* Invoke `extension.openPackageOnNpm` command when a Leaf node is clicked.
*/
getClickCommand(node: DepNode): string {
return node.kind === 'leaf' ? 'extension.openPackageOnNpm' : null;
}
provideRootNode(): DepNode {
return new Root();
}
resolveChildren(node: DepNode): Thenable<DepNode[]> {
return new Promise((resolve) => {
switch(node.kind) {
case 'root':
const packageJsonPath = path.join(this.workspaceRoot, 'package.json');
if (this.fileExists(packageJsonPath)) {
resolve(this.getDepsInPackageJson(packageJsonPath));
} else {
vscode.window.showInformationMessage('Workspace has no package.json');
resolve([]);
}
break;
/**
* npm3 has flat dependencies, so indirect dependencies are still in `node_modules`.
*/
case 'node':
resolve(this.getDepsInPackageJson(path.join(this.workspaceRoot, 'node_modules', node.moduleName, 'package.json')));
break;
case 'leaf':
resolve([]);
}
});
}
/**
* Given the path to package.json, read all its dependencies and devDependencies.
*/
private getDepsInPackageJson(packageJsonPath: string): DepNode[] {
if (this.fileExists(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const toDep = (moduleName: string): DepNode => {
if (this.fileExists(path.join(this.workspaceRoot, 'node_modules', moduleName))) {
return new Node(moduleName);
} else {
return new Leaf(moduleName);
}
}
const deps = Object.keys(packageJson.dependencies).map(toDep);
const devDeps = Object.keys(packageJson.devDependencies).map(toDep);
return deps.concat(devDeps);
} else {
return [];
}
}
private fileExists(filePath: string): boolean {
try {
fs.accessSync(filePath);
} catch (err) {
return false;
}
return true;
}
}
type DepNode = Root // Root node
| Node // A dependency installed to `node_modules`
| Leaf // A dependency not present in `node_modules`
;
class Root {
kind: 'root' = 'root';
}
class Node {
kind: 'node' = 'node';
constructor(
public moduleName: string
) {
}
}
class Leaf {
kind: 'leaf' = 'leaf'
constructor(
public moduleName: string
) {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "."
},
"exclude": [
"node_modules",
".vscode-test"
]
}

View File

@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This is the place for API experiments and proposal.
declare module 'vscode' {
export namespace window {
export function sampleFunction(): Thenable<any>;
}
export namespace window {
/**
* Register a [TreeExplorerNodeProvider](#TreeExplorerNodeProvider).
*
* @param providerId A unique id that identifies the provider.
* @param provider A [TreeExplorerNodeProvider](#TreeExplorerNodeProvider).
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerTreeExplorerNodeProvider(providerId: string, provider: TreeExplorerNodeProvider<any>): Disposable;
}
/**
* A node provider for a tree explorer contribution.
*
* Providers are registered through (#workspace.registerTreeExplorerNodeProvider) with a
* `providerId` that corresponds to the `treeExplorerNodeProviderId` in the extension's
* `contributes.explorer` section.
*
* The contributed tree explorer will ask the corresponding provider to provide the root
* node and resolve children for each node. In addition, the provider could **optionally**
* provide the following information for each node:
* - label: A human-readable label used for rendering the node.
* - hasChildren: Whether the node has children and is expandable.
* - clickCommand: A command to execute when the node is clicked.
*/
export interface TreeExplorerNodeProvider<T> {
/**
* Provide the root node. This function will be called when the tree explorer is activated
* for the first time. The root node is hidden and its direct children will be displayed on the first level of
* the tree explorer.
*
* @return The root node.
*/
provideRootNode(): T | Thenable<T>;
/**
* Resolve the children of `node`.
*
* @param node The node from which the provider resolves children.
* @return Children of `node`.
*/
resolveChildren(node: T): T[] | Thenable<T[]>;
/**
* Provide a human-readable string that will be used for rendering the node. Default to use
* `node.toString()` if not provided.
*
* @param node The node from which the provider computes label.
* @return A human-readable label.
*/
getLabel?(node: T): string;
/**
* Determine if `node` has children and is expandable. Default to `true` if not provided.
*
* @param node The node to determine if it has children and is expandable.
* @return A boolean that determines if `node` has children and is expandable.
*/
getHasChildren?(node: T): boolean;
/**
* Get the command to execute when `node` is clicked.
*
* Commands can be registered through [registerCommand](#commands.registerCommand). `node` will be provided
* as the first argument to the command's callback function.
*
* @param node The node that the command is associated with.
* @return The command to execute when `node` is clicked.
*/
getClickCommand?(node: T): string;
}
}