[VSCode] Task作成の覚書

Visual Studio Codeで外部プログラムを実行するためにExtensionでタスクが提供することができるようになっている。ただ、公式のサンプル([Task Providerのサンプル](https://code.visualstudio.com/api/extension-guides/task-provider))は、タスク自体が高度過ぎてどこまでがタスクの本質か分かりにくく感じたので、実用的ではないが簡単なタスクの例で作り方をまとめる。

# タスクとは
Visual Studio Codeで、ビルドやデバッグなど何かを実行することはタスクと呼ばれ、`Run Task...`から都度実行に必要なパラメータを設定して実行したり、`Configure Task`で予め実行に必要なパラメータを設定したものを実行することができるようになっている。

プロジェクトだけで使用するタスクなら、「[Custom Tasks](https://code.visualstudio.com/docs/editor/tasks#_custom-tasks)」に載っている方法で以下のように定義できる。`Configure Task`から`Create task.json from template`を選択し、タイプを選択すると`task.json`が生成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/9145b0b5-107d-2a0f-434f-fc07b9e77a41.png)

```json:task.json
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo",
      "type": "shell",
      "command": "echo Hello"
    }
  ]
}
```
`task.json`で定義したタスクは、`Run Task...`で選択可能になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/fde74528-456d-4990-34ba-9109a05a936a.png)

実行しようとすると、タスクの実行結果(メッセージなど)をどうするかという確認が表示される。(この辺は未調査)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/d8b72993-a6c4-92f5-867a-5f1e78ff7abc.png)
実行するとechoの結果がコンソールに表示される。

# Extensionでタスクを提供するには
Extensionでは、以下のようにしてタスクを提供する。ここで「タスク」とはVisual Studio Codeが実際に何かを実行するために使う`vscode.Task`クラスのオブジェクトを指し、「タスク定義」は「タスク」を生成するための情報を指すものとする。タスク定義には、Visual Studio Codeがタスクを識別するための`type`や、タスクが受け取るパラメータ(型や説明)が含まれる。

1. `package.json`の`contributes`に「タスク定義」を登録する
1. `extension.ts`などで、`TaskProvider`を定義する。
 1. `provideTasks`: `Run Task...`で利用可能なタスクを取得するのに使われる。
 1. `resolveTask`: `Configure Task`で設定済みの「タスク定義」から「タスク」を生成するのに使われる。
1. `activate`時に`TaskProvidr`を登録する

## Task Providerの定義
`vscode.TaskProvider`を継承したクラスを定義する。Visual Studio Codeがタスクを必要とするときにコールするメソッド`provideTasks`と`resolveTask`を定義する。

```ts:extension.ts抜粋
import * as vscode from 'vscode';
const taskType = 'greeting';  // package.json の taskDefinitions で設定した type の値

// Discriminated Unions でミス防止(いるかどうか)
// https://basarat.gitbook.io/typescript/type-system/discriminated-unions
interface GreetingTaskDefinition extends vscode.TaskDefinition {
  type: typeof taskType;
  message: string;         // taskDefinitions の properties に対応するプロパティ
}

// Task Providerの定義
class GreetingTaskProvider implements vscode.TaskProvider {  
  provideTasks(): Thenable<vscode.Task[]> | undefined {
    // 利用可能なタスクを返す。
    // Run Task...で一覧表示する度にコールされる。
    // 毎回生成し直す必要が無ければ、一度作成したものを保持して、要求されたときに返しても良い。
    return generateTasks();
  }

  resolveTask( task: vscode.Task ): vscode.Task | undefined {
    // task.json で定義したタスク定義を受け取り、Visual Studio Codeが実行するタスクを作成する
    const definition: GreetingTaskDefinition = <any>task.definition;
    return createGreetingTask( definition );
  }
}
```
#### タスクの生成
`provideTasks`でも`resolveTask`でも同じような要領でタスクを作成するので、例えば以下のように「タスク定義」から「タスク」を生成する関数として切り出しておくと良いと思う。また、`resolveTask`ではタスク名の表示はないようなので、`name`を省略できるようにしても良いと思う。

```ts:extension.ts抜粋
function createGreetingTask( definition: GreetingTaskDefinition, name: string = '' ) {
  // Visual Studio Codeが実行するタスクの作成
  return new vscode.Task(
    definition,                       // 最低限タスクを識別するための type があれば良い
    vscode.TaskScope.Workspace,       // Global/Workspace...Globalは未対応の模様
    name,                             // name
    definition.type,                  // source
                                      // Run Taks...での表示を決める。表示は「source: name」となる。
    new vscode.ShellExecution( `echo ${definition.message} > greeting.txt` ) // 実行するコマンド
  );
}
```
`provideTasks`は利用可能なタスクを返すので、複数のタスクをまとめて生成する関数を作成しても良いと思う。
なお、ここでは`async`である必要性はないものの、`provideTasks`は要求があった時にすぐタスクを作れないことを想定し、`Promise<vscode.Task[]>`が戻り値となっているため、それに合わせた戻り値にしている。

```ts:extension.ts抜粋
async function generateTasks(): Promise<vscode.Task[]> {
  const now = Date.now().toString();
  const task: vscode.Task[] = [
    createGreetingTask( { type: taskType, message: 'Good morning.' }, 'morning' ),
    createGreetingTask( { type: taskType, message: 'Good evening.' }, 'evening' ),
    createGreetingTask( { type: taskType, message: now }, 'now' )
  ];
  return task;
}
```

### Task Providerの登録
これはお決まりのコード。

```ts:extension.ts抜粋
const disposables: vscode.Disposable[] = [];

export function activate(context: vscode.ExtensionContext) {
  // Task Providerの登録
  disposables.push( vscode.tasks.registerTaskProvider( taskType, new GreetingTaskProvider() ) );
}

export function deactivate() {
  disposables.forEach( disposable => disposable.dispose() );
}
```


# Extensionのタスクの利用
Extensionが提供するタスクは、以下ようにして実行する。

* `Run Task...`から選択する
* `Configure Task`で設定したものを実行する
* Extensionから実行する

例えば、`Configure Task`以下のように設定すると、

```task.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "greeting",
      "message": "Greeting from task.json",
      "label": "greeting: task.json"
    }
  ]
}
```
以下の一番下のように設定したタスクが表示される。実行すれば`Greeting from task.json`と書かれたテキストファイルが生成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/f122c4a8-a75f-6da5-1c9e-471957318e2d.png)

Extension内部から実行することもでき、例えば以下のようにすれば`command`から実行することができる。

```ts:extension.ts抜粋
vscode.commands.registerCommand( 'execute-task', () => {
  vscode.tasks.executeTask( createGreetingTask( { type: taskType, message: 'Greeting from command.' } ) );
}
```

# まとめ
Visual Studio Codeでは、外部のプログラムの実行をタスクとして提供できる。タスク(`vscode.Task`)は`TaskProvider`を`activate`時に登録することで、`Configure Task`で設定したり、`Run Task...`で実行したりできるようになる。また、`vscode.tasks.executeTask`により、Extension内から直接実行することもできる。

このページでは扱っていないが、stderrに出力されるエラーを整形したい場合は、[Defining a problem matcher](https://code.visualstudio.com/Docs/editor/tasks#_defining-a-problem-matcher)に載っているようにProblem Matcherを作ると良いらしい。