メインコンテンツへ移動

プラグインの仕組み詳述_VSCodeプラグイン開発ノート1

無料2018-02-03#Tool#VSCode插件教程#VSCode extension tutorial#VSCode插件API#VSCode extension API

ついにSublime TextをDockから外した。VS Codeが少しずつ好きになってきた。

一. 拡張能力

VS CodeプラグインはUIのカスタマイズには適していません。例えばAtomのtool-barをVS Codeで実現するのは非常に困難です:

atom-tool-bar

豊富な拡張能力モデルを提供していますが、プラグインが下層のUI DOMに直接アクセスすることは許可されていません(つまり、プラグインによってIDEの外観を変更することは困難であり、UIのカスタマイズは制限されています)。これは、下層の継続的な最適化を容易にするための考慮事項です:

With VS Code, we’re continually trying to optimize use of the underlying web technologies to deliver an always available, highly responsive editor and we will continue to tune our use of the DOM as these technologies and our product evolve.

UI DOMのレイヤーは最適化に伴い頻繁に変更される可能性があります。VS Codeは、これらの最適化項目がプラグインの依存関係によって制限されることを望まないため、UIカスタマイズ能力を制限しています。

UIカスタマイズ以外では、IDEに関連する機能的な特性はすべて拡張がサポートされています。例えば、基礎的な構文ハイライト/APIのヒント、参照ジャンプ(定義へ移動)/ファイル検索、テーマのカスタマイズ、高度なデバッグプロトコルなどです。

P.S. 実際には、どうしてもUIを拡張したい場合、方法はあります(プラグイン実行環境を脱出する必要がありますが、かなりの労力を要します)。詳細は access electron API from vscode extension を参照してください。後のノートで詳しく紹介します。

二. 実行環境

パフォーマンスと互換性のために、プラグインは独立したプロセス(extension host process と呼ばれる)で実行されます。また、DOMへの直接アクセスは許可されていないため、インテリセンス(IntelliSense)などの一連の内蔵UIコンポーネントが提供されています。

したがって、プラグインがクラッシュしたり応答しなくなったりしても、IDEの正常な実行には影響しません。例えば:

// ref: my-extension/src/extension.ts
export function activate(context: vscode.ExtensionContext) {
  // hang up
  while (true);
}

一つのプラグインの無限ループは、IDEの正常な使用や他のプラグインのロード/アクティブ化には影響しませんが、プロセスリストでは Code Helper の CPU 使用率が100%近くになっているのを確認できます。プロセスレベルのサンドボックスがプラグインメカニズムの安定性を保証しています。

三. コアコンセプト

安定性:プラグインの隔離

プラグインは起動パフォーマンスやIDE自体の安定性に影響を与える可能性があるため、プロセス隔離によってこの問題を解決しています。プラグインは独立したプロセスで実行され、IDEとその起動時間には影響しません。

これはユーザーの視点から考えられたものであり、ユーザーがIDEに対して完全なコントロールを持つことを望んでいます。プラグインが何をしていても、IDEの基本機能の正常な使用には影響しません。

P.S. extension host process は特殊な Node プロセスであり、VS Code 拡張 API にアクセスできます。VS Code はこの種のプロセスに対してデバッグサポートも提供しています。

パフォーマンス:プラグインのアクティブ化

プラグインはすべて遅延読み込み(できるだけ遅く)されます。特定のシナリオでのみロード/アクティブ化されるため、それ以前はメモリなどのリソースを消費しません。

実装上は、プラグインが特定のアクティブ化イベント(activation events)を登録し、IDEがそれをトリガーして実行します。例えば、markdownプラグインはユーザーがmdファイルを開いたときにのみアクティブ化する必要があります。

アクティブ化の方法

プラグインには6つのアクティブ化方法があります:

onLanguage:${language} 特定の言語のドキュメントを開く
onCommand:${command} コマンドパレットを通じて特定のコマンドを実行する
onDebug デバッグモードに入る
workspaceContains:${toplevelfilename} 開いたフォルダ内に特定のファイルが含まれている
onView:${viewId} 指定したビューを展開する
* IDEを開くと同時にアクティブ化する

"activationEvents": ["*"] 以外はすべて条件付きアクティブ化であり、特定のシナリオや条件を満たす場合にのみプラグインをロード/アクティブ化します。

プラグインマニフェストファイル

マニフェストファイルはプラグインのメタ情報を記述するために使用されます。package.json を直接マニフェストファイルとして使用し、プラグインのロードをトリガーするアクティブ化イベント(activation events)や、プラグインが強化したい拡張点(contribution points)などの特有のフィールドが追加されています。

