ツリービュー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"
}
}
注: 拡張機能がVS Codeバージョン1.74より前を対象とする場合、
activationEvents
にonView:nodeDependencies
を明示的にリストする必要があります。
ビューの識別子と名前を指定する必要があり、以下の場所に提供できます
explorer
: サイドバーのエクスプローラービューdebug
: サイドバーの実行とデバッグビューscm
: サイドバーのソース管理ビューtest
: サイドバーのテストエクスプローラービュー- カスタムビューコンテナ
ツリーデータプロバイダー
2番目の手順は、登録したビューにデータを提供し、VS Codeがそのビューにデータを表示できるようにすることです。そのためには、まずTreeDataProviderを実装する必要があります。私たちのTreeDataProvider
はノードの依存関係データを提供しますが、他の種類のデータを提供するデータプロバイダーを持つこともできます。
このAPIには、実装する必要がある2つの必須メソッドがあります
getChildren(element?: T): ProviderResult<T[]>
- これを実装して、指定されたelement
またはルート(要素が渡されない場合)の子を返します。getTreeItem(element: T): TreeItem | Thenable<TreeItem>
- これを実装して、ビューに表示される要素のUI表現(TreeItem)を返します。
ユーザーがツリービューを開くと、getChildren
メソッドがelement
なしで呼び出されます。そこから、あなたの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の登録
3番目の手順は、上記のデータプロバイダーをビューに登録することです。
これは以下の2つの方法で行うことができます
-
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はアクティベーションイベントonView:${viewId}(上記の例ではonView:nodeDependencies
)を発行します。
注: VS Codeのバージョン1.74.0より前の場合、VS Codeがこのビューで拡張機能をアクティベートするには、このアクティベーションイベントを
package.json
に明示的に登録する必要があります。"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
に貢献を追加することで、これらの場所に表示されるように設定するコマンドです。
これら3つの場所に貢献するには、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://www.npmjs.com/).\n[Add Dependency](command:nodeDependencies.addEntry)"
}
]
}
ウェルカムコンテンツではリンクがサポートされています。慣例により、行単独のリンクはボタンとして扱われます。各ウェルカムコンテンツには、when
節を含めることもできます。詳細な例については、組み込みのGit拡張機能を参照してください。
TreeDataProvider
拡張機能の作成者は、ビューにデータを入力するために、プログラムでTreeDataProviderを登録する必要があります。
vscode.window.registerTreeDataProvider('nodeDependencies', new DepNodeProvider());
実装については、tree-view-sample
内のnodeDependencies.tsを参照してください。
TreeView
ビューに対してプログラムでUI操作を実行したい場合は、window.registerTreeDataProvider
の代わりにwindow.createTreeView
を使用できます。これにより、ビューにアクセスできるようになり、ビュー操作を実行できます。
vscode.window.createTreeView('ftpExplorer', {
treeDataProvider: new FtpTreeDataProvider()
});
実装については、tree-view-sample
内のftpExplorer.tsを参照してください。