VS Codeのエージェントモードを拡張するには、を試してください!

デバッガー拡張機能

Visual Studio Codeのデバッグアーキテクチャにより、拡張機能作成者は既存のデバッガーをVS Codeに簡単に統合でき、すべて共通のユーザーインターフェースを持たせることができます。

VS Codeには、組み込みのデバッガー拡張機能としてNode.jsデバッガー拡張機能が付属しています。これは、VS Codeがサポートする多くのデバッガー機能の素晴らしいショーケースです。

VS Code Debug Features

このスクリーンショットは、以下のデバッグ機能を示しています。

  1. デバッグ構成の管理。
  2. 開始/停止およびステップ実行のためのデバッグアクション。
  3. ソース、関数、条件付き、インラインブレークポイント、およびログポイント。
  4. マルチスレッドおよびマルチプロセスサポートを含むスタックトレース。
  5. ビューおよびホバーでの複雑なデータ構造のナビゲーション。
  6. ホバーまたはソースにインラインで表示される変数値。
  7. ウォッチ式の管理。
  8. オートコンプリートによるインタラクティブな評価のためのデバッグコンソール。

このドキュメントは、あらゆるデバッガーをVS Codeで動作させることができるデバッガー拡張機能を作成するのに役立ちます。

VS Code のデバッグアーキテクチャ

VS Code は、デバッガーバックエンドと通信するために導入した抽象プロトコルに基づいて、汎用的な(言語に依存しない)デバッガー UI を実装しています。デバッガーは通常このプロトコルを実装していないため、デバッガーをプロトコルに「適合させる」ための中間体が必要です。この中間体は通常、デバッガーと通信するスタンドアロンプロセスです。

VS Code Debug Architecture

この中間体を**デバッグアダプター**(略して**DA**)と呼び、DA と VS Code の間で使われる抽象プロトコルは**デバッグアダプタープロトコル**(略して**DAP**)です。デバッグアダプタープロトコルは VS Code から独立しているため、独自のウェブサイトがあり、そこで概要と概要、詳細な仕様、そして既知の実装とサポートツールのリストを見つけることができます。DAP の歴史と動機については、このブログ記事で説明されています。

デバッグアダプターはVS Codeから独立しており、他の開発ツールでも使用できるため、拡張機能と貢献点に基づいたVS Codeの拡張性アーキテクチャとは一致しません。

このため、VS Code は `debuggers` という貢献点を提供しており、そこで特定のデバッグタイプ(例: Node.js デバッガーの `node`)の下にデバッグアダプターを貢献できます。VS Code は、ユーザーがそのタイプのデバッグセッションを開始するたびに、登録された DA を起動します。

したがって、最もミニマルな形では、デバッガー拡張機能はデバッグアダプターの実装を宣言的に貢献するだけであり、拡張機能は基本的に追加のコードなしでデバッグアダプターのパッケージングコンテナとなります。

VS Code Debug Architecture 2

より現実的なデバッガー拡張機能は、VS Code に以下の宣言項目を多数、またはすべて貢献します。

  • デバッガーがサポートする言語のリスト。VS Code はこれらの言語でブレークポイントを設定できるように UI を有効にします。
  • デバッガーによって導入されたデバッグ構成属性のJSONスキーマ。VS Code はこのスキーマを使用して、launch.jsonエディターの構成を検証し、IntelliSenseを提供します。JSONスキーマの構築 `\$ref` と `definition` はサポートされていませんのでご注意ください。
  • VS Code によって作成される初期 launch.json のデフォルトデバッグ構成。
  • ユーザーがlaunch.jsonファイルに追加できるデバッグ構成スニペット。
  • デバッグ構成で使用できる変数の宣言。

contributes.breakpoints および contributes.debuggers の参照で詳細情報を見つけることができます。

上記の純粋に宣言的な貢献に加えて、デバッグ拡張機能 API は次のコードベースの機能も可能にします。

  • VS Codeによって作成される初期のlaunch.jsonのための動的に生成されるデフォルトデバッグ構成。
  • 使用するデバッグアダプターを動的に決定します。
  • デバッグ構成がデバッグアダプターに渡される前に、それらを検証または変更します。
  • デバッグアダプターと通信します。
  • デバッグコンソールにメッセージを送信します。

