尝试以扩展 VS Code 中的代理模式!

任务提供程序

用户通常在 Visual Studio Code 中通过 tasks.json 文件定义任务。然而,在软件开发过程中,有些任务可以由 VS Code 扩展通过任务提供程序自动检测。当从 VS Code 运行 任务:运行任务 命令时,所有活动任务提供程序都会贡献用户可以运行的任务。虽然 tasks.json 文件允许用户为特定文件夹或工作区手动定义任务,但任务提供程序可以检测工作区的详细信息,然后自动创建相应的 VS Code 任务。例如,任务提供程序可以检查是否存在特定的构建文件(例如 makeRakefile),并创建一个构建任务。本主题描述扩展如何自动检测并向最终用户提供任务。

本指南教您如何构建一个任务提供程序,该程序自动检测在 Rakefiles 中定义的任务。完整的源代码位于: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 任务贡献了一个任务定义。任务定义有两个属性 taskfiletask 是 Rake 任务的名称,file 指向包含任务的 Rakefiletask 属性是必需的,file 属性是可选的。如果省略 file 属性,则使用工作区根目录中的 Rakefile

When 子句

任务定义可以选择性地具有 when 属性。when 属性指定此类型的任务可用的条件。when 属性的函数方式与 VS Code 中其他具有 when 属性的位置相同。在创建任务定义时,应始终考虑以下上下文

  • shellExecutionSupported:当 VS Code 可以运行 ShellExecution 任务时为 True,例如当 VS Code 作为桌面应用程序运行或使用远程扩展(例如开发容器)时。
  • processExecutionSupported:当 VS Code 可以运行 ProcessExecution 任务时为 True,例如当 VS Code 作为桌面应用程序运行或使用远程扩展(例如开发容器)时。目前,它的值将始终与 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 一样,VS Code 调用 resolveTask 方法从扩展获取任务。resolveTask 可以代替 provideTasks 调用,旨在为实现它的提供程序提供可选的性能提升。例如,如果用户有一个运行扩展提供任务的键绑定,那么 VS Code 最好为该任务提供程序调用 resolveTask 并快速获取单个任务,而不是必须调用 provideTasks 并等待扩展提供其所有任务。最好有一个设置允许用户关闭单个任务提供程序,所以这很常见。用户可能会注意到来自特定提供程序的任务获取速度较慢,并关闭该提供程序。在这种情况下,用户可能仍会在其 tasks.json 中引用来自此提供程序的一些任务。如果未实现 resolveTask,则会发出警告,指出 tasks.json 中定义的任务未创建。通过 resolveTask,扩展仍然可以为 tasks.json 中定义的任务提供任务。

getRakeTasks 实现执行以下操作

  • 使用 rake -AT -f Rakefile 命令列出每个工作区文件夹中 Rakefile 中定义的所有 rake 任务。
  • 解析 stdio 输出。
  • 对于列出的每个任务,创建一个 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 在特定于操作系统的 shell 中执行 rake compile 命令(例如,在 Windows 下,命令将在 PowerShell 中执行;在 Ubuntu 下,它将在 bash 中执行)。如果任务应该直接执行进程(不生成 shell),则可以使用 vscode.ProcessExecutionProcessExecution 的优点是扩展可以完全控制传递给进程的参数。使用 ShellExecution 可以利用 shell 命令解释(例如 bash 中的通配符扩展)。如果使用单个命令行创建 ShellExecution,则扩展需要确保命令中正确的引用和转义(例如处理空格)。

CustomExecution

通常,最好使用 ShellExecutionProcessExecution,因为它们很简单。但是,如果您的任务需要在运行之间保存大量状态,不适合作为单独的脚本或进程运行,或者需要对输出进行大量处理,那么 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