Add a sample for CustomExecution tasks

Add a sample for CustomExecution tasks
This commit is contained in:
Alex Ross
2019-08-06 15:23:43 +02:00
committed by GitHub
5 changed files with 1478 additions and 142 deletions

View File

@ -4,8 +4,9 @@
"description": "Samples for VSCode's view API",
"version": "0.0.1",
"publisher": "vscode-samples",
"enableProposedApi": "true",
"engines": {
"vscode": "^1.32.0"
"vscode": "^1.37.0"
},
"categories": [
"Other"
@ -31,6 +32,22 @@
"description": "The Rake file that provides the task. Can be omitted."
}
}
},
{
"type": "custombuildscript",
"required": [
"flavor"
],
"properties": {
"flavor": {
"type": "string",
"description": "The build flavor. Should be either '32' or '64'."
},
"flags": {
"type": "array",
"description": "Additional build flags."
}
}
}
]
},

View File

@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as vscode from 'vscode';
interface CustomBuildTaskDefinition extends vscode.TaskDefinition {
/**
* The build flavor. Should be either '32' or '64'.
*/
flavor: string;
/**
* Additional build flags
*/
flags?: string[];
}
export class CustomBuildTaskProvider implements vscode.TaskProvider {
static CustomBuildScriptType: string = 'custombuildscript';
private tasks: vscode.Task[] | undefined;
// We use a CustomExecution task when state needs to be shared accross runs of the task or when
// the task requires use of some VS Code API to run.
// If you don't need to share state between runs and if you don't need to execute VS Code API in your task,
// then a simple ShellExecution or ProcessExecution should be enough.
// Since our build has this shared state, the CustomExecution is used below.
private sharedState: string | undefined;
constructor(private workspaceRoot: string){}
public async provideTasks(): Promise<vscode.Task[]> {
return this.getTasks();
}
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
const flavor: string = _task.definition.flavor;
if (flavor) {
const definition: CustomBuildTaskDefinition = <any>_task.definition;
return this.getTask(definition.flavor, definition.flags ? definition.flags : [], definition);
}
return undefined;
}
private getTasks(): vscode.Task[] {
if (this.tasks !== undefined) {
return this.tasks;
}
// In our fictional build, we have two build flavors
const flavors: string[] = ['32', '64'];
// Each flavor can have some options.
const flags: string[][] = [['watch', 'incremental'], ['incremental'], []];
this.tasks = [];
flavors.forEach(flavor => {
flags.forEach(flagGroup => {
this.tasks!.push(this.getTask(flavor, flagGroup));
});
});
return this.tasks;
}
private getTask(flavor: string, flags: string[], definition?: CustomBuildTaskDefinition): vscode.Task{
if (definition === undefined) {
definition = {
type: CustomBuildTaskProvider.CustomBuildScriptType,
flavor,
flags
};
}
return new vscode.Task2(definition, vscode.TaskScope.Workspace, `${flavor} ${flags.join(' ')}`,
CustomBuildTaskProvider.CustomBuildScriptType, new vscode.CustomExecution2(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);
}));
}
}
class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
private writeEmitter = new vscode.EventEmitter<string>();
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
private closeEmitter = new vscode.EventEmitter<void>();
onDidClose?: vscode.Event<void> = this.closeEmitter.event;
private fileWatcher: vscode.FileSystemWatcher | undefined;
constructor(private workspaceRoot: string, private flavor: string, private flags: string[], private getSharedState: () => string | undefined, private setSharedState: (state: string) => void) {
}
open(initialDimensions: vscode.TerminalDimensions | undefined): void {
// At this point we can start using the terminal.
if (this.flags.indexOf('watch') > -1) {
let pattern = path.join(this.workspaceRoot, 'customBuildFile');
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
this.fileWatcher.onDidChange(() => this.doBuild());
this.fileWatcher.onDidCreate(() => this.doBuild());
this.fileWatcher.onDidDelete(() => this.doBuild());
}
this.doBuild();
}
close(): void {
// The terminal has been closed. Shutdown the build.
if (this.fileWatcher) {
this.fileWatcher.dispose();
}
}
private async doBuild(): Promise<void> {
return new Promise<void>((resolve) => {
this.writeEmitter.fire('Starting build...\r\n');
let isIncremental = this.flags.indexOf('incremental') > -1;
if (isIncremental) {
if (this.getSharedState()) {
this.writeEmitter.fire('Using last build results: ' + this.getSharedState() + '\r\n');
} else {
isIncremental = false;
this.writeEmitter.fire('No result from last build. Doing full build.\r\n');
}
}
// Since we don't actually build anything in this example set a timeout instead.
setTimeout(() => {
const date = new Date();
this.setSharedState(date.toTimeString() + ' ' + date.toDateString());
this.writeEmitter.fire('Build complete.\r\n\r\n');
if (this.flags.indexOf('watch') === -1) {
this.closeEmitter.fire();
resolve();
}
}, isIncremental ? 1000 : 4000);
});
}
}