このドキュメントの残りの部分では、デバッガー拡張機能を開発する方法を示します。

モックデバッグ拡張

デバッグアダプターを一から作成するのはこのチュートリアルには少し重いため、教育目的の「デバッグアダプター・スターターキット」として作成したシンプルなDAから始めます。これは、実際のデバッガーと通信するのではなく、モックするため「モックデバッグ」と呼ばれています。モックデバッグはデバッガーをシミュレートし、ステップ、続行、ブレークポイント、例外、変数アクセスをサポートしますが、実際のデバッガーには接続されていません。

モックデバッグの開発設定に入る前に、まずVS Code Marketplaceからビルド済みバージョンをインストールして試してみましょう。

  • 拡張機能ビューレットに切り替えて、「mock」と入力してMock Debug拡張機能を検索します。
  • 拡張機能を「インストール」し、「リロード」します。

モックデバッグを試すには

  • 新しい空のフォルダ `mock test` を作成し、VS Code で開きます。
  • `readme.md` ファイルを作成し、任意のテキストを数行入力します。
  • 実行とデバッグビュー (⇧⌘D (Windows, Linux Ctrl+Shift+D)) に切り替えて、launch.json ファイルを作成リンクを選択します。
  • VS Code は、デフォルトの起動構成を作成するために「デバッガー」を選択させます。「Mock Debug」を選択してください。
  • 緑色の**開始**ボタンを押し、次にEnterを押して、提案されたファイル `readme.md` を確認します。

デバッグセッションが開始され、readmeファイル内を「ステップ」実行したり、ブレークポイントを設定してヒットさせたり、例外に遭遇したりすることができます(行に`exception`という単語がある場合)。

Mock Debugger running

Mock Debug を独自の開発の出発点として使用する前に、まずプリビルド版をアンインストールすることをお勧めします。

  • 拡張機能ビューレットに切り替えて、Mock Debug 拡張機能の歯車アイコンをクリックします。
  • 「アンインストール」アクションを実行し、ウィンドウを「リロード」します。

モックデバッグの開発環境設定

それでは、Mock Debug のソースを取得し、VS Code で開発を開始しましょう。

git clone https://github.com/microsoft/vscode-mock-debug.git
cd vscode-mock-debug
yarn

プロジェクトフォルダー `vscode-mock-debug` をVS Codeで開きます。

パッケージには何が入っていますか?

  • package.json は mock-debug 拡張機能のマニフェストです。
    • これは mock-debug 拡張機能の貢献を一覧表示します。
    • `compile` スクリプトと `watch` スクリプトは、TypeScript ソースを `out` フォルダーにトランスパイルし、その後のソース修正を監視するために使用されます。
    • `vscode-debugprotocol`、`vscode-debugadapter`、`vscode-debugadapter-testsupport` の依存関係は、Node ベースのデバッグアダプターの開発を簡素化する NPM モジュールです。
  • src/mockRuntime.ts は、シンプルなデバッグ API を持つ*モック*ランタイムです。
  • ランタイムをデバッグアダプタープロトコルに*適応させる*コードは、`src/mockDebug.ts` にあります。ここには、DAP のさまざまなリクエストのハンドラーがあります。
  • デバッガー拡張機能の実装はデバッグアダプター内に存在するため、拡張機能コード(つまり、拡張機能ホストプロセスで実行されるコード)はまったく必要ありません。しかし、Mock Debug には小さな `src/extension.ts` が含まれています。これは、デバッガー拡張機能の拡張機能コードで何ができるかを示すためです。

**拡張機能**起動構成を選択し、`F5` を押して Mock Debug 拡張機能をビルドして起動します。最初に、TypeScript ソースが `out` フォルダーに完全にトランスパイルされます。完全ビルド後、変更をトランスパイルする*ウォッチャー タスク*が開始されます。

