Task Provider
Task providers allow you to create custom build tasks, test runners, and automation workflows that integrate seamlessly with VS Code’s task system. Users can run your tasks from the command palette, configure them in tasks.json, or bind them to keyboard shortcuts.Overview
Tasks in VS Code represent background processes or scripts that can be executed, monitored, and managed by the editor. Task providers dynamically contribute tasks to the system.When to Use Task Providers
- Custom build systems and compilers
- Test runners and automation
- Code generation and scaffolding
- Deployment and CI/CD workflows
- Custom linting and validation
Task Provider Interface
TheTaskProvider interface defines how tasks are discovered and resolved:
interface TaskProvider<T extends Task = Task> {
// Provide all available tasks
provideTasks(token: CancellationToken): ProviderResult<T[]>;
// Resolve tasks from tasks.json
resolveTask(task: T, token: CancellationToken): ProviderResult<T>;
}
Basic Implementation
Define task type
Create a task definition in package.json:
{
"contributes": {
"taskDefinitions": [
{
"type": "myBuild",
"required": ["target"],
"properties": {
"target": {
"type": "string",
"description": "The build target (development, production)"
},
"optimize": {
"type": "boolean",
"description": "Enable optimization"
}
}
}
]
}
}
Create the provider
Implement the
TaskProvider interface:import * as vscode from 'vscode';
interface MyTaskDefinition extends vscode.TaskDefinition {
target: string;
optimize?: boolean;
}
class MyBuildTaskProvider implements vscode.TaskProvider {
static readonly Type = 'myBuild';
provideTasks(token: vscode.CancellationToken): vscode.Task[] {
const tasks: vscode.Task[] = [];
// Create tasks for different targets
const targets = ['development', 'production'];
for (const target of targets) {
const definition: MyTaskDefinition = {
type: MyBuildTaskProvider.Type,
target
};
// Create shell execution
const execution = new vscode.ShellExecution(
`npm run build:${target}`
);
// Create the task
const task = new vscode.Task(
definition,
vscode.TaskScope.Workspace,
`Build ${target}`,
MyBuildTaskProvider.Type,
execution,
['$eslint-stylish'] // Problem matcher
);
// Set task properties
task.group = vscode.TaskGroup.Build;
task.presentationOptions = {
reveal: vscode.TaskRevealKind.Always,
panel: vscode.TaskPanelKind.Dedicated
};
tasks.push(task);
}
return tasks;
}
resolveTask(task: vscode.Task, token: vscode.CancellationToken): vscode.Task | undefined {
const definition = task.definition as MyTaskDefinition;
if (definition.target) {
// Create execution for this task
const execution = new vscode.ShellExecution(
`npm run build:${definition.target}`
);
return new vscode.Task(
definition,
vscode.TaskScope.Workspace,
task.name,
MyBuildTaskProvider.Type,
execution,
['$eslint-stylish']
);
}
return undefined;
}
}
Register the provider
Register your task provider in the extension’s activate function:
export function activate(context: vscode.ExtensionContext) {
const provider = new MyBuildTaskProvider();
const disposable = vscode.tasks.registerTaskProvider(
MyBuildTaskProvider.Type,
provider
);
context.subscriptions.push(disposable);
}
Task Execution Types
VS Code supports three types of task executions:Shell Execution
Run commands in a shell:// Simple command
const execution = new vscode.ShellExecution('npm run build');
// With arguments
const execution = new vscode.ShellExecution('npm', ['run', 'build', '--production']);
// With options
const execution = new vscode.ShellExecution(
'npm',
['run', 'build'],
{
cwd: '/path/to/project',
env: { NODE_ENV: 'production' }
}
);
Process Execution
Run a process directly without shell:const execution = new vscode.ProcessExecution(
'node',
['build.js', '--mode', 'production'],
{
cwd: workspaceFolder.uri.fsPath,
env: {
NODE_ENV: 'production',
PATH: process.env.PATH
}
}
);
Custom Execution
Implement custom execution logic with full control:class MyCustomExecution implements vscode.CustomExecution {
constructor(private config: MyTaskDefinition) {}
async execute(
taskDefinition: vscode.TaskDefinition,
terminalDimensions: vscode.TerminalDimensions | undefined,
token: vscode.CancellationToken
): Promise<number> {
// Return a Pseudoterminal
return new MyPseudoterminal(this.config);
}
}
class MyPseudoterminal implements vscode.Pseudoterminal {
private writeEmitter = new vscode.EventEmitter<string>();
onDidWrite = this.writeEmitter.event;
private closeEmitter = new vscode.EventEmitter<number>();
onDidClose = this.closeEmitter.event;
constructor(private config: MyTaskDefinition) {}
open(initialDimensions: vscode.TerminalDimensions | undefined): void {
this.writeEmitter.fire('Starting custom task...\r\n');
this.runTask();
}
close(): void {
// Cleanup
}
private async runTask(): Promise<void> {
try {
// Do custom work
this.writeEmitter.fire('Processing...\r\n');
await this.processFiles();
this.writeEmitter.fire('Task completed successfully!\r\n');
this.closeEmitter.fire(0); // Exit code 0 = success
} catch (error) {
this.writeEmitter.fire(`Error: ${error}\r\n`);
this.closeEmitter.fire(1); // Exit code 1 = error
}
}
private async processFiles(): Promise<void> {
// Custom processing logic
}
}
// Use custom execution
const execution = new vscode.CustomExecution(
async () => new MyCustomExecution(definition)
);
Task Configuration
Task Groups
Organize tasks into predefined groups:const buildTask = new vscode.Task(/* ... */);
buildTask.group = vscode.TaskGroup.Build;
const testTask = new vscode.Task(/* ... */);
testTask.group = vscode.TaskGroup.Test;
const cleanTask = new vscode.Task(/* ... */);
cleanTask.group = vscode.TaskGroup.Clean;
// Make it the default task for its group
buildTask.group = { kind: vscode.TaskGroup.Build, isDefault: true };
Presentation Options
Control how the task output is displayed:const task = new vscode.Task(/* ... */);
task.presentationOptions = {
// When to reveal the terminal
reveal: vscode.TaskRevealKind.Always, // Always, Never, Silent
// Terminal reuse
panel: vscode.TaskPanelKind.Dedicated, // Shared, Dedicated, New
// Focus behavior
focus: true, // Focus terminal on task start
// Terminal visibility
echo: true, // Echo the command in terminal
// Show task in task list
showReuseMessage: false,
// Clear terminal before running
clear: true
};
Problem Matchers
Parse task output to create problems in the Problems panel:const task = new vscode.Task(
definition,
scope,
name,
source,
execution,
['$tsc', '$eslint-stylish'] // Built-in problem matchers
);
Custom Problem Matcher
Define in package.json:{
"contributes": {
"problemMatchers": [
{
"name": "myCompiler",
"owner": "myExtension",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
]
}
}
Monitoring Tasks
Listen to task events:export function activate(context: vscode.ExtensionContext) {
// Task started
context.subscriptions.push(
vscode.tasks.onDidStartTask(event => {
const task = event.execution.task;
vscode.window.showInformationMessage(`Task started: ${task.name}`);
})
);
// Task ended
context.subscriptions.push(
vscode.tasks.onDidEndTask(event => {
const task = event.execution.task;
vscode.window.showInformationMessage(`Task ended: ${task.name}`);
})
);
// Process started
context.subscriptions.push(
vscode.tasks.onDidStartTaskProcess(event => {
console.log(`Process ${event.processId} started`);
})
);
// Process ended
context.subscriptions.push(
vscode.tasks.onDidEndTaskProcess(event => {
const exitCode = event.exitCode;
if (exitCode !== 0) {
vscode.window.showErrorMessage(`Task failed with code ${exitCode}`);
}
})
);
}
Complete Example: Test Runner
Here’s a complete example of a test runner task provider:import * as vscode from 'vscode';
import * as path from 'path';
interface TestTaskDefinition extends vscode.TaskDefinition {
file?: string;
testName?: string;
coverage?: boolean;
}
class TestTaskProvider implements vscode.TaskProvider {
static readonly Type = 'mytest';
provideTasks(token: vscode.CancellationToken): vscode.Task[] {
const tasks: vscode.Task[] = [];
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
return tasks;
}
// Task: Run all tests
tasks.push(this.createTask(
{ type: TestTaskProvider.Type },
'Run All Tests',
'npm test',
workspaceFolder
));
// Task: Run with coverage
tasks.push(this.createTask(
{ type: TestTaskProvider.Type, coverage: true },
'Run Tests with Coverage',
'npm run test:coverage',
workspaceFolder
));
// Task: Watch mode
tasks.push(this.createTask(
{ type: TestTaskProvider.Type },
'Watch Tests',
'npm run test:watch',
workspaceFolder,
true // isBackground
));
return tasks;
}
resolveTask(task: vscode.Task, token: vscode.CancellationToken): vscode.Task | undefined {
const definition = task.definition as TestTaskDefinition;
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
return undefined;
}
let command = 'npm test';
if (definition.file) {
command = `npm test -- ${definition.file}`;
}
if (definition.testName) {
command += ` -t "${definition.testName}"`;
}
if (definition.coverage) {
command = 'npm run test:coverage';
}
return this.createTask(definition, task.name, command, workspaceFolder);
}
private createTask(
definition: TestTaskDefinition,
name: string,
command: string,
workspaceFolder: vscode.WorkspaceFolder,
isBackground = false
): vscode.Task {
const execution = new vscode.ShellExecution(command, {
cwd: workspaceFolder.uri.fsPath
});
const task = new vscode.Task(
definition,
workspaceFolder,
name,
TestTaskProvider.Type,
execution,
'$jest-test' // Problem matcher for Jest
);
task.group = vscode.TaskGroup.Test;
task.presentationOptions = {
reveal: vscode.TaskRevealKind.Always,
panel: vscode.TaskPanelKind.Dedicated,
clear: true
};
if (isBackground) {
task.isBackground = true;
task.problemMatchers = [{
owner: 'mytest',
pattern: {
regexp: '^$'
},
background: {
activeOnStart: true,
beginsPattern: 'Test run started',
endsPattern: 'Test run completed'
}
}];
}
return task;
}
}
export function activate(context: vscode.ExtensionContext) {
const provider = new TestTaskProvider();
context.subscriptions.push(
vscode.tasks.registerTaskProvider(TestTaskProvider.Type, provider)
);
// Command to run test for current file
context.subscriptions.push(
vscode.commands.registerCommand('myExtension.runTestFile', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const fileName = path.basename(editor.document.fileName);
const definition: TestTaskDefinition = {
type: TestTaskProvider.Type,
file: fileName
};
const task = new vscode.Task(
definition,
vscode.TaskScope.Workspace,
`Test ${fileName}`,
TestTaskProvider.Type,
new vscode.ShellExecution(`npm test -- ${fileName}`)
);
await vscode.tasks.executeTask(task);
})
);
}
Best Practices
Error Handling
provideTasks(token: vscode.CancellationToken): vscode.Task[] {
try {
// Check if required tools are available
if (!this.isToolInstalled()) {
vscode.window.showWarningMessage(
'Build tool not found. Please install it first.'
);
return [];
}
// Check cancellation
if (token.isCancellationRequested) {
return [];
}
return this.discoverTasks();
} catch (error) {
vscode.window.showErrorMessage(
`Failed to provide tasks: ${error}`
);
return [];
}
}
Task Caching
class CachedTaskProvider implements vscode.TaskProvider {
private taskCache: vscode.Task[] | undefined;
provideTasks(token: vscode.CancellationToken): vscode.Task[] {
if (this.taskCache) {
return this.taskCache;
}
this.taskCache = this.discoverTasks();
// Clear cache on configuration change
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('myExtension')) {
this.taskCache = undefined;
}
});
return this.taskCache;
}
}
Resources
Task Provider Guide
Complete task provider documentation
Task Sample
Working example code