View File

@ -2,157 +2,28 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as vscode from 'vscode';
import { RakeTaskProvider } from './rakeTaskProvider';
import { CustomBuildTaskProvider } from './customTaskProvider';
let taskProvider: vscode.Disposable | undefined;
let rakeTaskProvider: vscode.Disposable | undefined;
let customTaskProvider: vscode.Disposable | undefined;
export function activate(_context: vscode.ExtensionContext): void {
let workspaceRoot = vscode.workspace.rootPath;
if (!workspaceRoot) {
return;
}
let pattern = path.join(workspaceRoot, 'Rakefile');
let rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
fileWatcher.onDidChange(() => rakePromise = undefined);
fileWatcher.onDidCreate(() => rakePromise = undefined);
fileWatcher.onDidDelete(() => rakePromise = undefined);
taskProvider = vscode.tasks.registerTaskProvider('rake', {
provideTasks: () => {
if (!rakePromise) {
rakePromise = getRakeTasks();
}
return rakePromise;
},
resolveTask(_task: vscode.Task): vscode.Task | undefined {
return undefined;
}
});
rakeTaskProvider = vscode.tasks.registerTaskProvider(RakeTaskProvider.RakeType, new RakeTaskProvider(workspaceRoot));
customTaskProvider = vscode.tasks.registerTaskProvider(CustomBuildTaskProvider.CustomBuildScriptType, new CustomBuildTaskProvider(workspaceRoot));
}
export function deactivate(): void {
if (taskProvider) {
taskProvider.dispose();
if (rakeTaskProvider) {
rakeTaskProvider.dispose();
}
}
function exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value) => {
resolve(value);
});
});
}
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
cp.exec(command, options, (error, stdout, stderr) => {
if (error) {
reject({ error, stdout, stderr });
}
resolve({ stdout, stderr });
});
});
}
let _channel: vscode.OutputChannel;
function getOutputChannel(): vscode.OutputChannel {
if (!_channel) {
_channel = vscode.window.createOutputChannel('Rake Auto Detection');
if (customTaskProvider) {
customTaskProvider.dispose();
}
return _channel;
}
interface RakeTaskDefinition extends vscode.TaskDefinition {
/**
* The task name
*/
task: string;
/**
* The rake file containing the task
*/
file?: string;
}
const buildNames: string[] = ['build', 'compile', 'watch'];
function isBuildTask(name: string): boolean {
for (let buildName of buildNames) {
if (name.indexOf(buildName) !== -1) {
return true;
}
}
return false;
}
const testNames: string[] = ['test'];
function isTestTask(name: string): boolean {
for (let testName of testNames) {
if (name.indexOf(testName) !== -1) {
return true;
}
}
return false;
}
async function getRakeTasks(): Promise<vscode.Task[]> {
let workspaceRoot = vscode.workspace.rootPath;
let emptyTasks: vscode.Task[] = [];
if (!workspaceRoot) {
return emptyTasks;
}
let rakeFile = path.join(workspaceRoot, 'Rakefile');
if (!await exists(rakeFile)) {
return emptyTasks;
}
let commandLine = 'rake -AT -f Rakefile';
try {
let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
if (stderr && stderr.length > 0) {
getOutputChannel().appendLine(stderr);
getOutputChannel().show(true);
}
let result: vscode.Task[] = [];
if (stdout) {
let lines = stdout.split(/\r{0,1}\n/);
for (let line of lines) {
if (line.length === 0) {
continue;
}
let regExp = /rake\s(.*)#/;
let matches = regExp.exec(line);
if (matches && matches.length === 2) {
let taskName = matches[1].trim();
let kind: RakeTaskDefinition = {
type: 'rake',
task: taskName
};
let task = new vscode.Task(kind, taskName, 'rake', new vscode.ShellExecution(`rake ${taskName}`));
result.push(task);
let lowerCaseLine = line.toLowerCase();
if (isBuildTask(lowerCaseLine)) {
task.group = vscode.TaskGroup.Build;
} else if (isTestTask(lowerCaseLine)) {
task.group = vscode.TaskGroup.Test;
}
}
}
}
return result;
} catch (err) {
let channel = getOutputChannel();
if (err.stderr) {
channel.appendLine(err.stderr);
}
if (err.stdout) {
channel.appendLine(err.stdout);
}
channel.appendLine('Auto detecting rake tasts failed.');
channel.show(true);
return emptyTasks;
}
}
}

