2.4 Task插件

Task是一个可以用于定制处理流程的特性,特别是可以在不用写代码就能完成定制,可以完成类似Makefile的功能。本节我们将尝试如果通过插件编程来扩展Task的功能。

2.4.1 MnBook插件定制

VS Code针对常用的Go、Node.js、Ruby、TypeScript等语言都定制了配套的Task。特别是工作区对应某种类型的语言时,内置的插件系统会自动注册相应的Task。我们也可以通过Task插件给MnBook也实现类似的功能。

最终效果是对应一组MnBook的Task,如图所示:

点击后可以看到mnbook类型的Task下有build、preview和clean三个功能:

因为是新的类型,也可以在.vscode/tasks.json配置:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "mnbook: build",
            "type": "mnbook",
            "task": "build"
        }
    ]
}

这是用户视角的MnBook插件的效果。

2.4.2 package.json增加Task规范

要定义新的mnbook插件类型需要下定义其规范,对应的task有哪些功能和属性等。contributes部分如下:

{
  // 基础配置和其他插件类似
  "contributes": {
    "taskDefinitions": [
      {
        "type": "mnbook",
        "required": [ "task" ],
        "properties": {
          "task": {
            "type": "string",
            "description": "The mnbook task",
            "examples": ["build", "preview", "clean"]
          }
        }
      }
    ]
  }
}

在贡献点的taskDefinitions属性增加了mnbook新类型(依赖基础的task):其中有一个task属性,有build、preview和clean三个功能。

另外在activationEvents配置插件激活的事件:

{
  "activationEvents": [
    "onCommand:workbench.action.tasks.runTask"
  ]
}

当运行Task命令时插件被激活。

2.4.3 插件注册和销毁代码

插件的入口和销毁函数实现如下:

const vscode = require('vscode');
const pkg = require("./mnbookTaskProvider");

function activate(context /** @param {vscode.ExtensionContext} */) {
    const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath;

    mnbookTaskProvider = vscode.tasks.registerTaskProvider(
        pkg.MnbookTaskProvider.MnbookType,
        new pkg.MnbookTaskProvider(workspaceRoot)
    );
}

function deactivate() {
    if (mnbookTaskProvider) {
        mnbookTaskProvider.dispose();
    }
}

activate函数先获取工作区路径,然后通过vscode.tasks.registerTaskProvider注册新的插件类型。新的插件动力由MnbookTaskProvider类实现,在mnbookTaskProvider.js文件定义:

class MnbookTaskProvider {
    static MnbookType = 'mnbook';

    /** @type {string} */
    workspaceRoot = undefined;

    /** @type {Thenable<vscode.Task[]> | undefined} */
    mnbookPromise = undefined;

    constructor(workspaceRoot /** @param {string} */) {
        this.workspaceRoot = workspaceRoot;
    }

    provideTasks() {
        if(!this.mnbookPromise) {
            this.mnbookPromise = this.getTasks();
        }
        return this.mnbookPromise;
    }
    resolveTask(_task) {
        return undefined;
    }
}

Task的Provider实现必须提供provideTasksresolveTask两个方法,分别用于构造和修复用户要执行的Task。查看provideTasks实现可以看到this.mnbookPromise记录全部的task对象,由this.getTasks()方法初始化。

getTasks方法实现如下:

    getTasks() {
        const buildTask = new vscode.Task(
            {type: 'mnbook', task: 'build'}, // kind
            vscode.TaskScope.Workspace,      // scope
            'build',                         // name
            'mnbook',                        // source
            new vscode.ShellExecution(`mnbook build`), // execution
            `mnbook_build`
        );

        const previewTask = new vscode.Task(...);
        const cleanTask= new vscode.Task(...);

        return [buildTask, previewTask, cleanTask];
    }

定义好每个task,然后作为列表返回。每个task可以绑定执行的命令,比如vscode.ShellExecution("mnbook build")等价于执行一个mnbook build命令(也可以自定义扩展命令)。

2.4.4 插件输出信息

如果在使用Task的过程中输出一些信息,可以通过vscode.OutputChannel功能实现。比如:

/**@type {vscode.OutputChannel} */
let _channel = null;

/** @return {vscode.OutputChannel} */
function getOutputChannel()  {
    if (!_channel) {
        _channel = vscode.window.createOutputChannel('Mnbook Task Provider');
    }
    return _channel;
}

function activate(context /** @param {vscode.ExtensionContext} */) {
    const workspaceRoot =
        vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0
        ? vscode.workspace.workspaceFolders[0].uri.fsPath
        : undefined;
    if (!workspaceRoot) {
        getOutputChannel().appendLine('Mnbook task provider requires a workspace root.');
        getOutputChannel().show(true);
        return;
    }
    ...
}

如果缺少工作区就不注册Task类型,并输出提示信息。

2.4.5 插件感知

最好的插件是不用的时候感觉不到它的存在,在需要的时候自然就出现了。可以在插件代码中识别工作区的某些特征,然后针对性提供功能。另外在不同的操作系统环境,也可以提供更本地化的功能。