提交 f70b0b3e 编写于 作者: D Dirk Baeumer

Fixes #22069: Define extension story for tasks

上级 2d05792d
......@@ -31,7 +31,8 @@ const extensions = [
'javascript',
'css',
'html',
'git'
'git',
'gulp'
];
extensions.forEach(extension => npmInstall(`extensions/${extension}`));
\ No newline at end of file
{
"name": "gulp",
"publisher": "vscode",
"description": "Extension to add Gulp capabilities to VSCode.",
"displayName": "Gulp support for VSCode",
"version": "0.0.1",
"engines": {
"vscode": "*"
},
"enableProposedApi": true,
"categories": [
"Other"
],
"scripts": {
"compile": "gulp compile-extension:gulp",
"watch": "gulp watch-extension:gulp"
},
"dependencies": {
"vscode-nls": "^2.0.2"
},
"devDependencies": {
"@types/node": "^7.0.4"
},
"main": "./out/main",
"activationEvents": [
"onCommand:workbench.action.tasks.runTask",
"onCommand:workbench.action.tasks.build",
"onCommand:workbench.action.tasks.test"
]
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as vscode from 'vscode';
export function activate(_context: vscode.ExtensionContext): void {
let workspaceRoot = vscode.workspace.rootPath;
if (!workspaceRoot) {
return;
}
let gulpfile = path.join(workspaceRoot, 'gulpfile.js');
let gulpPromise: Thenable<vscode.TaskSet> | undefined = undefined;
let fileWatcher = vscode.workspace.createFileSystemWatcher(gulpfile);
fileWatcher.onDidChange(() => gulpPromise = undefined);
fileWatcher.onDidCreate(() => gulpPromise = undefined);
fileWatcher.onDidDelete(() => gulpPromise = undefined);
vscode.workspace.registerTaskProvider({
provideTasks: () => {
if (!gulpPromise) {
gulpPromise = getGulpTasks();
}
return gulpPromise;
}
});
}
function getGulpTasks(): Thenable<vscode.TaskSet> {
return new Promise<vscode.TaskSet>((resolve, _reject) => {
let workspaceRoot = vscode.workspace.rootPath;
let emptyTaskSet = { tasks: [] };
if (!workspaceRoot) {
return resolve(emptyTaskSet);
}
let gulpfile = path.join(workspaceRoot, 'gulpfile.js');
fs.exists(gulpfile, (value) => {
if (!value) {
resolve(emptyTaskSet);
return;
}
let commandLine: string;
let platform = process.platform;
if (platform === 'win32' && fs.existsSync(path.join(workspaceRoot!, 'node_modules', '.bin', 'gulp.cmd'))) {
commandLine = `${path.join('.', 'node_modules', '.bin', 'gulp.cmd')} --tasks-simple --no-color`;
} else if ((platform === 'linux' || platform === 'darwin') && fs.existsSync(path.join(workspaceRoot!, 'node_modules', '.bin', 'gulp.cmd'))) {
commandLine = `${path.join('.', 'node_modules', '.bin', 'gulp')} --tasks-simple --no-color`;
} else {
commandLine = 'gulp --tasks-simple --no-color';
}
cp.exec(commandLine, { cwd: workspaceRoot }, (error, stdout, stderr) => {
let channel = vscode.window.createOutputChannel('tasks');
if (stderr) {
channel.appendLine(stderr);
}
if (error) {
channel.appendLine(`Auto detecting gulp failed with error: ${error ? error.toString() : 'unknown'}`);
resolve(emptyTaskSet);
return;
}
let result: vscode.TaskSet = { tasks: [], buildTasks: [], testTasks: [] };
if (stdout) {
let buildTask: { id: string | undefined, rank: number } = { id: undefined, rank: 0 };
let testTask: { id: string | undefined, rank: number } = { id: undefined, rank: 0 };
let lines = stdout.split(/\r{0,1}\n/);
for (let line of lines) {
if (line.length === 0) {
continue;
}
result.tasks.push(new vscode.ShellTask(`gulp ${line}`, `gulp ${line}`));
let lowerCaseLine = line.toLowerCase();
if (lowerCaseLine === 'build') {
buildTask = { id: line, rank: 2 };
} else if (lowerCaseLine.indexOf('build') !== -1 && buildTask.rank < 1) {
buildTask = { id: line, rank: 1 };
} else if (lowerCaseLine === 'test') {
testTask = { id: line, rank: 2 };
} else if (lowerCaseLine.indexOf('test') !== -1 && testTask.rank < 1) {
testTask = { id: line, rank: 1 };
}
}
if (buildTask.id) {
result.buildTasks!.push(buildTask.id);
}
if (testTask.id) {
result.testTasks!.push(testTask.id);
}
}
resolve(result);
});
});
});
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference types='@types/node'/>
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"es6",
"es2015.promise"
],
"outDir": "./out",
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"exclude": [
"node_modules",
"out"
]
}
\ No newline at end of file
......@@ -52,26 +52,24 @@ export interface ProblemPattern {
line?: number;
column?: number;
character?: number;
endLine?: number;
endColumn?: number;
endCharacter?: number;
code?: number;
severity?: number;
loop?: boolean;
[key: string]: any;
}
export interface NamedProblemPattern extends ProblemPattern {
name: string;
}
export let problemPatternProperties = ['file', 'message', 'location', 'line', 'column', 'endLine', 'endColumn', 'code', 'severity', 'loop'];
export type MultiLineProblemPattern = ProblemPattern[];
export interface WatchingPattern {
regexp: RegExp;
......@@ -119,8 +117,6 @@ export interface NamedProblemMatcher extends ProblemMatcher {
name: string;
}
export type MultiLineProblemPattern = ProblemPattern[];
export interface NamedMultiLineProblemPattern {
name: string;
patterns: MultiLineProblemPattern;
......@@ -138,22 +134,21 @@ let valueMap: { [key: string]: string; } = {
interface Location {
startLineNumber: number;
startColumn: number;
startCharacter: number;
endLineNumber: number;
endColumn: number;
endCharacter: number;
}
interface ProblemData {
file?: string;
location?: string;
line?: string;
column?: string;
character?: string;
endLine?: string;
endColumn?: string;
endCharacter?: string;
message?: string;
severity?: string;
code?: string;
[key: string]: string;
}
export interface ProblemMatch {
......@@ -223,12 +218,12 @@ class AbstractLineMatcher implements ILineMatcher {
this.fillProperty(data, 'severity', pattern, matches, true);
this.fillProperty(data, 'location', pattern, matches, true);
this.fillProperty(data, 'line', pattern, matches);
this.fillProperty(data, 'column', pattern, matches);
this.fillProperty(data, 'character', pattern, matches);
this.fillProperty(data, 'endLine', pattern, matches);
this.fillProperty(data, 'endColumn', pattern, matches);
this.fillProperty(data, 'endCharacter', pattern, matches);
}
private fillProperty(data: ProblemData, property: string, pattern: ProblemPattern, matches: RegExpExecArray, trim: boolean = false): void {
private fillProperty(data: ProblemData, property: keyof ProblemData, pattern: ProblemPattern, matches: RegExpExecArray, trim: boolean = false): void {
if (Types.isUndefined(data[property]) && !Types.isUndefined(pattern[property]) && pattern[property] < matches.length) {
let value = matches[pattern[property]];
if (trim) {
......@@ -244,9 +239,9 @@ class AbstractLineMatcher implements ILineMatcher {
let marker: IMarkerData = {
severity: this.getSeverity(data),
startLineNumber: location.startLineNumber,
startColumn: location.startColumn,
startColumn: location.startCharacter,
endLineNumber: location.startLineNumber,
endColumn: location.endColumn,
endColumn: location.endCharacter,
message: data.message
};
if (!Types.isUndefined(data.code)) {
......@@ -273,9 +268,9 @@ class AbstractLineMatcher implements ILineMatcher {
return null;
}
let startLine = parseInt(data.line);
let startColumn = data.column ? parseInt(data.column) : undefined;
let startColumn = data.character ? parseInt(data.character) : undefined;
let endLine = data.endLine ? parseInt(data.endLine) : undefined;
let endColumn = data.endColumn ? parseInt(data.endColumn) : undefined;
let endColumn = data.endCharacter ? parseInt(data.endCharacter) : undefined;
return this.createLocation(startLine, startColumn, endLine, endColumn);
}
......@@ -295,12 +290,12 @@ class AbstractLineMatcher implements ILineMatcher {
private createLocation(startLine: number, startColumn: number, endLine: number, endColumn: number): Location {
if (startLine && startColumn && endColumn) {
return { startLineNumber: startLine, startColumn: startColumn, endLineNumber: endLine || startLine, endColumn: endColumn };
return { startLineNumber: startLine, startCharacter: startColumn, endLineNumber: endLine || startLine, endCharacter: endColumn };
}
if (startLine && startColumn) {
return { startLineNumber: startLine, startColumn: startColumn, endLineNumber: startLine, endColumn: startColumn };
return { startLineNumber: startLine, startCharacter: startColumn, endLineNumber: startLine, endCharacter: startColumn };
}
return { startLineNumber: startLine, startColumn: 1, endLineNumber: startLine, endColumn: Number.MAX_VALUE };
return { startLineNumber: startLine, startCharacter: 1, endLineNumber: startLine, endCharacter: Number.MAX_VALUE };
}
private getSeverity(data: ProblemData): Severity {
......@@ -491,8 +486,6 @@ export namespace Config {
* last problem pattern in a multi line problem matcher.
*/
loop?: boolean;
[key: string]: any;
}
export interface NamedProblemPattern extends ProblemPattern {
......@@ -729,24 +722,40 @@ class ProblemPatternParser extends Parser {
let result: ProblemPattern = {
regexp: this.createRegularExpression(value.regexp)
};
problemPatternProperties.forEach(property => {
if (!Types.isUndefined(value[property])) {
result[property] = value[property];
function copyProperty(result: ProblemPattern, source: Config.ProblemPattern, resultKey: keyof ProblemPattern, sourceKey: keyof Config.ProblemPattern) {
let value = source[sourceKey];
if (typeof value === 'number') {
result[resultKey] = value;
}
});
}
copyProperty(result, value, 'file', 'file');
copyProperty(result, value, 'location', 'location');
copyProperty(result, value, 'line', 'line');
copyProperty(result, value, 'character', 'column');
copyProperty(result, value, 'endLine', 'endLine');
copyProperty(result, value, 'endCharacter', 'endColumn');
copyProperty(result, value, 'severity', 'severity');
copyProperty(result, value, 'code', 'code');
copyProperty(result, value, 'message', 'message');
if (value.loop === true || value.loop === false) {
result.loop = value.loop;
}
if (setDefaults) {
if (result.location) {
result = Objects.mixin(result, {
let defaultValue: Partial<ProblemPattern> = {
file: 1,
message: 0
}, false);
};
result = Objects.mixin(result, defaultValue, false);
} else {
result = Objects.mixin(result, {
let defaultValue: Partial<ProblemPattern> = {
file: 1,
line: 2,
column: 3,
character: 3,
message: 0
}, false);
};
result = Objects.mixin(result, defaultValue, false);
}
}
return result;
......@@ -1040,7 +1049,7 @@ class ProblemPatternRegistryImpl implements IProblemPatternRegistry {
regexp: /^(.*):\s+line\s+(\d+),\s+col\s+(\d+),\s(.+?)(?:\s+\((\w)(\d+)\))?$/,
file: 1,
line: 2,
column: 3,
character: 3,
message: 4,
severity: 5,
code: 6
......@@ -1053,7 +1062,7 @@ class ProblemPatternRegistryImpl implements IProblemPatternRegistry {
{
regexp: /^\s+line\s+(\d+)\s+col\s+(\d+)\s+(.+?)(?:\s+\((\w)(\d+)\))?$/,
line: 1,
column: 2,
character: 2,
message: 3,
severity: 4,
code: 5,
......@@ -1064,7 +1073,7 @@ class ProblemPatternRegistryImpl implements IProblemPatternRegistry {
regexp: /^(.+):\sline\s(\d+),\scol\s(\d+),\s(Error|Warning|Info)\s-\s(.+)\s\((.+)\)$/,
file: 1,
line: 2,
column: 3,
character: 3,
severity: 4,
message: 5,
code: 6
......@@ -1077,7 +1086,7 @@ class ProblemPatternRegistryImpl implements IProblemPatternRegistry {
{
regexp: /^\s+(\d+):(\d+)\s+(error|warning|info)\s+(.+?)\s\s+(.*)$/,
line: 1,
column: 2,
character: 2,
severity: 3,
message: 4,
code: 5,
......@@ -1088,7 +1097,7 @@ class ProblemPatternRegistryImpl implements IProblemPatternRegistry {
regexp: /^([^:]*: )?((.:)?[^:]*):(\d+)(:(\d+))?: (.*)$/,
file: 2,
line: 4,
column: 6,
character: 6,
message: 7
});
}
......@@ -1267,7 +1276,7 @@ export class ProblemMatcherParser extends Parser {
if (!regexp) {
return null;
}
return file ? { regexp, file } : { regexp };
return file ? { regexp, file } : { regexp, file: 1 };
}
private createRegularExpression(value: string): RegExp {
......
......@@ -19,6 +19,473 @@ declare module 'vscode' {
report(value: T): void
}
/**
* Defines a problem pattern
*/
export interface ProblemPattern {
/**
* The regular expression to find a problem in the console output of an
* executed task.
*/
regexp: RegExp;
/**
* The match group index of the filename.
*
* Defaults to 1 if omitted.
*/
file?: number;
/**
* The match group index of the problems's location. Valid location
* patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn).
* If omitted the line and colum properties are used.
*/
location?: number;
/**
* The match group index of the problem's line in the source file.
*
* Defaults to 2 if omitted.
*/
line?: number;
/**
* The match group index of the problem's character in the source file.
*
* Defaults to 3 if omitted.
*/
character?: number;
/**
* The match group index of the problem's end line in the source file.
*
* Defaults to undefined. No end line is captured.
*/
endLine?: number;
/**
* The match group index of the problem's end character in the source file.
*
* Defaults to undefined. No end column is captured.
*/
endCharacter?: number;
/**
* The match group index of the problem's severity.
*
* Defaults to undefined. In this case the problem matcher's severity
* is used.
*/
severity?: number;
/**
* The match group index of the problems's code.
*
* Defaults to undefined. No code is captured.
*/
code?: number;
/**
* The match group index of the message. If omitted it defaults
* to 4 if location is specified. Otherwise it defaults to 5.
*/
message?: number;
/**
* Specifies if the last pattern in a multi line problem matcher should
* loop as long as it does match a line consequently. Only valid on the
* last problem pattern in a multi line problem matcher.
*/
loop?: boolean;
}
/**
* A multi line problem pattern.
*/
export type MultiLineProblemPattern = ProblemPattern[];
/**
* The way how the file location is interpreted
*/
export enum FileLocationKind {
/**
* VS Code should decide based on whether the file path found in the
* output is absolute or relative. A relative file path will be treated
* relative to the workspace root.
*/
Auto = 1,
/**
* Always treat the file path relative.
*/
Relative = 2,
/**
* Always treat the file path absolute.
*/
Absolute = 3
}
/**
* Controls to which kind of documents problems are applied.
*/
export enum ApplyToKind {
/**
* Problems are applied to all documents.
*/
AllDocuments = 1,
/**
* Problems are applied to open documents only.
*/
OpenDocuments = 2,
/**
* Problems are applied to closed documents only.
*/
ClosedDocuments = 3
}
/**
* A background monitor pattern
*/
export interface BackgroundPattern {
/**
* The actual regular expression
*/
regexp: RegExp;
/**
* The match group index of the filename. If provided the expression
* is matched for that file only.
*/
file?: number;
}
/**
* A description to control the activity of a problem matcher
* watching a background task.
*/
export interface BackgroundMonitor {
/**
* If set to true the monitor is in active mode when the task
* starts. This is equals of issuing a line that matches the
* beginPattern.
*/
activeOnStart?: boolean;
/**
* If matched in the output the start of a background activity is signaled.
*/
beginsPattern: RegExp | BackgroundPattern;
/**
* If matched in the output the end of a background activity is signaled.
*/
endsPattern: RegExp | BackgroundPattern;
}
/**
* Defines a problem matcher
*/
export interface ProblemMatcher {
/**
* The owner of a problem. Defaults to a generated id
* if omitted.
*/
owner?: string;
/**
* The type of documents problems detected by this matcher
* apply to. Default to `ApplyToKind.AllDocuments` if omitted.
*/
applyTo?: ApplyToKind;
/**
* How a file location recognize by a matcher should be interpreted. If omitted the file location
* if `FileLocationKind.Auto`.
*/
fileLocation?: FileLocationKind | string;
/**
* The actual pattern used by the problem matcher.
*/
pattern: ProblemPattern | MultiLineProblemPattern;
/**
* The default severity of a detected problem in the output. Used
* if the `ProblemPattern` doesn't define a severity match group.
*/
severity?: DiagnosticSeverity;
/**
* A background monitor for tasks that are running in the background.
*/
backgound?: BackgroundMonitor;
}
/**
* Controls the behaviour of the terminal's visibility.
*/
export enum RevealKind {
/**
* Always brings the terminal to front if the task is executed.
*/
Always = 1,
/**
* Only brings the terminal to front if a problem is detected executing the task
* (e.g. the task couldn't be started because).
*/
Silent = 2,
/**
* The terminal never comes to front when the task is executed.
*/
Never = 3
}
/**
* Controls terminal specific behaviour.
*/
export interface TerminalBehaviour {
/**
* Controls whether the terminal executing a task is brought to front or not.
* Defaults to `RevealKind.Always`.
*/
reveal?: RevealKind;
}
export interface ProcessOptions {
/**
* The current working directory of the executed program or shell.
* If omitted VSCode's current workspace root is used.
*/
cwd?: string;
/**
* The additional environment of the executed program or shell. If omitted
* the parent process' environment is used. If provided it is merged with
* the parent process' environment.
*/
env?: { [key: string]: string };
}
/**
* A task that starts an external process.
*/
export class ProcessTask {
/**
* Creates a process task.
*
* @param name the task's name. Is presented in the user interface.
* @param process the process to start.
* @param problemMatchers the problem matchers to use.
*/
constructor(name: string, process: string, ...problemMatchers: ProblemMatcher[]);
/**
* Creates a process task.
*
* @param name the task's name. Is presented in the user interface.
* @param process the process to start.
* @param args arguments to be passed to the process.
* @param problemMatchers the problem matchers to use.
*/
constructor(name: string, process: string, args: string[], ...problemMatchers: ProblemMatcher[]);
/**
* Creates a process task.
*
* @param name the task's name. Is presented in the user interface.
* @param process the process to start.
* @param args arguments to be passed to the process.
* @param options additional options for the started process.
* @param problemMatchers the problem matchers to use.
*/
constructor(name: string, process: string, args: string[], options: ProcessOptions, ...problemMatchers: ProblemMatcher[]);
/**
* The task's name
*/
readonly name: string;
/**
* The task's identifier. If omitted the name is
* used as an identifier.
*/
identifier: string;
/**
* Whether the task is a background task or not.
*/
isBackground: boolean;
/**
* The process to be executed.
*/
readonly process: string;
/**
* The arguments passed to the process. Can be omitted.
*/
args?: string[];
/**
* The process options used when the process is executed. Can be omitted.
*/
options?: ProcessOptions;
/**
* The terminal options. Can be omitted.
*/
terminal?: TerminalBehaviour;
/**
* The problem matchers attached to the task.
*/
readonly problemMatchers?: ProblemMatcher[];
}
export interface ShellOptions {
/**
* The shell executable.
*/
executable?: string;
/**
* The arguments to be passed to the shell executable.
*/
args?: string[];
/**
* The current working directory of the executed shell.
* If omitted VSCode's current workspace root is used.
*/
cwd?: string;
/**
* The additional environment of the executed shell. If omitted
* the parent process' environment is used. If provided it is merged with
* the parent process' environment.
*/
env?: { [key: string]: string };
}
/**
* A task that executes a shell command.
*/
export class ShellTask {
/**
* Creates a shell task.
*
* @param name the task's name. Is presented in the user interface.
* @param commandLine the command line to execute.
* @param problemMatchers the problem matchers to use.
*/
constructor(name: string, commandLine: string, ...problemMatchers: ProblemMatcher[]);
/**
* Creates a shell task.
*
* @param name the task's name. Is presented in the user interface.
* @param commandLine the command line to execute.
* @param options additional options used when creating the shell.
* @param problemMatchers the problem matchers to use.
*/
constructor(name: string, commandLine: string, options: ShellOptions, ...problemMatchers: ProblemMatcher[]);
/**
* The task's name
*/
readonly name: string;
/**
* The task's identifier. If omitted the name is
* used as an identifier.
*/
identifier: string;
/**
* Whether the task is a background task or not.
*/
isBackground: boolean;
/**
* The command line to execute.
*/
readonly commandLine: string;
/**
* The shell options used when the shell is executed. Can be omitted.
*/
options?: ShellOptions;
/**
* The terminal options. Can be omitted.
*/
terminal?: TerminalBehaviour;
/**
* The problem matchers attached to the task.
*/
readonly problemMatchers?: ProblemMatcher[];
}
export type Task = ProcessTask | ShellTask;
/**
* Result return from a task provider.
*/
export interface TaskSet {
/**
* The actual tasks returned.
*/
tasks: Task[];
/**
* The build tasks provided. Tasks must be identified using
* `Task.identifier`
*/
buildTasks?: string[];
/**
* The test tasks provided. Tasks must be identified using
* `Task.identifier`
*/
testTasks?: string[];
}
/**
* A task provider allows to add tasks to the task service.
* A task provider is registerd via #workspace.registerTaskProvider.
*/
export interface TaskProvider {
/**
* Provides additional tasks.
* @param token A cancellation token.
* @return a #TaskSet
*/
provideTasks(token: CancellationToken): ProviderResult<TaskSet>;
}
export namespace workspace {
/**
* Register a task provider.
*
* @param provider A task provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerTaskProvider(provider: TaskProvider): Disposable;
}
export namespace window {
/**
......
......@@ -34,6 +34,7 @@ import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages';
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
import { computeDiff } from 'vs/workbench/api/node/extHostFunctions';
import { ExtHostTask } from 'vs/workbench/api/node/extHostTask';
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
import URI from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
......@@ -111,6 +112,7 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ
const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set<ExtHostQuickOpen>(new ExtHostQuickOpen(threadService));
const extHostTerminalService = col.define(ExtHostContext.ExtHostTerminalService).set<ExtHostTerminalService>(new ExtHostTerminalService(threadService));
const extHostSCM = col.define(ExtHostContext.ExtHostSCM).set<ExtHostSCM>(new ExtHostSCM(threadService));
const extHostTask = col.define(ExtHostContext.ExtHostTask).set<ExtHostTask>(new ExtHostTask(threadService));
col.define(ExtHostContext.ExtHostExtensionService).set(extensionService);
col.finish(false, threadService);
......@@ -412,7 +414,10 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ
},
getConfiguration: (section?: string): vscode.WorkspaceConfiguration => {
return extHostConfiguration.getConfiguration(section);
}
},
registerTaskProvider: proposedApiFunction(extension, (provider: vscode.TaskProvider) => {
return extHostTask.registerTaskProvider(extension, provider);
})
};
class SCM {
......@@ -494,7 +499,12 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ
ViewColumn: extHostTypes.ViewColumn,
WorkspaceEdit: extHostTypes.WorkspaceEdit,
// functions
computeDiff
computeDiff,
FileLocationKind: extHostTypes.FileLocationKind,
ApplyToKind: extHostTypes.ApplyToKind,
RevealKind: extHostTypes.RevealKind,
ShellTask: extHostTypes.ShellTask,
ProcessTask: extHostTypes.ProcessTask
};
};
}
......
......@@ -33,6 +33,7 @@ import { MainThreadTerminalService } from './mainThreadTerminalService';
import { MainThreadWorkspace } from './mainThreadWorkspace';
import { MainProcessExtensionService } from './mainThreadExtensionService';
import { MainThreadFileSystemEventService } from './mainThreadFileSystemEventService';
import { MainThreadTask } from './mainThreadTask';
import { MainThreadSCM } from './mainThreadSCM';
// --- other interested parties
......@@ -86,6 +87,7 @@ export class ExtHostContribution implements IWorkbenchContribution {
col.define(MainContext.MainThreadTerminalService).set(create(MainThreadTerminalService));
col.define(MainContext.MainThreadWorkspace).set(create(MainThreadWorkspace));
col.define(MainContext.MainThreadSCM).set(create(MainThreadSCM));
col.define(MainContext.MainThreadTask).set(create(MainThreadTask));
if (this.extensionService instanceof MainProcessExtensionService) {
col.define(MainContext.MainProcessExtensionService).set(<MainProcessExtensionService>this.extensionService);
}
......
......@@ -37,6 +37,7 @@ import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { IApplyEditsOptions, IUndoStopOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent } from './mainThreadEditor';
import { InternalTreeExplorerNodeContent } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel';
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
export interface IEnvironment {
enableProposedApi: boolean;
......@@ -236,6 +237,11 @@ export abstract class MainThreadWorkspaceShape {
$applyWorkspaceEdit(edits: IResourceEdit[]): TPromise<boolean> { throw ni(); }
}
export abstract class MainThreadTaskShape {
$registerTaskProvider(handle: number): TPromise<any> { throw ni(); }
$unregisterTaskProvider(handle: number): TPromise<any> { throw ni(); }
}
export abstract class MainProcessExtensionServiceShape {
$localShowMessage(severity: Severity, msg: string): void { throw ni(); }
$onExtensionActivated(extensionId: string): void { throw ni(); }
......@@ -410,6 +416,10 @@ export abstract class ExtHostSCMShape {
$onInputBoxValueChange(value: string): TPromise<void> { throw ni(); }
}
export abstract class ExtHostTaskShape {
$provideTasks(handle: number): TPromise<TaskSet> { throw ni(); }
}
// --- proxy identifiers
export const MainContext = {
......@@ -432,7 +442,8 @@ export const MainContext = {
MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService', MainThreadTerminalServiceShape),
MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace', MainThreadWorkspaceShape),
MainProcessExtensionService: createMainId<MainProcessExtensionServiceShape>('MainProcessExtensionService', MainProcessExtensionServiceShape),
MainThreadSCM: createMainId<MainThreadSCMShape>('MainThreadSCM', MainThreadSCMShape)
MainThreadSCM: createMainId<MainThreadSCMShape>('MainThreadSCM', MainThreadSCMShape),
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask', MainThreadTaskShape)
};
export const ExtHostContext = {
......@@ -450,5 +461,6 @@ export const ExtHostContext = {
ExtHostQuickOpen: createExtId<ExtHostQuickOpenShape>('ExtHostQuickOpen', ExtHostQuickOpenShape),
ExtHostExtensionService: createExtId<ExtHostExtensionServiceShape>('ExtHostExtensionService', ExtHostExtensionServiceShape),
ExtHostTerminalService: createExtId<ExtHostTerminalServiceShape>('ExtHostTerminalService', ExtHostTerminalServiceShape),
ExtHostSCM: createExtId<ExtHostSCMShape>('ExtHostSCM', ExtHostSCMShape)
ExtHostSCM: createExtId<ExtHostSCMShape>('ExtHostSCM', ExtHostSCMShape),
ExtHostTask: createExtId<ExtHostTaskShape>('ExtHostTask', ExtHostTaskShape)
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import * as UUID from 'vs/base/common/uuid';
import { asWinJsPromise } from 'vs/base/common/async';
import * as Problems from 'vs/platform/markers/common/problemMatcher';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as TaskSystem from 'vs/workbench/parts/tasks/common/tasks';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench/api/node/extHost.protocol';
import { fromDiagnosticSeverity } from 'vs/workbench/api/node/extHostTypeConverters';
import * as types from 'vs/workbench/api/node/extHostTypes';
import * as vscode from 'vscode';
interface StringMap<V> {
[key: string]: V;
}
namespace ProblemPattern {
export function from(value: vscode.ProblemPattern | vscode.MultiLineProblemPattern): Problems.ProblemPattern | Problems.MultiLineProblemPattern {
if (value === void 0 || value === null) {
return undefined;
}
if (Array.isArray(value)) {
let result: Problems.ProblemPattern[] = [];
for (let pattern of value) {
let converted = fromSingle(pattern);
if (!converted) {
return undefined;
}
result.push(converted);
}
return result;
} else {
return fromSingle(value);
}
}
function copyProperty(target: Problems.ProblemPattern, source: vscode.ProblemPattern, tk: keyof Problems.ProblemPattern) {
let sk: keyof vscode.ProblemPattern = tk;
let value = source[sk];
if (typeof value === 'number') {
target[tk] = value;
}
}
function getValue(value: number, defaultValue: number): number {
if (value !== void 0 && value === null) {
return value;
}
return defaultValue;
}
function fromSingle(problemPattern: vscode.ProblemPattern): Problems.ProblemPattern {
if (problemPattern === void 0 || problemPattern === null || !(problemPattern.regexp instanceof RegExp)) {
return undefined;
}
let result: Problems.ProblemPattern = {
regexp: problemPattern.regexp
};
copyProperty(result, problemPattern, 'file');
copyProperty(result, problemPattern, 'location');
copyProperty(result, problemPattern, 'line');
copyProperty(result, problemPattern, 'character');
copyProperty(result, problemPattern, 'endLine');
copyProperty(result, problemPattern, 'endCharacter');
copyProperty(result, problemPattern, 'severity');
copyProperty(result, problemPattern, 'code');
copyProperty(result, problemPattern, 'message');
if (problemPattern.loop === true || problemPattern.loop === false) {
result.loop = problemPattern.loop;
}
if (result.location) {
result.file = getValue(result.file, 1);
result.message = getValue(result.message, 0);
} else {
result.file = getValue(result.file, 1);
result.line = getValue(result.line, 2);
result.character = getValue(result.character, 3);
result.message = getValue(result.message, 0);
}
return result;
}
}
namespace ApplyTo {
export function from(value: vscode.ApplyToKind): Problems.ApplyToKind {
if (value === void 0 || value === null) {
return Problems.ApplyToKind.allDocuments;
}
switch (value) {
case types.ApplyToKind.OpenDocuments:
return Problems.ApplyToKind.openDocuments;
case types.ApplyToKind.ClosedDocuments:
return Problems.ApplyToKind.closedDocuments;
}
return Problems.ApplyToKind.allDocuments;
}
}
namespace FileLocation {
export function from(value: vscode.FileLocationKind | string): { kind: Problems.FileLocationKind; prefix?: string } {
if (value === void 0 || value === null) {
return { kind: Problems.FileLocationKind.Auto };
}
if (typeof value === 'string') {
return { kind: Problems.FileLocationKind.Relative, prefix: value };
}
switch (value) {
case types.FileLocationKind.Absolute:
return { kind: Problems.FileLocationKind.Absolute };
case types.FileLocationKind.Relative:
return { kind: Problems.FileLocationKind.Relative, prefix: '${workspaceRoot}' };
}
return { kind: Problems.FileLocationKind.Auto };
}
}
namespace WatchingPattern {
export function from(value: RegExp | vscode.BackgroundPattern): Problems.WatchingPattern {
if (value === void 0 || value === null) {
return undefined;
}
if (value instanceof RegExp) {
return { regexp: value };
}
if (!(value.regexp instanceof RegExp)) {
return undefined;
}
let result: Problems.WatchingPattern = {
regexp: value.regexp
};
if (typeof value.file === 'number') {
result.file = value.file;
}
return result;
}
}
namespace WathingMatcher {
export function from(value: vscode.BackgroundMonitor): Problems.WatchingMatcher {
if (value === void 0 || value === null) {
return undefined;
}
let result: Problems.WatchingMatcher = {
activeOnStart: !!value.activeOnStart,
beginsPattern: WatchingPattern.from(value.beginsPattern),
endsPattern: WatchingPattern.from(value.endsPattern)
};
return result;
}
}
namespace ProblemMatcher {
export function from(values: vscode.ProblemMatcher[]): Problems.ProblemMatcher[] {
if (values === void 0 || values === null) {
return undefined;
}
let result: Problems.ProblemMatcher[];
for (let value of values) {
let converted = fromSingle(value);
if (converted) {
result.push(converted);
}
}
return result;
}
function fromSingle(problemMatcher: vscode.ProblemMatcher): Problems.ProblemMatcher {
if (problemMatcher === void 0 || problemMatcher === null) {
return undefined;
}
let location = FileLocation.from(problemMatcher.fileLocation);
let result: Problems.ProblemMatcher = {
owner: typeof problemMatcher.owner === 'string' ? problemMatcher.owner : UUID.generateUuid(),
applyTo: ApplyTo.from(problemMatcher.applyTo),
fileLocation: location.kind,
filePrefix: location.prefix,
pattern: ProblemPattern.from(problemMatcher.pattern),
severity: fromDiagnosticSeverity(problemMatcher.severity),
};
return result;
}
}
namespace RevealKind {
export function from(value: vscode.RevealKind): TaskSystem.ShowOutput {
if (value === void 0 || value === null) {
return TaskSystem.ShowOutput.Always;
}
switch (value) {
case types.RevealKind.Silent:
return TaskSystem.ShowOutput.Silent;
case types.RevealKind.Never:
return TaskSystem.ShowOutput.Never;
}
return TaskSystem.ShowOutput.Always;
}
}
namespace TerminalBehaviour {
export function from(value: vscode.TerminalBehaviour): { showOutput: TaskSystem.ShowOutput } {
if (value === void 0 || value === null) {
return { showOutput: TaskSystem.ShowOutput.Always };
}
return { showOutput: RevealKind.from(value.reveal) };
}
}
namespace Strings {
export function from(value: string[]): string[] {
if (value === void 0 || value === null) {
return undefined;
}
for (let element of value) {
if (typeof element !== 'string') {
return [];
}
}
return value;
}
}
namespace CommandOptions {
export function from(value: { cwd?: string; env?: { [key: string]: string; } }): TaskSystem.CommandOptions {
if (value === void 0 || value === null) {
return undefined;
}
let result: TaskSystem.CommandOptions = {
};
if (typeof value.cwd === 'string') {
result.cwd = value.cwd;
}
if (value.env) {
result.env = Object.create(null);
Object.keys(value.env).forEach(key => {
let envValue = value.env[key];
if (typeof envValue === 'string') {
result.env[key] = envValue;
}
});
}
return result;
}
}
namespace ShellConfiguration {
export function from(value: { executable?: string, args?: string[] }): boolean | TaskSystem.ShellConfiguration {
if (value === void 0 || value === null || typeof value.executable !== 'string') {
return true;
}
let result: TaskSystem.ShellConfiguration = {
executable: value.executable,
args: Strings.from(value.args)
};
return result;
}
}
namespace Tasks {
export function from(tasks: vscode.Task[], uuidMap: UUIDMap): TaskSystem.Task[] {
if (tasks === void 0 || tasks === null) {
return [];
}
let result: TaskSystem.Task[] = [];
try {
uuidMap.start();
for (let task of tasks) {
let converted = fromSingle(task, uuidMap);
if (converted) {
result.push(converted);
}
}
} finally {
uuidMap.finish();
}
return result;
}
function fromSingle(task: vscode.Task, uuidMap: UUIDMap): TaskSystem.Task {
if (typeof task.name !== 'string' || typeof task.identifier !== 'string') {
return undefined;
}
let command: TaskSystem.CommandConfiguration;
if (task instanceof types.ProcessTask) {
command = getProcessCommand(task);
} else if (task instanceof types.ShellTask) {
command = getShellCommand(task);
} else {
return undefined;
}
if (command === void 0) {
return undefined;
}
let outputChannel = TerminalBehaviour.from(task.terminal);
let result: TaskSystem.Task = {
_id: uuidMap.getUUID(task.identifier),
name: task.name,
identifier: task.identifier,
command: command,
showOutput: outputChannel.showOutput,
isBackground: !!task.isBackground,
suppressTaskName: true,
problemMatchers: ProblemMatcher.from(task.problemMatchers)
};
return result;
}
function getProcessCommand(value: vscode.ProcessTask): TaskSystem.CommandConfiguration {
if (typeof value.process !== 'string') {
return undefined;
}
let result: TaskSystem.CommandConfiguration = {
name: value.process,
args: Strings.from(value.args),
isShellCommand: false,
echo: false,
};
if (value.options) {
result.options = CommandOptions.from(value.options);
}
return result;
}
function getShellCommand(value: vscode.ShellTask): TaskSystem.CommandConfiguration {
if (typeof value.commandLine !== 'string') {
return undefined;
}
let result: TaskSystem.CommandConfiguration = {
name: value.commandLine,
isShellCommand: ShellConfiguration.from(value.options),
echo: false
};
if (value.options) {
result.options = CommandOptions.from(value.options);
}
return result;
}
}
class UUIDMap {
private _map: StringMap<string>;
private _unused: StringMap<boolean>;
constructor() {
this._map = Object.create(null);
}
public start(): void {
this._unused = Object.create(null);
Object.keys(this._map).forEach(key => this._unused[key] = true);
}
public getUUID(identifier: string): string {
delete this._unused[identifier];
let result = this._map[identifier];
if (result) {
return result;
}
result = UUID.generateUuid();
this._map[identifier] = result;
return result;
}
public finish(): void {
Object.keys(this._unused).forEach(key => delete this._map[key]);
this._unused = null;
}
}
namespace TaskSet {
const idMaps: Map<string, UUIDMap> = new Map<string, UUIDMap>();
function getUUIDMap(extensionId: string): UUIDMap {
let result = idMaps.get(extensionId);
if (result) {
return result;
}
result = new UUIDMap();
idMaps.set(extensionId, result);
return result;
}
export function from(extension: IExtensionDescription, value: vscode.TaskSet): TaskSystem.TaskSet {
if (value === void 0 || value === null || !Array.isArray(value.tasks)) {
return { tasks: Object.create(null) };
}
let tasks = Tasks.from(value.tasks, getUUIDMap(extension.id));
let buildTasks: string[];
let testTasks: string[];
if (value.buildTasks || value.testTasks) {
let map: Map<string, TaskSystem.Task> = new Map<string, TaskSystem.Task>();
tasks.forEach(task => map.set(task.identifier, task));
if (Array.isArray(value.buildTasks)) {
buildTasks = [];
for (let elem of value.buildTasks) {
if (typeof elem === 'string' && map.has(elem)) {
buildTasks.push(map.get(elem)._id);
}
}
}
if (Array.isArray(value.testTasks)) {
testTasks = [];
for (let elem of value.testTasks) {
if (typeof elem === 'string' && map.has(elem)) {
testTasks.push(map.get(elem)._id);
}
}
}
}
return {
tasks,
buildTasks,
testTasks
};
}
}
interface HandlerData {
provider: vscode.TaskProvider;
extension: IExtensionDescription;
}
export class ExtHostTask extends ExtHostTaskShape {
private _proxy: MainThreadTaskShape;
private _handleCounter: number;
private _handlers: Map<number, HandlerData>;
constructor(threadService: IThreadService) {
super();
this._proxy = threadService.get(MainContext.MainThreadTask);
this._handleCounter = 0;
this._handlers = new Map<number, HandlerData>();
};
public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable {
if (!provider) {
return new types.Disposable(() => { });
}
let handle = this.nextHandle();
this._handlers.set(handle, { provider, extension });
this._proxy.$registerTaskProvider(handle);
return new types.Disposable(() => {
this._handlers.delete(handle);
this._proxy.$unregisterTaskProvider(handle);
});
}
public $provideTasks(handle: number): TPromise<TaskSystem.TaskSet> {
let handler = this._handlers.get(handle);
if (!handler) {
return TPromise.wrapError<TaskSystem.TaskSet>(new Error('no handler found'));
}
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
return TaskSet.from(handler.extension, value);
});
}
private nextHandle(): number {
return this._handleCounter++;
}
}
\ No newline at end of file
......@@ -963,3 +963,156 @@ export class DocumentLink {
this.target = target;
}
}
export enum FileLocationKind {
Auto = 1,
Relative = 2,
Absolute = 3
}
export enum ApplyToKind {
AllDocuments = 1,
OpenDocuments = 2,
ClosedDocuments = 3
}
export enum RevealKind {
Always = 1,
Silent = 2,
Never = 3
}
export class BaseTask {
private _name: string;
private _problemMatchers: vscode.ProblemMatcher[];
public isBackground: boolean;
public identifier: string;
public terminal: vscode.TerminalBehaviour;
constructor(name: string, problemMatchers: vscode.ProblemMatcher[]) {
if (typeof name !== 'string') {
throw illegalArgument('name');
}
this._name = name;
this.identifier = name;
this._problemMatchers = problemMatchers;
this.isBackground = false;
}
get name(): string {
return this._name;
}
get problemMatchers(): vscode.ProblemMatcher[] {
return this._problemMatchers;
}
}
namespace ProblemMatcher {
export function is(value: any): value is vscode.ProblemMatcher {
let candidate: vscode.ProblemMatcher = value;
return candidate && !!candidate.pattern;
}
}
export class ProcessTask extends BaseTask {
private _process: string;
public args: string[];
public options: vscode.ProcessOptions;
private static parseArguments(restArgs: any[]): { args: string[]; options: vscode.ProcessOptions; problemMatchers: vscode.ProblemMatcher[] } {
let args: string[] = [];
let options: vscode.ProcessOptions = undefined;
let problemMatchers: vscode.ProblemMatcher[] = [];
if (!restArgs || restArgs.length === 0) {
return { args, options, problemMatchers };
}
let current: any = restArgs[0];
if (Array.isArray(current)) {
args = current;
restArgs.shift();
current = restArgs[0];
}
if (ProblemMatcher.is(current)) {
problemMatchers = restArgs;
} else if (current) {
options = current;
restArgs.shift();
if (restArgs.length > 0) {
problemMatchers = restArgs;
}
}
return { args, options, problemMatchers };
}
constructor(name: string, process: string, ...problemMatchers: vscode.ProblemMatcher[]);
constructor(name: string, process: string, args: string[], ...problemMatchers: vscode.ProblemMatcher[]);
constructor(name: string, process: string, args: string[], options: vscode.ProcessOptions, ...problemMatchers: vscode.ProblemMatcher[]);
constructor(name: string, process: string, ...rest: any[]) {
if (typeof process !== 'string') {
throw illegalArgument('process');
}
let { args, options, problemMatchers } = ProcessTask.parseArguments(rest);
super(name, problemMatchers);
this._process = process;
this.args = args;
this.options = options;
}
get process(): string {
return this._process;
}
}
export class ShellTask extends BaseTask {
private _commandLine: string;
public options: vscode.ShellOptions;
private static parseArguments(restArgs: any[]): { options: vscode.ShellOptions; problemMatchers: vscode.ProblemMatcher[] } {
let options: vscode.ShellOptions = undefined;
let problemMatchers: vscode.ProblemMatcher[] = [];
if (!restArgs || restArgs.length === 0) {
return { options, problemMatchers };
}
let current: any = restArgs[0];
if (current && !ProblemMatcher.is(current)) {
options = current;
restArgs.shift();
current = restArgs[0];
}
if (ProblemMatcher.is(current)) {
problemMatchers = restArgs;
}
return { options, problemMatchers };
}
constructor(name: string, commandLine: string, ...problemMatchers: vscode.ProblemMatcher[]);
constructor(name: string, commandLine: string, options: vscode.ShellOptions, ...problemMatchers: vscode.ProblemMatcher[]);
constructor(name: string, commandLine: string, ...rest: any[]) {
if (typeof commandLine !== 'string') {
throw illegalArgument('commandLine');
}
let { options, problemMatchers } = ShellTask.parseArguments(rest);
super(name, problemMatchers);
this._commandLine = commandLine;
this.options = options;
}
get commandLine(): string {
return this._commandLine;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape } from './extHost.protocol';
export class MainThreadTask extends MainThreadTaskShape {
private _proxy: ExtHostTaskShape;
constructor( @IThreadService threadService: IThreadService, @ITaskService private _taskService: ITaskService) {
super();
this._proxy = threadService.get(ExtHostContext.ExtHostTask);
}
public $registerTaskProvider(handle: number): TPromise<void> {
this._taskService.registerTaskProvider(handle, {
provideTasks: () => {
return this._proxy.$provideTasks(handle);
}
});
return TPromise.as<void>(undefined);
}
public $unregisterTaskProvider(handle: number): TPromise<any> {
this._taskService.unregisterTaskProvider(handle);
return TPromise.as<void>(undefined);
}
}
\ No newline at end of file
......@@ -835,7 +835,7 @@ export class DebugService implements debug.IDebugService {
}
// no task running, execute the preLaunchTask.
const taskPromise = this.taskService.run(filteredTasks[0].id).then(result => {
const taskPromise = this.taskService.run(filteredTasks[0]).then(result => {
this.lastTaskEvent = null;
return result;
}, err => {
......
......@@ -12,14 +12,15 @@ import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen');
import Model = require('vs/base/parts/quickopen/browser/quickOpenModel');
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { ITaskService, TaskDescription } from 'vs/workbench/parts/tasks/common/taskService';
import { Task } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
class TaskEntry extends Model.QuickOpenEntry {
private taskService: ITaskService;
private task: TaskDescription;
private task: Task;
constructor(taskService: ITaskService, task: TaskDescription, highlights: Model.IHighlight[] = []) {
constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) {
super(highlights);
this.taskService = taskService;
this.task = task;
......@@ -37,7 +38,7 @@ class TaskEntry extends Model.QuickOpenEntry {
if (mode === QuickOpen.Mode.PREVIEW) {
return false;
}
this.taskService.run(this.task.id);
this.taskService.run(this.task);
return true;
}
}
......
......@@ -345,8 +345,9 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement
this.emit(ProblemCollectorEvents.WatchingBeginDetected, {});
result = true;
let owner = beginMatcher.problemMatcher.owner;
if (matches[1]) {
let resource = getResource(matches[1], beginMatcher.problemMatcher);
let file = matches[beginMatcher.pattern.file];
if (file) {
let resource = getResource(file, beginMatcher.problemMatcher);
if (this.currentResourceAsString && this.currentResourceAsString === resource.toString()) {
this.resetCurrentResource();
}
......
......@@ -19,7 +19,7 @@ import {
isNamedProblemMatcher, ProblemMatcherRegistry
} from 'vs/platform/markers/common/problemMatcher';
import * as TaskSystem from './taskSystem';
import * as Tasks from './tasks';
/**
* Defines the problem handling strategy
......@@ -59,24 +59,6 @@ export interface PlatformTaskDescription {
args?: string[];
}
export interface CommandBinding {
/**
* The command identifer the task is bound to.
*/
identifier?: string;
/**
* The title to use
*/
title?: string;
/**
* An optional category
*/
category?: string;
}
/**
* The description of a task.
*/
......@@ -317,8 +299,8 @@ interface ParseContext {
}
namespace CommandOptions {
export function from(this: void, options: ProcessConfig.CommandOptions, context: ParseContext): TaskSystem.CommandOptions {
let result: TaskSystem.CommandOptions = {};
export function from(this: void, options: ProcessConfig.CommandOptions, context: ParseContext): Tasks.CommandOptions {
let result: Tasks.CommandOptions = {};
if (options.cwd !== void 0) {
if (Types.isString(options.cwd)) {
result.cwd = options.cwd;
......@@ -332,11 +314,11 @@ namespace CommandOptions {
return isEmpty(result) ? undefined : result;
}
export function isEmpty(value: TaskSystem.CommandOptions): boolean {
export function isEmpty(value: Tasks.CommandOptions): boolean {
return !value || value.cwd === void 0 && value.env === void 0;
}
export function merge(target: TaskSystem.CommandOptions, source: TaskSystem.CommandOptions): TaskSystem.CommandOptions {
export function merge(target: Tasks.CommandOptions, source: Tasks.CommandOptions): Tasks.CommandOptions {
if (isEmpty(source)) {
return target;
}
......@@ -355,7 +337,7 @@ namespace CommandOptions {
return target;
}
export function fillDefaults(value: TaskSystem.CommandOptions): TaskSystem.CommandOptions {
export function fillDefaults(value: Tasks.CommandOptions): Tasks.CommandOptions {
if (value && Object.isFrozen(value)) {
return value;
}
......@@ -368,7 +350,7 @@ namespace CommandOptions {
return value;
}
export function freeze(value: TaskSystem.CommandOptions): void {
export function freeze(value: Tasks.CommandOptions): void {
Object.freeze(value);
if (value.env) {
Object.freeze(value.env);
......@@ -387,7 +369,7 @@ namespace ShellConfiguration {
return candidate && Types.isString(candidate.executable) && (candidate.args === void 0 || Types.isStringArray(candidate.args));
}
export function from(this: void, config: ShellConfiguration, context: ParseContext): TaskSystem.ShellConfiguration {
export function from(this: void, config: ShellConfiguration, context: ParseContext): Tasks.ShellConfiguration {
if (!is(config)) {
return undefined;
}
......@@ -398,11 +380,11 @@ namespace ShellConfiguration {
return result;
}
export function isEmpty(value: TaskSystem.ShellConfiguration): boolean {
export function isEmpty(value: Tasks.ShellConfiguration): boolean {
return !value || value.executable === void 0 && (value.args === void 0 || value.args.length === 0);
}
export function merge(target: TaskSystem.ShellConfiguration, source: TaskSystem.ShellConfiguration): TaskSystem.ShellConfiguration {
export function merge(target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration {
if (isEmpty(source)) {
return target;
}
......@@ -414,10 +396,10 @@ namespace ShellConfiguration {
return target;
}
export function fillDefaults(value: TaskSystem.ShellConfiguration): void {
export function fillDefaults(value: Tasks.ShellConfiguration): void {
}
export function freeze(value: TaskSystem.ShellConfiguration): void {
export function freeze(value: Tasks.ShellConfiguration): void {
if (!value) {
return;
}
......@@ -441,10 +423,10 @@ namespace CommandConfiguration {
linux?: BaseCommandConfiguationShape;
}
export function from(this: void, config: CommandConfiguationShape, context: ParseContext): TaskSystem.CommandConfiguration {
let result: TaskSystem.CommandConfiguration = fromBase(config, context);
export function from(this: void, config: CommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration {
let result: Tasks.CommandConfiguration = fromBase(config, context);
let osConfig: TaskSystem.CommandConfiguration = undefined;
let osConfig: Tasks.CommandConfiguration = undefined;
if (config.windows && Platform.platform === Platform.Platform.Windows) {
osConfig = fromBase(config.windows, context);
} else if (config.osx && Platform.platform === Platform.Platform.Mac) {
......@@ -459,8 +441,12 @@ namespace CommandConfiguration {
return isEmpty(result) ? undefined : result;
}
function fromBase(this: void, config: BaseCommandConfiguationShape, context: ParseContext): TaskSystem.CommandConfiguration {
let result: TaskSystem.CommandConfiguration = {};
function fromBase(this: void, config: BaseCommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration {
let result: Tasks.CommandConfiguration = {
name: undefined,
isShellCommand: undefined,
echo: undefined
};
if (Types.isString(config.command)) {
result.name = config.command;
}
......@@ -493,15 +479,15 @@ namespace CommandConfiguration {
return isEmpty(result) ? undefined : result;
}
export function isEmpty(value: TaskSystem.CommandConfiguration): boolean {
export function isEmpty(value: Tasks.CommandConfiguration): boolean {
return !value || value.name === void 0 && value.isShellCommand === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options) && value.echo === void 0;
}
export function onlyEcho(value: TaskSystem.CommandConfiguration): boolean {
export function onlyEcho(value: Tasks.CommandConfiguration): boolean {
return value && value.echo !== void 0 && value.name === void 0 && value.isShellCommand === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options);
}
export function merge(target: TaskSystem.CommandConfiguration, source: TaskSystem.CommandConfiguration): TaskSystem.CommandConfiguration {
export function merge(target: Tasks.CommandConfiguration, source: Tasks.CommandConfiguration): Tasks.CommandConfiguration {
if (isEmpty(source)) {
return target;
}
......@@ -533,7 +519,7 @@ namespace CommandConfiguration {
return target;
}
export function fillDefaults(value: TaskSystem.CommandConfiguration): void {
export function fillDefaults(value: Tasks.CommandConfiguration): void {
if (!value || Object.isFrozen(value)) {
return;
}
......@@ -551,7 +537,7 @@ namespace CommandConfiguration {
}
}
export function freeze(value: TaskSystem.CommandConfiguration): void {
export function freeze(value: Tasks.CommandConfiguration): void {
Object.freeze(value);
if (value.args) {
Object.freeze(value.args);
......@@ -651,35 +637,10 @@ namespace ProblemMatcherConverter {
}
}
namespace CommandBinding {
export function isEmpty(value: TaskSystem.CommandBinding): boolean {
return !value || value.identifier === void 0 && value.title === void 0 && value.category === void 0;
}
export function from(this: void, binding: CommandBinding, context: ParseContext): TaskSystem.CommandBinding {
if (!binding) {
return undefined;
}
if (!Types.isString(binding.identifier)) {
context.problemReporter.warn(nls.localize('noCommandId', 'Warning: a command binding must defined an identifier. Ignoring binding.'));
return undefined;
}
let result: TaskSystem.CommandBinding = {
identifier: binding.identifier,
title: ''
};
if (Types.isString(binding.category)) {
result.category = binding.category;
}
return result;
}
}
namespace TaskDescription {
export interface TaskConfiguration {
tasks: IStringDictionary<TaskSystem.TaskDescription>;
tasks: Tasks.Task[];
buildTask?: string;
testTask?: string;
}
......@@ -692,7 +653,7 @@ namespace TaskDescription {
if (!tasks) {
return undefined;
}
let parsedTasks: IStringDictionary<TaskSystem.TaskDescription> = Object.create(null);
let parsedTasks: Tasks.Task[] = [];
let defaultBuildTask: { id: string; exact: number; } = { id: null, exact: -1 };
let defaultTestTask: { id: string; exact: number; } = { id: null, exact: -1 };
tasks.forEach((externalTask) => {
......@@ -702,12 +663,12 @@ namespace TaskDescription {
return;
}
let problemMatchers = ProblemMatcherConverter.from(externalTask.problemMatcher, context);
let command: TaskSystem.CommandConfiguration = externalTask.command !== void 0
let command: Tasks.CommandConfiguration = externalTask.command !== void 0
? CommandConfiguration.from(externalTask, context)
: externalTask.echoCommand !== void 0 ? { echo: !!externalTask.echoCommand } : undefined;
: externalTask.echoCommand !== void 0 ? { name: undefined, isShellCommand: undefined, echo: !!externalTask.echoCommand } : undefined;
let identifer = Types.isString(externalTask.identifier) ? externalTask.identifier : taskName;
let task: TaskSystem.TaskDescription = {
id: UUID.generateUuid(),
let task: Tasks.Task = {
_id: UUID.generateUuid(),
name: taskName,
identifier: identifer,
command,
......@@ -726,7 +687,7 @@ namespace TaskDescription {
task.promptOnClose = !!externalTask.promptOnClose;
}
if (Types.isString(externalTask.showOutput)) {
task.showOutput = TaskSystem.ShowOutput.fromString(externalTask.showOutput);
task.showOutput = Tasks.ShowOutput.fromString(externalTask.showOutput);
}
if (externalTask.command !== void 0) {
// if the task has its own command then we suppress the
......@@ -771,19 +732,19 @@ namespace TaskDescription {
}
}
if (addTask) {
parsedTasks[task.id] = task;
parsedTasks.push(task);
if (!Types.isUndefined(externalTask.isBuildCommand) && externalTask.isBuildCommand && defaultBuildTask.exact < 2) {
defaultBuildTask.id = task.id;
defaultBuildTask.id = task._id;
defaultBuildTask.exact = 2;
} else if (taskName === 'build' && defaultBuildTask.exact < 2) {
defaultBuildTask.id = task.id;
defaultBuildTask.id = task._id;
defaultBuildTask.exact = 1;
}
if (!Types.isUndefined(externalTask.isTestCommand) && externalTask.isTestCommand && defaultTestTask.exact < 2) {
defaultTestTask.id = task.id;
defaultTestTask.id = task._id;
defaultTestTask.exact = 2;
} else if (taskName === 'test' && defaultTestTask.exact < 2) {
defaultTestTask.id = task.id;
defaultTestTask.id = task._id;
defaultTestTask.exact = 1;
}
}
......@@ -837,7 +798,7 @@ namespace TaskDescription {
return target;
}
export function mergeGlobals(task: TaskSystem.TaskDescription, globals: Globals): void {
export function mergeGlobals(task: Tasks.Task, globals: Globals): void {
// We only merge a command from a global definition if there is no dependsOn
if (task.dependsOn === void 0) {
if (CommandConfiguration.isEmpty(task.command) && !CommandConfiguration.isEmpty(globals.command) && globals.command.name !== void 0) {
......@@ -864,7 +825,7 @@ namespace TaskDescription {
}
}
export function fillDefaults(task: TaskSystem.TaskDescription): void {
export function fillDefaults(task: Tasks.Task): void {
CommandConfiguration.fillDefaults(task.command);
if (task.args === void 0 && task.command === void 0) {
task.args = EMPTY_ARRAY;
......@@ -879,7 +840,7 @@ namespace TaskDescription {
task.isBackground = false;
}
if (task.showOutput === void 0) {
task.showOutput = TaskSystem.ShowOutput.Always;
task.showOutput = Tasks.ShowOutput.Always;
}
if (task.problemMatchers === void 0) {
task.problemMatchers = EMPTY_ARRAY;
......@@ -910,10 +871,10 @@ namespace TaskDescription {
}
interface Globals {
command?: TaskSystem.CommandConfiguration;
command?: Tasks.CommandConfiguration;
promptOnClose?: boolean;
suppressTaskName?: boolean;
showOutput?: TaskSystem.ShowOutput;
showOutput?: Tasks.ShowOutput;
}
namespace Globals {
......@@ -943,7 +904,7 @@ namespace Globals {
export function fromBase(this: void, config: BaseTaskRunnerConfiguration, context: ParseContext): Globals {
let result: Globals = {};
if (Types.isString(config.showOutput)) {
result.showOutput = TaskSystem.ShowOutput.fromString(config.showOutput);
result.showOutput = Tasks.ShowOutput.fromString(config.showOutput);
}
if (config.suppressTaskName !== void 0) {
result.suppressTaskName = !!config.suppressTaskName;
......@@ -979,7 +940,7 @@ namespace Globals {
value.suppressTaskName = false;
}
if (value.showOutput === void 0) {
value.showOutput = TaskSystem.ShowOutput.Always;
value.showOutput = Tasks.ShowOutput.Always;
}
if (value.promptOnClose === void 0) {
value.promptOnClose = true;
......@@ -994,20 +955,14 @@ namespace Globals {
}
}
export enum ExecutionEngine {
Unknown = 0,
Terminal = 1,
OutputPanel = 2
}
export namespace ExecutionEngine {
export function from(config: ExternalTaskRunnerConfiguration): ExecutionEngine {
export function from(config: ExternalTaskRunnerConfiguration): Tasks.ExecutionEngine {
return isTerminalConfig(config)
? ExecutionEngine.Terminal
? Tasks.ExecutionEngine.Terminal
: isRunnerConfig(config)
? ExecutionEngine.OutputPanel
: ExecutionEngine.Unknown;
? Tasks.ExecutionEngine.Process
: Tasks.ExecutionEngine.Unknown;
}
function isRunnerConfig(config: ExternalTaskRunnerConfiguration): boolean {
......@@ -1021,8 +976,8 @@ export namespace ExecutionEngine {
export interface ParseResult {
validationStatus: ValidationStatus;
configuration: TaskSystem.TaskRunnerConfiguration;
engine: ExecutionEngine;
taskSet: Tasks.TaskSet;
engine: Tasks.ExecutionEngine;
}
export interface IProblemReporter extends IProblemReporterBase {
......@@ -1039,18 +994,18 @@ class ConfigurationParser {
public run(fileConfig: ExternalTaskRunnerConfiguration): ParseResult {
let engine = ExecutionEngine.from(fileConfig);
if (engine === ExecutionEngine.Terminal) {
if (engine === Tasks.ExecutionEngine.Terminal) {
this.problemReporter.clearOutput();
}
let context: ParseContext = { problemReporter: this.problemReporter, namedProblemMatchers: undefined, isTermnial: engine === ExecutionEngine.Terminal };
let context: ParseContext = { problemReporter: this.problemReporter, namedProblemMatchers: undefined, isTermnial: engine === Tasks.ExecutionEngine.Terminal };
return {
validationStatus: this.problemReporter.status,
configuration: this.createTaskRunnerConfiguration(fileConfig, context),
taskSet: this.createTaskRunnerConfiguration(fileConfig, context),
engine
};
}
private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): TaskSystem.TaskRunnerConfiguration {
private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): Tasks.TaskSet {
let globals = Globals.from(fileConfig, context);
if (this.problemReporter.status.isFatal()) {
return undefined;
......@@ -1072,15 +1027,15 @@ class ConfigurationParser {
taskConfig = TaskDescription.merge(taskConfig, globalTasks);
if (TaskDescription.isEmpty(taskConfig)) {
let tasks: IStringDictionary<TaskSystem.TaskDescription> = Object.create(null);
let tasks: Tasks.Task[] = [];
let buildTask: string;
if (globals.command && globals.command.name) {
let matchers: ProblemMatcher[] = ProblemMatcherConverter.from(fileConfig.problemMatcher, context);;
let isBackground = fileConfig.isBackground ? !!fileConfig.isBackground : fileConfig.isWatching ? !!fileConfig.isWatching : undefined;
let task: TaskSystem.TaskDescription = {
id: UUID.generateUuid(),
let task: Tasks.Task = {
_id: UUID.generateUuid(),
name: globals.command.name,
identifier: globals.command.name,
identifier: UUID.generateUuid(),
command: undefined,
isBackground: isBackground,
showOutput: undefined,
......@@ -1089,8 +1044,8 @@ class ConfigurationParser {
};
TaskDescription.mergeGlobals(task, globals);
TaskDescription.fillDefaults(task);
tasks[task.id] = task;
buildTask = task.id;
tasks.push(task);
buildTask = task._id;
}
taskConfig = {
......
......@@ -9,9 +9,10 @@ import { Action } from 'vs/base/common/actions';
import { IEventEmitter } from 'vs/base/common/eventEmitter';
import { TerminateResponse } from 'vs/base/common/processes';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITaskSummary, TaskDescription, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem';
import { Task, TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskSummary, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem';
export { ITaskSummary, TaskDescription, TaskEvent, TaskType };
export { ITaskSummary, Task, TaskEvent, TaskType };
export const ITaskService = createDecorator<ITaskService>('taskService');
......@@ -22,6 +23,10 @@ export namespace TaskServiceEvents {
export let Terminated: string = 'terminated';
}
export interface ITaskProvider {
provideTasks(): TPromise<TaskSet>;
}
export interface ITaskService extends IEventEmitter {
_serviceBrand: any;
configureAction(): Action;
......@@ -29,9 +34,12 @@ export interface ITaskService extends IEventEmitter {
rebuild(): TPromise<ITaskSummary>;
clean(): TPromise<ITaskSummary>;
runTest(): TPromise<ITaskSummary>;
run(taskIdentifier: string): TPromise<ITaskSummary>;
run(task: string | Task): TPromise<ITaskSummary>;
inTerminal(): boolean;
isActive(): TPromise<boolean>;
terminate(): TPromise<TerminateResponse>;
tasks(): TPromise<TaskDescription[]>;
tasks(): TPromise<Task[]>;
registerTaskProvider(handle: number, taskProvider: ITaskProvider): void;
unregisterTaskProvider(handle: number): boolean;
}
\ No newline at end of file
......@@ -8,9 +8,7 @@ import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { TerminateResponse } from 'vs/base/common/processes';
import { IEventEmitter } from 'vs/base/common/eventEmitter';
import * as Types from 'vs/base/common/types';
import { ProblemMatcher } from 'vs/platform/markers/common/problemMatcher';
import { Task } from './tasks';
export enum TaskErrors {
NotConfigured,
......@@ -54,191 +52,6 @@ export namespace Triggers {
export let command: string = 'command';
}
export enum ShowOutput {
Always,
Silent,
Never
}
export namespace ShowOutput {
export function fromString(value: string): ShowOutput {
value = value.toLowerCase();
if (value === 'always') {
return ShowOutput.Always;
} else if (value === 'silent') {
return ShowOutput.Silent;
} else if (value === 'never') {
return ShowOutput.Never;
} else {
return undefined;
}
}
}
export interface CommandOptions {
/**
* The current working directory of the executed program or shell.
* If omitted VSCode's current workspace root is used.
*/
cwd?: string;
/**
* The environment of the executed program or shell. If omitted
* the parent process' environment is used.
*/
env?: { [key: string]: string; };
}
export interface ShellConfiguration {
/**
* The shell executable.
*/
executable: string;
/**
* The arguments to be passed to the shell executable.
*/
args?: string[];
}
export namespace ShellConfiguration {
export function is(value: any): value is ShellConfiguration {
let candidate: ShellConfiguration = value;
return candidate && Types.isString(candidate.executable) && (candidate.args === void 0 || Types.isStringArray(candidate.args));
}
}
export interface CommandConfiguration {
/**
* The command to execute
*/
name?: string;
/**
* Whether the command is a shell command or not
*/
isShellCommand?: boolean | ShellConfiguration;
/**
* Additional command options.
*/
options?: CommandOptions;
/**
* Command arguments.
*/
args?: string[];
/**
* The task selector if needed.
*/
taskSelector?: string;
/**
* Controls whether the executed command is printed to the output windows as well.
*/
echo?: boolean;
}
export interface CommandBinding {
/**
* The command identifier the task is bound to.
*/
identifier: string;
/**
* The title to use
*/
title: string;
/**
* An optional category
*/
category?: string;
}
/**
* A task description
*/
export interface TaskDescription {
/**
* The task's internal id
*/
id: string;
/**
* The task's name
*/
name: string;
/**
* The task's identifier.
*/
identifier: string;
/**
* The command configuration
*/
command: CommandConfiguration;
/**
* Suppresses the task name when calling the task using the task runner.
*/
suppressTaskName?: boolean;
/**
* Additional arguments passed to the command when this target is
* invoked.
*/
args?: string[];
/**
* Whether the task is a background task or not.
*/
isBackground?: boolean;
/**
* Whether the task should prompt on close for confirmation if running.
*/
promptOnClose?: boolean;
/**
* Controls whether the output of the running tasks is shown or not. Default
* value is "always".
*/
showOutput: ShowOutput;
/**
* The other tasks this task depends on.
*/
dependsOn?: string[];
/**
* The problem watchers to use for this task
*/
problemMatchers?: ProblemMatcher[];
}
/**
* Describs the settings of a task runner
*/
export interface TaskRunnerConfiguration {
/**
* The inferred build tasks
*/
buildTasks: string[];
/**
* The inferred test tasks;
*/
testTasks: string[];
/**
* The configured tasks
*/
tasks?: { [id: string]: TaskDescription; };
}
export interface ITaskSummary {
/**
* Exit code of the process.
......@@ -279,15 +92,14 @@ export interface TaskEvent {
type?: TaskType;
}
export interface ITaskResolver {
resolve(identifier: string): Task;
}
export interface ITaskSystem extends IEventEmitter {
build(): ITaskExecuteResult;
rebuild(): ITaskExecuteResult;
clean(): ITaskExecuteResult;
runTest(): ITaskExecuteResult;
run(taskIdentifier: string): ITaskExecuteResult;
run(task: Task, resolver: ITaskResolver): ITaskExecuteResult;
isActive(): TPromise<boolean>;
isActiveSync(): boolean;
canAutoTerminate(): boolean;
terminate(): TPromise<TerminateResponse>;
tasks(): TPromise<TaskDescription[]>;
}
\ No newline at end of file
......@@ -4,78 +4,78 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import NLS = require('vs/nls');
import { IStringDictionary } from 'vs/base/common/collections';
import * as Types from 'vs/base/common/types';
import * as UUID from 'vs/base/common/uuid';
import { IProblemReporter, Parser } from 'vs/base/common/parsers';
import { Executable, ExecutableParser, Config as ProcessConfig } from 'vs/base/common/processes';
import { ProblemMatcher, Config as ProblemMatcherConfig, ProblemMatcherParser, ProblemMatcherRegistry } from 'vs/platform/markers/common/problemMatcher';
export namespace Config {
import { ProblemMatcher } from 'vs/platform/markers/common/problemMatcher';
export interface CommandOptions {
/**
* The description of a task.
* The current working directory of the executed program or shell.
* If omitted VSCode's current workspace root is used.
*/
export interface Task {
cwd?: string;
/**
* The task's name.
*/
name?: string;
/**
* The trigger to automatically activate the task.
*/
trigger?: string | string[];
/**
* The environment of the executed program or shell. If omitted
* the parent process' environment is used.
*/
env?: { [key: string]: string; };
}
/**
* The executable
*/
executable?: ProcessConfig.Executable;
export interface ShellConfiguration {
/**
* The shell executable.
*/
executable: string;
/**
* The arguments to be passed to the shell executable.
*/
args?: string[];
}
/**
* Whether the executed command is kept alive and is watching the file system.
*/
isWatching?: boolean;
export namespace ShellConfiguration {
export function is(value: any): value is ShellConfiguration {
let candidate: ShellConfiguration = value;
return candidate && Types.isString(candidate.executable) && (candidate.args === void 0 || Types.isStringArray(candidate.args));
}
}
/**
* Whether the task should prompt on close for confirmation if running.
*/
promptOnClose?: boolean;
export interface CommandConfiguration {
/**
* The command to execute
*/
name: string;
/**
* Controls whether the output view of the running tasks is brought to front or not.
* See BaseTaskRunnerConfiguration#showOutput for details.
*/
showOutput?: string;
/**
* Whether the command is a shell command or not
*/
isShellCommand: boolean | ShellConfiguration;
/**
* Controls whether the executed command is printed to the output window as well.
*/
echoCommand?: boolean;
/**
* Additional command options.
*/
options?: CommandOptions;
/**
* Settings to control the task
*/
settings?: string;
/**
* Command arguments.
*/
args?: string[];
/**
* The problem matcher(s) to use to capture problems in the tasks
* output.
*/
problemMatcher?: ProblemMatcherConfig.ProblemMatcherType;
/**
* The task selector if needed.
*/
taskSelector?: string;
}
/**
* Controls whether the executed command is printed to the output windows as well.
*/
echo: boolean;
}
export enum ShowOutput {
Always,
Silent,
Never
Always = 1,
Silent = 2,
Never = 3
}
export namespace ShowOutput {
......@@ -94,214 +94,90 @@ export namespace ShowOutput {
}
/**
* The description of a task.
* A task description
*/
export interface Task {
/**
* The task's internal id
*/
id: string;
_id: string;
/**
* The task's name
*/
name?: string;
name: string;
/**
* The trigger to automatically activate the task.
* The task's identifier.
*/
trigger?: string[];
identifier: string;
/**
* The executable
* The command configuration
*/
executable: Executable;
command: CommandConfiguration;
/**
* Whether the executed command is kept alive and is watching the file system.
* Suppresses the task name when calling the task using the task runner.
*/
isWatching: boolean;
suppressTaskName?: boolean;
/**
* Whether the task should prompt on close for confirmation if running.
* Additional arguments passed to the command when this target is
* invoked.
*/
promptOnClose?: boolean;
args?: string[];
/**
* Controls whether the output view of the running tasks is brought to front or not.
* See BaseTaskRunnerConfiguration#showOutput for details.
* Whether the task is a background task or not.
*/
showOutput: ShowOutput;
isBackground?: boolean;
/**
* Controls whether the executed command is printed to the output window as well.
* Whether the task should prompt on close for confirmation if running.
*/
echoCommand: boolean;
promptOnClose?: boolean;
/**
* Settings to control the task
* Controls whether the output of the running tasks is shown or not. Default
* value is "always".
*/
settings: string;
showOutput: ShowOutput;
/**
* The problem matcher(s) to use to capture problems in the tasks
* output.
* The other tasks this task depends on.
*/
problemMatcher: ProblemMatcher[];
}
dependsOn?: string[];
export interface ParserSettings {
globals?: Executable;
emptyExecutable?: boolean;
emptyCommand?: boolean;
/**
* The problem watchers to use for this task
*/
problemMatchers?: ProblemMatcher[];
}
export class TaskParser extends Parser {
constructor(problemReporter: IProblemReporter) {
super(problemReporter);
}
public parse(json: Config.Task, parserSettings: ParserSettings = { globals: null, emptyExecutable: false, emptyCommand: false }): Task {
let id: string = UUID.generateUuid();
let name: string = null;
let trigger: string[] = null;
let settings: string = null;
if (this.is(json.name, Types.isString)) {
name = json.name;
}
if (this.is(json.trigger, Types.isString)) {
trigger = [<string>json.trigger];
} else if (this.is(json.trigger, Types.isStringArray)) {
trigger = <string[]>json.trigger;
}
if (name === null && trigger === null) {
this.error(NLS.localize('TaskParser.nameOrTrigger', 'A task must either define a name or a trigger.'));
return null;
}
let executable: Executable = json.executable ? (new ExecutableParser(this.problemReporter)).parse(json.executable, { emptyCommand: !!parserSettings.emptyCommand }) : null;
if (!executable && parserSettings.globals) {
executable = parserSettings.globals;
}
if (executable === null && !parserSettings.emptyExecutable) {
this.error(NLS.localize('TaskParser.noExecutable', 'A task must must define a valid executable.'));
return null;
}
let isWatching: boolean = false;
let showOutput: ShowOutput = ShowOutput.Always;
let echoCommand: boolean = false;
if (this.is(json.isWatching, Types.isBoolean)) {
isWatching = json.isWatching;
}
let promptOnClose: boolean = true;
if (this.is(json.promptOnClose, Types.isBoolean)) {
promptOnClose = json.promptOnClose;
} else {
promptOnClose = !isWatching;
}
if (this.is(json.showOutput, Types.isString)) {
showOutput = ShowOutput.fromString(json.showOutput) || ShowOutput.Always;
}
if (this.is(json.echoCommand, Types.isBoolean)) {
echoCommand = json.echoCommand;
}
if (this.is(json.settings, Types.isString)) {
settings = json.settings;
}
/**
* Describes a task set.
*/
export interface TaskSet {
/**
* The inferred build tasks
*/
buildTasks?: string[];
let problemMatcher: ProblemMatcher[] = [];
if (Types.isArray(json.problemMatcher)) {
(<(string | ProblemMatcherConfig.ProblemMatcher)[]>json.problemMatcher).forEach((value) => {
let matcher = this.parseProblemMatcher(value);
if (matcher) {
problemMatcher.push(matcher);
}
});
} else {
let matcher = this.parseProblemMatcher(json.problemMatcher);
if (matcher) {
problemMatcher.push(matcher);
}
}
return { id, name, trigger, executable, isWatching, promptOnClose, showOutput, echoCommand, settings, problemMatcher };
}
/**
* The inferred test tasks;
*/
testTasks?: string[];
private parseProblemMatcher(json: string | ProblemMatcherConfig.ProblemMatcher): ProblemMatcher {
if (Types.isString(json)) {
return json.length > 0 && json.charAt(0) === '$' ? ProblemMatcherRegistry.get(json.substr(1)) : null;
} else if (Types.isObject(json)) {
return new ProblemMatcherParser(this.problemReporter).parse(<ProblemMatcherConfig.ProblemMatcher>json);
} else {
return null;
}
}
/**
* The configured tasks
*/
tasks: Task[];
}
// let tasksExtPoint = ExtensionsRegistry.registerExtensionPoint<Config.Task | Config.Task[]>('tasks', {
// TODO@Dirk: provide JSON schema here
// });
// const extensionPoint: string = 'tasks';
export class TaskRegistry {
private tasks: IStringDictionary<Task>;
constructor() {
this.tasks = Object.create(null);
/*
tasksExtPoint.setHandler((extensions, collector) => {
// TODO@Dirk: validate extension description here and collect errors/warnings with `collector`
extensions.forEach(extension => {
let extensions = extension.value;
if (Types.isArray(extensions)) {
(<Config.Task[]>extensions).forEach(this.onTask, this);
} else {
this.onTask(extensions)
}
});
});
*/
}
// private onDescriptions(descriptions: IExtensionDescription[]) {
// descriptions.forEach(description => {
// let extensions = description.contributes[extensionPoint];
// if (Types.isArray(extensions)) {
// (<Config.Task[]>extensions).forEach(this.onTask, this);
// } else {
// this.onTask(extensions);
// }
// });
// }
// private onTask(json: Config.Task): void {
// let logger: ILogger = {
// log: (message) => { console.warn(message); }
// };
// let parser = new TaskParser(ProblemMatcherRegistry, logger);
// let result = parser.parse(json, { emptyExecutable: true, emptyCommand: true });
// this.add(result);
// }
public add(task: Task): void {
this.tasks[task.id] = task;
}
public get(id: string): Task {
return this.tasks[id];
}
public exists(id: string): boolean {
return !!this.tasks[id];
}
public remove(id: string): void {
delete this.tasks[id];
}
public all(): Task[] {
return Object.keys(this.tasks).map(key => this.tasks[key]);
}
}
export enum ExecutionEngine {
Unknown = 0,
Terminal = 1,
Process = 2
}
\ No newline at end of file
......@@ -30,9 +30,10 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output';
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors';
import { Task, ShowOutput, CommandOptions, ShellConfiguration } from 'vs/workbench/parts/tasks/common/tasks';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskRunnerConfiguration, TaskDescription, ShowOutput,
TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType, CommandOptions, ShellConfiguration
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType
} from 'vs/workbench/parts/tasks/common/taskSystem';
class TerminalDecoder {
......@@ -91,6 +92,7 @@ interface TerminalData {
interface ActiveTerminalData {
terminal: ITerminalInstance;
task: Task;
promise: TPromise<ITaskSummary>;
}
......@@ -98,35 +100,23 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
public static TelemetryEventName: string = 'taskService';
private configuration: TaskRunnerConfiguration;
private identifier2Task: IStringDictionary<TaskDescription>;
private outputChannel: IOutputChannel;
private activeTasks: IStringDictionary<ActiveTerminalData>;
private primaryTerminal: PrimaryTerminal;
private terminals: IStringDictionary<TerminalData>;
private idleTaskTerminals: IStringDictionary<string>;
constructor(configuration: TaskRunnerConfiguration, private terminalService: ITerminalService, private outputService: IOutputService,
constructor(private terminalService: ITerminalService, private outputService: IOutputService,
private markerService: IMarkerService, private modelService: IModelService, private configurationResolverService: IConfigurationResolverService,
private telemetryService: ITelemetryService, outputChannelId: string) {
super();
this.setConfiguration(configuration);
this.outputChannel = this.outputService.getChannel(outputChannelId);
this.activeTasks = Object.create(null);
this.terminals = Object.create(null);
this.idleTaskTerminals = Object.create(null);
}
public setConfiguration(configuration: TaskRunnerConfiguration) {
this.configuration = configuration;
this.identifier2Task = Object.create(null);
Object.keys(this.configuration.tasks).forEach((key) => {
let task = this.configuration.tasks[key];
this.identifier2Task[task.identifier] = task;
});
}
public log(value: string): void {
this.outputChannel.append(value + '\n');
}
......@@ -135,34 +125,8 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
this.outputChannel.show(true);
}
public build(): ITaskExecuteResult {
if (this.configuration.buildTasks.length === 0) {
throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noBuildTask', 'No build task defined in tasks.json'), TaskErrors.NoBuildTask);
}
return this.run(this.configuration.buildTasks[0], Triggers.shortcut);
}
public rebuild(): ITaskExecuteResult {
throw new Error('Task - Rebuild: not implemented yet');
}
public clean(): ITaskExecuteResult {
throw new Error('Task - Clean: not implemented yet');
}
public runTest(): ITaskExecuteResult {
if (this.configuration.testTasks.length === 0) {
throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noTestTask', 'No test task defined in tasks.json'), TaskErrors.NoTestTask);
}
return this.run(this.configuration.testTasks[0], Triggers.shortcut);
}
public run(taskIdentifier: string, trigger: string = Triggers.command): ITaskExecuteResult {
let task = this.configuration.tasks[taskIdentifier];
if (!task) {
throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noTask', 'Task \'{0}\' not found', taskIdentifier), TaskErrors.TaskNotFound);
}
let terminalData = this.activeTasks[task.id];
public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult {
let terminalData = this.activeTasks[task._id];
if (terminalData && terminalData.promise) {
if (task.showOutput === ShowOutput.Always) {
terminalData.terminal.setVisible(true);
......@@ -171,7 +135,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
}
try {
return { kind: TaskExecuteKind.Started, started: {}, promise: this.executeTask(Object.create(null), task, trigger) };
return { kind: TaskExecuteKind.Started, started: {}, promise: this.executeTask(Object.create(null), task, resolver, trigger) };
} catch (error) {
if (error instanceof TaskError) {
throw error;
......@@ -194,7 +158,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
}
public canAutoTerminate(): boolean {
return Object.keys(this.activeTasks).every(key => !this.configuration.tasks[key].promptOnClose);
return Object.keys(this.activeTasks).every(key => !this.activeTasks[key].task.promptOnClose);
}
public terminate(): TPromise<TerminateResponse> {
......@@ -206,26 +170,16 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
return TPromise.as<TerminateResponse>({ success: true });
}
public tasks(): TPromise<TaskDescription[]> {
let result: TaskDescription[];
if (!this.configuration || !this.configuration.tasks) {
result = [];
} else {
result = Object.keys(this.configuration.tasks).map(key => this.configuration.tasks[key]);
}
return TPromise.as(result);
}
private executeTask(startedTasks: IStringDictionary<TPromise<ITaskSummary>>, task: TaskDescription, trigger: string): TPromise<ITaskSummary> {
private executeTask(startedTasks: IStringDictionary<TPromise<ITaskSummary>>, task: Task, resolver: ITaskResolver, trigger: string): TPromise<ITaskSummary> {
let promises: TPromise<ITaskSummary>[] = [];
if (task.dependsOn) {
task.dependsOn.forEach((identifier) => {
let task = this.identifier2Task[identifier];
let task = resolver.resolve(identifier);
if (task) {
let promise = startedTasks[task.id];
let promise = startedTasks[task._id];
if (!promise) {
promise = this.executeTask(startedTasks, task, trigger);
startedTasks[task.id] = promise;
promise = this.executeTask(startedTasks, task, resolver, trigger);
startedTasks[task._id] = promise;
}
promises.push(promise);
}
......@@ -253,7 +207,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
}
}
private executeCommand(task: TaskDescription, trigger: string): TPromise<ITaskSummary> {
private executeCommand(task: Task, trigger: string): TPromise<ITaskSummary> {
let terminal: ITerminalInstance = undefined;
let executedCommand: string = undefined;
let promise: TPromise<ITaskSummary> = undefined;
......@@ -261,7 +215,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
let watchingProblemMatcher = new WatchingProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
let toUnbind: IDisposable[] = [];
let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.Watching };
let event: TaskEvent = { taskId: task._id, taskName: task.name, type: TaskType.Watching };
let eventCounter: number = 0;
toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingBeginDetected, () => {
eventCounter++;
......@@ -290,11 +244,11 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
const onExit = terminal.onExit((exitCode) => {
onData.dispose();
onExit.dispose();
delete this.activeTasks[task.id];
delete this.activeTasks[task._id];
if (this.primaryTerminal && this.primaryTerminal.terminal === terminal) {
this.primaryTerminal.busy = false;
}
this.idleTaskTerminals[task.id] = terminal.id.toString();
this.idleTaskTerminals[task._id] = terminal.id.toString();
watchingProblemMatcher.dispose();
toUnbind = dispose(toUnbind);
toUnbind = null;
......@@ -323,11 +277,11 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
const onExit = terminal.onExit((exitCode) => {
onData.dispose();
onExit.dispose();
delete this.activeTasks[task.id];
delete this.activeTasks[task._id];
if (this.primaryTerminal && this.primaryTerminal.terminal === terminal) {
this.primaryTerminal.busy = false;
}
this.idleTaskTerminals[task.id] = terminal.id.toString();
this.idleTaskTerminals[task._id] = terminal.id.toString();
startStopProblemMatcher.processLine(decoder.end());
startStopProblemMatcher.done();
startStopProblemMatcher.dispose();
......@@ -340,7 +294,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
if (task.showOutput === ShowOutput.Always) {
this.terminalService.showPanel(false);
}
this.activeTasks[task.id] = { terminal, promise };
this.activeTasks[task._id] = { terminal, task, promise };
return promise.then((summary) => {
try {
let telemetryEvent: TelemetryEvent = {
......@@ -367,7 +321,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
});
}
private createTerminal(task: TaskDescription): [ITerminalInstance, string] {
private createTerminal(task: Task): [ITerminalInstance, string] {
let options = this.resolveOptions(task.command.options);
let { command, args } = this.resolveCommandAndArgs(task);
let terminalName = nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', task.name);
......@@ -441,16 +395,21 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
});
shellLaunchConfig.env = env;
}
let terminalId = this.idleTaskTerminals[task.id];
let terminalId = this.idleTaskTerminals[task._id];
if (terminalId) {
let taskTerminal = this.terminals[terminalId];
if (taskTerminal) {
delete this.idleTaskTerminals[task.id];
delete this.idleTaskTerminals[task._id];
taskTerminal.terminal.reuseTerminal(shellLaunchConfig);
return [taskTerminal.terminal, command];
}
}
if (this.primaryTerminal && !this.primaryTerminal.busy) {
// We reuse the primary terminal. Make sure the last running task isn't referenced in the idle terminals
let terminalData = this.terminals[this.primaryTerminal.terminal.id.toString()];
if (terminalData) {
delete this.idleTaskTerminals[terminalData.lastTask];
}
this.primaryTerminal.terminal.reuseTerminal(shellLaunchConfig);
this.primaryTerminal.busy = true;
return [this.primaryTerminal.terminal, command];
......@@ -467,14 +426,14 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
this.primaryTerminal = undefined;
}
});
this.terminals[key] = { terminal: result, lastTask: task.id };
this.terminals[key] = { terminal: result, lastTask: task._id };
if (!task.isBackground && !this.primaryTerminal) {
this.primaryTerminal = { terminal: result, busy: true };
}
return [result, command];
}
private resolveCommandAndArgs(task: TaskDescription): { command: string, args: string[] } {
private resolveCommandAndArgs(task: Task): { command: string, args: string[] } {
// First we need to use the command args:
let args: string[] = task.command.args ? task.command.args.slice() : [];
// We need to first pass the task name
......@@ -538,6 +497,9 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
}
private resolveMatchers<T extends ProblemMatcher>(values: T[]): T[] {
if (values === void 0 || values === null) {
return [];
}
if (values.length === 0) {
return values;
}
......@@ -559,7 +521,12 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
}
private resolveOptions(options: CommandOptions): CommandOptions {
let result: CommandOptions = { cwd: this.resolveVariable(options.cwd) };
if (options === void 0 || options === null) {
return { cwd: this.resolveVariable('${cwd}') };
}
let result: CommandOptions = Types.isString(options.cwd)
? { cwd: this.resolveVariable(options.cwd) }
: { cwd: this.resolveVariable('${cwd}') };
if (options.env) {
result.env = Object.create(null);
Object.keys(options.env).forEach((key) => {
......
......@@ -27,14 +27,13 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskRunnerConfiguration,
TaskDescription, CommandOptions, ShowOutput, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType,
CommandConfiguration
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType
} from 'vs/workbench/parts/tasks/common/taskSystem';
import { Task, CommandOptions, ShowOutput, CommandConfiguration } from 'vs/workbench/parts/tasks/common/tasks';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
export class ProcessTaskSystem extends EventEmitter implements ITaskSystem {
public static TelemetryEventName: string = 'taskService';
......@@ -44,18 +43,16 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
private telemetryService: ITelemetryService;
private configurationResolverService: IConfigurationResolverService;
private configuration: TaskRunnerConfiguration;
private outputChannel: IOutputChannel;
private errorsShown: boolean;
private childProcess: LineProcess;
private activeTaskIdentifier: string;
private activeTask: Task;
private activeTaskPromise: TPromise<ITaskSummary>;
constructor(configuration: TaskRunnerConfiguration, markerService: IMarkerService, modelService: IModelService, telemetryService: ITelemetryService,
outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string, hasErrors: boolean) {
constructor(markerService: IMarkerService, modelService: IModelService, telemetryService: ITelemetryService,
outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string) {
super();
this.configuration = configuration;
this.markerService = markerService;
this.modelService = modelService;
this.outputService = outputService;
......@@ -63,68 +60,35 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
this.configurationResolverService = configurationResolverService;
this.childProcess = null;
this.activeTaskIdentifier = null;
this.activeTask = null;
this.activeTaskPromise = null;
this.outputChannel = this.outputService.getChannel(outputChannelId);
this.errorsShown = !hasErrors;
this.errorsShown = true;
}
public build(): ITaskExecuteResult {
let buildTaskIdentifier = this.configuration.buildTasks.length > 0 ? this.configuration.buildTasks[0] : undefined;
if (this.activeTaskIdentifier) {
let task = this.configuration.tasks[this.activeTaskIdentifier];
return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === buildTaskIdentifier, background: task.isBackground }, promise: this.activeTaskPromise };
}
if (!buildTaskIdentifier) {
throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noBuildTask', 'No task is marked as a build task in the tasks.json. Mark a task with \'isBuildCommand\'.'), TaskErrors.NoBuildTask);
}
return this.executeTask(buildTaskIdentifier, Triggers.shortcut);
}
public rebuild(): ITaskExecuteResult {
throw new Error('Task - Rebuild: not implemented yet');
public isActive(): TPromise<boolean> {
return TPromise.as(!!this.childProcess);
}
public clean(): ITaskExecuteResult {
throw new Error('Task - Clean: not implemented yet');
public isActiveSync(): boolean {
return !!this.childProcess;
}
public runTest(): ITaskExecuteResult {
let testTaskIdentifier = this.configuration.testTasks.length > 0 ? this.configuration.testTasks[0] : undefined;
if (this.activeTaskIdentifier) {
let task = this.configuration.tasks[this.activeTaskIdentifier];
return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === testTaskIdentifier, background: task.isBackground }, promise: this.activeTaskPromise };
}
if (!testTaskIdentifier) {
throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noTestTask', 'No test task configured.'), TaskErrors.NoTestTask);
public run(task: Task): ITaskExecuteResult {
if (this.activeTask) {
return { kind: TaskExecuteKind.Active, active: { same: this.activeTask._id === task._id, background: this.activeTask.isBackground }, promise: this.activeTaskPromise };
}
return this.executeTask(testTaskIdentifier, Triggers.shortcut);
return this.executeTask(task);
}
public run(taskIdentifier: string): ITaskExecuteResult {
if (this.activeTaskIdentifier) {
let task = this.configuration.tasks[this.activeTaskIdentifier];
return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === taskIdentifier, background: task.isBackground }, promise: this.activeTaskPromise };
}
return this.executeTask(taskIdentifier);
}
public isActive(): TPromise<boolean> {
return TPromise.as(!!this.childProcess);
}
public isActiveSync(): boolean {
return !!this.childProcess;
public hasErrors(value: boolean): void {
this.errorsShown = !value;
}
public canAutoTerminate(): boolean {
if (this.childProcess) {
if (this.activeTaskIdentifier) {
let task = this.configuration.tasks[this.activeTaskIdentifier];
if (task) {
return !task.promptOnClose;
}
if (this.activeTask) {
return !this.activeTask.promptOnClose;
}
return false;
}
......@@ -138,21 +102,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
return TPromise.as({ success: true });
}
public tasks(): TPromise<TaskDescription[]> {
let result: TaskDescription[];
if (!this.configuration || !this.configuration.tasks) {
result = [];
} else {
result = Object.keys(this.configuration.tasks).map(key => this.configuration.tasks[key]);
}
return TPromise.as(result);
}
private executeTask(taskIdentifier: string, trigger: string = Triggers.command): ITaskExecuteResult {
let task = this.configuration.tasks[taskIdentifier];
if (!task) {
throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.norebuild', 'No task to execute found.'), TaskErrors.TaskNotFound);
}
private executeTask(task: Task, trigger: string = Triggers.command): ITaskExecuteResult {
let telemetryEvent: TelemetryEvent = {
trigger: trigger,
command: 'other',
......@@ -161,17 +111,17 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
try {
let result = this.doExecuteTask(task, telemetryEvent);
result.promise = result.promise.then((success) => {
this.telemetryService.publicLog(ProcessRunnerSystem.TelemetryEventName, telemetryEvent);
this.telemetryService.publicLog(ProcessTaskSystem.TelemetryEventName, telemetryEvent);
return success;
}, (err: any) => {
telemetryEvent.success = false;
this.telemetryService.publicLog(ProcessRunnerSystem.TelemetryEventName, telemetryEvent);
this.telemetryService.publicLog(ProcessTaskSystem.TelemetryEventName, telemetryEvent);
return TPromise.wrapError<ITaskSummary>(err);
});
return result;
} catch (err) {
telemetryEvent.success = false;
this.telemetryService.publicLog(ProcessRunnerSystem.TelemetryEventName, telemetryEvent);
this.telemetryService.publicLog(ProcessTaskSystem.TelemetryEventName, telemetryEvent);
if (err instanceof TaskError) {
throw err;
} else if (err instanceof Error) {
......@@ -185,7 +135,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
}
}
private doExecuteTask(task: TaskDescription, telemetryEvent: TelemetryEvent): ITaskExecuteResult {
private doExecuteTask(task: Task, telemetryEvent: TelemetryEvent): ITaskExecuteResult {
let taskSummary: ITaskSummary = {};
let commandConfig: CommandConfiguration = task.command;
if (!this.errorsShown) {
......@@ -224,7 +174,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
if (task.isBackground) {
let watchingProblemMatcher = new WatchingProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
let toUnbind: IDisposable[] = [];
let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.Watching };
let event: TaskEvent = { taskId: task._id, taskName: task.name, type: TaskType.Watching };
let eventCounter: number = 0;
toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingBeginDetected, () => {
eventCounter++;
......@@ -236,7 +186,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
}));
watchingProblemMatcher.aboutToStart();
let delayer: Async.Delayer<any> = null;
this.activeTaskIdentifier = task.id;
this.activeTask = task;
this.activeTaskPromise = this.childProcess.start().then((success): ITaskSummary => {
this.childProcessEnded();
watchingProblemMatcher.dispose();
......@@ -283,10 +233,10 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
: { kind: TaskExecuteKind.Started, started: {}, promise: this.activeTaskPromise };
return result;
} else {
let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.SingleRun };
let event: TaskEvent = { taskId: task._id, taskName: task.name, type: TaskType.SingleRun };
this.emit(TaskSystemEvents.Active, event);
let startStopProblemMatcher = new StartStopProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
this.activeTaskIdentifier = task.id;
this.activeTask = task;
this.activeTaskPromise = this.childProcess.start().then((success): ITaskSummary => {
this.childProcessEnded();
startStopProblemMatcher.done();
......@@ -314,11 +264,11 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
private childProcessEnded(): void {
this.childProcess = null;
this.activeTaskIdentifier = null;
this.activeTask = null;
this.activeTaskPromise = null;
}
private handleError(task: TaskDescription, error: ErrorData): Promise {
private handleError(task: Task, error: ErrorData): Promise {
let makeVisible = false;
if (error.error && !error.terminated) {
let args: string = task.command.args ? task.command.args.join(' ') : '';
......@@ -342,7 +292,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
return Promise.wrapError(error);
}
private checkTerminated(task: TaskDescription, data: SuccessData | ErrorData): boolean {
private checkTerminated(task: Task, data: SuccessData | ErrorData): boolean {
if (data.terminated) {
this.log(nls.localize('TaskRunnerSystem.cancelRequested', '\nThe task \'{0}\' was terminated per user request.', task.name));
return true;
......
......@@ -12,7 +12,7 @@ import * as Platform from 'vs/base/common/platform';
import { ValidationStatus } from 'vs/base/common/parsers';
import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/platform/markers/common/problemMatcher';
import * as TaskSystem from 'vs/workbench/parts/tasks/common/taskSystem';
import * as Tasks from 'vs/workbench/parts/tasks/common/tasks';
import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration } from 'vs/workbench/parts/tasks/common/taskConfiguration';
class ProblemReporter implements IProblemReporter {
......@@ -55,11 +55,11 @@ class ProblemReporter implements IProblemReporter {
class ConfiguationBuilder {
public result: TaskSystem.TaskRunnerConfiguration;
public result: Tasks.TaskSet;
constructor() {
this.result = {
tasks: Object.create(null),
tasks: [],
buildTasks: [],
testTasks: []
};
......@@ -67,7 +67,7 @@ class ConfiguationBuilder {
public task(name: string, command: string): TaskBuilder {
let builder = new TaskBuilder(this, name, command);
this.result.tasks[builder.result.name] = builder.result;
this.result.tasks.push(builder.result);
return builder;
}
......@@ -83,7 +83,7 @@ class ConfiguationBuilder {
}
class CommandConfigurationBuilder {
public result: TaskSystem.CommandConfiguration;
public result: Tasks.CommandConfiguration;
constructor(public parent: TaskBuilder, command: string) {
this.result = {
......@@ -112,7 +112,7 @@ class CommandConfigurationBuilder {
return this;
}
public options(value: TaskSystem.CommandOptions): CommandConfigurationBuilder {
public options(value: Tasks.CommandOptions): CommandConfigurationBuilder {
this.result.options = value;
return this;
}
......@@ -130,17 +130,17 @@ class CommandConfigurationBuilder {
class TaskBuilder {
public result: TaskSystem.TaskDescription;
public result: Tasks.Task;
private commandBuilder: CommandConfigurationBuilder;
constructor(public parent: ConfiguationBuilder, name: string, command: string) {
this.commandBuilder = new CommandConfigurationBuilder(this, command);
this.result = {
id: name,
_id: name,
identifier: name,
name: name,
command: this.commandBuilder.result,
showOutput: TaskSystem.ShowOutput.Always,
showOutput: Tasks.ShowOutput.Always,
suppressTaskName: false,
isBackground: false,
promptOnClose: true,
......@@ -158,7 +158,7 @@ class TaskBuilder {
return this;
}
public showOutput(value: TaskSystem.ShowOutput): TaskBuilder {
public showOutput(value: Tasks.ShowOutput): TaskBuilder {
this.result.showOutput = value;
return this;
}
......@@ -249,7 +249,7 @@ class PatternBuilder {
file: 1,
message: 0,
line: 2,
column: 3
character: 3
};
}
......@@ -273,8 +273,8 @@ class PatternBuilder {
return this;
}
public column(value: number): PatternBuilder {
this.result.column = value;
public character(value: number): PatternBuilder {
this.result.character = value;
return this;
}
......@@ -283,8 +283,8 @@ class PatternBuilder {
return this;
}
public endColumn(value: number): PatternBuilder {
this.result.endColumn = value;
public endCharacter(value: number): PatternBuilder {
this.result.endCharacter = value;
return this;
}
......@@ -308,10 +308,9 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re
let reporter = new ProblemReporter();
let result = parse(external, reporter);
assert.ok(!reporter.receivedMessage);
let config = result.configuration;
let keys = Object.keys(config.tasks);
assert.strictEqual(keys.length, 1);
let task = config.tasks[keys[0]];
let taskSet = result.taskSet;
assert.strictEqual(taskSet.tasks.length, 1);
let task = taskSet.tasks[0];
assert.ok(task);
assert.strictEqual(task.problemMatchers.length, resolved);
......@@ -326,35 +325,40 @@ function testConfiguration(external: ExternalTaskRunnerConfiguration, builder: C
assertConfiguration(result, builder.result);
}
function assertConfiguration(result: ParseResult, expected: TaskSystem.TaskRunnerConfiguration) {
function assertConfiguration(result: ParseResult, expected: Tasks.TaskSet) {
assert.ok(result.validationStatus.isOK());
let actual = result.configuration;
let actual = result.taskSet;
assert.strictEqual(typeof actual.tasks, typeof expected.tasks);
let actualBuildTasks: string[] = [];
let actualTestTasks: string[] = [];
if (actual.tasks && expected.tasks) {
// We can't compare Ids since the parser uses UUID which are random
// So create a new map using the name.
let actualTasks: { [key: string]: TaskSystem.TaskDescription; } = Object.create(null);
Object.keys(actual.tasks).forEach((key) => {
let task = actual.tasks[key];
let actualTasks: { [key: string]: Tasks.Task; } = Object.create(null);
let actualId2Name: { [key: string]: string; } = Object.create(null);
actual.tasks.forEach(task => {
assert.ok(!actualTasks[task.name]);
actualTasks[task.name] = task;
actualId2Name[task._id] = task.name;
});
let expectedTasks: { [key: string]: Tasks.Task; } = Object.create(null);
expected.tasks.forEach(task => {
assert.ok(!expectedTasks[task.name]);
expectedTasks[task.name] = task;
});
let actualKeys = Object.keys(actualTasks);
let expectedKeys = Object.keys(expected.tasks);
assert.strictEqual(actualKeys.length, expectedKeys.length);
assert.strictEqual(actualKeys.length, Object.keys(expected.tasks).length);
actualKeys.forEach((key) => {
let actualTask = actualTasks[key];
let expectedTask = expected.tasks[key];
let expectedTask = expectedTasks[key];
assert.ok(expectedTask);
assertTask(actualTask, expectedTask);
});
actual.buildTasks.forEach((id) => {
actualBuildTasks.push(actual.tasks[id].name);
actualBuildTasks.push(actualId2Name[id]);
});
actual.testTasks.forEach((id) => {
actualTestTasks.push(actual.tasks[id].name);
actualTestTasks.push(actualId2Name[id]);
});
}
assertTaskConfig(actualBuildTasks, expected.buildTasks);
......@@ -369,8 +373,8 @@ function assertTaskConfig(actual: string[], expected: string[]): void {
}
}
function assertTask(actual: TaskSystem.TaskDescription, expected: TaskSystem.TaskDescription) {
assert.ok(actual.id);
function assertTask(actual: Tasks.Task, expected: Tasks.Task) {
assert.ok(actual._id);
assert.strictEqual(actual.name, expected.name, 'name');
assertCommandConfiguration(actual.command, expected.command);
assert.strictEqual(actual.showOutput, expected.showOutput, 'showOutput');
......@@ -386,7 +390,7 @@ function assertTask(actual: TaskSystem.TaskDescription, expected: TaskSystem.Tas
}
}
function assertCommandConfiguration(actual: TaskSystem.CommandConfiguration, expected: TaskSystem.CommandConfiguration) {
function assertCommandConfiguration(actual: Tasks.CommandConfiguration, expected: Tasks.CommandConfiguration) {
assert.strictEqual(typeof actual, typeof expected);
if (actual && expected) {
assert.strictEqual(actual.name, expected.name, 'name');
......@@ -446,9 +450,9 @@ function assertProblemPattern(actual: ProblemPattern, expected: ProblemPattern)
assert.strictEqual(actual.location, expected.location);
} else {
assert.strictEqual(actual.line, expected.line);
assert.strictEqual(actual.column, expected.column);
assert.strictEqual(actual.character, expected.character);
assert.strictEqual(actual.endLine, expected.endLine);
assert.strictEqual(actual.endColumn, expected.endColumn);
assert.strictEqual(actual.endCharacter, expected.endCharacter);
}
assert.strictEqual(actual.code, expected.code);
assert.strictEqual(actual.severity, expected.severity);
......@@ -487,7 +491,7 @@ suite('Tasks Configuration parsing tests', () => {
builder.
task('tsc', 'tsc').
suppressTaskName(true).
showOutput(TaskSystem.ShowOutput.Silent);
showOutput(Tasks.ShowOutput.Silent);
testConfiguration(
{
version: '0.1.0',
......@@ -548,7 +552,7 @@ suite('Tasks Configuration parsing tests', () => {
builder.
task('tsc', 'tsc').
suppressTaskName(true).
showOutput(TaskSystem.ShowOutput.Never);
showOutput(Tasks.ShowOutput.Never);
testConfiguration(
{
version: '0.1.0',
......@@ -708,7 +712,7 @@ suite('Tasks Configuration parsing tests', () => {
let builder = new ConfiguationBuilder().buildTask('tsc');
builder.
task('tsc', 'tsc').
showOutput(Platform.isWindows ? TaskSystem.ShowOutput.Always : TaskSystem.ShowOutput.Never).
showOutput(Platform.isWindows ? Tasks.ShowOutput.Always : Tasks.ShowOutput.Never).
suppressTaskName(true);
let external: ExternalTaskRunnerConfiguration = {
version: '0.1.0',
......@@ -850,7 +854,7 @@ suite('Tasks Configuration parsing tests', () => {
};
let builder = new ConfiguationBuilder().testTask('test');
builder.task('test', 'tsc').
showOutput(TaskSystem.ShowOutput.Never).
showOutput(Tasks.ShowOutput.Never).
args(['--p']).
isBackground(true).
promptOnClose(false).
......@@ -874,7 +878,7 @@ suite('Tasks Configuration parsing tests', () => {
};
let builder = new ConfiguationBuilder().testTask('test');
builder.task('test', 'tsc').
showOutput(TaskSystem.ShowOutput.Never).
showOutput(Tasks.ShowOutput.Never).
command().
echo(true);
......@@ -1027,7 +1031,7 @@ suite('Tasks Configuration parsing tests', () => {
let builder = new ConfiguationBuilder();
builder.task('taskName', 'tsc').problemMatcher().
pattern(/abc/).file(10).message(11).
line(12).column(13).endLine(14).endColumn(15).
line(12).character(13).endLine(14).endCharacter(15).
severity(16).code(17);
testConfiguration(external, builder);
});
......@@ -1338,13 +1342,13 @@ suite('Bugs / regression tests', () => {
let builder = new ConfiguationBuilder();
if (Platform.isWindows) {
builder.task('composeForDebug', 'powershell').
suppressTaskName(true).showOutput(TaskSystem.ShowOutput.Always).
suppressTaskName(true).showOutput(Tasks.ShowOutput.Always).
args(['-ExecutionPolicy', 'RemoteSigned', '.\\dockerTask.ps1', '-ComposeForDebug', '-Environment', 'debug']).
command().echo(true).options({ cwd: '${workspaceRoot}' });
testConfiguration(external, builder);
} else if (Platform.isMacintosh) {
builder.task('composeForDebug', '/bin/bash').
suppressTaskName(true).showOutput(TaskSystem.ShowOutput.Always).
suppressTaskName(true).showOutput(Tasks.ShowOutput.Always).
args(['-c', './dockerTask.sh composeForDebug debug']).
command().options({ cwd: '${workspaceRoot}' });
testConfiguration(external, builder);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册