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

タスクプロバイダー

ユーザーは通常、Visual Studio Code でタスクtasks.jsonファイルに定義します。ただし、ソフトウェア開発中には、VS Code 拡張機能がタスクプロバイダーを使用して自動的に検出できるタスクがいくつかあります。VS Code からタスク: タスクの実行コマンドを実行すると、すべてのアクティブなタスクプロバイダーがユーザーが実行できるタスクを提供します。tasks.jsonファイルでは、ユーザーが特定のフォルダーまたはワークスペースに対してタスクを手動で定義できますが、タスクプロバイダーはワークスペースに関する詳細を検出し、対応するVS Codeタスクを自動的に作成できます。たとえば、タスクプロバイダーは、makeRakefileなどの特定のビルドファイルがあるかどうかを確認し、ビルドタスクを作成できます。このトピックでは、拡張機能がエンドユーザーにタスクを自動検出して提供する方法について説明します。

このガイドでは、Rakefileで定義されたタスクを自動検出するタスクプロバイダーを構築する方法を説明します。完全なソースコードは次の場所にあります: https://github.com/microsoft/vscode-extension-samples/tree/main/task-provider-sample

タスク定義

システムでタスクを一意に識別するために、タスクを提供する拡張機能は、タスクを識別するプロパティを定義する必要があります。Rakeの例では、タスク定義は次のようになります

"taskDefinitions": [
    {
        "type": "rake",
        "required": [
            "task"
        ],
        "properties": {
            "task": {
                "type": "string",
                "description": "The Rake task to customize"
            },
            "file": {
                "type": "string",
                "description": "The Rake file that provides the task. Can be omitted."
            }
        }
    }
]

これは、rakeタスクのタスク定義を提供します。タスク定義には、taskfileの2つの属性があります。taskはRakeタスクの名前で、fileはタスクを含むRakefileを指します。taskプロパティは必須で、fileプロパティはオプションです。file属性が省略された場合、ワークスペースフォルダーのルートにあるRakefileが使用されます。

When 句

タスク定義には、オプションでwhenプロパティを含めることができます。whenプロパティは、このタイプのタスクが利用可能になる条件を指定します。whenプロパティは、whenプロパティがあるVS Code の他の場所と同じように機能します。タスク定義を作成する際には、常に以下のコンテキストを考慮する必要があります

  • shellExecutionSupported: VS Code がデスクトップアプリケーションとして実行されている場合や、Dev Containers などのリモート拡張機能を使用している場合など、VS Code がShellExecutionタスクを実行できる場合に True。
  • processExecutionSupported: VS Code がデスクトップアプリケーションとして実行されている場合や、Dev Containers などのリモート拡張機能を使用している場合など、VS Code がProcessExecutionタスクを実行できる場合に True。現在、常にshellExecutionSupportedと同じ値になります。
  • customExecutionSupported: VS Code がCustomExecutionを実行できる場合に True。これは常に True です。

タスクプロバイダー

拡張機能がコード補完をサポートできるようにする言語プロバイダーと同様に、拡張機能はタスクプロバイダーを登録して、利用可能なすべてのタスクを計算できます。これは、次のコードスニペットに示すように、vscode.tasks名前空間を使用して行われます

import * as vscode from 'vscode';

let rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
const taskProvider = vscode.tasks.registerTaskProvider('rake', {
  provideTasks: () => {
    if (!rakePromise) {
      rakePromise = getRakeTasks();
    }
    return rakePromise;
  },
  resolveTask(_task: vscode.Task): vscode.Task | undefined {
    const task = _task.definition.task;
    // A Rake task consists of a task and an optional file as specified in RakeTaskDefinition
    // Make sure that this looks like a Rake task by checking that there is a task.
    if (task) {
      // resolveTask requires that the same definition object be used.
      const definition: RakeTaskDefinition = <any>_task.definition;
      return new vscode.Task(
        definition,
        _task.scope ?? vscode.TaskScope.Workspace,
        definition.task,
        'rake',
        new vscode.ShellExecution(`rake ${definition.task}`)
      );
    }
    return undefined;
  }
});

