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から始めます。これは、実際のデバッガーと通信するのではなく、モック化するため「モックデバッグ」と呼ばれています。モックデバッグはデバッガーをシミュレートし、ステップ、続行、ブレークポイント、例外、変数アクセスをサポートしますが、実際のデバッガーには接続されていません。

mock-debug の開発環境セットアップに入る前に、まず VS Code Marketplace からビルド済みのバージョンをインストールして試してみましょう。

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

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

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

パッケージの中身は?

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

次に、Extension 起動構成を選択し、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 は現在、マルチセッションモードに入っています。これは、呼び出しスタックビューに2つのデバッグセッション「Extension」と「Server」の名前が表示されることで示されます。

Debugging Extension and Server

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

拡張機能とDAをデバッグするための別の、より簡単なアプローチは、以下で見つけることができます。

ファイル src/mockDebug.ts のメソッド launchRequest(...) の冒頭にブレークポイントを設定し、最後のステップとして、Mock Debug の起動構成にポート 4711debugServer 属性を追加して、モックデバッガーが 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.tssrc/mockRuntime.ts のモック実装を、「本物の」デバッガーまたはランタイムと通信するコードに置き換える必要があります。これには、デバッグアダプタープロトコルの理解と実装が伴います。これに関する詳細については、こちらを参照してください。

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

デバッグアダプターのデバッガー固有の実装を提供するだけでなく、デバッガー拡張機能は、様々なデバッグ関連のコントリビューションポイントに貢献する package.json を必要とします。

それでは、Mock Debug の package.json を詳しく見ていきましょう。

すべての VS Code 拡張機能と同様に、package.json は拡張機能の基本プロパティである名前発行元、およびバージョンを宣言します。categories フィールドを使用して、VS Code 拡張機能 Marketplace で拡張機能をより簡単に見つけられるようにします。

{
  "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 アプリケーションとして実装されている 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

launch.json の初期内容を package.json に静的に定義する代わりに、DebugConfigurationProvider を実装することで、初期構成を動的に計算することが可能です(詳細については、「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.tsMockConfigurationProvider は、launch.json が存在しないが、アクティブなエディターで Markdown ファイルが開いている場合にデバッグセッションが開始されるケースを検出するために resolveDebugConfiguration を実装しています。これは、ユーザーがエディターでファイルを開いており、launch.json を作成せずにそれをデバッグしたいという典型的なシナリオです。

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

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

この包括的な onDebug は、デバッグ機能が使用されるとすぐにトリガーされます。これは、拡張機能の起動コストが低い(つまり、起動シーケンスに多くの時間を費やさない)限り、うまく機能します。デバッグ拡張機能の起動にコストがかかる場合(例えば、言語サーバーを起動するため)、onDebug アクティベーションイベントは、比較的早くトリガーされ、特定のデバッグタイプを考慮しないため、他のデバッグ拡張機能に悪影響を与える可能性があります。

コストのかかるデバッグ拡張機能のより良いアプローチは、よりきめ細かなアクティベーションイベントを使用することです。

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

経験則: デバッグ拡張機能のアクティベーションが安価な場合は、onDebug を使用します。高価な場合は、DebugConfigurationProvider が対応するメソッド provideDebugConfigurations および/または resolveDebugConfiguration を実装しているかどうかに応じて、onDebugInitialConfigurations および/または onDebugResolve を使用します。

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

デバッガー拡張機能を作成したら、Marketplace に公開できます。

  • package.json 内の属性を、デバッガー拡張機能の名前と目的に合わせて更新します。
  • 「拡張機能の公開」に記載されているように、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」デバッグタイプに登録される方法を示しています。使用する実行モードは、グローバル変数 runModeexternalserver、または inline のいずれかの値に設定することで選択できます。

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