ソースをトランスパイルした後、"[Extension Development Host]"というラベルの付いた新しいVS Codeウィンドウが表示され、Mock Debug拡張機能がデバッグモードで実行されます。そのウィンドウから、`readme.md`ファイルを含む`mock test`プロジェクトを開き、'F5'でデバッグセッションを開始し、ステップ実行します。

Debugging Extension and Server

拡張機能をデバッグモードで実行しているため、`src/extension.ts` にブレークポイントを設定してヒットさせることができますが、前述したように、拡張機能で実行されている興味深いコードはあまりありません。興味深いコードは、別のプロセスであるデバッグアダプターで実行されます。

デバッグアダプター自体をデバッグするためには、デバッグモードで実行する必要があります。これは、デバッグアダプターを*サーバーモード*で実行し、VS Codeがそれに接続するように設定することで最も簡単に行えます。VS Codeのvscode-mock-debugプロジェクトで、ドロップダウンメニューから起動構成**Server**を選択し、緑色の開始ボタンを押します。

拡張機能のデバッグセッションがすでにアクティブであったため、VS Code のデバッガー UI は現在、**Extension**と**Server**の 2 つのデバッグセッション名が CALL STACK ビューに表示されることで示される、*マルチセッション*モードに入ります。

Debugging Extension and Server

これで、拡張機能とDAの両方を同時にデバッグできるようになりました。ここへ到達するより速い方法は、両方のセッションを自動的に起動する**Extension + Server**起動構成を使用することです。

拡張機能とDAをデバッグするもう一つの、さらにシンプルなアプローチは、以下にあります。

`src/mockDebug.ts` ファイルの `launchRequest(...)` メソッドの先頭にブレークポイントを設定し、最後に、モックテストの起動設定にポート `4711` の `debugServer` 属性を追加して、モックデバッガーをDAサーバーに接続するように設定します。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "mock",
      "request": "launch",
      "name": "mock test",
      "program": "${workspaceFolder}/readme.md",
      "stopOnEntry": true,
      "debugServer": 4711
    }
  ]
}

このデバッグ構成を起動すると、VS Code はモックデバッグアダプターを別のプロセスとして起動するのではなく、すでに実行中のサーバーのローカルポート4711に直接接続し、`launchRequest` のブレークポイントにヒットするはずです。

この設定で、Mock Debug を簡単に編集、トランスパイル、デバッグできるようになりました。

しかし、ここからが本当の作業です。`src/mockDebug.ts` と `src/mockRuntime.ts` のモック実装を、「実際の」デバッガーまたはランタイムと通信するコードに置き換える必要があります。これには、デバッグアダプタープロトコルを理解し、実装することが含まれます。これに関する詳細は、こちらで見つけることができます。

デバッガー拡張機能の package.json の構成

デバッグアダプターのデバッガー固有の実装を提供するだけでなく、デバッガー拡張機能には、さまざまなデバッグ関連の貢献点に貢献する `package.json` が必要です。

では、Mock Debug の `package.json` を詳しく見てみましょう。

すべての VS Code 拡張機能と同様に、`package.json` は拡張機能の基本的なプロパティである**name**、**publisher**、および**version**を宣言します。VS Code 拡張機能マーケットプレイスで拡張機能を見つけやすくするには、**categories**フィールドを使用します。

