mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
Fixes #25518: Cannot associate a problem matcher with an auto detected tasks
This commit is contained in:
parent
a331c647e7
commit
87cfa19524
|
@ -33,7 +33,8 @@ const extensions = [
|
|||
'html',
|
||||
'git',
|
||||
'gulp',
|
||||
'grunt'
|
||||
'grunt',
|
||||
'jake'
|
||||
];
|
||||
|
||||
extensions.forEach(extension => npmInstall(`extensions/${extension}`));
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"Other"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:gulp",
|
||||
"watch": "gulp watch-extension:gulp"
|
||||
"compile": "gulp compile-extension:grunt",
|
||||
"watch": "gulp watch-extension:grunt"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^2.0.2"
|
||||
|
|
|
@ -103,6 +103,7 @@ async function getGulpTasks(): Promise<vscode.Task[]> {
|
|||
let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
|
||||
if (stderr) {
|
||||
channel.appendLine(stderr);
|
||||
channel.show(true);
|
||||
}
|
||||
let result: vscode.Task[] = [];
|
||||
if (stdout) {
|
||||
|
|
48
extensions/jake/package.json
Normal file
48
extensions/jake/package.json
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "jake",
|
||||
"publisher": "vscode",
|
||||
"description": "Extension to add Jake capabilities to VSCode.",
|
||||
"displayName": "Jake support for VSCode",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "*"
|
||||
},
|
||||
"enableProposedApi": true,
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:jake",
|
||||
"watch": "gulp watch-extension:jake"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^7.0.18"
|
||||
},
|
||||
"main": "./out/main",
|
||||
"activationEvents": [
|
||||
"onCommand:workbench.action.tasks.runTask",
|
||||
"onCommand:workbench.action.tasks.build",
|
||||
"onCommand:workbench.action.tasks.test"
|
||||
],
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"id": "jake",
|
||||
"type": "object",
|
||||
"title": "Jake",
|
||||
"properties": {
|
||||
"jake.autoDetect": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"off",
|
||||
"on"
|
||||
],
|
||||
"default": "on",
|
||||
"description": "%config.jake.autoDetect%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
extensions/jake/package.nls.json
Normal file
3
extensions/jake/package.nls.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"config.jake.autoDetect": "Controls whether auto detection of Jake tasks is on or off. Default is on."
|
||||
}
|
151
extensions/jake/src/main.ts
Normal file
151
extensions/jake/src/main.ts
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.config(process.env.VSCODE_NLS_CONFIG)();
|
||||
|
||||
type AutoDetect = 'on' | 'off';
|
||||
let taskProvider: vscode.Disposable | undefined;
|
||||
|
||||
export function activate(_context: vscode.ExtensionContext): void {
|
||||
let workspaceRoot = vscode.workspace.rootPath;
|
||||
if (!workspaceRoot) {
|
||||
return;
|
||||
}
|
||||
let pattern = path.join(workspaceRoot, '{Jakefile,Jakefile.js}');
|
||||
let jakePromise: Thenable<vscode.Task[]> | undefined = undefined;
|
||||
let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
fileWatcher.onDidChange(() => jakePromise = undefined);
|
||||
fileWatcher.onDidCreate(() => jakePromise = undefined);
|
||||
fileWatcher.onDidDelete(() => jakePromise = undefined);
|
||||
|
||||
function onConfigurationChanged() {
|
||||
let autoDetect = vscode.workspace.getConfiguration('jake').get<AutoDetect>('autoDetect');
|
||||
if (taskProvider && autoDetect === 'off') {
|
||||
jakePromise = undefined;
|
||||
taskProvider.dispose();
|
||||
taskProvider = undefined;
|
||||
} else if (!taskProvider && autoDetect === 'on') {
|
||||
taskProvider = vscode.workspace.registerTaskProvider({
|
||||
provideTasks: () => {
|
||||
if (!jakePromise) {
|
||||
jakePromise = getJakeTasks();
|
||||
}
|
||||
return jakePromise;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
vscode.workspace.onDidChangeConfiguration(onConfigurationChanged);
|
||||
onConfigurationChanged();
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
if (taskProvider) {
|
||||
taskProvider.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function exists(file: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, _reject) => {
|
||||
fs.exists(file, (value) => {
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
|
||||
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
||||
cp.exec(command, options, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject({ error, stdout, stderr });
|
||||
}
|
||||
resolve({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getJakeTasks(): Promise<vscode.Task[]> {
|
||||
let workspaceRoot = vscode.workspace.rootPath;
|
||||
let emptyTasks: vscode.Task[] = [];
|
||||
if (!workspaceRoot) {
|
||||
return emptyTasks;
|
||||
}
|
||||
let jakefile = path.join(workspaceRoot, 'Jakefile');
|
||||
if (!await exists(jakefile)) {
|
||||
jakefile = path.join(workspaceRoot, 'Jakefile.js');
|
||||
if (! await exists(jakefile)) {
|
||||
return emptyTasks;
|
||||
}
|
||||
}
|
||||
|
||||
let jakeCommand: string;
|
||||
let platform = process.platform;
|
||||
if (platform === 'win32' && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'jake.cmd'))) {
|
||||
jakeCommand = path.join('.', 'node_modules', '.bin', 'jake.cmd');
|
||||
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(workspaceRoot!, 'node_modules', '.bin', 'jake'))) {
|
||||
jakeCommand = path.join('.', 'node_modules', '.bin', 'jake');
|
||||
} else {
|
||||
jakeCommand = 'jake';
|
||||
}
|
||||
|
||||
let commandLine = `${jakeCommand} --tasks`;
|
||||
let channel = vscode.window.createOutputChannel('tasks');
|
||||
try {
|
||||
let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot });
|
||||
if (stderr) {
|
||||
channel.appendLine(stderr);
|
||||
channel.show(true);
|
||||
}
|
||||
let result: vscode.Task[] = [];
|
||||
if (stdout) {
|
||||
let buildTask: { task: vscode.Task | undefined, rank: number } = { task: undefined, rank: 0 };
|
||||
let testTask: { task: vscode.Task | undefined, rank: number } = { task: undefined, rank: 0 };
|
||||
let lines = stdout.split(/\r{0,1}\n/);
|
||||
for (let line of lines) {
|
||||
if (line.length === 0) {
|
||||
continue;
|
||||
}
|
||||
let regExp = /^jake\s+([^\s]+)\s/g;
|
||||
let matches = regExp.exec(line);
|
||||
if (matches && matches.length === 2) {
|
||||
let taskName = matches[1];
|
||||
let task = new vscode.ShellTask(`jake: ${taskName}`, `${jakeCommand} ${taskName}`);
|
||||
task.identifier = `jake.${taskName}`;
|
||||
result.push(task);
|
||||
let lowerCaseLine = line.toLowerCase();
|
||||
if (lowerCaseLine === 'build') {
|
||||
buildTask = { task, rank: 2 };
|
||||
} else if (lowerCaseLine.indexOf('build') !== -1 && buildTask.rank < 1) {
|
||||
buildTask = { task, rank: 1 };
|
||||
} else if (lowerCaseLine === 'test') {
|
||||
testTask = { task, rank: 2 };
|
||||
} else if (lowerCaseLine.indexOf('test') !== -1 && testTask.rank < 1) {
|
||||
testTask = { task, rank: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buildTask.task) {
|
||||
buildTask.task.group = vscode.TaskGroup.Build;
|
||||
}
|
||||
if (testTask.task) {
|
||||
testTask.task.group = vscode.TaskGroup.Test;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (err.stderr) {
|
||||
channel.appendLine(err.stderr);
|
||||
}
|
||||
channel.appendLine(localize('execFailed', 'Auto detecting Jake failed with error: {0}', err.error ? err.error.toString() : 'unknown'));
|
||||
return emptyTasks;
|
||||
}
|
||||
}
|
8
extensions/jake/src/typings/refs.d.ts
vendored
Normal file
8
extensions/jake/src/typings/refs.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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'/>
|
18
extensions/jake/tsconfig.json
Normal file
18
extensions/jake/tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"outDir": "./out",
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
|
@ -268,7 +268,7 @@ namespace ShellConfiguration {
|
|||
|
||||
namespace Tasks {
|
||||
|
||||
export function from(tasks: vscode.Task[], uuidMap: UUIDMap): TaskSystem.Task[] {
|
||||
export function from(tasks: vscode.Task[], extension: IExtensionDescription, uuidMap: UUIDMap): TaskSystem.Task[] {
|
||||
if (tasks === void 0 || tasks === null) {
|
||||
return [];
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ namespace Tasks {
|
|||
try {
|
||||
uuidMap.start();
|
||||
for (let task of tasks) {
|
||||
let converted = fromSingle(task, uuidMap);
|
||||
let converted = fromSingle(task, extension, uuidMap);
|
||||
if (converted) {
|
||||
result.push(converted);
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ namespace Tasks {
|
|||
return result;
|
||||
}
|
||||
|
||||
function fromSingle(task: vscode.Task, uuidMap: UUIDMap): TaskSystem.Task {
|
||||
function fromSingle(task: vscode.Task, extension: IExtensionDescription, uuidMap: UUIDMap): TaskSystem.Task {
|
||||
if (typeof task.name !== 'string' || typeof task.identifier !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -306,6 +306,7 @@ namespace Tasks {
|
|||
command.echo = behaviour.echo;
|
||||
let result: TaskSystem.Task = {
|
||||
_id: uuidMap.getUUID(task.identifier),
|
||||
_source: { kind: TaskSystem.TaskSourceKind.Extension, detail: extension.id },
|
||||
name: task.name,
|
||||
identifier: task.identifier,
|
||||
group: types.TaskGroup.is(task.group) ? task.group : undefined,
|
||||
|
@ -421,7 +422,7 @@ export class ExtHostTask extends ExtHostTaskShape {
|
|||
}
|
||||
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
|
||||
return {
|
||||
tasks: Tasks.from(value, this.getUUIDMap(handler.extension.id)),
|
||||
tasks: Tasks.from(value, handler.extension, this.getUUIDMap(handler.extension.id)),
|
||||
extension: handler.extension
|
||||
};
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ 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 { Task } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { Task, TaskSourceKind } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
|
||||
export class TaskEntry extends Model.QuickOpenEntry {
|
||||
|
@ -31,11 +31,20 @@ export class TaskEntry extends Model.QuickOpenEntry {
|
|||
}
|
||||
}
|
||||
|
||||
export class TaskGroupEntry extends Model.QuickOpenEntryGroup {
|
||||
constructor(entry: TaskEntry, groupLabel: string, withBorder: boolean) {
|
||||
super(entry, groupLabel, withBorder);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
|
||||
|
||||
private tasks: TPromise<Task[]>;
|
||||
|
||||
|
||||
constructor(
|
||||
@IQuickOpenService protected quickOpenService: IQuickOpenService,
|
||||
@ITaskService protected taskService: ITaskService
|
||||
protected quickOpenService: IQuickOpenService,
|
||||
protected taskService: ITaskService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -43,38 +52,64 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
|
|||
this.taskService = taskService;
|
||||
}
|
||||
|
||||
public onOpen(): void {
|
||||
this.tasks = this.getTasks();
|
||||
}
|
||||
|
||||
public onClose(canceled: boolean): void {
|
||||
this.tasks = undefined;
|
||||
}
|
||||
|
||||
public getResults(input: string): TPromise<Model.QuickOpenModel> {
|
||||
return this.getTasks().then(tasks => tasks
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(task => ({ task: task, highlights: Filters.matchesContiguousSubString(input, task.name) }))
|
||||
.filter(({ highlights }) => !!highlights)
|
||||
.map(({ task, highlights }) => this.createEntry(this.taskService, task, highlights))
|
||||
, _ => []).then(e => new Model.QuickOpenModel(e));
|
||||
return this.tasks.then((tasks) => {
|
||||
let entries: Model.QuickOpenEntry[] = [];
|
||||
if (tasks.length === 0) {
|
||||
return new Model.QuickOpenModel(entries);
|
||||
}
|
||||
tasks = tasks.sort((a, b) => {
|
||||
let aKind = a._source.kind;
|
||||
let bKind = b._source.kind;
|
||||
if (aKind === bKind) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
if (aKind === TaskSourceKind.Workspace) {
|
||||
return -1;
|
||||
} else {
|
||||
return +1;
|
||||
}
|
||||
});
|
||||
let hasWorkspace: boolean = tasks[0]._source.kind === TaskSourceKind.Workspace;
|
||||
let hasExtension: boolean = tasks[tasks.length - 1]._source.kind === TaskSourceKind.Extension;
|
||||
let groupWorkspace = hasWorkspace && hasExtension;
|
||||
let groupExtension = groupWorkspace;
|
||||
let hadWorkspace = false;
|
||||
for (let task of tasks) {
|
||||
let highlights = Filters.matchesContiguousSubString(input, task.name);
|
||||
if (!highlights) {
|
||||
continue;
|
||||
}
|
||||
if (task._source.kind === TaskSourceKind.Workspace && groupWorkspace) {
|
||||
groupWorkspace = false;
|
||||
hadWorkspace = true;
|
||||
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('workspace', 'From Workspace'), false));
|
||||
} else if (task._source.kind === TaskSourceKind.Extension && groupExtension) {
|
||||
groupExtension = false;
|
||||
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('extension', 'From Extensions'), hadWorkspace));
|
||||
} else {
|
||||
entries.push(this.createEntry(this.taskService, task, highlights));
|
||||
}
|
||||
}
|
||||
return new Model.QuickOpenModel(entries);
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract getTasks(): TPromise<Task[]>;
|
||||
|
||||
protected abstract createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): TaskEntry;
|
||||
|
||||
public getClass(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
public canRun(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public getAutoFocus(input: string): QuickOpen.IAutoFocus {
|
||||
return {
|
||||
autoFocusFirstEntry: !!input
|
||||
};
|
||||
}
|
||||
|
||||
public onClose(canceled: boolean): void {
|
||||
return;
|
||||
}
|
||||
|
||||
public getGroupLabel(): string {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -20,10 +20,6 @@ class TaskEntry extends base.TaskEntry {
|
|||
super(taskService, task, highlights);
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel());
|
||||
}
|
||||
|
||||
public run(mode: QuickOpen.Mode, context: Model.IContext): boolean {
|
||||
if (mode === QuickOpen.Mode.PREVIEW) {
|
||||
return false;
|
||||
|
|
|
@ -22,10 +22,6 @@ class TaskEntry extends base.TaskEntry {
|
|||
super(taskService, task, highlights);
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel());
|
||||
}
|
||||
|
||||
public run(mode: QuickOpen.Mode, context: Model.IContext): boolean {
|
||||
if (mode === QuickOpen.Mode.PREVIEW) {
|
||||
return false;
|
||||
|
|
|
@ -631,13 +631,24 @@ namespace ProblemMatcherConverter {
|
|||
}
|
||||
}
|
||||
|
||||
interface TaskParseResult {
|
||||
tasks: Tasks.Task[] | undefined;
|
||||
annotatingTasks: Tasks.Task[] | undefined;
|
||||
}
|
||||
|
||||
namespace TaskDescription {
|
||||
|
||||
export function from(this: void, tasks: TaskDescription[], globals: Globals, context: ParseContext): Tasks.Task[] {
|
||||
export let source: Tasks.TaskSource = {
|
||||
kind: Tasks.TaskSourceKind.Workspace,
|
||||
detail: '.settins\tasks.json'
|
||||
};
|
||||
|
||||
export function from(this: void, tasks: TaskDescription[], globals: Globals, context: ParseContext): TaskParseResult {
|
||||
if (!tasks) {
|
||||
return undefined;
|
||||
}
|
||||
let parsedTasks: Tasks.Task[] = [];
|
||||
let annotatingTasks: Tasks.Task[] = [];
|
||||
let defaultBuildTask: { task: Tasks.Task; rank: number; } = { task: null, rank: -1 };
|
||||
let defaultTestTask: { task: Tasks.Task; rank: number; } = { task: null, rank: -1 };
|
||||
tasks.forEach((externalTask) => {
|
||||
|
@ -653,6 +664,7 @@ namespace TaskDescription {
|
|||
let identifer = Types.isString(externalTask.identifier) ? externalTask.identifier : taskName;
|
||||
let task: Tasks.Task = {
|
||||
_id: UUID.generateUuid(),
|
||||
_source: source,
|
||||
name: taskName,
|
||||
identifier: identifer,
|
||||
command,
|
||||
|
@ -691,6 +703,10 @@ namespace TaskDescription {
|
|||
task.problemMatchers = problemMatchers;
|
||||
}
|
||||
mergeGlobals(task, globals);
|
||||
if (context.isTermnial && isAnnotating(task)) {
|
||||
annotatingTasks.push(task);
|
||||
return;
|
||||
}
|
||||
fillDefaults(task);
|
||||
let addTask: boolean = true;
|
||||
if (context.isTermnial && task.command && task.command.name && task.command.isShellCommand && task.command.args && task.command.args.length > 0) {
|
||||
|
@ -739,10 +755,10 @@ namespace TaskDescription {
|
|||
if (defaultTestTask.task) {
|
||||
defaultTestTask.task.group = Tasks.TaskGroup.Test;
|
||||
}
|
||||
return parsedTasks.length === 0 ? undefined : parsedTasks;
|
||||
return parsedTasks.length === 0 && annotatingTasks.length === 0 ? undefined : { tasks: parsedTasks, annotatingTasks: annotatingTasks };
|
||||
}
|
||||
|
||||
export function merge(target: Tasks.Task[], source: Tasks.Task[]): Tasks.Task[] {
|
||||
export function mergeTasks(target: Tasks.Task[], source: Tasks.Task[]): Tasks.Task[] {
|
||||
if (source === void 0 || source.length === 0) {
|
||||
return target;
|
||||
}
|
||||
|
@ -841,6 +857,30 @@ namespace TaskDescription {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isAnnotating(task: Tasks.Task): boolean {
|
||||
return (task.command === void 0 || task.command.name === void 0) && (task.dependsOn === void 0 || task.dependsOn.length === 0);
|
||||
}
|
||||
|
||||
export function merge(target: Tasks.Task, source: Tasks.Task): Tasks.Task {
|
||||
if (!target) {
|
||||
return source;
|
||||
}
|
||||
if (!source) {
|
||||
return target;
|
||||
}
|
||||
|
||||
mergeProperty(target, source, 'group');
|
||||
target.command = CommandConfiguration.merge(target.command, source.command);
|
||||
mergeProperty(target, source, 'suppressTaskName');
|
||||
mergeProperty(target, source, 'args');
|
||||
mergeProperty(target, source, 'isBackground');
|
||||
mergeProperty(target, source, 'promptOnClose');
|
||||
mergeProperty(target, source, 'showOutput');
|
||||
mergeProperty(target, source, 'dependsOn');
|
||||
mergeProperty(target, source, 'problemMatchers');
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
interface Globals {
|
||||
|
@ -950,6 +990,7 @@ export namespace ExecutionEngine {
|
|||
export interface ParseResult {
|
||||
validationStatus: ValidationStatus;
|
||||
tasks: Tasks.Task[];
|
||||
annotatingTasks: Tasks.Task[];
|
||||
engine: Tasks.ExecutionEngine;
|
||||
}
|
||||
|
||||
|
@ -960,7 +1001,6 @@ export interface IProblemReporter extends IProblemReporterBase {
|
|||
class ConfigurationParser {
|
||||
|
||||
private problemReporter: IProblemReporter;
|
||||
|
||||
constructor(problemReporter: IProblemReporter) {
|
||||
this.problemReporter = problemReporter;
|
||||
}
|
||||
|
@ -970,21 +1010,27 @@ class ConfigurationParser {
|
|||
if (engine === Tasks.ExecutionEngine.Terminal) {
|
||||
this.problemReporter.clearOutput();
|
||||
}
|
||||
let context: ParseContext = { problemReporter: this.problemReporter, namedProblemMatchers: undefined, isTermnial: engine === Tasks.ExecutionEngine.Terminal };
|
||||
let context: ParseContext = {
|
||||
problemReporter: this.problemReporter,
|
||||
namedProblemMatchers: undefined,
|
||||
isTermnial: engine === Tasks.ExecutionEngine.Terminal
|
||||
};
|
||||
let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context);
|
||||
return {
|
||||
validationStatus: this.problemReporter.status,
|
||||
tasks: this.createTaskRunnerConfiguration(fileConfig, context),
|
||||
tasks: taskParseResult.tasks,
|
||||
annotatingTasks: taskParseResult.annotatingTasks,
|
||||
engine
|
||||
};
|
||||
}
|
||||
|
||||
private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): Tasks.Task[] {
|
||||
private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): TaskParseResult {
|
||||
let globals = Globals.from(fileConfig, context);
|
||||
if (this.problemReporter.status.isFatal()) {
|
||||
return undefined;
|
||||
}
|
||||
context.namedProblemMatchers = ProblemMatcherConverter.namedFrom(fileConfig.declares, context);
|
||||
let globalTasks: Tasks.Task[];
|
||||
let globalTasks: TaskParseResult;
|
||||
if (fileConfig.windows && Platform.platform === Platform.Platform.Windows) {
|
||||
globalTasks = TaskDescription.from(fileConfig.windows.tasks, globals, context);
|
||||
} else if (fileConfig.osx && Platform.platform === Platform.Platform.Mac) {
|
||||
|
@ -993,36 +1039,44 @@ class ConfigurationParser {
|
|||
globalTasks = TaskDescription.from(fileConfig.linux.tasks, globals, context);
|
||||
}
|
||||
|
||||
let tasks: Tasks.Task[];
|
||||
let result: TaskParseResult = { tasks: undefined, annotatingTasks: undefined };
|
||||
if (fileConfig.tasks) {
|
||||
tasks = TaskDescription.from(fileConfig.tasks, globals, context);
|
||||
result = TaskDescription.from(fileConfig.tasks, globals, context);
|
||||
}
|
||||
if (globalTasks) {
|
||||
result.tasks = TaskDescription.mergeTasks(result.tasks, globalTasks.tasks);
|
||||
result.annotatingTasks = TaskDescription.mergeTasks(result.annotatingTasks, globalTasks.annotatingTasks);
|
||||
}
|
||||
tasks = TaskDescription.merge(tasks, globalTasks);
|
||||
|
||||
if (!tasks || tasks.length === 0) {
|
||||
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: Tasks.Task = {
|
||||
_id: UUID.generateUuid(),
|
||||
name: globals.command.name,
|
||||
identifier: globals.command.name,
|
||||
group: Tasks.TaskGroup.Build,
|
||||
command: undefined,
|
||||
isBackground: isBackground,
|
||||
showOutput: undefined,
|
||||
suppressTaskName: true, // this must be true since we infer the task from the global data.
|
||||
problemMatchers: matchers
|
||||
};
|
||||
TaskDescription.mergeGlobals(task, globals);
|
||||
TaskDescription.fillDefaults(task);
|
||||
tasks = [task];
|
||||
}
|
||||
if ((!result.tasks || result.tasks.length === 0) && (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: Tasks.Task = {
|
||||
_id: UUID.generateUuid(),
|
||||
_source: TaskDescription.source,
|
||||
name: globals.command.name,
|
||||
identifier: globals.command.name,
|
||||
group: Tasks.TaskGroup.Build,
|
||||
command: undefined,
|
||||
isBackground: isBackground,
|
||||
showOutput: undefined,
|
||||
suppressTaskName: true, // this must be true since we infer the task from the global data.
|
||||
problemMatchers: matchers
|
||||
};
|
||||
TaskDescription.mergeGlobals(task, globals);
|
||||
TaskDescription.fillDefaults(task);
|
||||
result.tasks = [task];
|
||||
}
|
||||
return tasks || [];
|
||||
result.tasks = result.tasks || [];
|
||||
result.annotatingTasks = result.annotatingTasks || [];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function parse(configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter): ParseResult {
|
||||
return (new ConfigurationParser(logger)).run(configuration);
|
||||
}
|
||||
|
||||
export function mergeTasks(target: Tasks.Task, source: Tasks.Task): Tasks.Task {
|
||||
return TaskDescription.merge(target, source);
|
||||
}
|
|
@ -110,6 +110,17 @@ export namespace TaskGroup {
|
|||
|
||||
export type TaskGroup = 'clean' | 'build' | 'rebuildAll' | 'test';
|
||||
|
||||
export enum TaskSourceKind {
|
||||
Workspace = 1,
|
||||
Extension = 2,
|
||||
Generic = 3
|
||||
}
|
||||
|
||||
export interface TaskSource {
|
||||
kind: TaskSourceKind;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A task description
|
||||
*/
|
||||
|
@ -120,6 +131,11 @@ export interface Task {
|
|||
*/
|
||||
_id: string;
|
||||
|
||||
/**
|
||||
* Indicated the source of the task (e.g tasks.json or extension)
|
||||
*/
|
||||
_source: TaskSource;
|
||||
|
||||
/**
|
||||
* The task's name
|
||||
*/
|
||||
|
|
|
@ -69,7 +69,7 @@ import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutpu
|
|||
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
|
||||
import { ITaskSystem, ITaskResolver, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskSystemEvents } from 'vs/workbench/parts/tasks/common/taskSystem';
|
||||
import { Task, TaskSet, TaskGroup, ExecutionEngine, ShowOutput } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { Task, TaskSet, TaskGroup, ExecutionEngine, ShowOutput, TaskSourceKind } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { ITaskService, TaskServiceEvents, ITaskProvider } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates';
|
||||
|
||||
|
@ -489,6 +489,10 @@ class ProblemReporter implements TaskConfig.IProblemReporter {
|
|||
|
||||
interface WorkspaceTaskResult {
|
||||
set: TaskSet;
|
||||
annotatingTasks: {
|
||||
byIdentifier: IStringDictionary<Task>;
|
||||
byName: IStringDictionary<Task>;
|
||||
};
|
||||
hasErrors: boolean;
|
||||
}
|
||||
|
||||
|
@ -522,7 +526,8 @@ class TaskService extends EventEmitter implements ITaskService {
|
|||
|
||||
private _configHasErrors: boolean;
|
||||
private _providers: Map<number, ITaskProvider>;
|
||||
private _workspaceTasksPromise: TPromise<TaskSet>;
|
||||
|
||||
private _workspaceTasksPromise: TPromise<WorkspaceTaskResult>;
|
||||
|
||||
private _taskSystem: ITaskSystem;
|
||||
private _taskSystemListeners: IDisposable[];
|
||||
|
@ -759,6 +764,7 @@ class TaskService extends EventEmitter implements ITaskService {
|
|||
let id: string = UUID.generateUuid();
|
||||
let task: Task = {
|
||||
_id: id,
|
||||
_source: { kind: TaskSourceKind.Generic },
|
||||
name: id,
|
||||
identifier: id,
|
||||
dependsOn: primaryTasks.map(task => task._id),
|
||||
|
@ -894,30 +900,94 @@ class TaskService extends EventEmitter implements ITaskService {
|
|||
provider.provideTasks().done(done, error);
|
||||
});
|
||||
}
|
||||
// Do this last since the then of a resolved promise returns immediatelly.
|
||||
counter++;
|
||||
this.getWorkspaceTasks().done(done, error);
|
||||
}).then((result) => {
|
||||
return this.getWorkspaceTasks().then((workspaceTaskResult) => {
|
||||
let workspaceTasksToDelete: Task[] = [];
|
||||
let annotatingTasks = workspaceTaskResult.annotatingTasks;
|
||||
let legacyAnnotatingTasks = workspaceTaskResult.set ? this.getLegacyAnnotatingTasks(workspaceTaskResult.set) : undefined;
|
||||
if (annotatingTasks || legacyAnnotatingTasks) {
|
||||
for (let set of result) {
|
||||
for (let task of set.tasks) {
|
||||
if (annotatingTasks) {
|
||||
let annotatingTask = annotatingTasks.byIdentifier[task.identifier] || annotatingTasks.byName[task.name];
|
||||
if (annotatingTask) {
|
||||
TaskConfig.mergeTasks(task, annotatingTask);
|
||||
task._source.kind = TaskSourceKind.Workspace;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (legacyAnnotatingTasks) {
|
||||
let legacyAnnotatingTask = legacyAnnotatingTasks[task.identifier];
|
||||
if (legacyAnnotatingTask) {
|
||||
TaskConfig.mergeTasks(task, legacyAnnotatingTask);
|
||||
task._source.kind = TaskSourceKind.Workspace;
|
||||
workspaceTasksToDelete.push(legacyAnnotatingTask);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (workspaceTaskResult.set) {
|
||||
if (workspaceTasksToDelete.length > 0) {
|
||||
let tasks = workspaceTaskResult.set.tasks;
|
||||
let newSet: TaskSet = {
|
||||
extension: workspaceTaskResult.set.extension,
|
||||
tasks: []
|
||||
};
|
||||
let toDelete = workspaceTasksToDelete.reduce<IStringDictionary<boolean>>((map, task) => {
|
||||
map[task._id] = true;
|
||||
return map;
|
||||
}, Object.create(null));
|
||||
newSet.tasks = tasks.filter(task => !toDelete[task._id]);
|
||||
result.push(newSet);
|
||||
} else {
|
||||
result.push(workspaceTaskResult.set);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, () => {
|
||||
// If we can't read the tasks.json file provide at least the contributed tasks
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getWorkspaceTasks(): TPromise<TaskSet> {
|
||||
private getLegacyAnnotatingTasks(workspaceTasks: TaskSet): IStringDictionary<Task> {
|
||||
let result: IStringDictionary<Task>;
|
||||
function getResult() {
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
result = Object.create(null);
|
||||
return result;
|
||||
}
|
||||
for (let task of workspaceTasks.tasks) {
|
||||
let commandName = task.command && task.command.name;
|
||||
// This is for backwards compatibility with the 0.1.0 task annotation code
|
||||
// if we had a gulp, jake or grunt command a task specification was a annotation
|
||||
if (commandName === 'gulp' || commandName === 'grunt' || commandName === 'jake') {
|
||||
getResult()[`${commandName}.${task.name}`] = task;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private getWorkspaceTasks(): TPromise<WorkspaceTaskResult> {
|
||||
if (this._workspaceTasksPromise) {
|
||||
return this._workspaceTasksPromise;
|
||||
}
|
||||
this._workspaceTasksPromise = this.computeWorkspaceTasks().then(value => {
|
||||
this._configHasErrors = value.hasErrors;
|
||||
if (this._taskSystem instanceof ProcessTaskSystem) {
|
||||
this._taskSystem.hasErrors(this._configHasErrors);
|
||||
}
|
||||
return value.set;
|
||||
});
|
||||
this.updateWorkspaceTasks();
|
||||
return this._workspaceTasksPromise;
|
||||
}
|
||||
|
||||
private updateWorkspaceTasks(): void {
|
||||
this._workspaceTasksPromise = this.computeWorkspaceTasks().then(value => {
|
||||
this._configHasErrors = value.hasErrors;
|
||||
return value.set;
|
||||
if (this._taskSystem instanceof ProcessTaskSystem) {
|
||||
this._taskSystem.hasErrors(this._configHasErrors);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -966,7 +1036,7 @@ class TaskService extends EventEmitter implements ITaskService {
|
|||
return configPromise.then((resolved) => {
|
||||
return ProblemMatcherRegistry.onReady().then((): WorkspaceTaskResult => {
|
||||
if (!resolved || !resolved.config) {
|
||||
return { set: undefined, hasErrors: resolved !== void 0 ? resolved.hasErrors : false };
|
||||
return { set: undefined, annotatingTasks: undefined, hasErrors: resolved !== void 0 ? resolved.hasErrors : false };
|
||||
}
|
||||
let problemReporter = new ProblemReporter(this._outputChannel);
|
||||
let parseResult = TaskConfig.parse(resolved.config, problemReporter);
|
||||
|
@ -977,9 +1047,22 @@ class TaskService extends EventEmitter implements ITaskService {
|
|||
}
|
||||
if (problemReporter.status.isFatal()) {
|
||||
problemReporter.fatal(nls.localize('TaskSystem.configurationErrors', 'Error: the provided task configuration has validation errors and can\'t not be used. Please correct the errors first.'));
|
||||
return { set: undefined, hasErrors };
|
||||
return { set: undefined, annotatingTasks: undefined, hasErrors };
|
||||
}
|
||||
return { set: { tasks: parseResult.tasks }, hasErrors };
|
||||
let annotatingTasks: { byIdentifier: IStringDictionary<Task>; byName: IStringDictionary<Task>; };
|
||||
if (parseResult.annotatingTasks && parseResult.annotatingTasks.length > 0) {
|
||||
annotatingTasks = {
|
||||
byIdentifier: Object.create(null),
|
||||
byName: Object.create(null)
|
||||
};
|
||||
for (let task of parseResult.annotatingTasks) {
|
||||
annotatingTasks.byIdentifier[task.identifier] = task;
|
||||
if (task.name) {
|
||||
annotatingTasks.byName[task.name] = task;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { set: { tasks: parseResult.tasks }, annotatingTasks: annotatingTasks, hasErrors };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -123,6 +123,7 @@ class TaskBuilder {
|
|||
this.commandBuilder = new CommandConfigurationBuilder(this, command);
|
||||
this.result = {
|
||||
_id: name,
|
||||
_source: { kind: Tasks.TaskSourceKind.Workspace },
|
||||
identifier: name,
|
||||
name: name,
|
||||
command: this.commandBuilder.result,
|
||||
|
|
Loading…
Reference in a new issue