ツリービューAPI

ツリービューAPIを使用すると、拡張機能はVisual Studio Codeのサイドバーにコンテンツを表示できます。このコンテンツはツリーとして構造化されており、VS Codeの組み込みビューのスタイルに準拠しています。

たとえば、組み込みの参照検索ビュー拡張機能は、参照検索結果を個別のビューとして表示します。

References Search View

すべての参照を検索の結果は、参照: 結果ツリービューに表示されます。これは、参照ビューコンテナ内にあります。

このガイドでは、ツリービューとビューコンテナをVisual Studio Codeに提供する拡張機能を作成する方法を説明します。

ツリービューAPIの基本

ツリービューAPIを説明するために、Node Dependenciesというサンプル拡張機能を作成します。この拡張機能は、ツリービューを使用して現在のフォルダー内のすべてのNode.jsの依存関係を表示します。ツリービューを追加する手順は、package.jsonでツリービューをコントリビュートし、TreeDataProviderを作成し、TreeDataProviderを登録することです。このサンプル拡張機能の完全なソースコードは、vscode-extension-samples GitHubリポジトリのtree-view-sampleにあります。

package.jsonでのコントリビューション

まず、package.jsoncontributes.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"
  }
}

: 拡張機能が1.74より前のVS Codeバージョンをターゲットとする場合、activationEventsonView:nodeDependenciesを明示的にリストする必要があります。

ビューの識別子と名前を指定する必要があり、以下の場所にコントリビュートできます

  • explorer: サイドバーのエクスプローラービュー
  • debug: サイドバーの「実行とデバッグ」ビュー
  • scm: サイドバーのソース管理ビュー
  • test: サイドバーのテストエクスプローラービュー
  • カスタムビューコンテナ

ツリーデータプロバイダー

2番目のステップは、登録したビューにデータを提供し、VS Codeがそのビューにデータを表示できるようにすることです。そのためには、まずTreeDataProviderを実装する必要があります。私たちのTreeDataProviderはノードの依存関係データを提供しますが、他の種類のデータを提供するデータプロバイダーを持つこともできます。

このAPIには、実装する必要のある2つの必須メソッドがあります

  • getChildren(element?: T): ProviderResult<T[]> - 指定されたelementまたはルート (elementが渡されない場合) の子を返すようにこれを実装します。
  • getTreeItem(element: T): TreeItem | Thenable<TreeItem> - ビューに表示される要素のUI表現 (TreeItem) を返すようにこれを実装します。

ユーザーがツリービューを開くと、getChildrenメソッドはelementなしで呼び出されます。そこから、TreeDataProviderはトップレベルのツリー項目を返す必要があります。この例では、トップレベルのツリー項目のcollapsibleStateTreeItemCollapsibleState.Collapsedであり、トップレベルのツリー項目が折りたたまれた状態で表示されることを意味します。collapsibleStateTreeItemCollapsibleState.Expandedに設定すると、ツリー項目は展開された状態で表示されます。collapsibleStateをデフォルトのTreeItemCollapsibleState.Noneのままにすると、ツリー項目に子がないことを示します。collapsibleStateTreeItemCollapsibleState.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)
    });
    

拡張機能の動作例です

View

ツリービューコンテンツの更新

私たちのノードの依存関係ビューはシンプルで、データが表示された後は更新されません。しかし、ビューに更新ボタンを設け、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.jsoncontributesセクションに以下を追加します

    "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.jsoncontributesセクションに以下を追加します

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

アクティベーション

拡張機能は、ユーザーが拡張機能が提供する機能を必要とするときにのみアクティブ化されることが重要です。この場合、ユーザーがビューの使用を開始したときにのみ拡張機能をアクティブ化することを検討する必要があります。拡張機能がビューのコントリビューションを宣言すると、VS Codeはこれを自動的に行います。ユーザーがビューを開くと、VS CodeはアクティベーションイベントonView:${viewId} (上記の例ではonView:nodeDependencies) を発行します。

: VS Codeバージョン1.74.0より前の場合は、このビューで拡張機能をアクティブ化するために、このアクティベーションイベントをpackage.jsonに明示的に登録する必要があります

"activationEvents": [
       "onView:nodeDependencies",
],

ビューコンテナ

ビューコンテナには、組み込みのビューコンテナと共にアクティビティバーまたはパネルに表示されるビューのリストが含まれています。組み込みのビューコンテナの例としては、ソース管理とエクスプローラーがあります。

View Container

ビューコンテナをコントリビュートするには、まずpackage.jsoncontributes.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.jsoncontributes.viewsコントリビューションポイントを使用できます。

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

ビューには、オプションでvisiblecollapsed、またはhiddenに設定できるvisibilityプロパティもあります。このプロパティは、このビューがワークスペースで初めて開かれたときにのみVS Codeによって尊重されます。その後、可視性はユーザーが選択したものになります。多くのビューを持つビューコンテナがある場合、またはビューが拡張機能のすべてのユーザーにとって有用でない場合は、ビューをcollapsedまたはhiddenに設定することを検討してください。hiddenビューは、ビューコンテナの「ビュー」メニューに表示されます。

Views Menu

ビューアクション

アクションは、個々のツリー項目にインラインアイコンとして、ツリー項目のコンテキストメニューに、そしてビュータイトルのビュー上部に表示されます。アクションは、package.jsonにコントリビューションを追加することで、これらの場所に表示されるように設定するコマンドです。

これら3つの場所へのコントリビューションには、package.jsonで以下のメニューコントリビューションポイントを使用できます

  • view/title - ビュータイトルにアクションを表示する場所。プライマリまたはインラインアクションは"group": "navigation"を使用し、残りは...メニューにあるセカンダリアクションです。
  • view/item/context - ツリー項目のアクションを表示する場所。インラインアクションは"group": "inline"を使用し、残りは...メニューにあるセカンダリアクションです。

when句を使用して、これらのアクションの可視性を制御できます。

View Actions

"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)"
    }
  ]
}

Welcome Content

ウェルカムコンテンツではリンクがサポートされています。慣例により、それ自体で1行を占めるリンクはボタンとして扱われます。各ウェルカムコンテンツには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を参照してください。

© . This site is unofficial and not affiliated with Microsoft.