IDEは起動プロセス中にプラグインマニフェストファイルをスキャンし、UIに関連するものはUIを拡張し、UIに関連しないものは拡張点とプラグイン機能を関連付けます。

また、プラグインの実行環境は Node プロセスであるため、npm パッケージはすべて利用可能です。依存モジュールも同様に package.json で宣言されます。注意:ユーザーがプラグインをインストールする際、npm install自動的に実行されません。そのため、プラグインを公開する前に依存モジュールをパッケージ化しておく必要があります。詳細は Installation and Packaging を参照してください。

P.S. 拡張点は AOP における Join point(結合点)に似ており、「ここでの拡張/強化を許可する」という意味です。例えば、新しいカスタムコマンドを追加することは、commands 拡張点の強化にあたります。

manifest

// package.json
{
  // プラグイン名称
  "name": "my-extension",
  // 表示名
  "displayName": "MyExtension",
  // 説明情報
  "description": "An awesome vscode extension",
  // バージョン番号 semver形式
  "version": "0.0.1",
  // プラグインマーケットプレイスで表示されるアイコン
  "icon": "img/icon.png",
  // 公開者名
  "publisher": "ayqy",
  // vscodeバージョンの要件
  "engines": {
    "vscode": "^1.19.0"
  },
  // 所属カテゴリ。Languages, Snippets, Linters, Themesなどから選択
  "categories": ["Other"],
  // ロード/アクティブ化の方法
  "activationEvents": ["onLanguage:javascript"],
  // エントリファイルのパス
  "main": "./out/extension",
  // 拡張点の関連付けを登録
  "contributes": {
    "languages": [
      {
        "id": "javascript",
        "aliases": ["JavaScript", "javascript"],
        "extensions": [".js"]
      }
    ]
  }
}

P.S. 完全な情報は Extension Manifest File - package.json を参照してください。

extension.ts/activate は、package.json で宣言された activationEvents に基づいて一度だけトリガーされます。トリガー条件は、特定の言語のファイルを開くことや、特定のコマンドを実行することなどです。アクティブ化された後は、IDEが閉じられるかクラッシュするまで extension.ts/deactivate はトリガーされません。そのため、一般的な使い方は以下の通りです:

  • activate: プラグインがアクティブ化されたとき、機能モジュールのシングルトンを初期化する(一度だけ実行)

  • deactivate: IDEが終了する直前、後片付けを行う。ただし、最大10秒しか待機されないと言われているため、時間のかかる処理は避けるべきです。

拡張点

サポートされている拡張タイプであり、すべて package.json/contributes の下に宣言されます。以下が含まれます:

configuration プラグインの設定項目。ユーザーはSettingsから設定可能
configurationDefaults プラグイン設定項目のデフォルト値
commands コマンドの追加。ユーザーはコマンドパレットから特定のコマンドを入力してプラグイン機能を実行できる
menus コマンドに関連付けられたメニュー項目の追加。ユーザーがメニューをクリックした際に対応するコマンドを実行する
keybindings コマンドに関連付けられたショートカットキーの追加。ユーザーが特定のキーを押した際に対応するコマンドを実行する
languages ファイルタイプとの関連付けや新しい言語の拡張。ユーザーが特定の条件を満たすファイルを開いた際に対応するコマンドを実行する
debuggers デバッガの追加。VS Codeデバッグプロトコルを通じてIDEと通信する
breakpoints debuggersと連携し、デバッガがサポートする言語タイプを宣言する
grammars 新しいTextMate文法定義の追加。構文ハイライトに使用
themes カスタムテーマの追加
snippets コードスニペットの追加
jsonValidation JSON形式のバリデーションの追加
views 左側のファイルエクスプローラービューやデバッグビューへのサイドバーの追加
problemMatchers エラーマッチングの追加。lint結果からerrorやwarningなどを解析する
problemPatterns problemMatchersと連携し、マッチングパターンを定義する

menus唯一のUI拡張の公式な手段です。拡張をサポートしているメニューは以下の通りです:

コマンドパレット検索ボックス下のメニュー commandPalette
ファイルエクスプローラーの右クリックメニュー explorer/context
エディタ
  右クリックメニュー editor/context
  タイトルバーメニュー editor/title
  タイトルバーの右クリックメニュー editor/title/context
デバッグビュー
  コールスタックの右クリックメニュー debug/callstack/context
SCM(ソース管理)ビュー
  タイトルバーメニュー scm/title
  ファイルグループメニュー scm/resourceGroup/context
  ファイルステータスメニュー scm/resource/context
  ファイル変更メニュー scm/change/title
左側ビュー
  ファイルエクスプローラーセクション view/title
  デバッグビューセクション view/item/context