{
  "name": "mock-debug",
  "displayName": "Mock Debug",
  "version": "0.24.0",
  "publisher": "...",
  "description": "Starter extension for developing debug adapters for VS Code.",
  "author": {
    "name": "...",
    "email": "..."
  },
  "engines": {
    "vscode": "^1.17.0",
    "node": "^7.9.0"
  },
  "icon": "images/mock-debug-icon.png",
  "categories": ["Debuggers"],

  "contributes": {
    "breakpoints": [{ "language": "markdown" }],
    "debuggers": [
      {
        "type": "mock",
        "label": "Mock Debug",

        "program": "./out/mockDebug.js",
        "runtime": "node",

        "configurationAttributes": {
          "launch": {
            "required": ["program"],
            "properties": {
              "program": {
                "type": "string",
                "description": "Absolute path to a text file.",
                "default": "${workspaceFolder}/${command:AskForProgramName}"
              },
              "stopOnEntry": {
                "type": "boolean",
                "description": "Automatically stop after launch.",
                "default": true
              }
            }
          }
        },

        "initialConfigurations": [
          {
            "type": "mock",
            "request": "launch",
            "name": "Ask for file name",
            "program": "${workspaceFolder}/${command:AskForProgramName}",
            "stopOnEntry": true
          }
        ],

        "configurationSnippets": [
          {
            "label": "Mock Debug: Launch",
            "description": "A new configuration for launching a mock debug program",
            "body": {
              "type": "mock",
              "request": "launch",
              "name": "${2:Launch Program}",
              "program": "^\"\\${workspaceFolder}/${1:Program}\""
            }
          }
        ],

        "variables": {
          "AskForProgramName": "extension.mock-debug.getProgramName"
        }
      }
    ]
  },

  "activationEvents": ["onDebug", "onCommand:extension.mock-debug.getProgramName"]
}

次に、デバッグ拡張機能に固有の貢献が含まれる**contributes**セクションを見てみましょう。

まず、**breakpoints**貢献点を使用して、ブレークポイント設定が有効になる言語のリストを示します。これがないと、Markdownファイルにブレークポイントを設定することはできません。

次は**debuggers**セクションです。ここでは、デバッグ**タイプ** `mock` の下に1つのデバッガーが導入されています。ユーザーはこのタイプを起動構成で参照できます。オプションの属性**label**は、UIに表示する際にデバッグタイプにわかりやすい名前を付けるために使用できます。

デバッグ拡張機能はデバッグアダプターを使用するため、そのコードへの相対パスが**program**属性として指定されます。拡張機能を自己完結型にするために、アプリケーションは拡張機能フォルダー内に存在する必要があります。慣例として、このアプリケーションは`out`または`bin`という名前のフォルダー内に保持しますが、別の名前を使用しても構いません。

VS Code は異なるプラットフォームで実行されるため、DA プログラムが異なるプラットフォームもサポートしていることを確認する必要があります。これには次のオプションがあります。

  1. プログラムがプラットフォーム非依存の方法で実装されている場合(例:すべてのサポート対象プラットフォームで利用可能なランタイム上で実行されるプログラム)、**runtime**属性を介してこのランタイムを指定できます。現在、VS Code は `node` および `mono` ランタイムをサポートしています。上記の Mock デバッグアダプターはこのアプローチを使用しています。

  2. DA の実装がプラットフォームごとに異なる実行可能ファイルを必要とする場合、**program** 属性は次のように特定のプラットフォームに対して修飾できます。

    "debuggers": [{
        "type": "gdb",
        "windows": {
            "program": "./bin/gdbDebug.exe",
        },
        "osx": {
            "program": "./bin/gdbDebug.sh",
        },
        "linux": {
            "program": "./bin/gdbDebug.sh",
        }
    }]
    
  3. 両方のアプローチを組み合わせることも可能です。以下の例は、macOSとLinuxではランタイムが必要ですが、Windowsでは不要なモノラルアプリケーションとして実装されたMono DAのものです。

    "debuggers": [{
        "type": "mono",
        "program": "./bin/monoDebug.exe",
        "osx": {
            "runtime": "mono"
        },
        "linux": {
            "runtime": "mono"
        }
    }]
    

**configurationAttributes**は、このデバッガーで利用可能な`launch.json`属性のスキーマを宣言します。このスキーマは、`launch.json`の検証と、起動構成を編集する際のIntelliSenseとホバーヘルプのサポートに使用されます。

**initialConfigurations**は、このデバッガーのデフォルトの`launch.json`の初期コンテンツを定義します。この情報は、プロジェクトに`launch.json`がなく、ユーザーがデバッグセッションを開始したり、実行とデバッグビューで**launch.jsonファイルを作成**リンクを選択したりした場合に使用されます。この場合、VS Codeはユーザーにデバッグ環境を選択させ、対応する`launch.json`を作成します。

