diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce5e4bdc4a29d..079882b8b36ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## v1.5.0
+- [tasks] fix vscode.tasks.fetchTasks to no longer filter modified configured tasks aligning to VS Code [#8399](https://github.com/eclipse-theia/theia/pull/8399)
- [security] updated version range of `decompress` to fix the known [security vulnerability](https://snyk.io/vuln/SNYK-JS-DECOMPRESS-557358) [#8924](https://github.com/eclipse-theia/theia/pull/8294)
- Note: the updated dependency may have a [performance impact](https://github.com/eclipse-theia/theia/pull/7715#issuecomment-667434288) on the deployment of plugins.
diff --git a/packages/task/src/browser/task-configurations.spec.ts b/packages/task/src/browser/task-configurations.spec.ts
new file mode 100644
index 0000000000000..191cc2c58fda6
--- /dev/null
+++ b/packages/task/src/browser/task-configurations.spec.ts
@@ -0,0 +1,174 @@
+/********************************************************************************
+ * Copyright (C) 2020 SAP SE or an SAP affiliate company and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import {assert} from 'chai';
+import {Container, injectable} from 'inversify';
+import {enableJSDOM} from '@theia/core/lib/browser/test/jsdom';
+
+const disableJSDOM = enableJSDOM();
+import {ApplicationProps} from '@theia/application-package/lib/application-props';
+import {FrontendApplicationConfigProvider} from '@theia/core/lib/browser/frontend-application-config-provider';
+
+FrontendApplicationConfigProvider.set({
+ ...ApplicationProps.DEFAULT.frontend.config
+});
+import {TaskConfigurations} from './task-configurations';
+import {TaskConfigurationManager} from './task-configuration-manager';
+import {TaskDefinitionRegistry} from './task-definition-registry';
+import {TaskProvider, TaskProviderRegistry} from './task-contribution';
+import {TaskTemplateSelector} from './task-templates';
+import {TaskSchemaUpdater} from './task-schema-updater';
+import {TaskSourceResolver} from './task-source-resolver';
+import {ProvidedTaskConfigurations} from './provided-task-configurations';
+import {TaskConfiguration, TaskConfigurationScope, TaskCustomization, TaskScope} from '../common/task-protocol';
+import {QuickPickService} from '@theia/core/lib/common/quick-pick-service';
+import {ContributionProvider, Event, ILogger} from '@theia/core/lib/common';
+import {
+ ApplicationShell,
+ LabelProviderContribution,
+ PreferenceProvider,
+ PreferenceScope,
+ WidgetManager
+} from '@theia/core/lib/browser';
+import {FileService} from '@theia/filesystem/lib/browser/file-service';
+import {WorkspaceService} from '@theia/workspace/lib/browser';
+import {WorkspaceVariableContribution} from '@theia/workspace/lib/browser/workspace-variable-contribution';
+import {Signal} from '@phosphor/signaling';
+import {EditorManager} from '@theia/editor/lib/browser';
+import {PreferenceConfigurations} from '@theia/core/lib/browser/preferences/preference-configurations';
+import {FileChangeType} from '@theia/filesystem/lib/common/files';
+import {IJSONSchema} from '@theia/core/lib/common/json-schema';
+import {MockLogger} from '@theia/core/lib/common/test/mock-logger';
+import {MockPreferenceProvider} from '@theia/core/lib/browser/preferences/test';
+
+after(() => disableJSDOM());
+
+@injectable()
+class MockTaskConfigurationManager extends TaskConfigurationManager {
+
+ changeTasksConfigFire(): void {
+ this.onDidChangeTaskConfigEmitter.fire({scope: TaskScope.Global, type: FileChangeType.ADDED});
+ }
+
+ getTasks(scope: TaskConfigurationScope): (TaskCustomization | TaskConfiguration)[] {
+ return [
+ {
+ 'type': 'echo',
+ 'label': 'task c',
+ 'text': 'Configured task c'
+ },
+ {
+ 'type': 'echo',
+ 'label': 'task a',
+ 'text': 'Detected task a'
+ }
+ ];
+ }
+}
+
+class MockTaskProvider implements TaskProvider {
+ async provideTasks(): Promise {
+ const taskA: TaskConfiguration = {
+ type: 'echo',
+ group: 'build',
+ _scope: '1',
+ label: 'task a',
+ text: 'Detected task a'
+ };
+ const taskB: TaskConfiguration = {
+ type: 'echo',
+ group: 'build',
+ _scope: '1',
+ label: 'task b',
+ text: 'Detected task b'
+ };
+ return [
+ taskA, taskB
+ ];
+ }
+}
+
+describe('getTasks', () => {
+
+ let testContainer: Container;
+ const taskConfigurationManager = new MockTaskConfigurationManager();
+
+ it('returns all configured tasks: customized and configured', done => {
+ const taskConfiguration = testContainer.get(TaskConfigurations);
+ taskConfigurationManager.changeTasksConfigFire();
+ setTimeout(async () => {
+ const res = await taskConfiguration.getTasks(1);
+ assert.equal(res.length, 2);
+ assert.equal(res[0].label, 'task c');
+ assert.equal(res[1].label, 'task a');
+ done();
+ }, 50);
+ });
+
+ before(() => {
+ testContainer = new Container();
+ testContainer.bind(ILogger).toDynamicValue(ctx => new MockLogger());
+ const workspaceService = new WorkspaceService();
+ testContainer.bind(WorkspaceService).toConstantValue(workspaceService);
+ testContainer.bind(WorkspaceVariableContribution).toSelf().inSingletonScope();
+ testContainer.bind(ApplicationShell).toConstantValue({
+ currentChanged: new Signal({}),
+ widgets: () => []
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any);
+ testContainer.bind(EditorManager).toConstantValue({});
+ testContainer.bind(QuickPickService).toConstantValue({});
+ const taskDefinitionRegistry = new TaskDefinitionRegistry();
+ taskDefinitionRegistry.register({
+ taskType: 'echo',
+ source: 'echotaskprovider',
+ properties: {
+ required: ['label', 'text'],
+ all: ['label', 'text'],
+ schema: new class implements IJSONSchema {
+ }
+ }
+ });
+ testContainer.bind(TaskDefinitionRegistry).toConstantValue(taskDefinitionRegistry);
+ testContainer.bind(TaskConfigurationManager).toConstantValue(taskConfigurationManager);
+
+ testContainer.bind(ProvidedTaskConfigurations).toSelf();
+ testContainer.bind(TaskProviderRegistry).toSelf().inSingletonScope();
+
+ const taskProviderRegistry = testContainer.get(TaskProviderRegistry);
+ taskProviderRegistry.register('echo', new MockTaskProvider());
+
+ testContainer.bind(TaskTemplateSelector).toSelf().inSingletonScope();
+ testContainer.bind(TaskSchemaUpdater).toConstantValue({
+ onDidChangeTaskSchema: Event.None,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ validate(data?: any): boolean {
+ return true;
+ }
+ });
+ testContainer.bind(TaskSourceResolver).toConstantValue({});
+ testContainer.bind(TaskConfigurations).toSelf().inSingletonScope();
+ testContainer.bind(FileService).toConstantValue({});
+ testContainer.bind(PreferenceProvider).toDynamicValue(ctx => new MockPreferenceProvider(PreferenceScope.Workspace)).inSingletonScope();
+ testContainer.bind(PreferenceConfigurations).toConstantValue({});
+ testContainer.bind(WidgetManager).toSelf().inSingletonScope();
+ testContainer.bind>(ContributionProvider).toDynamicValue(ctx => ({
+ getContributions(): LabelProviderContribution[] {
+ return [];
+ }
+ })).inSingletonScope();
+ });
+});
diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts
index 0de1dfaaa3bae..d273c3b0a7fb9 100644
--- a/packages/task/src/browser/task-configurations.ts
+++ b/packages/task/src/browser/task-configurations.ts
@@ -130,7 +130,7 @@ export class TaskConfigurations implements Disposable {
* The invalid task configs are not returned.
*/
async getTasks(token: number): Promise {
- const configuredTasks = Array.from(this.tasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.values())), [] as TaskConfiguration[]);
+ const configuredTasksMap = new Map(this.tasksMap);
const detectedTasksAsConfigured: TaskConfiguration[] = [];
for (const [rootFolder, customizations] of Array.from(this.taskCustomizationMap.entries())) {
for (const cus of customizations) {
@@ -139,9 +139,19 @@ export class TaskConfigurations implements Disposable {
if (detected) {
// there might be a provided task that has a different scope from the task we're inspecting
detectedTasksAsConfigured.push({ ...detected, ...cus });
+ } else {
+ if (configuredTasksMap.has(rootFolder)) {
+ configuredTasksMap.get(rootFolder)!.set(cus['label'], cus as TaskConfiguration);
+ } else {
+ const newConfigMap = new Map();
+ newConfigMap.set(cus['label'], cus as TaskConfiguration);
+ configuredTasksMap.set(rootFolder, newConfigMap);
+ }
}
}
}
+ const configuredTasks = Array.from(configuredTasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.values())), [] as TaskConfiguration[]);
+
return [...configuredTasks, ...detectedTasksAsConfigured];
}