P.S. どれも目立たない場所ばかりで、大胆なUIカスタマイズはサポートされていません。例えば、左端のサイドバー(Activity Bar)にアイコンを追加することさえできません。

タイトルバー上のメニュー拡張はカスタムアイコンをサポートしていますが、定義方法が少し特殊です。例えば:

    "commands": [{
      "command": "markdown.showPreviewToSide",
      "title": "%markdown.previewSide.title%",
      "category": "Markdown",
      "icon": {
        "light": "./media/PreviewOnRightPane_16x.svg",
        "dark": "./media/PreviewOnRightPane_16x_dark.svg"
      }
    }],
    "menus": {
			"editor/title": [
				{
					"command": "markdown.showPreviewToSide",
					"when": "editorLangId == markdown",
					"alt": "markdown.showPreview",
					"group": "navigation"
				}
      ]
    }

commandicon を定義し、menucommand に関連付けることで、menu に対応する icon が表示されます。

拡張API

環境の隔離により、プラグインが使用できるAPIを厳格に制限することが非常に容易になりました。プラグインはIDEが提供する拡張APIにしかアクセスできず、勝手なことはできません(UI DOMやスタイルの変更など。公式にサポートされているテーマのカスタマイズ項目を除きます)。

API設計の原則

プラグインAPIはいくつかの原則に従っています:

  • Promiseベース:非同期操作はすべて Promise で記述されます。

  • キャンセル型トークン:CancellationToken を追加の引数として渡し、キャンセル状態のチェックやキャンセル通知の受信を行います。

  • 解放可能なリソース管理:保持しているリソースはすべて手動で解放する必要があります。例えば、イベントリスナー、コマンド、UIインタラクションなどです。

  • イベントAPI:購読メソッド(on[Will|Did]VerbNoun)を呼び出し、リスナー(event 引数を受け取る)を渡すと Disposable が返されます。

  • 厳格な null チェック:TypeScript を通じて undefinednull を厳格に区別します。

P.S. 「解放可能」(Disposable)についての詳細は、Dispose pattern を参照してください。

API概要

APIは名前空間ごとに組織されています。グローバルな名前空間は以下の通りです:

commands コマンドの実行/登録。IDE自身のコマンドや他のプラグインが登録したコマンドも可能(例:executeCommand)
debug デバッグ関連のAPI(例:startDebugging)
env IDEに関連する環境情報(例:machineId, sessionId)
extensions プラグイン間のAPI呼び出し。extensionDependencyでプラグインの依存関係を宣言
languages プログラミング言語関連のAPI(例:createDiagnosticCollection, registerDocumentFormattingEditProvider)
scm ソースコード管理(バージョン管理)API(例:createSourceControl)
window エディタウィンドウ関連のAPI(例:onDidChangeTextEditorSelection, createTerminal, showTextDocument)
workspace ワークスペースレベルのAPI(フォルダを開いている場合のみ。例:findFiles, openTextDocument, saveAll)

例えば、workspace.findFiles + languages.registerDefinitionProvider を通じて Haste のグローバルモジュール参照ジャンプサポートを実現できます。

また、一部のAPIはコマンド形式で提供されます(前述の「IDE独自の」コマンド)。例えば vscode.previewHtmlvscode.openFoldereditorScroll などです。

プロトコルベースの拡張

プラグインプロセスとIDEの間は特定のプロトコルを通じて通信します。実装上は JSON 形式の stdin/stdout を介して通信します。

このパターンのさらに強力な点は、プラグインを任意の言語で実装できることです。決められた通信プロトコルに従ってさえいれば問題ありません。

四. 言語関連の拡張

設定ファイルを通じて構文ハイライト、コードスニペット、インテリジェントな括弧のペアリングをサポートします。より複雑なものは拡張APIまたは language server を使用して作成します。

設定型拡張

  • 構文ハイライト:文字列、コメント、キーワードなどの構文ロールの区別を基本としてサポートし、高度な機能として変数や関数参照などのセマンティックな区別をサポートします。

  • コードスニペット:snippetsによるクイック入力。シンプルなプレースホルダーから、高度な入れ子構造のプレースホルダーまでサポートします。

  • インテリジェントな括弧のペアリング:括弧、引用符、複数行コメントなど、対になって出現する要素の自動補完をサポートします。

注意:言語拡張について、VS Code は標準の Text Mate GrammartmLanguage 形式)をサポートしています。Monaco Editor のマイナーな Monarch-style よりもはるかに使いやすいです。詳細は Colorization Clarification を参照してください。

プログラミング型拡張