View File

@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as vscode from 'vscode';
export class RakeTaskProvider implements vscode.TaskProvider {
static RakeType: string = 'rake';
private rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
constructor(workspaceRoot: string) {
let pattern = path.join(workspaceRoot, 'Rakefile');
let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
fileWatcher.onDidChange(() => this.rakePromise = undefined);
fileWatcher.onDidCreate(() => this.rakePromise = undefined);
fileWatcher.onDidDelete(() => this.rakePromise = undefined);
}
public provideTasks(): Thenable<vscode.Task[]> | undefined {
if (!this.rakePromise) {
this.rakePromise = getRakeTasks();
}
return this.rakePromise;
}
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
return undefined;
}
}
function exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value) => {
resolve(value);
});
});
}
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
cp.exec(command, options, (error, stdout, stderr) => {
if (error) {
reject({ error, stdout, stderr });
}
resolve({ stdout, stderr });
});
});
}
let _channel: vscode.OutputChannel;
function getOutputChannel(): vscode.OutputChannel {
if (!_channel) {
_channel = vscode.window.createOutputChannel('Rake Auto Detection');
}
return _channel;
}
interface RakeTaskDefinition extends vscode.TaskDefinition {
/**
* The task name
*/
task: string;
/**
* The rake file containing the task
*/
file?: string;
}
const buildNames: string[] = ['build', 'compile', 'watch'];
function isBuildTask(name: string): boolean {
for (let buildName of buildNames) {
if (name.indexOf(buildName) !== -1) {
return true;
}
}
return false;
}
const testNames: string[] = ['test'];
function isTestTask(name: string): boolean {
for (let testName of testNames) {
if (name.indexOf(testName) !== -1) {
return true;
}
}
return false;
}
async function getRakeTasks(): Promise<vscode.Task[]> {
let workspaceRoot = vscode.workspace.rootPath;
let emptyTasks: vscode.Task[] = [];
if (!workspaceRoot) {
return emptyTasks;
}
let rakeFile = path.join(workspaceRoot, 'Rakefile');
if (!await exists(rakeFile)) {
return emptyTasks;
}
let commandLine = 'rake -AT -f Rakefile';
try {
let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
if (stderr && stderr.length > 0) {
getOutputChannel().appendLine(stderr);
getOutputChannel().show(true);
}
let result: vscode.Task[] = [];
if (stdout) {
let lines = stdout.split(/\r{0,1}\n/);
for (let line of lines) {
if (line.length === 0) {
continue;
}
let regExp = /rake\s(.*)#/;
let matches = regExp.exec(line);
if (matches && matches.length === 2) {
let taskName = matches[1].trim();
let kind: RakeTaskDefinition = {
type: 'rake',
task: taskName
};
let task = new vscode.Task(kind, taskName, 'rake', new vscode.ShellExecution(`rake ${taskName}`));
result.push(task);
let lowerCaseLine = line.toLowerCase();
if (isBuildTask(lowerCaseLine)) {
task.group = vscode.TaskGroup.Build;
} else if (isTestTask(lowerCaseLine)) {
task.group = vscode.TaskGroup.Test;
}
}
}
}
return result;
} catch (err) {
let channel = getOutputChannel();
if (err.stderr) {
channel.appendLine(err.stderr);
}
if (err.stdout) {
channel.appendLine(err.stdout);
}
channel.appendLine('Auto detecting rake tasts failed.');
channel.show(true);
return emptyTasks;
}
}

File diff suppressed because it is too large Load Diff