From 2fe72626c84329f92d416b14933ab39373ce8337 Mon Sep 17 00:00:00 2001 From: Liang Huang Date: Sat, 27 Jul 2019 17:41:38 -0400 Subject: [PATCH] align "configure task" and "task quick open" with vs code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - edit the right task.json when clicking "configure task" in multi-root workspace (fixed #4919) - in the current Theia, when users configure a detected task, the entire task config is written into tasks.json, which introduces redundancy. With this change, only properties that define the detected task, plus `problemMatcher`, are written into tasks.json. (fixed #5679) - allow users to override any task properties other than `type`, and those that are used to define the in its task definition. - `TaskConfigurations.taskCustomizations` is a flat array, and the user can only customize one type of detected task in one way. With this change Theia supports having different ways of task customization in different root folders. - The detected tasks, once customized, should be displayed as configured tasks in the quick open. (fixed #5747) - The same task shouldn’t have more than one customization. Otherwise it would cause ambiguities and duplication in tasks.json (fixed #5719) Signed-off-by: Liang Huang --- CHANGELOG.md | 12 +- .../provided-task-configurations.spec.ts | 2 + .../browser/provided-task-configurations.ts | 58 ++++++- packages/task/src/browser/quick-open-task.ts | 6 +- .../task/src/browser/task-configurations.ts | 143 +++++++++++++++--- .../src/browser/task-definition-registry.ts | 4 +- packages/task/src/browser/task-service.ts | 76 +++++----- packages/task/src/common/task-protocol.ts | 8 +- 8 files changed, 238 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 064cea92f4a98..cdb5ab318627f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,17 @@ ## v0.10.0 -- [vscode] added env variable substitution support [5811](https://github.com/theia-ide/theia/pull/5811) +- [vscode] added env variable substitution support [#5811](https://github.com/theia-ide/theia/pull/5811) +- [task] added support for VS Code task contribution points: `taskDefinitions`, `problemMatchers`, and `problemPatterns` [#5777](https://github.com/theia-ide/theia/pull/5777) +- [task] added multi-root support to "configure task" and customizing tasks in `tasks.json` [#5777](https://github.com/theia-ide/theia/pull/5777) +- [task] changed the way that "configure task" copies the entire task config, to only writting properties that define the detected task plus [#5777](https://github.com/theia-ide/theia/pull/5777)`problemMatcher`, into `tasks.json` +- [task] fixed the problem where a detected task can be customized more than once [#5777](https://github.com/theia-ide/theia/pull/5777) +- [task] displayed the customized tasks as "configured tasks" in the task quick open [#5777](https://github.com/theia-ide/theia/pull/5777) +- [task] allowed users to override any task properties other than the ones used in the task definition [#5777](https://github.com/theia-ide/theia/pull/5777) + +Breaking changes: + +- [task] `TaskService.getConfiguredTasks()` returns `Promise` instead of `TaskConfiguration[]` [#5777](https://github.com/theia-ide/theia/pull/5777) ## v0.9.0 - [core] added `theia-widget-noInfo` css class to be used by widgets when displaying no information messages [#5717](https://github.com/theia-ide/theia/pull/5717) diff --git a/packages/task/src/browser/provided-task-configurations.spec.ts b/packages/task/src/browser/provided-task-configurations.spec.ts index 6849cc8257c5b..151155bd3e0ca 100644 --- a/packages/task/src/browser/provided-task-configurations.spec.ts +++ b/packages/task/src/browser/provided-task-configurations.spec.ts @@ -17,6 +17,7 @@ import { assert } from 'chai'; import { Container } from 'inversify'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; +import { TaskDefinitionRegistry } from './task-definition-registry'; import { TaskProviderRegistry } from './task-contribution'; import { TaskConfiguration } from '../common'; @@ -26,6 +27,7 @@ describe('provided-task-configurations', () => { container = new Container(); container.bind(ProvidedTaskConfigurations).toSelf().inSingletonScope(); container.bind(TaskProviderRegistry).toSelf().inSingletonScope(); + container.bind(TaskDefinitionRegistry).toSelf().inSingletonScope(); }); it('provided-task-search', async () => { diff --git a/packages/task/src/browser/provided-task-configurations.ts b/packages/task/src/browser/provided-task-configurations.ts index 2369b11060fee..11dfee12c2432 100644 --- a/packages/task/src/browser/provided-task-configurations.ts +++ b/packages/task/src/browser/provided-task-configurations.ts @@ -15,8 +15,10 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { TaskConfiguration } from '../common/task-protocol'; import { TaskProviderRegistry } from './task-contribution'; +import { TaskDefinitionRegistry } from './task-definition-registry'; +import { TaskConfiguration, TaskCustomization } from '../common'; +import URI from '@theia/core/lib/common/uri'; @injectable() export class ProvidedTaskConfigurations { @@ -30,13 +32,14 @@ export class ProvidedTaskConfigurations { @inject(TaskProviderRegistry) protected readonly taskProviderRegistry: TaskProviderRegistry; + @inject(TaskDefinitionRegistry) + protected readonly taskDefinitionRegistry: TaskDefinitionRegistry; + /** returns a list of provided tasks */ async getTasks(): Promise { - const providedTasks: TaskConfiguration[] = []; const providers = this.taskProviderRegistry.getProviders(); - for (const provider of providers) { - providedTasks.push(...await provider.provideTasks()); - } + const providedTasks: TaskConfiguration[] = (await Promise.all(providers.map(p => p.provideTasks()))) + .reduce((acc, taskArray) => acc.concat(taskArray), []); this.cacheTasks(providedTasks); return providedTasks; } @@ -52,6 +55,51 @@ export class ProvidedTaskConfigurations { } } + /** + * Finds the detected task for the given task customization. + * The detected task is considered as a "match" to the task customization if it has all the `required` properties. + * In case that more than one customization is found, return the one that has the biggest number of matched properties. + * + * @param customization the task customization + * @return the detected task for the given task customization. If the task customization is not found, `undefined` is returned. + */ + async getTaskToCustomize(customization: TaskCustomization, rootFolderPath: string): Promise { + const definition = this.taskDefinitionRegistry.getDefinition(customization); + if (!definition) { + return undefined; + } + + const matchedTasks: TaskConfiguration[] = []; + let highest = -1; + const tasks = await this.getTasks(); + for (const task of tasks) { // find detected tasks that match the `definition` + let score = 0; + if (!definition.properties.required.every(requiredProp => customization[requiredProp] !== undefined)) { + continue; + } + score += definition.properties.required.length; // number of required properties + const requiredProps = new Set(definition.properties.required); + // number of optional properties + score += definition.properties.all.filter(p => !requiredProps.has(p) && customization[p] !== undefined).length; + if (score >= highest) { + if (score > highest) { + highest = score; + matchedTasks.length = 0; + } + matchedTasks.push(task); + } + } + + // find the task that matches the `customization`. + // The scenario where more than one match is found should not happen unless users manually enter multiple customizations for one type of task + // If this does happen, return the first match + const rootFolderUri = new URI(rootFolderPath).toString(); + const matchedTask = matchedTasks.filter(t => + rootFolderUri === t._scope && definition.properties.all.every(p => t[p] === customization[p]) + )[0]; + return matchedTask; + } + protected getCachedTask(source: string, taskLabel: string): TaskConfiguration | undefined { const labelConfigMap = this.tasksMap.get(source); if (labelConfigMap) { diff --git a/packages/task/src/browser/quick-open-task.ts b/packages/task/src/browser/quick-open-task.ts index cd9c7ef6129c7..e5eae470680d8 100644 --- a/packages/task/src/browser/quick-open-task.ts +++ b/packages/task/src/browser/quick-open-task.ts @@ -20,7 +20,7 @@ import { QuickOpenGroupItem, QuickOpenMode, QuickOpenHandler, QuickOpenOptions, QuickOpenActionProvider, QuickOpenGroupItemOptions } from '@theia/core/lib/browser/quick-open/'; import { TaskService } from './task-service'; -import { TaskInfo, TaskConfiguration } from '../common/task-protocol'; +import { ContributedTaskConfiguration, TaskInfo, TaskConfiguration } from '../common/task-protocol'; import { TaskConfigurations } from './task-configurations'; import { TaskDefinitionRegistry } from './task-definition-registry'; import URI from '@theia/core/lib/common/uri'; @@ -66,7 +66,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { /** Initialize this quick open model with the tasks. */ async init(): Promise { const recentTasks = this.taskService.recentTasks; - const configuredTasks = this.taskService.getConfiguredTasks(); + const configuredTasks = await this.taskService.getConfiguredTasks(); const providedTasks = await this.taskService.getProvidedTasks(); const { filteredRecentTasks, filteredConfiguredTasks, filteredProvidedTasks } = this.getFilteredTasks(recentTasks, configuredTasks, providedTasks); @@ -213,7 +213,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { const filteredProvidedTasks: TaskConfiguration[] = []; providedTasks.forEach(provided => { - const exist = [...filteredRecentTasks, ...configuredTasks].some(t => TaskConfiguration.equals(provided, t)); + const exist = [...filteredRecentTasks, ...configuredTasks].some(t => ContributedTaskConfiguration.equals(provided, t)); if (!exist) { filteredProvidedTasks.push(provided); } diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index b45cf24311640..ebec9a66f6be2 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -15,8 +15,9 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { TaskConfiguration, TaskCustomization, ContributedTaskConfiguration } from '../common'; +import { ContributedTaskConfiguration, TaskConfiguration, TaskCustomization, TaskDefinition } from '../common'; import { TaskDefinitionRegistry } from './task-definition-registry'; +import { ProvidedTaskConfigurations } from './provided-task-configurations'; import { Disposable, DisposableCollection, ResourceProvider } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; import { FileSystemWatcher, FileChangeEvent } from '@theia/filesystem/lib/browser/filesystem-watcher'; @@ -44,11 +45,14 @@ export class TaskConfigurations implements Disposable { protected readonly toDispose = new DisposableCollection(); /** - * Map of source (path of root folder that the task config comes from) and task config map. + * Map of source (path of root folder that the task configs come from) and task config map. * For the inner map (i.e., task config map), the key is task label and value TaskConfiguration */ protected tasksMap = new Map>(); - protected taskCustomizations: TaskCustomization[] = []; + /** + * Map of source (path of root folder that the task configs come from) and task customizations map. + */ + protected taskCustomizationMap = new Map(); protected watchedConfigFileUris: string[] = []; protected watchersMap = new Map(); // map of watchers for task config files, where the key is folder uri @@ -72,6 +76,9 @@ export class TaskConfigurations implements Disposable { @inject(TaskDefinitionRegistry) protected readonly taskDefinitionRegistry: TaskDefinitionRegistry; + @inject(ProvidedTaskConfigurations) + protected readonly providedTaskConfigurations: ProvidedTaskConfigurations; + constructor( @inject(FileSystemWatcher) protected readonly watcherServer: FileSystemWatcher, @inject(FileSystem) protected readonly fileSystem: FileSystem @@ -160,14 +167,28 @@ export class TaskConfigurations implements Disposable { return Array.from(this.tasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.keys())), [] as string[]); } - /** returns the list of known tasks */ - getTasks(): TaskConfiguration[] { - return Array.from(this.tasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.values())), [] as TaskConfiguration[]); + /** + * returns the list of known tasks, which includes: + * - all the configured tasks in `tasks.json`, and + * - the customized detected tasks + */ + async getTasks(): Promise { + const configuredTasks = Array.from(this.tasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.values())), [] as TaskConfiguration[]); + const detectedTasksAsConfigured: TaskConfiguration[] = []; + for (const [rootFolder, customizations] of Array.from(this.taskCustomizationMap.entries())) { + for (const cus of customizations) { + const detected = await this.providedTaskConfigurations.getTaskToCustomize(cus, rootFolder); + if (detected) { + detectedTasksAsConfigured.push(detected); + } + } + } + return [...configuredTasks, ...detectedTasksAsConfigured]; } /** returns the task configuration for a given label or undefined if none */ - getTask(source: string, taskLabel: string): TaskConfiguration | undefined { - const labelConfigMap = this.tasksMap.get(source); + getTask(rootFolderPath: string, taskLabel: string): TaskConfiguration | undefined { + const labelConfigMap = this.tasksMap.get(rootFolderPath); if (labelConfigMap) { return labelConfigMap.get(taskLabel); } @@ -179,8 +200,55 @@ export class TaskConfigurations implements Disposable { this.tasksMap.delete(source); } - getTaskCustomizations(type: string): TaskCustomization[] { - return this.taskCustomizations.filter(c => c.type === type); + /** + * Removes task customization objects found in the given task config file from the memory. + * Please note: this function does not modify the task config file. + */ + removeTaskCustomizations(configFileUri: string) { + const source = this.getSourceFolderFromConfigUri(configFileUri); + this.taskCustomizationMap.delete(source); + } + + /** + * Returns the task customizations by type from a given root folder in the workspace. + * @param type the type of task customizations + * @param rootFolder the root folder to find task customizations from. If `undefined`, this function returns an empty array. + */ + getTaskCustomizations(type: string, rootFolder?: string): TaskCustomization[] { + if (!rootFolder) { + return []; + } + + const customizationInRootFolder = this.taskCustomizationMap.get(new URI(rootFolder).path.toString()); + if (customizationInRootFolder) { + return customizationInRootFolder.filter(c => c.type === type); + } + return []; + } + + /** + * Returns the customization object in `tasks.json` for the given task. Please note, this function + * returns `undefined` if the given task is not a detected task, because configured tasks don't need + * customization objects - users can modify its config directly in `tasks.json`. + * @param taskConfig The task config, which could either be a configured task or a detected task. + */ + getCustomizationForTask(taskConfig: TaskConfiguration): TaskCustomization | undefined { + if (!this.isDetectedTask(taskConfig)) { + return undefined; + } + + const customizationByType = this.getTaskCustomizations(taskConfig.taskType || taskConfig.type, taskConfig._scope) || []; + const hasCustomization = customizationByType.length > 0; + if (hasCustomization) { + const taskDefinition = this.taskDefinitionRegistry.getDefinition(taskConfig); + if (taskDefinition) { + const cus = customizationByType.filter(customization => + taskDefinition.properties.required.every(rp => customization[rp] === taskConfig[rp]) + )[0]; // Only support having one customization per task + return cus; + } + } + return undefined; } /** returns the string uri of where the config file would be, if it existed under a given root directory */ @@ -226,19 +294,19 @@ export class TaskConfigurations implements Disposable { // user is editing the file in the auto-save mode, having momentarily // non-parsing JSON. this.removeTasks(configFileUri); + this.removeTaskCustomizations(configFileUri); + const rootFolderUri = this.getSourceFolderFromConfigUri(configFileUri); if (configuredTasksArray.length > 0) { const newTaskMap = new Map(); for (const task of configuredTasksArray) { newTaskMap.set(task.label, task); } - const source = this.getSourceFolderFromConfigUri(configFileUri); - this.tasksMap.set(source, newTaskMap); + this.tasksMap.set(rootFolderUri, newTaskMap); } if (customizations.length > 0) { - this.taskCustomizations.length = 0; - this.taskCustomizations = customizations; + this.taskCustomizationMap.set(rootFolderUri, customizations); } } } @@ -275,8 +343,21 @@ export class TaskConfigurations implements Disposable { return; } - const configFileUri = this.getConfigFileUri(workspace.uri); - if (!this.getTasks().some(t => t.label === task.label)) { + const isDetectedTask = this.isDetectedTask(task); + let sourceFolderUri: string | undefined; + if (isDetectedTask) { + sourceFolderUri = task._scope; + } else { + sourceFolderUri = task._source; + } + if (!sourceFolderUri) { + console.error('Global task cannot be customized'); + return; + } + + const configFileUri = this.getConfigFileUri(sourceFolderUri); + const configuredAndCustomizedTasks = await this.getTasks(); + if (!configuredAndCustomizedTasks.some(t => ContributedTaskConfiguration.equals(t, task))) { await this.saveTask(configFileUri, task); } @@ -287,6 +368,24 @@ export class TaskConfigurations implements Disposable { } } + private getTaskCustomizationTemplate(task: TaskConfiguration): TaskCustomization | undefined { + const definition = this.getTaskDefinition(task); + if (!definition) { + console.error('Detected / Contributed tasks should have a task definition.'); + return; + } + const customization: TaskCustomization = { type: task.taskType || task.type }; + definition.properties.all.forEach(p => { + if (task[p] !== undefined) { + customization[p] = task[p]; + } + }); + return { + ...customization, + problemMatcher: [] + }; + } + /** Writes the task to a config file. Creates a config file if this one does not exist */ async saveTask(configFileUri: string, task: TaskConfiguration): Promise { if (configFileUri && !await this.fileSystem.exists(configFileUri)) { @@ -294,12 +393,13 @@ export class TaskConfigurations implements Disposable { } const { _source, $ident, ...preparedTask } = task; + const customizedTaskTemplate = this.getTaskCustomizationTemplate(task) || preparedTask; try { const response = await this.fileSystem.resolveContent(configFileUri); const content = response.content; const formattingOptions = { tabSize: 4, insertSpaces: true, eol: '' }; - const edits = jsoncparser.modify(content, ['tasks', -1], preparedTask, { formattingOptions }); + const edits = jsoncparser.modify(content, ['tasks', -1], customizedTaskTemplate, { formattingOptions }); const result = jsoncparser.applyEdits(content, edits); const resource = await this.resourceProvider(new URI(configFileUri)); @@ -330,8 +430,15 @@ export class TaskConfigurations implements Disposable { /** checks if the config is a detected / contributed task */ private isDetectedTask(task: TaskConfiguration): task is ContributedTaskConfiguration { - const taskDefinition = this.taskDefinitionRegistry.getDefinition(task); + const taskDefinition = this.getTaskDefinition(task); // it is considered as a customization if the task definition registry finds a def for the task configuration return !!taskDefinition; } + + private getTaskDefinition(task: TaskConfiguration): TaskDefinition | undefined { + return this.taskDefinitionRegistry.getDefinition({ + ...task, + type: task.taskType || task.type + }); + } } diff --git a/packages/task/src/browser/task-definition-registry.ts b/packages/task/src/browser/task-definition-registry.ts index 771e916d4f340..303aad3d488b0 100644 --- a/packages/task/src/browser/task-definition-registry.ts +++ b/packages/task/src/browser/task-definition-registry.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable } from 'inversify'; -import { TaskDefinition, TaskConfiguration } from '../common'; +import { TaskConfiguration, TaskCustomization, TaskDefinition } from '../common'; @injectable() export class TaskDefinitionRegistry { @@ -41,7 +41,7 @@ export class TaskDefinitionRegistry { * @param taskConfiguration the task configuration * @return the task definition for the task configuration. If the task definition is not found, `undefined` is returned. */ - getDefinition(taskConfiguration: TaskConfiguration): TaskDefinition | undefined { + getDefinition(taskConfiguration: TaskConfiguration | TaskCustomization): TaskDefinition | undefined { const definitions = this.getDefinitions(taskConfiguration.taskType || taskConfiguration.type); let matchedDefinition: TaskDefinition | undefined; let highest = -1; diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index ef152878a97e0..587d746347f2d 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -27,14 +27,14 @@ import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manag import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { VariableResolverService } from '@theia/variable-resolver/lib/browser'; import { + ContributedTaskConfiguration, ProblemMatcher, ProblemMatchData, - ProblemMatcherContribution, + TaskCustomization, TaskServer, TaskExitedEvent, TaskInfo, TaskConfiguration, - TaskCustomization, TaskOutputProcessedEvent, RunTaskOption } from '../common'; @@ -203,13 +203,16 @@ export class TaskService implements TaskConfigurationClient { /** Returns an array of the task configurations configured in tasks.json and provided by the extensions. */ async getTasks(): Promise { - const configuredTasks = this.getConfiguredTasks(); + const configuredTasks = await this.getConfiguredTasks(); const providedTasks = await this.getProvidedTasks(); - return [...configuredTasks, ...providedTasks]; + const notCustomizedProvidedTasks = providedTasks.filter(provided => + !configuredTasks.some(configured => ContributedTaskConfiguration.equals(configured, provided)) + ); + return [...configuredTasks, ...notCustomizedProvidedTasks]; } /** Returns an array of the task configurations which are configured in tasks.json files */ - getConfiguredTasks(): TaskConfiguration[] { + getConfiguredTasks(): Promise { return this.taskConfigurations.getTasks(); } @@ -299,33 +302,34 @@ export class TaskService implements TaskConfigurationClient { /** * Runs a task, by the source and label of the task configuration. - * It looks for configured and provided tasks. + * It looks for configured and detected tasks. */ async run(source: string, taskLabel: string): Promise { let task = await this.getProvidedTask(source, taskLabel); - const matchers: (string | ProblemMatcherContribution)[] = []; - if (!task) { // if a provided task cannot be found, search from tasks.json + const customizationObject: TaskCustomization = { type: '' }; + if (!task) { // if a detected task cannot be found, search from tasks.json task = this.taskConfigurations.getTask(source, taskLabel); if (!task) { this.logger.error(`Can't get task launch configuration for label: ${taskLabel}`); return; } else if (task.problemMatcher) { - if (Array.isArray(task.problemMatcher)) { - matchers.push(...task.problemMatcher); - } else { - matchers.push(task.problemMatcher); - } + Object.assign(customizationObject, { + type: task.type, + problemMatcher: task.problemMatcher + }); + } + } else { // if a detected task is found, check if it is customized in tasks.json + const customizationFound = this.taskConfigurations.getCustomizationForTask(task); + if (customizationFound) { + Object.assign(customizationObject, customizationFound); } - } else { // if a provided task is found, check if it is customized in tasks.json - const taskType = task.taskType || task.type; - const customizations = this.taskConfigurations.getTaskCustomizations(taskType); - const matcherContributions = this.getProblemMatchers(task, customizations); - matchers.push(...matcherContributions); } await this.problemMatcherRegistry.onReady(); + const notResolvedMatchers = customizationObject.problemMatcher ? + (Array.isArray(customizationObject.problemMatcher) ? customizationObject.problemMatcher : [customizationObject.problemMatcher]) : []; const resolvedMatchers: ProblemMatcher[] = []; // resolve matchers before passing them to the server - for (const matcher of matchers) { + for (const matcher of notResolvedMatchers) { let resolvedMatcher: ProblemMatcher | undefined; if (typeof matcher === 'string') { resolvedMatcher = this.problemMatcherRegistry.get(matcher); @@ -343,13 +347,24 @@ export class TaskService implements TaskConfigurationClient { } } this.runTask(task, { - customization: { type: task.taskType || task.type, problemMatcher: resolvedMatchers } + customization: { ...customizationObject, ...{ problemMatcher: resolvedMatchers } } }); } async runTask(task: TaskConfiguration, option?: RunTaskOption): Promise { const source = task._source; const taskLabel = task.label; + if (option && option.customization) { + const taskDefinition = this.taskDefinitionRegistry.getDefinition(task); + if (taskDefinition) { // use the customization object to override the task config + Object.keys(option.customization).forEach(customizedProperty => { + // properties used to define the task cannot be customized + if (customizedProperty !== 'type' && !taskDefinition.properties.all.some(pDefinition => pDefinition === customizedProperty)) { + task[customizedProperty] = option.customization![customizedProperty]; + } + }); + } + } const resolver = this.taskResolverRegistry.getResolver(task.type); let resolvedTask: TaskConfiguration; @@ -383,27 +398,6 @@ export class TaskService implements TaskConfigurationClient { } } - private getProblemMatchers(taskConfiguration: TaskConfiguration, customizations: TaskCustomization[]): (string | ProblemMatcherContribution)[] { - const hasCustomization = customizations.length > 0; - const problemMatchers: (string | ProblemMatcherContribution)[] = []; - if (hasCustomization) { - const taskDefinition = this.taskDefinitionRegistry.getDefinition(taskConfiguration); - if (taskDefinition) { - const cus = customizations.filter(customization => - taskDefinition.properties.required.every(rp => customization[rp] === taskConfiguration[rp]) - )[0]; // Only support having one customization per task - if (cus && cus.problemMatcher) { - if (Array.isArray(cus.problemMatcher)) { - problemMatchers.push(...cus.problemMatcher); - } else { - problemMatchers.push(cus.problemMatcher); - } - } - } - } - return problemMatchers; - } - private async removeProblemMarks(option?: RunTaskOption): Promise { if (option && option.customization) { const matchersFromOption = option.customization.problemMatcher || []; diff --git a/packages/task/src/common/task-protocol.ts b/packages/task/src/common/task-protocol.ts index 1cb8aa322dfb0..294bee2332fdd 100644 --- a/packages/task/src/common/task-protocol.ts +++ b/packages/task/src/common/task-protocol.ts @@ -35,7 +35,8 @@ export interface TaskConfiguration extends TaskCustomization { } export namespace TaskConfiguration { export function equals(one: TaskConfiguration, other: TaskConfiguration): boolean { - return one.label === other.label && one._source === other._source; + return (one.taskType || one.type) === (other.taskType || other.type) && + one.label === other.label && one._source === other._source; } } @@ -52,6 +53,11 @@ export interface ContributedTaskConfiguration extends TaskConfiguration { */ readonly _scope: string | undefined; } +export namespace ContributedTaskConfiguration { + export function equals(one: TaskConfiguration, other: TaskConfiguration): boolean { + return TaskConfiguration.equals(one, other) && one._scope === other._scope; + } +} /** Runtime information about Task. */ export interface TaskInfo {