Debugger Quickpick

`package.json`で`launch.json`の初期コンテンツを静的に定義する代わりに、`DebugConfigurationProvider`を実装することで初期構成を動的に計算することも可能です(詳細はUsing a DebugConfigurationProviderを参照)。

**configurationSnippets**は、`launch.json`の編集時にIntelliSenseで表示される起動構成スニペットを定義します。慣例として、多くのスニペット候補のリストで明確に識別できるよう、スニペットの`label`属性の前にデバッグ環境名を付けてください。

**variables**の貢献は、「変数」と「コマンド」を紐付けます。これらの変数は、**${command:xyz}**構文を使って起動構成で使用でき、デバッグセッションが開始されると、変数はバインドされたコマンドから返される値に置換されます。

コマンドの実装は拡張機能内にあり、UI を伴わない単純な式から、拡張機能 API で利用可能な UI 機能に基づいた洗練された機能まで多岐にわたります。Mock Debug は変数 `AskForProgramName` をコマンド `extension.mock-debug.getProgramName` にバインドします。`src/extension.ts` におけるこのコマンドの実装では、`showInputBox` を使用してユーザーにプログラム名の入力を促します。

vscode.commands.registerCommand('extension.mock-debug.getProgramName', config => {
  return vscode.window.showInputBox({
    placeHolder: 'Please enter the name of a markdown file in the workspace folder',
    value: 'readme.md'
  });
});

変数は、起動構成の文字列型の値であれば、**${command:AskForProgramName}** として使用できるようになりました。

DebugConfigurationProvider の使用

package.json におけるデバッグ貢献の静的な性質では不十分な場合、`DebugConfigurationProvider` を使用して、デバッグ拡張機能の以下の側面を動的に制御できます。

  • 新しく作成された launch.json の初期デバッグ構成は、ワークスペースで利用可能なコンテキスト情報などに基づいて動的に生成できます。
  • 起動構成は、新しいデバッグセッションを開始する前に*解決*(または変更)できます。これにより、ワークスペースで利用可能な情報に基づいてデフォルト値を入力できます。2つの*解決*メソッドが存在します。`resolveDebugConfiguration`は、変数が起動構成に置換される前に呼び出され、`resolveDebugConfigurationWithSubstitutedVariables`は、すべての変数が置換された後に呼び出されます。前者は、検証ロジックがデバッグ構成に追加の変数を挿入する場合に使用する必要があります。後者は、検証ロジックがすべてのデバッグ構成属性の最終値にアクセスする必要がある場合に使用する必要があります。

`src/extension.ts` の `MockConfigurationProvider` は `resolveDebugConfiguration` を実装して、launch.json が存在しないが、アクティブなエディターで Markdown ファイルが開かれている場合にデバッグセッションが開始されるケースを検出します。これは、ユーザーがエディターでファイルを開いていて、launch.json を作成せずにデバッグしたいという典型的なシナリオです。

デバッグ構成プロバイダーは、通常、拡張機能の`activate`関数で`vscode.debug.registerDebugConfigurationProvider`を介して、特定のデバッグタイプに登録されます。`DebugConfigurationProvider`が十分に早く登録されるように、デバッグ機能が使用されるとすぐに拡張機能がアクティブ化される必要があります。これは、`package.json`で`onDebug`イベントの拡張機能アクティベーションを設定することで簡単に実現できます。

"activationEvents": [
    "onDebug",
    // ...
],

このキャッチオール`onDebug`は、任意のデバッグ機能が使用されるとすぐにトリガーされます。これは、拡張機能の起動コストが安い限り(つまり、起動シーケンスに多くの時間を費やさない限り)問題なく動作します。デバッグ拡張機能の起動にコストがかかる場合(例えば、言語サーバーを起動するためなど)、`onDebug`アクティベーションイベントは、かなり早い段階でトリガーされ、特定のデバッグタイプを考慮しないため、他のデバッグ拡張機能に悪影響を与える可能性があります。

