diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 2306007194c..babbdf49a8a 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import * as UUID from 'vs/base/common/uuid'; import { asWinJsPromise } from 'vs/base/common/async'; @@ -308,13 +309,16 @@ namespace Tasks { if (command === void 0) { return undefined; } + let source = { + kind: TaskSystem.TaskSourceKind.Extension, + label: typeof task.source === 'string' ? task.source : extension.name, + detail: extension.id + }; + let label = nls.localize('task.label', '{0}: {1}', source.label, task.name); let result: TaskSystem.Task = { _id: uuidMap.getUUID(task.identifier), - _source: { - kind: TaskSystem.TaskSourceKind.Extension, - label: typeof task.source === 'string' ? task.source : extension.name, - detail: extension.id - }, + _source: source, + _label: label, name: task.name, identifier: task.identifier ? task.identifier : `${extension.id}.${task.name}`, group: types.TaskGroup.is(task.group) ? task.group : undefined, diff --git a/src/vs/workbench/parts/tasks/browser/quickOpen.ts b/src/vs/workbench/parts/tasks/browser/quickOpen.ts index e4208bafd3a..2b0eb83c769 100644 --- a/src/vs/workbench/parts/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/quickOpen.ts @@ -13,21 +13,18 @@ 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, TaskSourceKind, computeLabel } 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'; import { ActionBarContributor, ContributableActionProvider } from 'vs/workbench/browser/actions'; export class TaskEntry extends Model.QuickOpenEntry { - private _label: string; - constructor(protected taskService: ITaskService, protected _task: Task, highlights: Model.IHighlight[] = []) { super(highlights); - this._label = computeLabel(_task); } public getLabel(): string { - return this._label; + return this.task._label; } public getAriaLabel(): string { diff --git a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts index 862419a7fea..bf17dd08930 100644 --- a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts @@ -301,6 +301,11 @@ export interface ExternalTaskRunnerConfiguration extends BaseTaskRunnerConfigura _runner?: string; + /** + * Determines the runner to use + */ + runner?: string; + /** * The config's version number */ @@ -341,7 +346,8 @@ function mergeProperty(target: T, source: T, key: K) { interface ParseContext { problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; - isTermnial: boolean; + engine: Tasks.ExecutionEngine; + schemaVersion: Tasks.JsonSchemaVersion; } namespace CommandOptions { @@ -587,7 +593,7 @@ namespace CommandConfiguration { result.options = CommandOptions.from(config.options, context); if (result.options && result.options.shell === void 0 && isShellConfiguration) { result.options.shell = ShellConfiguration.from(config.isShellCommand as ShellConfiguration, context); - if (!context.isTermnial) { + if (context.engine !== Tasks.ExecutionEngine.Terminal) { context.problemReporter.warn(nls.localize('ConfigurationParser.noShell', 'Warning: shell configuration is only supported when executing tasks in the terminal.')); } } @@ -776,6 +782,7 @@ namespace TaskDescription { let annotatingTasks: Tasks.Task[] = []; let defaultBuildTask: { task: Tasks.Task; rank: number; } = { task: undefined, rank: -1 }; let defaultTestTask: { task: Tasks.Task; rank: number; } = { task: undefined, rank: -1 }; + let schema2_0_0: boolean = context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0; tasks.forEach((externalTask) => { let taskName = externalTask.taskName; if (!taskName) { @@ -792,6 +799,7 @@ namespace TaskDescription { let task: Tasks.Task = { _id: UUID.generateUuid(), _source: source, + _label: taskName, name: taskName, identifier: identifer, command @@ -835,7 +843,7 @@ namespace TaskDescription { if (problemMatchers) { task.problemMatchers = problemMatchers; } - if (context.isTermnial && isAnnotating(task)) { + if (schema2_0_0 && isAnnotating(task)) { mergeGlobalsIntoAnnnotation(task, globals); annotatingTasks.push(task); return; @@ -843,15 +851,15 @@ namespace TaskDescription { mergeGlobals(task, globals); fillDefaults(task); let addTask: boolean = true; - if (context.isTermnial && task.command && task.command.name && task.command.type === Tasks.CommandType.Shell && task.command.args && task.command.args.length > 0) { + if (context.engine === Tasks.ExecutionEngine.Terminal && task.command && task.command.name && task.command.type === Tasks.CommandType.Shell && task.command.args && task.command.args.length > 0) { if (hasUnescapedSpaces(task.command.name) || task.command.args.some(hasUnescapedSpaces)) { context.problemReporter.warn(nls.localize('taskConfiguration.shellArgs', 'Warning: the task \'{0}\' is a shell command and either the command name or one of its arguments has unescaped spaces. To ensure correct command line quoting please merge args into the command.', task.name)); } } - if (context.isTermnial) { + if (schema2_0_0) { if ((task.command === void 0 || task.command.name === void 0) && (task.dependsOn === void 0 || task.dependsOn.length === 0)) { context.problemReporter.error(nls.localize( - 'taskConfiguration.noCommandOrDependsOn', 'Error: the task \'{0}\' neither specifies a command or a dependsOn property. The task will be ignored. Its definition is:\n{1}', + 'taskConfiguration.noCommandOrDependsOn', 'Error: the task \'{0}\' neither specifies a command nor a dependsOn property. The task will be ignored. Its definition is:\n{1}', task.name, JSON.stringify(externalTask, undefined, 4) )); addTask = false; @@ -1094,19 +1102,43 @@ namespace Globals { export namespace ExecutionEngine { export function from(config: ExternalTaskRunnerConfiguration): Tasks.ExecutionEngine { - return isTerminalConfig(config) - ? Tasks.ExecutionEngine.Terminal - : isRunnerConfig(config) - ? Tasks.ExecutionEngine.Process - : Tasks.ExecutionEngine.Unknown; + let runner = config.runner || config._runner; + let result: Tasks.ExecutionEngine; + if (runner) { + switch (runner) { + case 'terminal': + result = Tasks.ExecutionEngine.Terminal; + break; + case 'process': + result = Tasks.ExecutionEngine.Process; + break; + } + } + let schemaVersion = JsonSchemaVersion.from(config); + if (schemaVersion === Tasks.JsonSchemaVersion.V0_1_0) { + return result || Tasks.ExecutionEngine.Process; + } else if (schemaVersion === Tasks.JsonSchemaVersion.V2_0_0) { + return Tasks.ExecutionEngine.Terminal; + } else { + throw new Error('Shouldn\'t happen.'); + } } - function isRunnerConfig(config: ExternalTaskRunnerConfiguration): boolean { - return (!config._runner || config._runner === 'program') && (config.version === '0.1.0' || !config.version); - } +} - function isTerminalConfig(config: ExternalTaskRunnerConfiguration): boolean { - return config._runner === 'terminal' || config.version === '2.0.0'; +export namespace JsonSchemaVersion { + + export function from(config: ExternalTaskRunnerConfiguration): Tasks.JsonSchemaVersion { + let version = config.version; + if (!version) { + return Tasks.JsonSchemaVersion.V2_0_0; + } + switch (version) { + case '0.1.0': + return Tasks.JsonSchemaVersion.V0_1_0; + default: + return Tasks.JsonSchemaVersion.V2_0_0; + } } } @@ -1130,13 +1162,15 @@ class ConfigurationParser { public run(fileConfig: ExternalTaskRunnerConfiguration): ParseResult { let engine = ExecutionEngine.from(fileConfig); + let schemaVersion = JsonSchemaVersion.from(fileConfig); if (engine === Tasks.ExecutionEngine.Terminal) { this.problemReporter.clearOutput(); } let context: ParseContext = { problemReporter: this.problemReporter, namedProblemMatchers: undefined, - isTermnial: engine === Tasks.ExecutionEngine.Terminal + engine, + schemaVersion, }; let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context); return { @@ -1177,6 +1211,7 @@ class ConfigurationParser { let task: Tasks.Task = { _id: UUID.generateUuid(), _source: TaskDescription.source, + _label: globals.command.name, name: globals.command.name, identifier: globals.command.name, group: Tasks.TaskGroup.Build, diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index b798e6314e5..0cc7270b723 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import nls = require('vs/nls'); import * as Types from 'vs/base/common/types'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -183,6 +182,11 @@ export interface Task { */ _id: string; + /** + * The cached label. + */ + _label: string; + /** * Indicated the source of the task (e.g tasks.json or extension) */ @@ -241,20 +245,16 @@ export interface Task { } export enum ExecutionEngine { - Unknown = 0, - Terminal = 1, - Process = 2 + Process = 1, + Terminal = 2 +} + +export enum JsonSchemaVersion { + V0_1_0 = 1, + V2_0_0 = 2 } export interface TaskSet { tasks: Task[]; extension?: IExtensionDescription; -} - -export function computeLabel(task: Task): string { - if (task._source.kind === TaskSourceKind.Extension) { - return nls.localize('taskEntry.label', '{0}: {1}', task._source.label, task.name); - } else { - return task.name; - } } \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts index 24441e1eba0..af5ca29ae20 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts @@ -13,32 +13,41 @@ import commonSchema from './jsonSchemaCommon'; const schema: IJSONSchema = { oneOf: [ { - 'allOf': [ + allOf: [ { - 'type': 'object', - 'required': ['version'], - 'properties': { - 'version': { - 'type': 'string', - 'enum': ['0.1.0'], - 'description': nls.localize('JsonSchema.version', 'The config\'s version number') + type: 'object', + required: ['version'], + properties: { + version: { + type: 'string', + enum: ['0.1.0'], + description: nls.localize('JsonSchema.version', 'The config\'s version number') }, - 'windows': { - '$ref': '#/definitions/taskRunnerConfiguration', - 'description': nls.localize('JsonSchema.windows', 'Windows specific command configuration') + _runner: { + deprecationMessage: nls.localize('JsonSchema._runner', 'The runner has graduated. Use the offical runner property') }, - 'osx': { - '$ref': '#/definitions/taskRunnerConfiguration', - 'description': nls.localize('JsonSchema.mac', 'Mac specific command configuration') + runner: { + type: 'string', + enum: ['process', 'terminal'], + default: 'process', + description: nls.localize('JsonSchema.runner', 'Defines whether the task is executed as a process and the output is shown in the output window or inside the terminal.') }, - 'linux': { - '$ref': '#/definitions/taskRunnerConfiguration', - 'description': nls.localize('JsonSchema.linux', 'Linux specific command configuration') + windows: { + $ref: '#/definitions/taskRunnerConfiguration', + description: nls.localize('JsonSchema.windows', 'Windows specific command configuration') + }, + osx: { + $ref: '#/definitions/taskRunnerConfiguration', + description: nls.localize('JsonSchema.mac', 'Mac specific command configuration') + }, + linux: { + $ref: '#/definitions/taskRunnerConfiguration', + description: nls.localize('JsonSchema.linux', 'Linux specific command configuration') } } }, { - '$ref': '#/definitions/taskRunnerConfiguration' + $ref: '#/definitions/taskRunnerConfiguration' } ] } diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 540ff68d8ca..f6a16bb6ba8 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -71,7 +71,7 @@ import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs 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, TaskSourceKind, computeLabel as computeTaskLabel } from 'vs/workbench/parts/tasks/common/tasks'; +import { Task, TaskSet, TaskGroup, ExecutionEngine, 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'; @@ -455,7 +455,7 @@ interface WorkspaceTaskResult { set: TaskSet; annotatingTasks: { byIdentifier: IStringDictionary; - byName: IStringDictionary; + byLabel: IStringDictionary; }; hasErrors: boolean; } @@ -547,7 +547,7 @@ class TaskService extends EventEmitter implements ITaskService { ? ExecutionEngine.Terminal : this._taskSystem instanceof ProcessTaskSystem ? ExecutionEngine.Process - : ExecutionEngine.Unknown; + : undefined; if (currentExecutionEngine !== this.getExecutionEngine()) { this.messageService.show(Severity.Info, nls.localize('TaskSystem.noHotSwap', 'Changing the task execution engine requires restarting VS Code. The change is ignored.')); } @@ -709,7 +709,7 @@ class TaskService extends EventEmitter implements ITaskService { return TPromise.as(undefined); } let fileConfig = configuration.config; - let customize = { taskName: computeTaskLabel(task), identifier: task.identifier }; + let customize = { taskName: task._label, identifier: task.identifier }; if (!fileConfig) { fileConfig = { version: '2.0.0', @@ -746,7 +746,7 @@ class TaskService extends EventEmitter implements ITaskService { sets.forEach((set) => { set.tasks.forEach((task) => { uuidMap[task._id] = task; - labelMap[computeTaskLabel(task)] = task; + labelMap[task._label] = task; identifierMap[task.identifier] = task; if (group && task.group === group) { if (task._source.kind === TaskSourceKind.Workspace) { @@ -779,6 +779,7 @@ class TaskService extends EventEmitter implements ITaskService { let task: Task = { _id: id, _source: { kind: TaskSourceKind.Generic, label: 'generic' }, + _label: id, name: id, identifier: id, dependsOn: extensionTasks.map(task => task._id), @@ -794,7 +795,7 @@ class TaskService extends EventEmitter implements ITaskService { sets.forEach((set) => { set.tasks.forEach((task) => { - labelMap[computeTaskLabel(task)] = task; + labelMap[task._label] = task; identifierMap[task.identifier] = task; }); }); @@ -922,10 +923,11 @@ class TaskService extends EventEmitter implements ITaskService { for (let set of result) { for (let task of set.tasks) { if (annotatingTasks) { - let annotatingTask = annotatingTasks.byIdentifier[task.identifier] || annotatingTasks.byName[task.name]; + let annotatingTask = annotatingTasks.byIdentifier[task.identifier] || annotatingTasks.byLabel[task._label]; if (annotatingTask) { TaskConfig.mergeTasks(task, annotatingTask); task.name = annotatingTask.name; + task._label = annotatingTask._label; task._source.kind = TaskSourceKind.Workspace; continue; } @@ -936,6 +938,7 @@ class TaskService extends EventEmitter implements ITaskService { TaskConfig.mergeTasks(task, legacyAnnotatingTask); task._source.kind = TaskSourceKind.Workspace; task.name = legacyAnnotatingTask.name; + task._label = legacyAnnotatingTask._label; workspaceTasksToDelete.push(legacyAnnotatingTask); continue; } @@ -1015,29 +1018,36 @@ class TaskService extends EventEmitter implements ITaskService { } if (config) { let engine = TaskConfig.ExecutionEngine.from(config); - if (engine === ExecutionEngine.Process && this.hasDetectorSupport(config)) { - configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, config).detect(true).then((value): WorkspaceConfigurationResult => { - let hasErrors = this.printStderr(value.stderr); - let detectedConfig = value.config; - if (!detectedConfig) { - return { config, hasErrors }; - } - let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(config); - let configuredTasks: IStringDictionary = Object.create(null); - if (!result.tasks) { - if (detectedConfig.tasks) { - result.tasks = detectedConfig.tasks; + if (engine === ExecutionEngine.Process) { + if (this.hasDetectorSupport(config)) { + configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, config).detect(true).then((value): WorkspaceConfigurationResult => { + let hasErrors = this.printStderr(value.stderr); + let detectedConfig = value.config; + if (!detectedConfig) { + return { config, hasErrors }; } - } else { - result.tasks.forEach(task => configuredTasks[task.taskName] = task); - detectedConfig.tasks.forEach((task) => { - if (!configuredTasks[task.taskName]) { - result.tasks.push(task); + let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(config); + let configuredTasks: IStringDictionary = Object.create(null); + if (!result.tasks) { + if (detectedConfig.tasks) { + result.tasks = detectedConfig.tasks; } - }); - } - return { config: result, hasErrors }; - }); + } else { + result.tasks.forEach(task => configuredTasks[task.taskName] = task); + detectedConfig.tasks.forEach((task) => { + if (!configuredTasks[task.taskName]) { + result.tasks.push(task); + } + }); + } + return { config: result, hasErrors }; + }); + } else { + configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService).detect(true).then((value) => { + let hasErrors = this.printStderr(value.stderr); + return { config: value.config, hasErrors }; + }); + } } else { configPromise = TPromise.as({ config, hasErrors: false }); } @@ -1061,16 +1071,16 @@ class TaskService extends EventEmitter implements ITaskService { 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, annotatingTasks: undefined, hasErrors }; } - let annotatingTasks: { byIdentifier: IStringDictionary; byName: IStringDictionary; }; + let annotatingTasks: { byIdentifier: IStringDictionary; byLabel: IStringDictionary; }; if (parseResult.annotatingTasks && parseResult.annotatingTasks.length > 0) { annotatingTasks = { byIdentifier: Object.create(null), - byName: Object.create(null) + byLabel: Object.create(null) }; for (let task of parseResult.annotatingTasks) { annotatingTasks.byIdentifier[task.identifier] = task; - if (task.name) { - annotatingTasks.byName[task.name] = task; + if (task._label) { + annotatingTasks.byLabel[task._label] = task; } } } @@ -1087,6 +1097,16 @@ class TaskService extends EventEmitter implements ITaskService { return TaskConfig.ExecutionEngine.from(config); } + /* + private getJsonSchemaVersion(): JsonSchemaVersion { + let { config } = this.getConfiguration(); + if (!config) { + return JsonSchemaVersion.V2_0_0; + } + return TaskConfig.JsonSchemaVersion.from(config); + } + */ + private getConfiguration(): { config: TaskConfig.ExternalTaskRunnerConfiguration; hasParseErrors: boolean } { let result = this.configurationService.getConfiguration('tasks'); if (!result) { diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts index c958d68336b..bd75eb83f01 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts @@ -145,6 +145,7 @@ class TaskBuilder { this.result = { _id: name, _source: { kind: Tasks.TaskSourceKind.Workspace, label: 'workspace' }, + _label: name, identifier: name, name: name, command: this.commandBuilder.result,