diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 294c1b216a9..ed4e81fcac2 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -44,7 +44,7 @@ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output'; @@ -178,6 +178,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _schemaVersion: JsonSchemaVersion | undefined; private _executionEngine: ExecutionEngine | undefined; private _workspaceFolders: IWorkspaceFolder[] | undefined; + private _workspace: IWorkspace | undefined; private _ignoredWorkspaceFolders: IWorkspaceFolder[] | undefined; private _showIgnoreMessage?: boolean; private _providers: Map; @@ -425,7 +426,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._showIgnoreMessage; } - private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion]): void { + private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined]): void { if (!setup) { setup = this.computeWorkspaceFolderSetup(); } @@ -447,6 +448,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._ignoredWorkspaceFolders = setup[1]; this._executionEngine = setup[2]; this._schemaVersion = setup[3]; + this._workspace = setup[4]; } protected showOutput(runSource: TaskRunSource = TaskRunSource.User): void { @@ -1402,13 +1404,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (let folder of this.workspaceFolders) { promises.push(this.computeWorkspaceFolderTasks(folder, runSource).then((value) => value, () => undefined)); } - return Promise.all(promises).then((values) => { + return Promise.all(promises).then(async (values) => { let result = new Map(); for (let value of values) { if (value) { result.set(value.workspaceFolder.uri.toString(), value); } } + const userTasks = await this.computeUserTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); + if (userTasks) { + result.set('settings', userTasks); + } + const workspaceFileTasks = await this.computeWorkspaceFileTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); + if (workspaceFileTasks && this._workspace && this._workspace.configuration) { + result.set(this._workspace.configuration.toString(), workspaceFileTasks); + } return result; }); } @@ -1429,7 +1439,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return ProblemMatcherRegistry.onReady().then(async (): Promise => { let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme); let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter); + let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson); let hasErrors = false; if (!parseResult.validationStatus.isOK()) { hasErrors = true; @@ -1456,6 +1466,101 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private testParseExternalConfig(config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, location: string): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, hasParseErrors: boolean } { + if (!config) { + return { config: undefined, hasParseErrors: false }; + } + let parseErrors: string[] = (config as any).$parseErrors; + if (parseErrors) { + let isAffected = false; + for (const parseError of parseErrors) { + if (/tasks\.json$/.test(parseError)) { + isAffected = true; + break; + } + } + if (isAffected) { + this._outputChannel.append(nls.localize('TaskSystem.invalidTaskJsonOther', 'Error: The content of the tasks json in {0} has syntax errors. Please correct them before executing a task.\n', location)); + this.showOutput(); + return { config, hasParseErrors: true }; + } + } + return { config, hasParseErrors: false }; + } + + private async computeWorkspaceFileTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { + if (this.executionEngine === ExecutionEngine.Process) { + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').workspace, nls.localize('TasksSystem.locationWorkspaceConfig', 'workspace file')); + let customizedTasks: { byIdentifier: IStringDictionary; } = { + byIdentifier: Object.create(null) + }; + + const custom: CustomTask[] = []; + await this.computeTasksForSingleConfig(workspaceFolder, configuration.config, runSource, custom, customizedTasks.byIdentifier, TaskConfig.TaskConfigSource.WorkspaceFile); + const engine = configuration.config ? TaskConfig.ExecutionEngine.from(configuration.config) : ExecutionEngine.Terminal; + if (engine === ExecutionEngine.Process) { + this.notificationService.warn(nls.localize('TaskSystem.versionWorkspaceFile', 'Only tasks version 2.0.0 permitted in .codeworkspace.')); + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + return { workspaceFolder, set: { tasks: custom }, configurations: customizedTasks, hasErrors: configuration.hasParseErrors }; + } + + private async computeUserTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { + if (this.executionEngine === ExecutionEngine.Process) { + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').user, nls.localize('TasksSystem.locationUserConfig', 'user settings')); + let customizedTasks: { byIdentifier: IStringDictionary; } = { + byIdentifier: Object.create(null) + }; + + const custom: CustomTask[] = []; + await this.computeTasksForSingleConfig(workspaceFolder, configuration.config, runSource, custom, customizedTasks.byIdentifier, TaskConfig.TaskConfigSource.User); + const engine = configuration.config ? TaskConfig.ExecutionEngine.from(configuration.config) : ExecutionEngine.Terminal; + if (engine === ExecutionEngine.Process) { + this.notificationService.warn(nls.localize('TaskSystem.versionSettings', 'Only tasks version 2.0.0 permitted in user settings.')); + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + return { workspaceFolder, set: { tasks: custom }, configurations: customizedTasks, hasErrors: configuration.hasParseErrors }; + } + + private emptyWorkspaceTaskResults(workspaceFolder: IWorkspaceFolder): WorkspaceFolderTaskResult { + return { workspaceFolder, set: undefined, configurations: undefined, hasErrors: false }; + } + + private async computeTasksForSingleConfig(workspaceFolder: IWorkspaceFolder, config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, runSource: TaskRunSource, custom: CustomTask[], customized: IStringDictionary, source: TaskConfig.TaskConfigSource): Promise { + if (!config) { + return false; + } + let taskSystemInfo: TaskSystemInfo | undefined = workspaceFolder ? this._taskSystemInfos.get(workspaceFolder.uri.scheme) : undefined; + let problemReporter = new ProblemReporter(this._outputChannel); + let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source); + let hasErrors = false; + if (!parseResult.validationStatus.isOK()) { + this.showOutput(runSource); + hasErrors = true; + } + 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 hasErrors; + } + if (parseResult.configured && parseResult.configured.length > 0) { + for (let task of parseResult.configured) { + customized[task.configures._key] = task; + } + } + if (!(await this._areJsonTasksSupportedPromise) && (parseResult.custom.length > 0)) { + console.warn('Custom workspace tasks are not supported.'); + } else { + for (let task of parseResult.custom) { + custom.push(task); + } + } + return hasErrors; + } + private computeConfiguration(workspaceFolder: IWorkspaceFolder): Promise { let { config, hasParseErrors } = this.getConfiguration(workspaceFolder); return Promise.resolve({ workspaceFolder, config, hasErrors: hasParseErrors }); @@ -1463,18 +1568,19 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise; - private computeWorkspaceFolderSetup(): [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion] { + private computeWorkspaceFolderSetup(): [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined] { let workspaceFolders: IWorkspaceFolder[] = []; let ignoredWorkspaceFolders: IWorkspaceFolder[] = []; let executionEngine = ExecutionEngine.Terminal; let schemaVersion = JsonSchemaVersion.V2_0_0; - + let workspace: IWorkspace | undefined; if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { let workspaceFolder: IWorkspaceFolder = this.contextService.getWorkspace().folders[0]; workspaceFolders.push(workspaceFolder); executionEngine = this.computeExecutionEngine(workspaceFolder); schemaVersion = this.computeJsonSchemaVersion(workspaceFolder); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + workspace = this.contextService.getWorkspace(); for (let workspaceFolder of this.contextService.getWorkspace().folders) { if (schemaVersion === this.computeJsonSchemaVersion(workspaceFolder)) { workspaceFolders.push(workspaceFolder); @@ -1487,7 +1593,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } } - return [workspaceFolders, ignoredWorkspaceFolders, executionEngine, schemaVersion]; + return [workspaceFolders, ignoredWorkspaceFolders, executionEngine, schemaVersion, workspace]; } private computeExecutionEngine(workspaceFolder: IWorkspaceFolder): ExecutionEngine { @@ -1508,7 +1614,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected getConfiguration(workspaceFolder: IWorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; hasParseErrors: boolean } { let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY - ? Objects.deepClone(this.configurationService.getValue('tasks', { resource: workspaceFolder.uri })) + ? Objects.deepClone(this.configurationService.inspect('tasks', { resource: workspaceFolder.uri }).workspaceFolder) : undefined; if (!result) { return { config: undefined, hasParseErrors: false }; @@ -1660,7 +1766,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => { let description: string | undefined; - if (this.needsFolderQualification()) { + if (task._source.kind === TaskSourceKind.User) { + description = nls.localize('taskQuickPick.userSettings', 'User Settings'); + } else if (task._source.kind === TaskSourceKind.WorkspaceFile) { + description = task.getWorkspaceFileName(); + } else if (this.needsFolderQualification()) { let workspaceFolder = task.getWorkspaceFolder(); if (workspaceFolder) { description = workspaceFolder.name; diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 966902be47e..c6d53dc412b 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -255,9 +255,8 @@ const actionBarRegistry = Registry.as(ActionBarExtensions.Ac actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); // tasks.json validation -let schemaId = 'vscode://schemas/tasks'; let schema: IJSONSchema = { - id: schemaId, + id: tasksSchemaId, description: 'Task definition file', type: 'object', allowTrailingCommas: true, @@ -283,6 +282,7 @@ let schema: IJSONSchema = { import schemaVersion1 from '../common/jsonSchema_v1'; import schemaVersion2, { updateProblemMatchers } from '../common/jsonSchema_v2'; import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; +import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, @@ -290,9 +290,9 @@ schema.definitions = { schema.oneOf = [...(schemaVersion2.oneOf || []), ...(schemaVersion1.oneOf || [])]; let jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); -jsonRegistry.registerSchema(schemaId, schema); +jsonRegistry.registerSchema(tasksSchemaId, schema); ProblemMatcherRegistry.onMatcherChanged(() => { updateProblemMatchers(); - jsonRegistry.notifySchemaChanged(schemaId); + jsonRegistry.notifySchemaChanged(tasksSchemaId); }); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 56cc9ad1c0c..dcefd54e051 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -18,7 +18,7 @@ import { isNamedProblemMatcher, ProblemMatcherRegistry } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from './tasks'; import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -683,6 +683,7 @@ export namespace RunOptions { interface ParseContext { workspaceFolder: IWorkspaceFolder; + workspace: IWorkspace | undefined; problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; uuidMap: UUIDMap; @@ -1181,8 +1182,7 @@ namespace ProblemMatcherConverter { } } -const source: Partial = { - kind: Tasks.TaskSourceKind.Workspace, +const partialSource: Partial = { label: 'Workspace', config: undefined }; @@ -1328,7 +1328,7 @@ namespace ConfiguringTask { customize: string; } - export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number): Tasks.ConfiguringTask | undefined { + export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number, source: TaskConfigSource): Tasks.ConfiguringTask | undefined { if (!external) { return undefined; } @@ -1383,9 +1383,24 @@ namespace ConfiguringTask { index, element: external }; + let taskSource: Tasks.FileBasedTaskSource; + switch (source) { + case TaskConfigSource.User: { + taskSource = Objects.assign({} as Tasks.UserTaskSource, partialSource, { kind: Tasks.TaskSourceKind.User, config: configElement }); + break; + } + case TaskConfigSource.WorkspaceFile: { + taskSource = Objects.assign({} as Tasks.WorkspaceFileTaskSource, partialSource, { kind: Tasks.TaskSourceKind.WorkspaceFile, config: configElement }); + break; + } + default: { + taskSource = Objects.assign({} as Tasks.WorkspaceTaskSource, partialSource, { kind: Tasks.TaskSourceKind.Workspace, config: configElement }); + break; + } + } let result: Tasks.ConfiguringTask = new Tasks.ConfiguringTask( `${typeDeclaration.extensionId}.${taskIdentifier._key}`, - Objects.assign({} as Tasks.WorkspaceTaskSource, source, { config: configElement }), + taskSource, undefined, type, taskIdentifier, @@ -1419,7 +1434,7 @@ namespace ConfiguringTask { } namespace CustomTask { - export function from(this: void, external: CustomTask, context: ParseContext, index: number): Tasks.CustomTask | undefined { + export function from(this: void, external: CustomTask, context: ParseContext, index: number, source: TaskConfigSource): Tasks.CustomTask | undefined { if (!external) { return undefined; } @@ -1440,9 +1455,25 @@ namespace CustomTask { return undefined; } + let taskSource: Tasks.FileBasedTaskSource; + switch (source) { + case TaskConfigSource.User: { + taskSource = Objects.assign({} as Tasks.UserTaskSource, partialSource, { kind: Tasks.TaskSourceKind.User, config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }); + break; + } + case TaskConfigSource.WorkspaceFile: { + taskSource = Objects.assign({} as Tasks.WorkspaceFileTaskSource, partialSource, { kind: Tasks.TaskSourceKind.WorkspaceFile, config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder, workspace: context.workspace } }); + break; + } + default: { + taskSource = Objects.assign({} as Tasks.WorkspaceTaskSource, partialSource, { kind: Tasks.TaskSourceKind.Workspace, config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }); + break; + } + } + let result: Tasks.CustomTask = new Tasks.CustomTask( context.uuidMap.getUUID(taskName), - Objects.assign({} as Tasks.WorkspaceTaskSource, source, { config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }), + taskSource, taskName, Tasks.CUSTOMIZED_TASK_TYPE, undefined, @@ -1574,7 +1605,7 @@ namespace TaskParser { return customize === undefined && (type === undefined || type === null || type === Tasks.CUSTOMIZED_TASK_TYPE || type === 'shell' || type === 'process'); } - export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext): TaskParseResult { + export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource): TaskParseResult { let result: TaskParseResult = { custom: [], configured: [] }; if (!externals) { return result; @@ -1586,7 +1617,7 @@ namespace TaskParser { for (let index = 0; index < externals.length; index++) { let external = externals[index]; if (isCustomTask(external)) { - let customTask = CustomTask.from(external, context, index); + let customTask = CustomTask.from(external, context, index, source); if (customTask) { CustomTask.fillGlobals(customTask, globals); CustomTask.fillDefaults(customTask, context); @@ -1624,7 +1655,7 @@ namespace TaskParser { result.custom.push(customTask); } } else { - let configuredTask = ConfiguringTask.from(external, context, index); + let configuredTask = ConfiguringTask.from(external, context, index, source); if (configuredTask) { configuredTask.addTaskLoadMessages(context.taskLoadIssues); result.configured.push(configuredTask); @@ -1872,25 +1903,34 @@ class UUIDMap { } } +export enum TaskConfigSource { + TasksJson, + WorkspaceFile, + User +} + class ConfigurationParser { private workspaceFolder: IWorkspaceFolder; + private workspace: IWorkspace | undefined; private problemReporter: IProblemReporter; private uuidMap: UUIDMap; private platform: Platform; - constructor(workspaceFolder: IWorkspaceFolder, platform: Platform, problemReporter: IProblemReporter, uuidMap: UUIDMap) { + constructor(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, problemReporter: IProblemReporter, uuidMap: UUIDMap) { this.workspaceFolder = workspaceFolder; + this.workspace = workspace; this.platform = platform; this.problemReporter = problemReporter; this.uuidMap = uuidMap; } - public run(fileConfig: ExternalTaskRunnerConfiguration): ParseResult { + public run(fileConfig: ExternalTaskRunnerConfiguration, source: TaskConfigSource): ParseResult { let engine = ExecutionEngine.from(fileConfig); let schemaVersion = JsonSchemaVersion.from(fileConfig); let context: ParseContext = { workspaceFolder: this.workspaceFolder, + workspace: this.workspace, problemReporter: this.problemReporter, uuidMap: this.uuidMap, namedProblemMatchers: {}, @@ -1899,7 +1939,7 @@ class ConfigurationParser { platform: this.platform, taskLoadIssues: [] }; - let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context); + let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context, source); return { validationStatus: this.problemReporter.status, custom: taskParseResult.custom, @@ -1908,7 +1948,7 @@ class ConfigurationParser { }; } - private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): TaskParseResult { + private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext, source: TaskConfigSource): TaskParseResult { let globals = Globals.from(fileConfig, context); if (this.problemReporter.status.isFatal()) { return { custom: [], configured: [] }; @@ -1917,13 +1957,13 @@ class ConfigurationParser { let globalTasks: Tasks.CustomTask[] | undefined = undefined; let externalGlobalTasks: Array | undefined = undefined; if (fileConfig.windows && context.platform === Platform.Windows) { - globalTasks = TaskParser.from(fileConfig.windows.tasks, globals, context).custom; + globalTasks = TaskParser.from(fileConfig.windows.tasks, globals, context, source).custom; externalGlobalTasks = fileConfig.windows.tasks; } else if (fileConfig.osx && context.platform === Platform.Mac) { - globalTasks = TaskParser.from(fileConfig.osx.tasks, globals, context).custom; + globalTasks = TaskParser.from(fileConfig.osx.tasks, globals, context, source).custom; externalGlobalTasks = fileConfig.osx.tasks; } else if (fileConfig.linux && context.platform === Platform.Linux) { - globalTasks = TaskParser.from(fileConfig.linux.tasks, globals, context).custom; + globalTasks = TaskParser.from(fileConfig.linux.tasks, globals, context, source).custom; externalGlobalTasks = fileConfig.linux.tasks; } if (context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0 && globalTasks && globalTasks.length > 0 && externalGlobalTasks && externalGlobalTasks.length > 0) { @@ -1940,7 +1980,7 @@ class ConfigurationParser { let result: TaskParseResult = { custom: [], configured: [] }; if (fileConfig.tasks) { - result = TaskParser.from(fileConfig.tasks, globals, context); + result = TaskParser.from(fileConfig.tasks, globals, context, source); } if (globalTasks) { result.custom = TaskParser.assignTasks(result.custom, globalTasks); @@ -1989,7 +2029,7 @@ class ConfigurationParser { } let uuidMaps: Map = new Map(); -export function parse(workspaceFolder: IWorkspaceFolder, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter): ParseResult { +export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource): ParseResult { let uuidMap = uuidMaps.get(workspaceFolder.uri.toString()); if (!uuidMap) { uuidMap = new UUIDMap(); @@ -1997,7 +2037,7 @@ export function parse(workspaceFolder: IWorkspaceFolder, platform: Platform, con } try { uuidMap.start(); - return (new ConfigurationParser(workspaceFolder, platform, logger, uuidMap)).run(configuration); + return (new ConfigurationParser(workspaceFolder, workspace, platform, logger, uuidMap)).run(configuration, source); } finally { uuidMap.finish(); } diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 24d77f427d7..0737e5fea02 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -5,12 +5,13 @@ import * as nls from 'vs/nls'; import * as Types from 'vs/base/common/types'; +import * as resources from 'vs/base/common/resources'; import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import * as Objects from 'vs/base/common/objects'; import { UriComponents } from 'vs/base/common/uri'; import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -374,10 +375,13 @@ export namespace TaskSourceKind { export const Workspace: 'workspace' = 'workspace'; export const Extension: 'extension' = 'extension'; export const InMemory: 'inMemory' = 'inMemory'; + export const WorkspaceFile: 'workspaceFile' = 'workspaceFile'; + export const User: 'user' = 'user'; } export interface TaskSourceConfigElement { workspaceFolder: IWorkspaceFolder; + workspace?: IWorkspace; file: string; index: number; element: any; @@ -410,8 +414,20 @@ export interface InMemoryTaskSource extends BaseTaskSource { readonly kind: 'inMemory'; } -export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | InMemoryTaskSource; +export interface UserTaskSource extends BaseTaskSource { + readonly kind: 'user'; + readonly config: TaskSourceConfigElement; + readonly customizes?: KeyedTaskIdentifier; +} +export interface WorkspaceFileTaskSource extends BaseTaskSource { + readonly kind: 'workspaceFile'; + readonly config: TaskSourceConfigElement; + readonly customizes?: KeyedTaskIdentifier; +} + +export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | InMemoryTaskSource | UserTaskSource | WorkspaceFileTaskSource; +export type FileBasedTaskSource = WorkspaceTaskSource | UserTaskSource | WorkspaceFileTaskSource; export interface TaskIdentifier { type: string; [name: string]: any; @@ -566,6 +582,10 @@ export abstract class CommonTask { return undefined; } + public getWorkspaceFileName(): string | undefined { + return undefined; + } + public getTelemetryKind(): string { return 'unknown'; } @@ -619,7 +639,7 @@ export class CustomTask extends CommonTask { /** * Indicated the source of the task (e.g. tasks.json or extension) */ - _source: WorkspaceTaskSource; + _source: FileBasedTaskSource; hasDefinedMatchers: boolean; @@ -628,7 +648,7 @@ export class CustomTask extends CommonTask { */ command: CommandConfiguration = {}; - public constructor(id: string, source: WorkspaceTaskSource, label: string, type: string, command: CommandConfiguration | undefined, + public constructor(id: string, source: FileBasedTaskSource, label: string, type: string, command: CommandConfiguration | undefined, hasDefinedMatchers: boolean, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { super(id, label, undefined, runOptions, configurationProperties, source); this._source = source; @@ -700,7 +720,11 @@ export class CustomTask extends CommonTask { if (!workspaceFolder) { return undefined; } - let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder.uri.toString(), id: this.configurationProperties.identifier! }; + let id: string = this.configurationProperties.identifier!; + if (this._source.kind !== TaskSourceKind.Workspace) { + id += this._source.kind; + } + let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder.uri.toString(), id }; return JSON.stringify(key); } @@ -708,6 +732,10 @@ export class CustomTask extends CommonTask { return this._source.config.workspaceFolder; } + public getWorkspaceFileName(): string | undefined { + return (this._source.config.workspace && this._source.config.workspace.configuration) ? resources.basename(this._source.config.workspace.configuration) : undefined; + } + public getTelemetryKind(): string { if (this._source.customizes) { return 'workspace>extension'; @@ -726,11 +754,11 @@ export class ConfiguringTask extends CommonTask { /** * Indicated the source of the task (e.g. tasks.json or extension) */ - _source: WorkspaceTaskSource; + _source: FileBasedTaskSource; configures: KeyedTaskIdentifier; - public constructor(id: string, source: WorkspaceTaskSource, label: string | undefined, type: string | undefined, + public constructor(id: string, source: FileBasedTaskSource, label: string | undefined, type: string | undefined, configures: KeyedTaskIdentifier, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { super(id, label, type, runOptions, configurationProperties, source); this._source = source; @@ -748,6 +776,10 @@ export class ConfiguringTask extends CommonTask { public getDefinition(): KeyedTaskIdentifier { return this.configures; } + + public getWorkspaceFileName(): string | undefined { + return (this._source.config.workspace && this._source.config.workspace.configuration) ? resources.basename(this._source.config.workspace.configuration) : undefined; + } } export class ContributedTask extends CommonTask { diff --git a/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts index 9a81fcf9441..2564323b1c4 100644 --- a/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts @@ -10,17 +10,19 @@ import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { ValidationStatus } from 'vs/base/common/parsers'; import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { WorkspaceFolder, Workspace, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; -import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; +import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; -const workspaceFolder: IWorkspaceFolder = new WorkspaceFolder({ +const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), name: 'folderOne', index: 0 }); +const workspace: IWorkspace = new Workspace('id', [workspaceFolder]); + class ProblemReporter implements IProblemReporter { private _validationStatus: ValidationStatus = new ValidationStatus(); @@ -357,7 +359,7 @@ class PatternBuilder { function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, resolved: number) { let reporter = new ProblemReporter(); - let result = parse(workspaceFolder, Platform.platform, external, reporter); + let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson); assert.ok(!reporter.receivedMessage); assert.strictEqual(result.custom.length, 1); let task = result.custom[0]; @@ -368,7 +370,7 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re function testConfiguration(external: ExternalTaskRunnerConfiguration, builder: ConfiguationBuilder): void { builder.done(); let reporter = new ProblemReporter(); - let result = parse(workspaceFolder, Platform.platform, external, reporter); + let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson); if (reporter.receivedMessage) { assert.ok(false, reporter.lastMessage); } @@ -1730,4 +1732,4 @@ suite('Bugs / regression tests', () => { runtime(Tasks.RuntimeType.Shell); testConfiguration(external, builder); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index a5f15850dbf..a470043aa73 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -15,6 +15,7 @@ export const machineSettingsSchemaId = 'vscode://schemas/settings/machine'; export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; export const launchSchemaId = 'vscode://schemas/launch'; +export const tasksSchemaId = 'vscode://schemas/tasks'; export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]; export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE];