工作提供者
使用者通常在 Visual Studio Code 的 tasks.json 檔案中定義工作 (tasks)。然而,在軟體開發過程中,有些工作可以由具備工作提供者 (Task Provider) 的 VS Code 擴充功能自動偵測。當從 VS Code 執行 Tasks: Run Task (執行工作) 命令時,所有啟用的工作提供者都會貢獻出使用者可以執行的工作。雖然 tasks.json 檔案讓使用者能針對特定資料夾或工作區手動定義工作,但工作提供者可以偵測工作區的詳細資訊,進而自動建立對應的 VS Code 工作。例如,工作提供者可以檢查是否有特定的建置檔案(如 make 或 Rakefile),並建立建置工作。本主題將說明擴充功能如何自動偵測並提供工作給終端使用者。
本指南將教您如何建立一個能自動偵測 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 工作貢獻了一個工作定義。該工作定義具有兩個屬性:task 和 file。task 是 Rake 工作的名稱,而 file 則指向包含該工作的 Rakefile。task 屬性是必要的,file 屬性則是選用的。如果省略 file 屬性,則會使用工作區根目錄中的 Rakefile。
When 子句
工作定義可以選擇性地包含一個 when 屬性。when 屬性指定了該類型工作在何種條件下可用。when 屬性的運作方式與 VS Code 中其他具備 when 屬性的地方相同。建立工作定義時,應始終考慮以下上下文:
shellExecutionSupported:當 VS Code 可以執行ShellExecution工作時為 True,例如當 VS Code 作為桌面應用程式執行,或使用遠端擴充功能(如 Dev Containers)時。processExecutionSupported:當 VS Code 可以執行ProcessExecution工作時為 True,例如當 VS Code 作為桌面應用程式執行,或使用遠端擴充功能(如 Dev Containers)時。目前,它的值始終與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 呼叫以從擴充功能獲取工作。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.ProcessExecution。ProcessExecution 的優點在於擴充功能可以完全控制傳遞給處理程序的參數。使用 ShellExecution 則會利用 Shell 命令直譯(例如 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。