provideTasksと同様に、resolveTaskメソッドは、VS Codeによって拡張機能からタスクを取得するために呼び出されます。resolveTaskprovideTasksの代わりに呼び出すことができ、それを実装するプロバイダーのオプションのパフォーマンス向上を提供することを目的としています。たとえば、ユーザーが拡張機能によって提供されるタスクを実行するキーバインドを持っている場合、VS Codeがそのタスクプロバイダーに対してresolveTaskを呼び出して、1つのタスクを迅速に取得する方が、provideTasksを呼び出して拡張機能がすべてのタスクを提供するのを待つよりも良いでしょう。ユーザーが個々のタスクプロバイダーを無効にできる設定を持つことは良い習慣であり、これは一般的です。ユーザーは、特定のプロバイダーからのタスクの取得が遅いことに気づき、プロバイダーを無効にするかもしれません。この場合、ユーザーはtasks.jsonでこのプロバイダーからのタスクの一部を参照する可能性があります。resolveTaskが実装されていない場合、tasks.jsonで定義されたタスクが作成されなかったという警告が表示されます。resolveTaskを使用すると、拡張機能はtasks.jsonで定義されたタスクに対してタスクを提供できます。

getRakeTasksの実装は次のとおりです

  • 各ワークスペースフォルダーでrake -AT -f Rakefileコマンドを使用して、Rakefileに定義されているすべてのRakeタスクを一覧表示します。
  • 標準出力出力を解析します。
  • リストされたすべてのタスクに対して、vscode.Taskの実装を作成します。

Rakeタスクのインスタンス化には、package.jsonファイルで定義されているタスク定義が必要であるため、VS CodeはTypeScriptインターフェイスを使用して構造を次のように定義します

interface RakeTaskDefinition extends vscode.TaskDefinition {
  /**
   * The task name
   */
  task: string;

  /**
   * The rake file containing the task
   */
  file?: string;
}

最初のワークスペースフォルダーのcompileというタスクから出力が得られると仮定すると、対応するタスク作成は次のようになります

let task = new vscode.Task(
  { type: 'rake', task: 'compile' },
  vscode.workspace.workspaceFolders[0],
  'compile',
  'rake',
  new vscode.ShellExecution('rake compile')
);

出力にリストされているすべてのタスクに対して、上記のパターンを使用して対応するVS Codeタスクが作成され、getRakeTasks呼び出しからすべてのタスクの配列が返されます。

ShellExecutionは、OSに固有のシェルでrake compileコマンドを実行します(たとえば、WindowsではコマンドはPowerShellで実行され、Ubuntuではbashで実行されます)。タスクが直接プロセスを実行する必要がある場合(シェルを起動せずに)、vscode.ProcessExecutionを使用できます。ProcessExecutionは、拡張機能がプロセスに渡される引数を完全に制御できるという利点があります。ShellExecutionを使用すると、シェルコマンドの解釈(bashでのワイルドカード展開など)が利用されます。ShellExecutionが単一のコマンドラインで作成された場合、拡張機能はコマンド内の適切な引用符とエスケープ(たとえば、空白を処理するため)を確保する必要があります。

CustomExecution

一般に、ShellExecutionまたはProcessExecutionはシンプルなので、これらを使用するのが最適です。しかし、タスクが実行間で多くの保存された状態を必要とする場合、個別のスクリプトやプロセスとしてうまく機能しない場合、または出力の広範な処理を必要とする場合は、CustomExecutionが適しているかもしれません。CustomExecutionの既存の使用例は、通常、複雑なビルドシステム向けです。CustomExecutionには、タスクが実行されるときに実行されるコールバックのみがあります。これにより、タスクができることの柔軟性が高まりますが、タスクプロバイダーは、必要なプロセス管理と出力解析の責任も負うことになります。タスクプロバイダーは、Pseudoterminalを実装し、それをCustomExecutionコールバックから返す責任も負います。

return new vscode.Task(
  definition,
  vscode.TaskScope.Workspace,
  `${flavor} ${flags.join(' ')}`,
  CustomBuildTaskProvider.CustomBuildScriptType,
  new vscode.CustomExecution(
    async (): Promise<vscode.Pseudoterminal> => {
      // When the task is executed, this callback will run. Here, we setup for running the task.
      return new CustomBuildTaskTerminal(
        this.workspaceRoot,
        flavor,
        flags,
        () => this.sharedState,
        (state: string) => (this.sharedState = state)
      );
    }
  )
);

Pseudoterminalの実装を含む完全な例は、https://github.com/microsoft/vscode-extension-samples/tree/main/task-provider-sample/src/customTaskProvider.tsにあります。