コストのかかるデバッグ拡張機能の場合、より粒度の細かいアクティベーションイベントを使用する方が良いアプローチです。

  • onDebugInitialConfigurations は、DebugConfigurationProviderprovideDebugConfigurations メソッドが呼び出される直前に発生します。
  • onDebugResolve:type は、指定された型の DebugConfigurationProviderresolveDebugConfiguration または resolveDebugConfigurationWithSubstitutedVariables メソッドが呼び出される直前に発生します。

**経験則:** デバッグ拡張機能の起動が低コストであれば、`onDebug`を使用します。高コストであれば、`DebugConfigurationProvider`が対応するメソッド`provideDebugConfigurations`および/または`resolveDebugConfiguration`を実装しているかどうかに応じて、`onDebugInitialConfigurations`および/または`onDebugResolve`を使用します。

デバッガー拡張機能の公開

デバッガー拡張機能を作成したら、マーケットプレイスに公開できます。

  • `package.json` の属性を、デバッガー拡張機能の名前と目的に合わせて更新します。
  • Publishing Extension で説明されているように、Marketplace にアップロードします。

デバッガー拡張機能の開発への代替アプローチ

これまでに見てきたように、デバッガー拡張機能の開発では、通常、拡張機能とデバッグアダプターの両方を2つの並行セッションでデバッグする必要があります。上記で説明したように、VS Code はこれをうまくサポートしていますが、拡張機能とデバッグアダプターの両方が1つのプログラムであり、1つのデバッグセッションでデバッグできる場合、開発はより簡単になります。

このアプローチは、デバッグアダプターがTypeScript/JavaScriptで実装されている限り、実際には簡単に実現できます。基本的な考え方は、デバッグアダプターを拡張機能内で直接実行し、VS Codeがセッションごとに新しい外部デバッグアダプターを起動するのではなく、それに接続するようにすることです。

このため、VS Codeはデバッグアダプターがどのように作成され、実行されるかを制御するための拡張APIを提供しています。`DebugAdapterDescriptorFactory`には、デバッグセッションが開始され、デバッグアダプターが必要になったときにVS Codeによって呼び出される`createDebugAdapterDescriptor`メソッドがあります。このメソッドは、デバッグアダプターがどのように実行されるかを記述する記述子オブジェクト(`DebugAdapterDescriptor`)を返す必要があります。

現在、VS Code はデバッグアダプターを実行する3つの異なる方法をサポートしており、結果として3つの異なる記述子型を提供しています。

  • DebugAdapterExecutable: このオブジェクトは、パスとオプションの引数、ランタイムを持つ外部実行可能ファイルとしてのデバッグアダプターを記述します。実行可能ファイルは、デバッグアダプタープロトコルを実装し、stdin/stdout を介して通信する必要があります。これは VS Code のデフォルトの動作モードであり、DebugAdapterDescriptorFactory が明示的に登録されていない場合、VS Code は package.json の対応する値を使用してこの記述子を自動的に使用します。
  • DebugAdapterServer: このオブジェクトは、特定のローカルまたはリモートポートを介して通信するサーバーとして実行されるデバッグアダプターを記述します。vscode-debugadapter npm モジュールに基づくデバッグアダプター実装は、このサーバーモードを自動的にサポートします。
  • DebugAdapterInlineImplementation: このオブジェクトは、vscode.DebugAdapter インターフェースを実装する JavaScript または Typescript オブジェクトとしてのデバッグアダプターを記述します。`vscode-debugadapter` npm モジュールのバージョン 1.38-pre.4 以降に基づくデバッグアダプター実装は、インターフェースを自動的に実装します。

Mock Debugは、3種類のDebugAdapterDescriptorFactoriesの例と、それらが「mock」デバッグタイプにどのように登録されているかを示しています。使用する実行モードは、グローバル変数`runMode`を`external`、`server`、`inline`のいずれかの可能な値に設定することで選択できます。

開発においては、`inline` モードと `server` モードは、拡張機能とデバッグアダプターを単一のプロセス内でデバッグできるため、特に便利です。