簡単な設定で対応できないものは、すべて拡張API(プラグイン作成)を通じて実現します。2つの方法があります:

  • language server protocol を実装してIDEと通信する(完全に独立)

  • Provider を登録してカスタム能力を提供する(フックのような方式)

使用感としては、前者は手間がかかりますがより強力で柔軟であり、後者は手軽で直接的ですが柔軟性は劣ります。サポートされている拡張能力は以下の通りです:

  • ホバーヒント:型やドキュメントなどの情報の表示を基本とし、メソッドシグネチャの構文ハイライトなどをサポートします。

  • 補完ヒント:補完候補の隣に詳細情報を表示することをサポートします。

  • エラーチェック:保存時に開いているファイルの内容をチェックしてエラーを表示することを基本とし、高度な機能としてディレクトリ内の任意のリソースに対するチェックをサポートします。

  • メソッドシグネチャ:メソッドシグネチャに引数の説明ドキュメントを含めることができます。

  • 定義へ移動:定義が複数ある場合にすべて表示することをサポートします。

  • 参照の検索:すべての参照箇所の具体的な位置を返すことをサポートします。

  • 選択範囲のハイライト:現在のドキュメント内のすべての同一の参照を返すことをサポートします。

  • メソッド/変数宣言リスト:ドキュメント内で宣言されているすべての識別子とその定義位置を返すことをサポートします。

  • クイック修正:WarningやErrorに対して推奨される対応策を提示し、素早く修正します。ミスの訂正から、重複コードの関数化といった高度なソースコード修正までサポートします。

  • コンテキストアクション:コードのコンテキストに応じて、追加の情報や操作オプションを提供します。表示だけでなく、カスタムコマンドの追加も可能です。

  • 名前変更:参照箇所を含めた名前変更をサポートし、ワークスペース内のファイル間をまたぐ名前変更も可能です。

  • コードの整形:全文、選択範囲、または入力中の整形をサポートします。

五. 開発ステップ

環境要件

  • VS Code

  • Yeoman と Yo Code - Extension Generator:npm install -g yo generator-code で一括インストールできます。

ステップ

スキャフォールディングを通じてプロジェクトテンプレートを生成します:

yo code

コマンド対話形式でプラグインのタイプを選択します:

New Extension (TypeScript)
New Extension (JavaScript)
New Color Theme
New Language Support
New Code Snippets
New Extension Pack

TypeScript をお勧めします。他は文字通りの意味です。その中で Extension Pack(プラグインパック)は面白く、プラグインを組み合わせたプラグインです。React Native の Nuclide のようなものです。

プラグイン名などのメタ情報を入力すると、プラグインプロジェクトが生成されます。その後、VS Code でそのプロジェクトを単独で開き(ワークスペースに他のプロジェクトディレクトリがあってはいけません)、F5キーでデバッグを開始してプラグインのデバッグに入ります。

プラグインのエントリファイルは my-extension/src/extension.ts です。プロジェクト構造の規範については、VS Code 内蔵プラグインを参考にできます:

// ref: https://github.com/Microsoft/vscode/tree/master/extensions/markdown
markdown/
  media/
    *.svg
    *.css
  snippets/
    markdown.json
  syntaxes/
    *.tmLanguage
  src/
    features/
      *Provider.ts
    typings/
      *.d.ts
    commandManager.ts
    commands.ts
    logger.ts
    markdownEngine.ts
    security.ts
    telemetryReporter.ts

六. パッケージ化と公開

CLIツール vsce が提供されています:

npm install -g vsce

パッケージ化

プラグインディレクトリに入り、.vsix ファイルにパッケージ化します:

cd my-extension
vsce package

my-extension.vsix というローカルパッケージ(node_modules 依存関係を含む)が生成されます。公開したくない場合は、自力で配布・インストールする方法を考える必要があります。npm registry のように自分でデプロイして社内環境でプライベートプラグインを置くといった自由度は Visual Studio Marketplace(VS Code プラグイン市場)にはありません:

If you want to share your extension with others privately, you can send them your packaged extension .vsix file.

Sharing Privately with Others より)

Visual Studio Marketplace を自前でデプロイする方法はないため、プラグインの更新問題(自動ダウンロードやインストール通知など)は手動で解決する方法を考える必要があります。

公開

プラグインマーケットプレイスに公開するには、以下の手順が必要です:

  1. Visual Studio Team Services アカウントを登録する

  2. Security ページで Personal Access Token を作成する

  3. vsce create-publisher (publisher name) コマンドで公開者(publisher)を登録する

  4. vsce login (publisher name) コマンドでログインする

  5. vsce publish -p <token> コマンドで公開する

詳細は Publishing Extensions を参照してください。

参考文献

コメント

コメントはまだありません

コメントを書く