🚀 在 VS Code 中免費取得

樹狀檢視 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 貢獻

首先,您必須讓 VS Code 知道您正在貢獻一個檢視,方法是在 package.json 中使用 contributes.views 貢獻點。

以下是我們擴充功能第一個版本的 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 版本,您必須在 activationEvents 中明確列出 onView:nodeDependencies

您必須為檢視指定識別碼和名稱,並且可以貢獻到以下位置

  • explorer:側邊欄中的檔案總管檢視
  • debug:側邊欄中的執行和偵錯檢視
  • scm:側邊欄中的原始碼控制檢視
  • test:側邊欄中的測試總管檢視
  • 自訂檢視容器

樹狀資料提供者

第二步是為您註冊的檢視提供資料,以便 VS Code 可以在檢視中顯示資料。為此,您應首先實作 TreeDataProvider。我們的 TreeDataProvider 將提供節點依賴項目資料,但您可以擁有提供其他資料類型的資料提供者。

在此 API 中有兩個必要的方法需要您實作

  • getChildren(element?: T): ProviderResult<T[]> - 實作此方法以傳回給定 element 或根目錄(如果未傳遞任何元素)的子項目。
  • getTreeItem(element: T): TreeItem | Thenable<TreeItem> - 實作此方法以傳回元素在檢視中顯示的 UI 表示 (TreeItem)。

當使用者開啟樹狀檢視時,將在沒有 element 的情況下呼叫 getChildren 方法。從那裡,您的 TreeDataProvider 應傳回您的最上層樹狀項目。在我們的範例中,最上層樹狀項目的 collapsibleStateTreeItemCollapsibleState.Collapsed,這表示最上層樹狀項目將顯示為已摺疊。將 collapsibleState 設定為 TreeItemCollapsibleState.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

第三步是將上述資料提供者註冊到您的檢視。

這可以透過以下兩種方式完成

  • 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 會發出 activationEvent onView:${viewId}(以上範例為 onView:nodeDependencies)。

注意:對於 1.74.0 之前的 VS Code 版本,您必須在 package.json 中明確註冊此啟用事件,VS Code 才能在此檢視上啟用您的擴充功能

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

檢視容器

檢視容器包含在活動列或面板中與內建檢視容器一起顯示的檢視清單。內建檢視容器的範例包括原始碼控制和檔案總管。

View Container

若要貢獻檢視容器,您應首先使用 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 屬性,可以設定為 visiblecollapsedhidden。VS Code 僅在第一次使用此檢視開啟工作區時才會遵守此屬性。之後,可見性會設定為使用者選擇的任何值。如果您有一個包含許多檢視的檢視容器,或者如果您的檢視對您的擴充功能的每個使用者都無用,請考慮將檢視設定為 collapsedhiddenhidden 檢視將會出現在檢視容器的「檢視」選單中

Views Menu

檢視動作

動作在您的個別樹狀項目上以內嵌圖示、在樹狀項目內容選單中以及在檢視標題中的檢視頂端提供。動作是您設定為在這些位置顯示的命令,方法是將貢獻新增至您的 package.json

若要貢獻到這三個位置,您可以在 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

歡迎內容中支援連結。依照慣例,單獨一行的連結是一個按鈕。每個歡迎內容也可以包含 when 子句。如需更多範例,請參閱 內建 Git 擴充功能

TreeDataProvider

擴充功能作者應以程式設計方式註冊 TreeDataProvider,以在檢視中填入資料。

vscode.window.registerTreeDataProvider('nodeDependencies', new DepNodeProvider());

請參閱 tree-view-sample 中的 nodeDependencies.ts 以取得實作。

TreeView

如果您想要以程式設計方式在檢視上執行一些 UI 操作,您可以使用 window.createTreeView 而不是 window.registerTreeDataProvider。這將提供對檢視的存取權,您可以使用它來執行檢視操作。

vscode.window.createTreeView('ftpExplorer', {
  treeDataProvider: new FtpTreeDataProvider()
});

請參閱 tree-view-sample 中的 ftpExplorer.ts 以取得實作。