abstractTaskService.ts 100.7 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import Severity from 'vs/base/common/severity';
import * as Objects from 'vs/base/common/objects';
9
import * as resources from 'vs/base/common/resources';
10
import * as json from 'vs/base/common/json';
11
import { URI } from 'vs/base/common/uri';
E
Erich Gamma 已提交
12 13
import { IStringDictionary } from 'vs/base/common/collections';
import { Action } from 'vs/base/common/actions';
14
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
15
import { Event, Emitter } from 'vs/base/common/event';
E
Erich Gamma 已提交
16
import * as Types from 'vs/base/common/types';
17
import { TerminateResponseCode } from 'vs/base/common/processes';
18
import * as strings from 'vs/base/common/strings';
19
import { ValidationStatus, ValidationState } from 'vs/base/common/parsers';
20
import * as UUID from 'vs/base/common/uuid';
21
import * as Platform from 'vs/base/common/platform';
22
import { LRUCache } from 'vs/base/common/map';
E
Erich Gamma 已提交
23

A
Alex Ross 已提交
24
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
25
import { IMarkerService } from 'vs/platform/markers/common/markers';
E
Erich Gamma 已提交
26
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
27
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
28
import { IFileService, IFileStat } from 'vs/platform/files/common/files';
29
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
30
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
31
import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher';
B
Benjamin Pasero 已提交
32
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
33
import { IProgressService, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress';
34

35
import { IOpenerService } from 'vs/platform/opener/common/opener';
36
import { IHostService } from 'vs/workbench/services/host/browser/host';
37
import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification';
38
import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
39

E
Erich Gamma 已提交
40 41
import { IModelService } from 'vs/editor/common/services/modelService';

42
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
S
Sandeep Somavarapu 已提交
43
import Constants from 'vs/workbench/contrib/markers/browser/constants';
44
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
45
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
46
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
47
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
E
Erich Gamma 已提交
48

49
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
A
Alex Ross 已提交
50
import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output';
E
Erich Gamma 已提交
51

52
import { ITerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
53

54
import { ITaskSystem, ITaskResolver, ITaskSummary, TaskExecuteKind, TaskError, TaskErrors, TaskTerminateResponse, TaskSystemInfo, ITaskExecuteResult } from 'vs/workbench/contrib/tasks/common/taskSystem';
55 56
import {
	Task, CustomTask, ConfiguringTask, ContributedTask, InMemoryTask, TaskEvent,
A
Alex Ross 已提交
57
	TaskSet, TaskGroup, GroupType, ExecutionEngine, JsonSchemaVersion, TaskSourceKind,
58 59
	TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource,
	KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition
60 61 62
} from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService';
import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
E
Erich Gamma 已提交
63

64
import * as TaskConfig from '../common/taskConfiguration';
65
import { TerminalTaskSystem } from './terminalTaskSystem';
66

C
Christof Marti 已提交
67
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
68

69
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
70
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
A
Alex Ross 已提交
71
import { RunAutomaticTasks } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks';
72

73
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
74
import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService';
75 76 77 78 79
import { format } from 'vs/base/common/jsonFormatter';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { applyEdits } from 'vs/base/common/jsonEdit';
import { ITextEditor } from 'vs/workbench/common/editor';
import { ITextEditorSelection } from 'vs/platform/editor/common/editor';
80
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
81
import { find } from 'vs/base/common/arrays';
82
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
83

84
const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history';
85
const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail';
86
const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt';
87
const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip';
88

A
Alex Ross 已提交
89 90
const SETTINGS_GROUP_KEY = 'settings';

A
Alex Ross 已提交
91
export namespace ConfigureTaskAction {
92 93
	export const ID = 'workbench.action.tasks.configureTaskRunner';
	export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task");
94 95
}

A
Alex Ross 已提交
96 97
type TaskQuickPickEntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; });

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
class ProblemReporter implements TaskConfig.IProblemReporter {

	private _validationStatus: ValidationStatus;

	constructor(private _outputChannel: IOutputChannel) {
		this._validationStatus = new ValidationStatus();
	}

	public info(message: string): void {
		this._validationStatus.state = ValidationState.Info;
		this._outputChannel.append(message + '\n');
	}

	public warn(message: string): void {
		this._validationStatus.state = ValidationState.Warning;
		this._outputChannel.append(message + '\n');
	}

	public error(message: string): void {
		this._validationStatus.state = ValidationState.Error;
		this._outputChannel.append(message + '\n');
	}

	public fatal(message: string): void {
		this._validationStatus.state = ValidationState.Fatal;
		this._outputChannel.append(message + '\n');
	}

	public get status(): ValidationStatus {
		return this._validationStatus;
	}
}

A
Alex Ross 已提交
131
export interface WorkspaceFolderConfigurationResult {
S
Sandeep Somavarapu 已提交
132
	workspaceFolder: IWorkspaceFolder;
133
	config: TaskConfig.ExternalTaskRunnerConfiguration | undefined;
134 135 136
	hasErrors: boolean;
}

137
interface TaskCustomizationTelemetryEvent {
138 139 140
	properties: string[];
}

A
Alex Ross 已提交
141 142 143 144
function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder {
	return 'uri' in folder;
}

145 146 147 148 149 150 151
class TaskMap {
	private _store: Map<string, Task[]> = new Map();

	public forEach(callback: (value: Task[], folder: string) => void): void {
		this._store.forEach(callback);
	}

A
Alex Ross 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165
	private getKey(workspaceFolder: IWorkspace | IWorkspaceFolder | string): string {
		let key: string | undefined;
		if (Types.isString(workspaceFolder)) {
			key = workspaceFolder;
		} else {
			const uri: URI | null | undefined = isWorkspaceFolder(workspaceFolder) ? workspaceFolder.uri : workspaceFolder.configuration;
			key = uri ? uri.toString() : '';
		}
		return key;
	}

	public get(workspaceFolder: IWorkspace | IWorkspaceFolder | string): Task[] {
		const key = this.getKey(workspaceFolder);
		let result: Task[] | undefined = this._store.get(key);
166 167
		if (!result) {
			result = [];
A
Alex Ross 已提交
168
			this._store.set(key, result);
169 170 171 172
		}
		return result;
	}

A
Alex Ross 已提交
173 174 175
	public add(workspaceFolder: IWorkspace | IWorkspaceFolder | string, ...task: Task[]): void {
		const key = this.getKey(workspaceFolder);
		let values = this._store.get(key);
176 177
		if (!values) {
			values = [];
A
Alex Ross 已提交
178
			this._store.set(key, values);
179 180 181 182 183 184 185 186 187 188 189
		}
		values.push(...task);
	}

	public all(): Task[] {
		let result: Task[] = [];
		this._store.forEach((values) => result.push(...values));
		return result;
	}
}

C
Christof Marti 已提交
190
interface TaskQuickPickEntry extends IQuickPickItem {
191
	task: Task | undefined | null;
192 193
}

194 195 196 197 198 199 200
interface ProblemMatcherDisableMetrics {
	type: string;
}
type ProblemMatcherDisableMetricsClassification = {
	type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};

A
Alex Ross 已提交
201
export abstract class AbstractTaskService extends Disposable implements ITaskService {
202

203
	// private static autoDetectTelemetryName: string = 'taskServer.autoDetect';
204 205
	private static readonly RecentlyUsedTasks_Key = 'workbench.tasks.recentlyUsedTasks';
	private static readonly IgnoreTask010DonotShowAgain_key = 'workbench.tasks.ignoreTask010Shown';
206

207
	private static CustomizationTelemetryEventName: string = 'taskService.customize';
208
	public _serviceBrand: undefined;
J
Johannes Rieken 已提交
209 210
	public static OutputChannelId: string = 'tasks';
	public static OutputChannelLabel: string = nls.localize('tasks', "Tasks");
E
Erich Gamma 已提交
211

212 213
	private static nextHandle: number = 0;

214 215 216
	private _schemaVersion: JsonSchemaVersion | undefined;
	private _executionEngine: ExecutionEngine | undefined;
	private _workspaceFolders: IWorkspaceFolder[] | undefined;
217
	private _workspace: IWorkspace | undefined;
218
	private _ignoredWorkspaceFolders: IWorkspaceFolder[] | undefined;
219
	private _showIgnoreMessage?: boolean;
220
	private _providers: Map<number, ITaskProvider>;
221
	private _providerTypes: Map<number, string>;
A
Alex Ross 已提交
222
	protected _taskSystemInfos: Map<string, TaskSystemInfo>;
223

A
Alex Ross 已提交
224
	protected _workspaceTasksPromise?: Promise<Map<string, WorkspaceFolderTaskResult>>;
A
Alex Ross 已提交
225
	protected _areJsonTasksSupportedPromise: Promise<boolean> = Promise.resolve(false);
226

A
Alex Ross 已提交
227 228
	protected _taskSystem?: ITaskSystem;
	protected _taskSystemListener?: IDisposable;
229
	private _recentlyUsedTasks: LRUCache<string, string> | undefined;
230

A
Alex Ross 已提交
231
	protected _taskRunningState: IContextKey<boolean>;
D
Dirk Baeumer 已提交
232

A
Alex Ross 已提交
233 234
	protected _outputChannel: IOutputChannel;
	protected readonly _onDidStateChange: Emitter<TaskEvent>;
E
Erich Gamma 已提交
235

D
Dirk Baeumer 已提交
236
	constructor(
237
		@IConfigurationService private readonly configurationService: IConfigurationService,
A
Alex Ross 已提交
238 239
		@IMarkerService protected readonly markerService: IMarkerService,
		@IOutputService protected readonly outputService: IOutputService,
240
		@IPanelService private readonly panelService: IPanelService,
241
		@IEditorService private readonly editorService: IEditorService,
A
Alex Ross 已提交
242 243 244
		@IFileService protected readonly fileService: IFileService,
		@IWorkspaceContextService protected readonly contextService: IWorkspaceContextService,
		@ITelemetryService protected readonly telemetryService: ITelemetryService,
245
		@ITextFileService private readonly textFileService: ITextFileService,
246
		@ILifecycleService lifecycleService: ILifecycleService,
A
Alex Ross 已提交
247
		@IModelService protected readonly modelService: IModelService,
248 249
		@IExtensionService private readonly extensionService: IExtensionService,
		@IQuickInputService private readonly quickInputService: IQuickInputService,
A
Alex Ross 已提交
250
		@IConfigurationResolverService protected readonly configurationResolverService: IConfigurationResolverService,
251 252
		@ITerminalService private readonly terminalService: ITerminalService,
		@IStorageService private readonly storageService: IStorageService,
253
		@IProgressService private readonly progressService: IProgressService,
254
		@IOpenerService private readonly openerService: IOpenerService,
255
		@IHostService private readonly _hostService: IHostService,
256 257
		@IDialogService private readonly dialogService: IDialogService,
		@INotificationService private readonly notificationService: INotificationService,
D
Dirk Baeumer 已提交
258
		@IContextKeyService contextKeyService: IContextKeyService,
259
		@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
260 261
		@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
		@ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService,
262
		@IRemotePathService private readonly remotePathService: IRemotePathService,
263 264
		@ITextModelService private readonly textModelResolverService: ITextModelService,
		@IPreferencesService private readonly preferencesService: IPreferencesService
265
	) {
266 267
		super();

268
		this._workspaceTasksPromise = undefined;
269
		this._taskSystem = undefined;
270
		this._taskSystemListener = undefined;
A
Alex Ross 已提交
271
		this._outputChannel = this.outputService.getChannel(AbstractTaskService.OutputChannelId)!;
272
		this._providers = new Map<number, ITaskProvider>();
273
		this._providerTypes = new Map<number, string>();
274
		this._taskSystemInfos = new Map<string, TaskSystemInfo>();
275
		this._register(this.contextService.onDidChangeWorkspaceFolders(() => {
276
			if (!this._taskSystem && !this._workspaceTasksPromise) {
277 278
				return;
			}
D
Dirk Baeumer 已提交
279
			let folderSetup = this.computeWorkspaceFolderSetup();
D
Dirk Baeumer 已提交
280
			if (this.executionEngine !== folderSetup[2]) {
281
				if (this._taskSystem && this._taskSystem.getActiveTasks().length > 0) {
282 283 284 285 286 287 288 289
					this.notificationService.prompt(
						Severity.Info,
						nls.localize(
							'TaskSystem.noHotSwap',
							'Changing the task execution engine with an active task running requires to reload the Window'
						),
						[{
							label: nls.localize('reloadWindow', "Reload Window"),
290
							run: () => this._hostService.reload()
B
Benjamin Pasero 已提交
291 292
						}],
						{ sticky: true }
293
					);
294 295 296 297 298
					return;
				} else {
					this.disposeTaskSystemListeners();
					this._taskSystem = undefined;
				}
D
Dirk Baeumer 已提交
299
			}
D
Dirk Baeumer 已提交
300
			this.updateSetup(folderSetup);
D
Dirk Baeumer 已提交
301
			this.updateWorkspaceTasks();
302
		}));
303
		this._register(this.configurationService.onDidChangeConfiguration(() => {
304 305 306 307 308 309
			if (!this._taskSystem && !this._workspaceTasksPromise) {
				return;
			}
			if (!this._taskSystem || this._taskSystem instanceof TerminalTaskSystem) {
				this._outputChannel.clear();
			}
310 311

			this.setTaskLRUCacheLimit();
312
			this.updateWorkspaceTasks(TaskRunSource.ConfigurationChange);
313
		}));
D
Dirk Baeumer 已提交
314
		this._taskRunningState = TASK_RUNNING_STATE.bindTo(contextKeyService);
315
		this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown())));
316
		this._onDidStateChange = this._register(new Emitter());
317
		this.registerCommands();
318 319 320 321 322 323 324 325 326 327 328 329 330
		this.configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise<string | undefined> => {
			let tasks = await this.getTasksForGroup(TaskGroup.Build);
			if (tasks.length > 0) {
				let { defaults, users } = this.splitPerGroupType(tasks);
				if (defaults.length === 1) {
					return defaults[0]._label;
				} else if (defaults.length + users.length > 0) {
					tasks = defaults.concat(users);
				}
			}

			let entry: TaskQuickPickEntry | null | undefined;
			if (tasks && tasks.length > 0) {
331
				entry = await this.showQuickPick(tasks, nls.localize('TaskService.pickBuildTaskForLabel', 'Select the build task (there is no default build task defined)'));
332 333 334 335 336 337 338 339
			}

			let task: Task | undefined | null = entry ? entry.task : undefined;
			if (!task) {
				return undefined;
			}
			return task._label;
		});
340 341
	}

342 343 344 345
	public get onDidStateChange(): Event<TaskEvent> {
		return this._onDidStateChange.event;
	}

346 347 348 349
	public get supportsMultipleTaskExecutions(): boolean {
		return this.inTerminal();
	}

350
	private registerCommands(): void {
351 352 353 354 355 356 357 358 359 360 361 362 363 364
		CommandsRegistry.registerCommand({
			id: 'workbench.action.tasks.runTask',
			handler: (accessor, arg) => {
				this.runTaskCommand(arg);
			},
			description: {
				description: 'Run Task',
				args: [{
					name: 'args',
					schema: {
						'type': 'string',
					}
				}]
			}
365 366
		});

A
Alex Ross 已提交
367
		CommandsRegistry.registerCommand('workbench.action.tasks.reRunTask', (accessor, arg) => {
368
			this.reRunTaskCommand();
A
Alex Ross 已提交
369 370
		});

371
		CommandsRegistry.registerCommand('workbench.action.tasks.restartTask', (accessor, arg) => {
372
			this.runRestartTaskCommand(arg);
373 374
		});

375
		CommandsRegistry.registerCommand('workbench.action.tasks.terminate', (accessor, arg) => {
376
			this.runTerminateCommand(arg);
377 378 379 380 381 382 383 384 385 386 387 388 389
		});

		CommandsRegistry.registerCommand('workbench.action.tasks.showLog', () => {
			if (!this.canRunCommand()) {
				return;
			}
			this.showOutput();
		});

		CommandsRegistry.registerCommand('workbench.action.tasks.build', () => {
			if (!this.canRunCommand()) {
				return;
			}
390
			this.runBuildCommand();
391 392 393 394 395
		});

		CommandsRegistry.registerCommand('workbench.action.tasks.test', () => {
			if (!this.canRunCommand()) {
				return;
396
			}
397
			this.runTestCommand();
398
		});
399

400 401 402 403
		CommandsRegistry.registerCommand('workbench.action.tasks.configureTaskRunner', () => {
			this.runConfigureTasks();
		});

404 405 406 407 408 409 410
		CommandsRegistry.registerCommand('workbench.action.tasks.configureDefaultBuildTask', () => {
			this.runConfigureDefaultBuildTask();
		});

		CommandsRegistry.registerCommand('workbench.action.tasks.configureDefaultTestTask', () => {
			this.runConfigureDefaultTestTask();
		});
411

412 413
		CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', async () => {
			return this.runShowTasks();
414
		});
415 416 417 418 419 420 421 422 423

		CommandsRegistry.registerCommand('workbench.action.tasks.toggleProblems', () => {
			const panel = this.panelService.getActivePanel();
			if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) {
				this.layoutService.setPanelHidden(true);
			} else {
				this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true);
			}
		});
424

425
		CommandsRegistry.registerCommand('workbench.action.tasks.openUserTasks', async () => {
426 427
			const resource = this.getResourceForKind(TaskSourceKind.User);
			if (resource) {
428 429 430 431 432 433 434 435
				this.openTaskFile(resource, TaskSourceKind.User);
			}
		});

		CommandsRegistry.registerCommand('workbench.action.tasks.openWorkspaceFileTasks', async () => {
			const resource = this.getResourceForKind(TaskSourceKind.WorkspaceFile);
			if (resource) {
				this.openTaskFile(resource, TaskSourceKind.WorkspaceFile);
436 437
			}
		});
E
Erich Gamma 已提交
438 439
	}

D
Dirk Baeumer 已提交
440
	private get workspaceFolders(): IWorkspaceFolder[] {
441
		if (!this._workspaceFolders) {
D
Dirk Baeumer 已提交
442 443
			this.updateSetup();
		}
444
		return this._workspaceFolders!;
D
Dirk Baeumer 已提交
445 446
	}

D
Dirk Baeumer 已提交
447
	private get ignoredWorkspaceFolders(): IWorkspaceFolder[] {
448
		if (!this._ignoredWorkspaceFolders) {
D
Dirk Baeumer 已提交
449 450
			this.updateSetup();
		}
451
		return this._ignoredWorkspaceFolders!;
D
Dirk Baeumer 已提交
452 453
	}

A
Alex Ross 已提交
454
	protected get executionEngine(): ExecutionEngine {
R
Rob Lourens 已提交
455
		if (this._executionEngine === undefined) {
D
Dirk Baeumer 已提交
456 457
			this.updateSetup();
		}
458
		return this._executionEngine!;
D
Dirk Baeumer 已提交
459 460 461
	}

	private get schemaVersion(): JsonSchemaVersion {
R
Rob Lourens 已提交
462
		if (this._schemaVersion === undefined) {
D
Dirk Baeumer 已提交
463 464
			this.updateSetup();
		}
465
		return this._schemaVersion!;
D
Dirk Baeumer 已提交
466 467
	}

D
Dirk Baeumer 已提交
468
	private get showIgnoreMessage(): boolean {
R
Rob Lourens 已提交
469
		if (this._showIgnoreMessage === undefined) {
A
Alex Ross 已提交
470
			this._showIgnoreMessage = !this.storageService.getBoolean(AbstractTaskService.IgnoreTask010DonotShowAgain_key, StorageScope.WORKSPACE, false);
D
Dirk Baeumer 已提交
471
		}
472
		return this._showIgnoreMessage;
D
Dirk Baeumer 已提交
473 474
	}

475
	private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined]): void {
D
Dirk Baeumer 已提交
476 477 478
		if (!setup) {
			setup = this.computeWorkspaceFolderSetup();
		}
479 480 481 482
		this._workspaceFolders = setup[0];
		if (this._ignoredWorkspaceFolders) {
			if (this._ignoredWorkspaceFolders.length !== setup[1].length) {
				this._showIgnoreMessage = undefined;
D
Dirk Baeumer 已提交
483 484
			} else {
				let set: Set<string> = new Set();
485
				this._ignoredWorkspaceFolders.forEach(folder => set.add(folder.uri.toString()));
D
Dirk Baeumer 已提交
486 487
				for (let folder of setup[1]) {
					if (!set.has(folder.uri.toString())) {
488
						this._showIgnoreMessage = undefined;
D
Dirk Baeumer 已提交
489 490 491 492 493
						break;
					}
				}
			}
		}
494 495 496
		this._ignoredWorkspaceFolders = setup[1];
		this._executionEngine = setup[2];
		this._schemaVersion = setup[3];
497
		this._workspace = setup[4];
D
Dirk Baeumer 已提交
498 499
	}

A
Alex Ross 已提交
500
	protected showOutput(runSource: TaskRunSource = TaskRunSource.User): void {
A
Alex Ross 已提交
501
		if ((runSource === TaskRunSource.User) || (runSource === TaskRunSource.ConfigurationChange)) {
502 503 504 505 506 507 508
			this.notificationService.prompt(Severity.Warning, nls.localize('taskServiceOutputPrompt', 'There are task errors. See the output for details.'),
				[{
					label: nls.localize('showOutput', "Show output"),
					run: () => {
						this.outputService.showChannel(this._outputChannel.id, true);
					}
				}]);
509
		}
510 511
	}

E
Erich Gamma 已提交
512
	private disposeTaskSystemListeners(): void {
513 514 515
		if (this._taskSystemListener) {
			this._taskSystemListener.dispose();
		}
E
Erich Gamma 已提交
516 517
	}

518
	public registerTaskProvider(provider: ITaskProvider, type: string): IDisposable {
519
		if (!provider) {
520 521 522
			return {
				dispose: () => { }
			};
523
		}
A
Alex Ross 已提交
524
		let handle = AbstractTaskService.nextHandle++;
525
		this._providers.set(handle, provider);
526
		this._providerTypes.set(handle, type);
527 528 529
		return {
			dispose: () => {
				this._providers.delete(handle);
530
				this._providerTypes.delete(handle);
531 532
			}
		};
533 534
	}

535 536 537 538
	public registerTaskSystem(key: string, info: TaskSystemInfo): void {
		this._taskSystemInfos.set(key, info);
	}

G
Gabriel DeBacker 已提交
539
	public extensionCallbackTaskComplete(task: Task, result: number): Promise<void> {
540 541 542
		if (!this._taskSystem) {
			return Promise.resolve();
		}
G
Gabriel DeBacker 已提交
543
		return this._taskSystem.customExecutionComplete(task, result);
544 545
	}

A
Alex Ross 已提交
546 547
	public getTask(folder: IWorkspace | IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise<Task | undefined> {
		const name = Types.isString(folder) ? folder : isWorkspaceFolder(folder) ? folder.name : folder.configuration ? resources.basename(folder.configuration) : undefined;
D
Dirk Baeumer 已提交
548
		if (this.ignoredWorkspaceFolders.some(ignored => ignored.name === name)) {
549
			return Promise.reject(new Error(nls.localize('TaskServer.folderIgnored', 'The folder {0} is ignored since it uses task version 0.1.0', name)));
D
Dirk Baeumer 已提交
550
		}
551 552 553 554
		const key: string | KeyedTaskIdentifier | undefined = !Types.isString(identifier)
			? TaskDefinition.createTaskIdentifier(identifier, console)
			: identifier;

R
Rob Lourens 已提交
555
		if (key === undefined) {
556
			return Promise.resolve(undefined);
557
		}
558
		return this.getGroupedTasks().then((map) => {
A
Alex Ross 已提交
559 560 561
			let values = map.get(folder);
			values = values.concat(map.get(SETTINGS_GROUP_KEY));

562 563 564
			if (!values) {
				return undefined;
			}
565
			return find(values, task => task.matches(key, compareId));
566 567 568
		});
	}

A
Alex Ross 已提交
569
	protected abstract versionAndEngineCompatible(filter?: TaskFilter): boolean;
570

A
Alex Ross 已提交
571 572
	public tasks(filter?: TaskFilter): Promise<Task[]> {
		if (!this.versionAndEngineCompatible(filter)) {
573
			return Promise.resolve<Task[]>([]);
574
		}
575
		return this.getGroupedTasks(filter ? filter.type : undefined).then((map) => {
576 577 578 579 580 581
			if (!filter || !filter.type) {
				return map.all();
			}
			let result: Task[] = [];
			map.forEach((tasks) => {
				for (let task of tasks) {
582
					if (ContributedTask.is(task) && task.defines.type === filter.type) {
583
						result.push(task);
584 585 586 587
					} else if (CustomTask.is(task)) {
						if (task.type === filter.type) {
							result.push(task);
						} else {
A
Alex Ross 已提交
588
							let customizes = task.customizes();
589 590 591 592
							if (customizes && customizes.type === filter.type) {
								result.push(task);
							}
						}
593 594 595 596 597
					}
				}
			});
			return result;
		});
598
	}
599

600 601 602 603
	public createSorter(): TaskSorter {
		return new TaskSorter(this.contextService.getWorkspace() ? this.contextService.getWorkspace().folders : []);
	}

J
Johannes Rieken 已提交
604
	public isActive(): Promise<boolean> {
605
		if (!this._taskSystem) {
606
			return Promise.resolve(false);
607 608 609 610
		}
		return this._taskSystem.isActive();
	}

J
Johannes Rieken 已提交
611
	public getActiveTasks(): Promise<Task[]> {
612
		if (!this._taskSystem) {
613
			return Promise.resolve([]);
614
		}
615
		return Promise.resolve(this._taskSystem.getActiveTasks());
616 617
	}

618 619 620 621 622 623 624
	public getBusyTasks(): Promise<Task[]> {
		if (!this._taskSystem) {
			return Promise.resolve([]);
		}
		return Promise.resolve(this._taskSystem.getBusyTasks());
	}

625
	public getRecentlyUsedTasks(): LRUCache<string, string> {
626 627 628
		if (this._recentlyUsedTasks) {
			return this._recentlyUsedTasks;
		}
629 630 631
		const quickOpenHistoryLimit = this.configurationService.getValue<number>(QUICKOPEN_HISTORY_LIMIT_CONFIG);
		this._recentlyUsedTasks = new LRUCache<string, string>(quickOpenHistoryLimit);

A
Alex Ross 已提交
632
		let storageValue = this.storageService.get(AbstractTaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE);
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
		if (storageValue) {
			try {
				let values: string[] = JSON.parse(storageValue);
				if (Array.isArray(values)) {
					for (let value of values) {
						this._recentlyUsedTasks.set(value, value);
					}
				}
			} catch (error) {
				// Ignore. We use the empty result
			}
		}
		return this._recentlyUsedTasks;
	}

648 649 650 651 652 653 654
	private setTaskLRUCacheLimit() {
		const quickOpenHistoryLimit = this.configurationService.getValue<number>(QUICKOPEN_HISTORY_LIMIT_CONFIG);
		if (this._recentlyUsedTasks) {
			this._recentlyUsedTasks.limit = quickOpenHistoryLimit;
		}
	}

655
	private setRecentlyUsedTask(key: string): void {
656
		this.getRecentlyUsedTasks().set(key, key);
657 658 659 660
		this.saveRecentlyUsedTasks();
	}

	private saveRecentlyUsedTasks(): void {
661
		if (!this._taskSystem || !this._recentlyUsedTasks) {
662 663
			return;
		}
664 665 666 667 668
		const quickOpenHistoryLimit = this.configurationService.getValue<number>(QUICKOPEN_HISTORY_LIMIT_CONFIG);
		// setting history limit to 0 means no LRU sorting
		if (quickOpenHistoryLimit === 0) {
			return;
		}
669
		let values = this._recentlyUsedTasks.values();
670 671
		if (values.length > quickOpenHistoryLimit) {
			values = values.slice(0, quickOpenHistoryLimit);
672
		}
A
Alex Ross 已提交
673
		this.storageService.store(AbstractTaskService.RecentlyUsedTasks_Key, JSON.stringify(values), StorageScope.WORKSPACE);
674
	}
675

676
	private openDocumentation(): void {
677 678 679
		this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=733558'));
	}

J
Johannes Rieken 已提交
680
	public build(): Promise<ITaskSummary> {
681
		return this.getGroupedTasks().then((tasks) => {
D
Dirk Baeumer 已提交
682
			let runnable = this.createRunnableTask(tasks, TaskGroup.Build);
683
			if (!runnable || !runnable.task) {
D
Dirk Baeumer 已提交
684
				if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
685 686 687 688
					throw new TaskError(Severity.Info, nls.localize('TaskService.noBuildTask1', 'No build task defined. Mark a task with \'isBuildCommand\' in the tasks.json file.'), TaskErrors.NoBuildTask);
				} else {
					throw new TaskError(Severity.Info, nls.localize('TaskService.noBuildTask2', 'No build task defined. Mark a task with as a \'build\' group in the tasks.json file.'), TaskErrors.NoBuildTask);
				}
689 690 691 692
			}
			return this.executeTask(runnable.task, runnable.resolver);
		}).then(value => value, (error) => {
			this.handleError(error);
693
			return Promise.reject(error);
694 695 696
		});
	}

J
Johannes Rieken 已提交
697
	public runTest(): Promise<ITaskSummary> {
698
		return this.getGroupedTasks().then((tasks) => {
D
Dirk Baeumer 已提交
699
			let runnable = this.createRunnableTask(tasks, TaskGroup.Test);
700
			if (!runnable || !runnable.task) {
D
Dirk Baeumer 已提交
701
				if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
702 703 704 705
					throw new TaskError(Severity.Info, nls.localize('TaskService.noTestTask1', 'No test task defined. Mark a task with \'isTestCommand\' in the tasks.json file.'), TaskErrors.NoTestTask);
				} else {
					throw new TaskError(Severity.Info, nls.localize('TaskService.noTestTask2', 'No test task defined. Mark a task with as a \'test\' group in the tasks.json file.'), TaskErrors.NoTestTask);
				}
706 707 708 709
			}
			return this.executeTask(runnable.task, runnable.resolver);
		}).then(value => value, (error) => {
			this.handleError(error);
710
			return Promise.reject(error);
711 712 713
		});
	}

714
	public run(task: Task | undefined, options?: ProblemMatcherRunOptions, runSource: TaskRunSource = TaskRunSource.System): Promise<ITaskSummary | undefined> {
715 716 717
		if (!task) {
			throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound);
		}
718
		return this.getGroupedTasks().then((grouped) => {
719 720 721 722 723 724 725 726 727
			let resolver = this.createResolver(grouped);
			if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) {
				return this.attachProblemMatcher(task).then((toExecute) => {
					if (toExecute) {
						return this.executeTask(toExecute, resolver);
					} else {
						return Promise.resolve(undefined);
					}
				});
728
			}
729
			return this.executeTask(task, resolver);
730 731 732 733 734 735 736 737
		}).then((value) => {
			if (runSource === TaskRunSource.User) {
				this.getWorkspaceTasks().then(workspaceTasks => {
					RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, workspaceTasks);
				});
			}
			return value;
		}, (error) => {
738
			this.handleError(error);
739
			return Promise.reject(error);
740 741 742
		});
	}

743 744
	private isProvideTasksEnabled(): boolean {
		const settingValue = this.configurationService.getValue('task.autoDetect');
745
		return settingValue === 'on';
746 747
	}

748
	private isProblemMatcherPromptEnabled(type?: string): boolean {
749
		const settingValue = this.configurationService.getValue(PROBLEM_MATCHER_NEVER_CONFIG);
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
		if (Types.isBoolean(settingValue)) {
			return !settingValue;
		}
		if (type === undefined) {
			return true;
		}
		const settingValueMap: IStringDictionary<boolean> = <any>settingValue;
		return !settingValueMap[type];
	}

	private getTypeForTask(task: Task): string {
		let type: string;
		if (CustomTask.is(task)) {
			let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element;
			type = (<any>configProperties).type;
		} else {
			type = task.getDefinition()!.type;
		}
		return type;
	}

	private shouldAttachProblemMatcher(task: Task): boolean {
		const enabled = this.isProblemMatcherPromptEnabled(this.getTypeForTask(task));
		if (enabled === false) {
774 775
			return false;
		}
D
Dirk Baeumer 已提交
776
		if (!this.canCustomize(task)) {
777 778
			return false;
		}
R
Rob Lourens 已提交
779
		if (task.configurationProperties.group !== undefined && task.configurationProperties.group !== TaskGroup.Build) {
780 781
			return false;
		}
R
Rob Lourens 已提交
782
		if (task.configurationProperties.problemMatchers !== undefined && task.configurationProperties.problemMatchers.length > 0) {
783 784
			return false;
		}
785
		if (ContributedTask.is(task)) {
786
			return !task.hasDefinedMatchers && !!task.configurationProperties.problemMatchers && (task.configurationProperties.problemMatchers.length === 0);
787
		}
788 789
		if (CustomTask.is(task)) {
			let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element;
790
			return configProperties.problemMatcher === undefined && !task.hasDefinedMatchers;
791 792
		}
		return false;
793 794
	}

795 796 797 798 799 800
	private async updateNeverProblemMatcherSetting(type: string): Promise<void> {
		this.telemetryService.publicLog2<ProblemMatcherDisableMetrics, ProblemMatcherDisableMetricsClassification>('problemMatcherDisabled', { type });
		const current = this.configurationService.getValue(PROBLEM_MATCHER_NEVER_CONFIG);
		if (current === true) {
			return;
		}
801 802 803 804 805
		let newValue: IStringDictionary<boolean>;
		if (current !== false) {
			newValue = <any>current;
		} else {
			newValue = Object.create(null);
806
		}
807 808
		newValue[type] = true;
		return this.configurationService.updateValue(PROBLEM_MATCHER_NEVER_CONFIG, newValue, ConfigurationTarget.USER);
809 810
	}

811
	private attachProblemMatcher(task: ContributedTask | CustomTask): Promise<Task | undefined> {
C
Christof Marti 已提交
812
		interface ProblemMatcherPickEntry extends IQuickPickItem {
813
			matcher: NamedProblemMatcher | undefined;
814
			never?: boolean;
815
			learnMore?: boolean;
816
			setting?: string;
817
		}
C
Christof Marti 已提交
818
		let entries: QuickPickInput<ProblemMatcherPickEntry>[] = [];
819 820
		for (let key of ProblemMatcherRegistry.keys()) {
			let matcher = ProblemMatcherRegistry.get(key);
821 822 823
			if (matcher.deprecated) {
				continue;
			}
824 825 826 827 828 829 830 831 832 833 834
			if (matcher.name === matcher.label) {
				entries.push({ label: matcher.name, matcher: matcher });
			} else {
				entries.push({
					label: matcher.label,
					description: `$${matcher.name}`,
					matcher: matcher
				});
			}
		}
		if (entries.length > 0) {
835 836 837 838 839 840 841
			entries = entries.sort((a, b) => {
				if (a.label && b.label) {
					return a.label.localeCompare(b.label);
				} else {
					return 0;
				}
			});
C
Christof Marti 已提交
842
			entries.unshift({ type: 'separator', label: nls.localize('TaskService.associate', 'associate') });
843 844 845 846 847 848 849
			let taskType: string;
			if (CustomTask.is(task)) {
				let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element;
				taskType = (<any>configProperties).type;
			} else {
				taskType = task.getDefinition().type;
			}
850
			entries.unshift(
851
				{ label: nls.localize('TaskService.attachProblemMatcher.continueWithout', 'Continue without scanning the task output'), matcher: undefined },
852 853
				{ label: nls.localize('TaskService.attachProblemMatcher.never', 'Never scan the task output for this task'), matcher: undefined, never: true },
				{ label: nls.localize('TaskService.attachProblemMatcher.neverType', 'Never scan the task output for {0} tasks', taskType), matcher: undefined, setting: taskType },
854
				{ label: nls.localize('TaskService.attachProblemMatcher.learnMoreAbout', 'Learn more about scanning the task output'), matcher: undefined, learnMore: true }
855
			);
C
Christof Marti 已提交
856
			return this.quickInputService.pick(entries, {
857
				placeHolder: nls.localize('selectProblemMatcher', 'Select for which kind of errors and warnings to scan the task output'),
858
			}).then(async (selected) => {
859 860 861 862
				if (selected) {
					if (selected.learnMore) {
						this.openDocumentation();
						return undefined;
863 864 865
					} else if (selected.never) {
						this.customize(task, { problemMatcher: [] }, true);
						return task;
866
					} else if (selected.matcher) {
A
Alex Ross 已提交
867
						let newTask = task.clone();
868
						let matcherReference = `$${selected.matcher.name}`;
869
						let properties: CustomizationProperties = { problemMatcher: [matcherReference] };
A
Alex Ross 已提交
870
						newTask.configurationProperties.problemMatchers = [matcherReference];
871
						let matcher = ProblemMatcherRegistry.get(selected.matcher.name);
R
Rob Lourens 已提交
872
						if (matcher && matcher.watching !== undefined) {
873
							properties.isBackground = true;
A
Alex Ross 已提交
874
							newTask.configurationProperties.isBackground = true;
875 876
						}
						this.customize(task, properties, true);
877
						return newTask;
878 879 880
					} else if (selected.setting) {
						await this.updateNeverProblemMatcherSetting(selected.setting);
						return task;
881 882 883 884
					} else {
						return task;
					}
				} else {
885
					return undefined;
886 887 888
				}
			});
		}
889
		return Promise.resolve(task);
890 891
	}

J
Johannes Rieken 已提交
892
	public getTasksForGroup(group: string): Promise<Task[]> {
893
		return this.getGroupedTasks().then((groups) => {
894
			let result: Task[] = [];
895 896
			groups.forEach((tasks) => {
				for (let task of tasks) {
A
Alex Ross 已提交
897
					if (task.configurationProperties.group === group) {
898 899
						result.push(task);
					}
900
				}
901
			});
902 903 904 905
			return result;
		});
	}

906 907
	public needsFolderQualification(): boolean {
		return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
D
Dirk Baeumer 已提交
908 909 910
	}

	public canCustomize(task: Task): boolean {
D
Dirk Baeumer 已提交
911
		if (this.schemaVersion !== JsonSchemaVersion.V2_0_0) {
D
Dirk Baeumer 已提交
912 913 914 915 916 917
			return false;
		}
		if (CustomTask.is(task)) {
			return true;
		}
		if (ContributedTask.is(task)) {
A
Alex Ross 已提交
918
			return !!task.getWorkspaceFolder();
D
Dirk Baeumer 已提交
919 920
		}
		return false;
921 922
	}

923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
	private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise<ITextEditor | null | undefined> {
		if (resource === undefined) {
			return Promise.resolve(undefined);
		}
		let selection: ITextEditorSelection | undefined;
		return this.fileService.readFile(resource).then(content => content.value).then(async content => {
			if (!content) {
				return undefined;
			}
			if (task) {
				const contentValue = content.toString();
				let stringValue: string;
				if (typeof task === 'string') {
					stringValue = task;
				} else {
					const model = (await this.textModelResolverService.createModelReference(resource)).object.textEditorModel;
					const { tabSize, insertSpaces } = model.getOptions();
					const eol = model.getEOL();
					const edits = format(JSON.stringify(task), undefined, { eol, tabSize, insertSpaces });
					let stringified = applyEdits(JSON.stringify(task), edits);
					const regex = new RegExp(eol + '\\t', 'g');
					stringified = stringified.replace(regex, eol + '\t\t\t');
					const twoTabs = '\t\t';
					stringValue = twoTabs + stringified.slice(0, stringified.length - 1) + twoTabs + stringified.slice(stringified.length - 1);
				}

				const index = contentValue.indexOf(stringValue);
				let startLineNumber = 1;
				for (let i = 0; i < index; i++) {
					if (contentValue.charAt(i) === '\n') {
						startLineNumber++;
					}
				}
				let endLineNumber = startLineNumber;
				for (let i = 0; i < stringValue.length; i++) {
					if (stringValue.charAt(i) === '\n') {
						endLineNumber++;
					}
				}
				selection = startLineNumber > 1 ? { startLineNumber, startColumn: startLineNumber === endLineNumber ? 4 : 3, endLineNumber, endColumn: startLineNumber === endLineNumber ? undefined : 4 } : undefined;
			}

			return this.editorService.openEditor({
				resource,
				options: {
					pinned: false,
					forceReload: true, // because content might have changed
					selection,
					revealInCenterIfOutsideViewport: !!selection
				}
			});
		});
	}

J
Johannes Rieken 已提交
977
	public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise<void> {
978
		const workspaceFolder = task.getWorkspaceFolder();
D
Dirk Baeumer 已提交
979
		if (!workspaceFolder) {
980
			return Promise.resolve(undefined);
D
Dirk Baeumer 已提交
981 982
		}
		let configuration = this.getConfiguration(workspaceFolder);
D
Dirk Baeumer 已提交
983
		if (configuration.hasParseErrors) {
984
			this.notificationService.warn(nls.localize('customizeParseErrors', 'The current task configuration has errors. Please fix the errors first before customizing a task.'));
985
			return Promise.resolve<void>(undefined);
D
Dirk Baeumer 已提交
986
		}
987

D
Dirk Baeumer 已提交
988
		let fileConfig = configuration.config;
989 990
		let index: number | undefined;
		let toCustomize: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | undefined;
991
		let taskConfig = CustomTask.is(task) ? task._source.config : undefined;
992 993 994
		if (taskConfig && taskConfig.element) {
			index = taskConfig.index;
			toCustomize = taskConfig.element;
995 996 997 998 999
		} else if (ContributedTask.is(task)) {
			toCustomize = {
			};
			let identifier: TaskConfig.TaskIdentifier = Objects.assign(Object.create(null), task.defines);
			delete identifier['_key'];
1000
			Object.keys(identifier).forEach(key => (<any>toCustomize)![key] = identifier[key]);
A
Alex Ross 已提交
1001 1002
			if (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length > 0 && Types.isStringArray(task.configurationProperties.problemMatchers)) {
				toCustomize.problemMatcher = task.configurationProperties.problemMatchers;
1003
			}
A
Alex Ross 已提交
1004 1005 1006
			if (task.configurationProperties.group) {
				toCustomize.group = task.configurationProperties.group;
			}
1007 1008
		}
		if (!toCustomize) {
1009
			return Promise.resolve(undefined);
1010
		}
1011 1012
		if (properties) {
			for (let property of Object.getOwnPropertyNames(properties)) {
1013
				let value = (<any>properties)[property];
R
Rob Lourens 已提交
1014
				if (value !== undefined && value !== null) {
1015
					(<any>toCustomize)[property] = value;
1016 1017 1018
				}
			}
		} else {
1019
			if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length === 0)) {
1020
				toCustomize.problemMatcher = [];
1021
			}
1022
		}
1023

1024
		let promise: Promise<void> | undefined;
D
Dirk Baeumer 已提交
1025
		if (!fileConfig) {
1026
			let value = {
D
Dirk Baeumer 已提交
1027
				version: '2.0.0',
1028
				tasks: [toCustomize]
D
Dirk Baeumer 已提交
1029
			};
1030 1031
			let content = [
				'{',
1032
				nls.localize('tasksJsonComment', '\t// See https://go.microsoft.com/fwlink/?LinkId=733558 \n\t// for the documentation about the tasks.json format'),
1033
			].join('\n') + JSON.stringify(value, null, '\t').substr(1);
1034
			let editorConfig = this.configurationService.getValue<any>();
1035 1036 1037
			if (editorConfig.editor.insertSpaces) {
				content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + strings.repeat(' ', s2.length * editorConfig.editor.tabSize));
			}
1038
			promise = this.textFileService.create(workspaceFolder.toResource('.vscode/tasks.json'), content).then(() => { });
D
Dirk Baeumer 已提交
1039
		} else {
1040
			// We have a global task configuration
1041
			if ((index === -1) && properties) {
R
Rob Lourens 已提交
1042
				if (properties.problemMatcher !== undefined) {
1043
					fileConfig.problemMatcher = properties.problemMatcher;
1044
					promise = this.writeConfiguration(workspaceFolder, 'tasks.problemMatchers', fileConfig.problemMatcher);
R
Rob Lourens 已提交
1045
				} else if (properties.group !== undefined) {
1046
					fileConfig.group = properties.group;
1047
					promise = this.writeConfiguration(workspaceFolder, 'tasks.group', fileConfig.group);
1048
				}
1049 1050 1051 1052
			} else {
				if (!Array.isArray(fileConfig.tasks)) {
					fileConfig.tasks = [];
				}
R
Rob Lourens 已提交
1053
				if (index === undefined) {
1054 1055 1056 1057
					fileConfig.tasks.push(toCustomize);
				} else {
					fileConfig.tasks[index] = toCustomize;
				}
1058
				promise = this.writeConfiguration(workspaceFolder, 'tasks.tasks', fileConfig.tasks);
D
Dirk Baeumer 已提交
1059
			}
1060
		}
1061
		if (!promise) {
1062
			return Promise.resolve(undefined);
1063
		}
1064
		return promise.then(() => {
1065
			let event: TaskCustomizationTelemetryEvent = {
1066 1067
				properties: properties ? Object.getOwnPropertyNames(properties) : []
			};
K
kieferrm 已提交
1068
			/* __GDPR__
K
kieferrm 已提交
1069 1070 1071 1072
				"taskService.customize" : {
					"properties" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
A
Alex Ross 已提交
1073
			this.telemetryService.publicLog(AbstractTaskService.CustomizationTelemetryEventName, event);
D
Dirk Baeumer 已提交
1074
			if (openConfig) {
1075
				this.openEditorAtTask(workspaceFolder.toResource('.vscode/tasks.json'), toCustomize);
D
Dirk Baeumer 已提交
1076 1077 1078 1079
			}
		});
	}

1080
	private writeConfiguration(workspaceFolder: IWorkspaceFolder, key: string, value: any): Promise<void> | undefined {
D
Dirk Baeumer 已提交
1081
		if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
1082
			return this.configurationService.updateValue(key, value, { resource: workspaceFolder.uri }, ConfigurationTarget.WORKSPACE);
D
Dirk Baeumer 已提交
1083
		} else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
1084
			return this.configurationService.updateValue(key, value, { resource: workspaceFolder.uri }, ConfigurationTarget.WORKSPACE_FOLDER);
D
Dirk Baeumer 已提交
1085 1086 1087 1088 1089
		} else {
			return undefined;
		}
	}

1090
	private getResourceForKind(kind: string): URI | undefined {
1091
		this.updateSetup();
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
		switch (kind) {
			case TaskSourceKind.User: {
				return resources.joinPath(resources.dirname(this.preferencesService.userSettingsResource), 'tasks.json');
			}
			case TaskSourceKind.WorkspaceFile: {
				if (this._workspace && this._workspace.configuration) {
					return this._workspace.configuration;
				}
			}
			default: {
				return undefined;
			}
		}
	}

	private getResourceForTask(task: CustomTask): URI {
		let uri = this.getResourceForKind(task._source.kind);
		if (!uri) {
A
Alex Ross 已提交
1110 1111 1112 1113 1114 1115
			const taskFolder = task.getWorkspaceFolder();
			if (taskFolder) {
				uri = taskFolder.toResource(task._source.config.file);
			} else {
				uri = this.workspaceFolders[0].uri;
			}
1116 1117 1118 1119
		}
		return uri;
	}

J
Johannes Rieken 已提交
1120
	public openConfig(task: CustomTask | undefined): Promise<void> {
1121
		let resource: URI | undefined;
1122
		if (task) {
1123
			resource = this.getResourceForTask(task);
1124 1125 1126
		} else {
			resource = (this._workspaceFolders && (this._workspaceFolders.length > 0)) ? this._workspaceFolders[0].toResource('.vscode/tasks.json') : undefined;
		}
1127
		return this.openEditorAtTask(resource, task ? task._label : undefined).then(() => undefined);
1128 1129
	}

1130
	private createRunnableTask(tasks: TaskMap, group: TaskGroup): { task: Task; resolver: ITaskResolver } | undefined {
1131 1132 1133 1134 1135
		interface ResolverData {
			id: Map<string, Task>;
			label: Map<string, Task>;
			identifier: Map<string, Task>;
		}
1136

1137
		let resolverData: Map<string, ResolverData> = new Map();
1138 1139
		let workspaceTasks: Task[] = [];
		let extensionTasks: Task[] = [];
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
		tasks.forEach((tasks, folder) => {
			let data = resolverData.get(folder);
			if (!data) {
				data = {
					id: new Map<string, Task>(),
					label: new Map<string, Task>(),
					identifier: new Map<string, Task>()
				};
				resolverData.set(folder, data);
			}
			for (let task of tasks) {
				data.id.set(task._id, task);
				data.label.set(task._label, task);
1153 1154 1155
				if (task.configurationProperties.identifier) {
					data.identifier.set(task.configurationProperties.identifier, task);
				}
A
Alex Ross 已提交
1156
				if (group && task.configurationProperties.group === group) {
1157 1158 1159 1160 1161
					if (task._source.kind === TaskSourceKind.Workspace) {
						workspaceTasks.push(task);
					} else {
						extensionTasks.push(task);
					}
1162
				}
D
Dirk Baeumer 已提交
1163
			}
1164 1165
		});
		let resolver: ITaskResolver = {
1166 1167
			resolve: (uri: URI, alias: string) => {
				let data = resolverData.get(uri.toString());
1168 1169 1170 1171
				if (!data) {
					return undefined;
				}
				return data.id.get(alias) || data.label.get(alias) || data.identifier.get(alias);
1172 1173
			}
		};
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
		if (workspaceTasks.length > 0) {
			if (workspaceTasks.length > 1) {
				this._outputChannel.append(nls.localize('moreThanOneBuildTask', 'There are many build tasks defined in the tasks.json. Executing the first one.\n'));
			}
			return { task: workspaceTasks[0], resolver };
		}
		if (extensionTasks.length === 0) {
			return undefined;
		}

1184 1185
		// We can only have extension tasks if we are in version 2.0.0. Then we can even run
		// multiple build tasks.
1186 1187
		if (extensionTasks.length === 1) {
			return { task: extensionTasks[0], resolver };
1188 1189
		} else {
			let id: string = UUID.generateUuid();
A
Alex Ross 已提交
1190 1191 1192 1193 1194 1195 1196 1197
			let task: InMemoryTask = new InMemoryTask(
				id,
				{ kind: TaskSourceKind.InMemory, label: 'inMemory' },
				id,
				'inMemory',
				{ reevaluateOnRerun: true },
				{
					identifier: id,
1198
					dependsOn: extensionTasks.map((extensionTask) => { return { uri: extensionTask.getWorkspaceFolder()!.uri, task: extensionTask._id }; }),
A
Alex Ross 已提交
1199 1200 1201
					name: id,
				}
			);
1202
			return { task, resolver };
E
Erich Gamma 已提交
1203 1204 1205
		}
	}

1206 1207 1208 1209
	private createResolver(grouped: TaskMap): ITaskResolver {
		interface ResolverData {
			label: Map<string, Task>;
			identifier: Map<string, Task>;
1210
			taskIdentifier: Map<string, Task>;
1211
		}
1212

1213 1214 1215 1216
		let resolverData: Map<string, ResolverData> = new Map();
		grouped.forEach((tasks, folder) => {
			let data = resolverData.get(folder);
			if (!data) {
1217
				data = { label: new Map<string, Task>(), identifier: new Map<string, Task>(), taskIdentifier: new Map<string, Task>() };
1218 1219 1220 1221
				resolverData.set(folder, data);
			}
			for (let task of tasks) {
				data.label.set(task._label, task);
1222 1223 1224
				if (task.configurationProperties.identifier) {
					data.identifier.set(task.configurationProperties.identifier, task);
				}
A
Alex Ross 已提交
1225
				let keyedIdentifier = task.getDefinition(true);
R
Rob Lourens 已提交
1226
				if (keyedIdentifier !== undefined) {
1227 1228
					data.taskIdentifier.set(keyedIdentifier._key, task);
				}
1229
			}
1230
		});
1231

1232
		return {
1233 1234
			resolve: (uri: URI, identifier: string | TaskIdentifier | undefined) => {
				let data = uri ? resolverData.get(uri.toString()) : undefined;
1235
				if (!data || !identifier) {
1236 1237
					return undefined;
				}
1238 1239 1240 1241
				if (Types.isString(identifier)) {
					return data.label.get(identifier) || data.identifier.get(identifier);
				} else {
					let key = TaskDefinition.createTaskIdentifier(identifier, console);
R
Rob Lourens 已提交
1242
					return key !== undefined ? data.taskIdentifier.get(key._key) : undefined;
1243
				}
1244
			}
1245 1246 1247
		};
	}

J
Johannes Rieken 已提交
1248
	private executeTask(task: Task, resolver: ITaskResolver): Promise<ITaskSummary> {
1249
		return ProblemMatcherRegistry.onReady().then(() => {
1250
			return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved
1251
				let executeResult = this.getTaskSystem().run(task, resolver);
A
Alex Ross 已提交
1252
				return this.handleExecuteResult(executeResult);
1253
			});
1254 1255 1256
		});
	}

J
Johannes Rieken 已提交
1257
	private handleExecuteResult(executeResult: ITaskExecuteResult): Promise<ITaskSummary> {
1258 1259 1260 1261 1262 1263 1264
		if (executeResult.task.taskLoadMessages && executeResult.task.taskLoadMessages.length > 0) {
			executeResult.task.taskLoadMessages.forEach(loadMessage => {
				this._outputChannel.append(loadMessage + '\n');
			});
			this.showOutput();
		}

A
Alex Ross 已提交
1265
		let key = executeResult.task.getRecentlyUsedKey();
A
Alex Ross 已提交
1266
		if (key) {
1267
			this.setRecentlyUsedTask(key);
A
Alex Ross 已提交
1268 1269 1270
		}
		if (executeResult.kind === TaskExecuteKind.Active) {
			let active = executeResult.active;
1271
			if (active && active.same) {
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
				if (this._taskSystem?.isTaskVisible(executeResult.task)) {
					const message = nls.localize('TaskSystem.activeSame.noBackground', 'The task \'{0}\' is already active.', executeResult.task.getQualifiedLabel());
					this.notificationService.prompt(Severity.Info, message,
						[{
							label: nls.localize('terminateTask', "Terminate Task"),
							run: () => this.terminate(executeResult.task)
						},
						{
							label: nls.localize('restartTask', "Restart Task"),
							run: () => this.restart(executeResult.task)
						}],
						{ sticky: true }
					);
A
Alex Ross 已提交
1285
				} else {
1286
					this._taskSystem?.revealTask(executeResult.task);
A
Alex Ross 已提交
1287 1288 1289 1290 1291 1292 1293 1294
				}
			} else {
				throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask);
			}
		}
		return executeResult.promise;
	}

1295
	public restart(task: Task): void {
1296 1297 1298
		if (!this._taskSystem) {
			return;
		}
1299
		this._taskSystem.terminate(task).then((response) => {
1300
			if (response.success) {
A
Alex Ross 已提交
1301 1302 1303
				this.run(task).then(undefined, reason => {
					// eat the error, it has already been surfaced to the user and we don't care about it here
				});
1304
			} else {
A
Alex Ross 已提交
1305
				this.notificationService.warn(nls.localize('TaskSystem.restartFailed', 'Failed to terminate and restart task {0}', Types.isString(task) ? task : task.configurationProperties.name));
1306 1307 1308 1309 1310
			}
			return response;
		});
	}

J
Johannes Rieken 已提交
1311
	public terminate(task: Task): Promise<TaskTerminateResponse> {
1312
		if (!this._taskSystem) {
1313
			return Promise.resolve({ success: true, task: undefined });
1314
		}
1315
		return this._taskSystem.terminate(task);
1316 1317
	}

J
Johannes Rieken 已提交
1318
	public terminateAll(): Promise<TaskTerminateResponse[]> {
1319
		if (!this._taskSystem) {
1320
			return Promise.resolve<TaskTerminateResponse[]>([]);
1321
		}
1322
		return this._taskSystem.terminateAll();
1323 1324
	}

A
Alex Ross 已提交
1325 1326 1327 1328 1329 1330
	protected createTerminalTaskSystem(): ITaskSystem {
		return new TerminalTaskSystem(
			this.terminalService, this.outputService, this.panelService, this.markerService,
			this.modelService, this.configurationResolverService, this.telemetryService,
			this.contextService, this.environmentService,
			AbstractTaskService.OutputChannelId, this.fileService, this.terminalInstanceService,
1331
			this.remotePathService,
A
Alex Ross 已提交
1332 1333 1334
			(workspaceFolder: IWorkspaceFolder) => {
				if (!workspaceFolder) {
					return undefined;
1335
				}
A
Alex Ross 已提交
1336
				return this._taskSystemInfos.get(workspaceFolder.uri.scheme);
D
Dirk Baeumer 已提交
1337
			}
A
Alex Ross 已提交
1338
		);
1339 1340
	}

A
Alex Ross 已提交
1341 1342
	protected abstract getTaskSystem(): ITaskSystem;

1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357
	private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary<boolean>): Promise<TaskSet> {
		return new Promise<TaskSet>(async (resolve, reject) => {
			let isDone = false;
			provider.provideTasks(validTypes).then((value) => {
				isDone = true;
				resolve(value);
			}, (e) => {
				isDone = true;
				reject(e);
			});
			let settingValue: boolean | string[] = this.configurationService.getValue('task.slowProviderWarning');
			if ((settingValue === true) || (Types.isStringArray(settingValue) && (settingValue.indexOf(type) < 0))) {
				setTimeout(() => {
					if (!isDone) {
						const settings: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.settings', "Settings"), run: () => this.preferencesService.openSettings(false, undefined) };
1358
						const disableAll: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.disableAll', "Disable All"), run: () => this.configurationService.updateValue('task.autoDetect', 'off') };
1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370
						const dontShow: IPromptChoice = {
							label: nls.localize('TaskSystem.slowProvider.dontShow', "Don't warn again for {0} tasks", type), run: () => {
								if (!Types.isStringArray(settingValue)) {
									settingValue = [];
								}
								settingValue.push(type);
								return this.configurationService.updateValue('task.slowProviderWarning', settingValue);
							}
						};
						this.notificationService.prompt(Severity.Warning, nls.localize('TaskSystem.slowProvider', "The {0} task provider is slow. The extension that provides {0} tasks may provide a setting to disable it, or you can disable all tasks providers", type),
							[settings, disableAll, dontShow]);
					}
1371
				}, 4000);
1372 1373 1374 1375
			}
		});
	}

1376
	private getGroupedTasks(type?: string): Promise<TaskMap> {
1377
		return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => {
1378 1379
			let validTypes: IStringDictionary<boolean> = Object.create(null);
			TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true);
1380 1381
			validTypes['shell'] = true;
			validTypes['process'] = true;
B
Benjamin Pasero 已提交
1382
			return new Promise<TaskSet[]>(resolve => {
D
Dirk Baeumer 已提交
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392
				let result: TaskSet[] = [];
				let counter: number = 0;
				let done = (value: TaskSet) => {
					if (value) {
						result.push(value);
					}
					if (--counter === 0) {
						resolve(result);
					}
				};
1393 1394
				let error = (error: any) => {
					try {
1395
						if (error && Types.isString(error.message)) {
1396
							this._outputChannel.append('Error: ');
1397
							this._outputChannel.append(error.message);
1398
							this._outputChannel.append('\n');
1399
							this.showOutput();
1400 1401
						} else {
							this._outputChannel.append('Unknown error received while collecting tasks from providers.\n');
1402
							this.showOutput();
1403 1404 1405 1406 1407
						}
					} finally {
						if (--counter === 0) {
							resolve(result);
						}
D
Dirk Baeumer 已提交
1408 1409
					}
				};
1410
				if (this.isProvideTasksEnabled() && (this.schemaVersion === JsonSchemaVersion.V2_0_0) && (this._providers.size > 0)) {
1411 1412 1413
					for (const [handle, provider] of this._providers) {
						if ((type === undefined) || (type === this._providerTypes.get(handle))) {
							counter++;
1414
							this.provideTasksWithWarning(provider, this._providerTypes.get(handle)!, validTypes).then(done, error);
1415 1416
						}
					}
D
Dirk Baeumer 已提交
1417
				} else {
1418 1419
					resolve(result);
				}
D
Dirk Baeumer 已提交
1420
			});
D
Dirk Baeumer 已提交
1421
		}).then((contributedTaskSets) => {
1422 1423
			let result: TaskMap = new TaskMap();
			let contributedTasks: TaskMap = new TaskMap();
1424

D
Dirk Baeumer 已提交
1425 1426
			for (let set of contributedTaskSets) {
				for (let task of set.tasks) {
A
Alex Ross 已提交
1427
					let workspaceFolder = task.getWorkspaceFolder();
D
Dirk Baeumer 已提交
1428
					if (workspaceFolder) {
1429
						contributedTasks.add(workspaceFolder, task);
D
Dirk Baeumer 已提交
1430 1431 1432
					}
				}
			}
1433 1434 1435 1436

			return this.getWorkspaceTasks().then(async (customTasks) => {
				const customTasksKeyValuePairs = Array.from(customTasks);
				const customTasksPromises = customTasksKeyValuePairs.map(async ([key, folderTasks]) => {
D
Dirk Baeumer 已提交
1437
					let contributed = contributedTasks.get(key);
1438 1439
					if (!folderTasks.set) {
						if (contributed) {
1440
							result.add(key, ...contributed);
1441 1442 1443 1444
						}
						return;
					}

D
Dirk Baeumer 已提交
1445
					if (!contributed) {
1446
						result.add(key, ...folderTasks.set.tasks);
D
Dirk Baeumer 已提交
1447 1448 1449 1450 1451
					} else {
						let configurations = folderTasks.configurations;
						let legacyTaskConfigurations = folderTasks.set ? this.getLegacyTaskConfigurations(folderTasks.set) : undefined;
						let customTasksToDelete: Task[] = [];
						if (configurations || legacyTaskConfigurations) {
D
Dirk Baeumer 已提交
1452
							let unUsedConfigurations: Set<string> = new Set<string>();
1453 1454 1455
							if (configurations) {
								Object.keys(configurations.byIdentifier).forEach(key => unUsedConfigurations.add(key));
							}
D
Dirk Baeumer 已提交
1456 1457
							for (let task of contributed) {
								if (!ContributedTask.is(task)) {
1458 1459
									continue;
								}
D
Dirk Baeumer 已提交
1460 1461 1462
								if (configurations) {
									let configuringTask = configurations.byIdentifier[task.defines._key];
									if (configuringTask) {
D
Dirk Baeumer 已提交
1463
										unUsedConfigurations.delete(task.defines._key);
1464
										result.add(key, TaskConfig.createCustomTask(task, configuringTask));
D
Dirk Baeumer 已提交
1465
									} else {
1466
										result.add(key, task);
D
Dirk Baeumer 已提交
1467 1468 1469 1470
									}
								} else if (legacyTaskConfigurations) {
									let configuringTask = legacyTaskConfigurations[task.defines._key];
									if (configuringTask) {
1471
										result.add(key, TaskConfig.createCustomTask(task, configuringTask));
D
Dirk Baeumer 已提交
1472
										customTasksToDelete.push(configuringTask);
D
Dirk Baeumer 已提交
1473
									} else {
1474
										result.add(key, task);
D
Dirk Baeumer 已提交
1475 1476
									}
								} else {
1477
									result.add(key, task);
D
Dirk Baeumer 已提交
1478
								}
1479
							}
D
Dirk Baeumer 已提交
1480 1481 1482 1483 1484 1485 1486 1487 1488
							if (customTasksToDelete.length > 0) {
								let toDelete = customTasksToDelete.reduce<IStringDictionary<boolean>>((map, task) => {
									map[task._id] = true;
									return map;
								}, Object.create(null));
								for (let task of folderTasks.set.tasks) {
									if (toDelete[task._id]) {
										continue;
									}
1489
									result.add(key, task);
1490
								}
D
Dirk Baeumer 已提交
1491
							} else {
1492
								result.add(key, ...folderTasks.set.tasks);
1493
							}
1494 1495 1496 1497

							const unUsedConfigurationsAsArray = Array.from(unUsedConfigurations);

							const unUsedConfigurationPromises = unUsedConfigurationsAsArray.map(async (value) => {
1498
								let configuringTask = configurations!.byIdentifier[value];
1499 1500 1501 1502 1503

								for (const [handle, provider] of this._providers) {
									if (configuringTask.type === this._providerTypes.get(handle)) {
										try {
											const resolvedTask = await provider.resolveTask(configuringTask);
A
Alex Ross 已提交
1504
											if (resolvedTask && (resolvedTask._id === configuringTask._id)) {
1505 1506 1507 1508 1509 1510 1511 1512 1513
												result.add(key, TaskConfig.createCustomTask(resolvedTask, configuringTask));
												return;
											}
										} catch (error) {
											// Ignore errors. The task could not be provided by any of the providers.
										}
									}
								}

D
Dirk Baeumer 已提交
1514 1515
								this._outputChannel.append(nls.localize(
									'TaskService.noConfiguration',
1516 1517
									'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n',
									configuringTask.configures.type,
D
Dirk Baeumer 已提交
1518 1519 1520 1521
									JSON.stringify(configuringTask._source.config.element, undefined, 4)
								));
								this.showOutput();
							});
1522 1523

							await Promise.all(unUsedConfigurationPromises);
D
Dirk Baeumer 已提交
1524
						} else {
1525 1526
							result.add(key, ...folderTasks.set.tasks);
							result.add(key, ...contributed);
1527 1528
						}
					}
D
Dirk Baeumer 已提交
1529
				});
1530 1531 1532

				await Promise.all(customTasksPromises);

1533 1534 1535
				return result;
			}, () => {
				// If we can't read the tasks.json file provide at least the contributed tasks
1536
				let result: TaskMap = new TaskMap();
D
Dirk Baeumer 已提交
1537
				for (let set of contributedTaskSets) {
1538
					for (let task of set.tasks) {
1539 1540 1541 1542
						const folder = task.getWorkspaceFolder();
						if (folder) {
							result.add(folder, task);
						}
1543
					}
D
Dirk Baeumer 已提交
1544
				}
1545 1546
				return result;
			});
1547 1548 1549
		});
	}

1550
	private getLegacyTaskConfigurations(workspaceTasks: TaskSet): IStringDictionary<CustomTask> | undefined {
1551
		let result: IStringDictionary<CustomTask> | undefined;
1552
		function getResult(): IStringDictionary<CustomTask> {
1553 1554 1555 1556
			if (result) {
				return result;
			}
			result = Object.create(null);
1557
			return result!;
1558 1559
		}
		for (let task of workspaceTasks.tasks) {
1560 1561 1562 1563 1564
			if (CustomTask.is(task)) {
				let commandName = task.command && task.command.name;
				// This is for backwards compatibility with the 0.1.0 task annotation code
				// if we had a gulp, jake or grunt command a task specification was a annotation
				if (commandName === 'gulp' || commandName === 'grunt' || commandName === 'jake') {
1565
					let identifier = NKeyedTaskIdentifier.create({
1566
						type: commandName,
A
Alex Ross 已提交
1567
						task: task.configurationProperties.name
1568
					});
1569 1570
					getResult()[identifier._key] = task;
				}
1571 1572 1573 1574 1575
			}
		}
		return result;
	}

J
Johannes Rieken 已提交
1576
	public getWorkspaceTasks(runSource: TaskRunSource = TaskRunSource.User): Promise<Map<string, WorkspaceFolderTaskResult>> {
1577 1578 1579
		if (this._workspaceTasksPromise) {
			return this._workspaceTasksPromise;
		}
1580
		this.updateWorkspaceTasks(runSource);
1581
		return this._workspaceTasksPromise!;
1582 1583
	}

A
Alex Ross 已提交
1584
	protected abstract updateWorkspaceTasks(runSource: TaskRunSource | void): void;
1585

A
Alex Ross 已提交
1586
	protected computeWorkspaceTasks(runSource: TaskRunSource = TaskRunSource.User): Promise<Map<string, WorkspaceFolderTaskResult>> {
D
Dirk Baeumer 已提交
1587
		if (this.workspaceFolders.length === 0) {
1588
			return Promise.resolve(new Map<string, WorkspaceFolderTaskResult>());
1589
		} else {
1590
			let promises: Promise<WorkspaceFolderTaskResult | undefined>[] = [];
D
Dirk Baeumer 已提交
1591
			for (let folder of this.workspaceFolders) {
1592
				promises.push(this.computeWorkspaceFolderTasks(folder, runSource).then((value) => value, () => undefined));
1593
			}
1594
			return Promise.all(promises).then(async (values) => {
1595 1596 1597 1598 1599 1600
				let result = new Map<string, WorkspaceFolderTaskResult>();
				for (let value of values) {
					if (value) {
						result.set(value.workspaceFolder.uri.toString(), value);
					}
				}
1601 1602
				const userTasks = await this.computeUserTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined);
				if (userTasks) {
A
Alex Ross 已提交
1603
					result.set(SETTINGS_GROUP_KEY, userTasks);
1604 1605 1606 1607 1608
				}
				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);
				}
1609 1610 1611 1612 1613
				return result;
			});
		}
	}

A
Alex Ross 已提交
1614 1615 1616 1617
	public setJsonTasksSupported(areSupported: Promise<boolean>) {
		this._areJsonTasksSupportedPromise = areSupported;
	}

1618
	private computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise<WorkspaceFolderTaskResult> {
D
Dirk Baeumer 已提交
1619
		return (this.executionEngine === ExecutionEngine.Process
1620 1621 1622 1623
			? this.computeLegacyConfiguration(workspaceFolder)
			: this.computeConfiguration(workspaceFolder)).
			then((workspaceFolderConfiguration) => {
				if (!workspaceFolderConfiguration || !workspaceFolderConfiguration.config || workspaceFolderConfiguration.hasErrors) {
1624
					return Promise.resolve({ workspaceFolder, set: undefined, configurations: undefined, hasErrors: workspaceFolderConfiguration ? workspaceFolderConfiguration.hasErrors : false });
1625
				}
A
Alex Ross 已提交
1626
				return ProblemMatcherRegistry.onReady().then(async (): Promise<WorkspaceFolderTaskResult> => {
1627
					let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme);
1628
					let problemReporter = new ProblemReporter(this._outputChannel);
1629
					let parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson);
1630 1631 1632
					let hasErrors = false;
					if (!parseResult.validationStatus.isOK()) {
						hasErrors = true;
1633
						this.showOutput(runSource);
1634 1635 1636 1637 1638
					}
					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 { workspaceFolder, set: undefined, configurations: undefined, hasErrors };
					}
1639
					let customizedTasks: { byIdentifier: IStringDictionary<ConfiguringTask>; } | undefined;
1640 1641 1642 1643 1644 1645 1646 1647
					if (parseResult.configured && parseResult.configured.length > 0) {
						customizedTasks = {
							byIdentifier: Object.create(null)
						};
						for (let task of parseResult.configured) {
							customizedTasks.byIdentifier[task.configures._key] = task;
						}
					}
A
Alex Ross 已提交
1648 1649 1650 1651
					if (!(await this._areJsonTasksSupportedPromise) && (parseResult.custom.length > 0)) {
						console.warn('Custom workspace tasks are not supported.');
					}
					return { workspaceFolder, set: { tasks: await this._areJsonTasksSupportedPromise ? parseResult.custom : [] }, configurations: customizedTasks, hasErrors };
1652 1653 1654 1655
				});
			});
	}

1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681
	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<WorkspaceFolderTaskResult> {
		if (this.executionEngine === ExecutionEngine.Process) {
			return this.emptyWorkspaceTaskResults(workspaceFolder);
		}
S
rename  
Sandeep Somavarapu 已提交
1682
		const configuration = this.testParseExternalConfig(this.configurationService.inspect<TaskConfig.ExternalTaskRunnerConfiguration>('tasks').workspaceValue, nls.localize('TasksSystem.locationWorkspaceConfig', 'workspace file'));
1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700
		let customizedTasks: { byIdentifier: IStringDictionary<ConfiguringTask>; } = {
			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<WorkspaceFolderTaskResult> {
		if (this.executionEngine === ExecutionEngine.Process) {
			return this.emptyWorkspaceTaskResults(workspaceFolder);
		}
S
rename  
Sandeep Somavarapu 已提交
1701
		const configuration = this.testParseExternalConfig(this.configurationService.inspect<TaskConfig.ExternalTaskRunnerConfiguration>('tasks').userValue, nls.localize('TasksSystem.locationUserConfig', 'user settings'));
1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750
		let customizedTasks: { byIdentifier: IStringDictionary<ConfiguringTask>; } = {
			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<ConfiguringTask>, source: TaskConfig.TaskConfigSource): Promise<boolean> {
		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;
	}

1751
	private computeConfiguration(workspaceFolder: IWorkspaceFolder): Promise<WorkspaceFolderConfigurationResult> {
1752
		let { config, hasParseErrors } = this.getConfiguration(workspaceFolder);
1753
		return Promise.resolve<WorkspaceFolderConfigurationResult>({ workspaceFolder, config, hasErrors: hasParseErrors });
1754 1755
	}

A
Alex Ross 已提交
1756
	protected abstract computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise<WorkspaceFolderConfigurationResult>;
1757

1758
	private computeWorkspaceFolderSetup(): [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined] {
S
Sandeep Somavarapu 已提交
1759
		let workspaceFolders: IWorkspaceFolder[] = [];
D
Dirk Baeumer 已提交
1760
		let ignoredWorkspaceFolders: IWorkspaceFolder[] = [];
D
Dirk Baeumer 已提交
1761 1762
		let executionEngine = ExecutionEngine.Terminal;
		let schemaVersion = JsonSchemaVersion.V2_0_0;
1763
		let workspace: IWorkspace | undefined;
D
Dirk Baeumer 已提交
1764
		if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
1765
			let workspaceFolder: IWorkspaceFolder = this.contextService.getWorkspace().folders[0];
D
Dirk Baeumer 已提交
1766 1767 1768
			workspaceFolders.push(workspaceFolder);
			executionEngine = this.computeExecutionEngine(workspaceFolder);
			schemaVersion = this.computeJsonSchemaVersion(workspaceFolder);
D
Dirk Baeumer 已提交
1769
		} else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
1770
			workspace = this.contextService.getWorkspace();
1771
			for (let workspaceFolder of this.contextService.getWorkspace().folders) {
D
Dirk Baeumer 已提交
1772
				if (schemaVersion === this.computeJsonSchemaVersion(workspaceFolder)) {
D
Dirk Baeumer 已提交
1773
					workspaceFolders.push(workspaceFolder);
1774
				} else {
D
Dirk Baeumer 已提交
1775
					ignoredWorkspaceFolders.push(workspaceFolder);
1776 1777
					this._outputChannel.append(nls.localize(
						'taskService.ignoreingFolder',
1778
						'Ignoring task configurations for workspace folder {0}. Multi folder workspace task support requires that all folders use task version 2.0.0\n',
1779
						workspaceFolder.uri.fsPath));
1780 1781 1782
				}
			}
		}
1783
		return [workspaceFolders, ignoredWorkspaceFolders, executionEngine, schemaVersion, workspace];
1784
	}
1785

S
Sandeep Somavarapu 已提交
1786
	private computeExecutionEngine(workspaceFolder: IWorkspaceFolder): ExecutionEngine {
1787
		let { config } = this.getConfiguration(workspaceFolder);
1788
		if (!config) {
1789
			return ExecutionEngine._default;
1790 1791 1792 1793
		}
		return TaskConfig.ExecutionEngine.from(config);
	}

S
Sandeep Somavarapu 已提交
1794
	private computeJsonSchemaVersion(workspaceFolder: IWorkspaceFolder): JsonSchemaVersion {
1795
		let { config } = this.getConfiguration(workspaceFolder);
1796 1797 1798 1799 1800 1801
		if (!config) {
			return JsonSchemaVersion.V2_0_0;
		}
		return TaskConfig.JsonSchemaVersion.from(config);
	}

A
Alex Ross 已提交
1802
	protected getConfiguration(workspaceFolder: IWorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; hasParseErrors: boolean } {
D
Dirk Baeumer 已提交
1803
		let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY
S
rename  
Sandeep Somavarapu 已提交
1804
			? Objects.deepClone(this.configurationService.inspect<TaskConfig.ExternalTaskRunnerConfiguration>('tasks', { resource: workspaceFolder.uri }).workspaceFolderValue)
1805
			: undefined;
1806
		if (!result) {
1807
			return { config: undefined, hasParseErrors: false };
1808 1809 1810 1811
		}
		let parseErrors: string[] = (result as any).$parseErrors;
		if (parseErrors) {
			let isAffected = false;
1812 1813
			for (const parseError of parseErrors) {
				if (/tasks\.json$/.test(parseError)) {
1814 1815 1816
					isAffected = true;
					break;
				}
1817
			}
1818
			if (isAffected) {
1819
				this._outputChannel.append(nls.localize('TaskSystem.invalidTaskJson', 'Error: The content of the tasks.json file has syntax errors. Please correct them before executing a task.\n'));
1820
				this.showOutput();
1821
				return { config: undefined, hasParseErrors: true };
1822
			}
1823 1824
		}
		return { config: result, hasParseErrors: false };
1825 1826
	}

1827
	public inTerminal(): boolean {
1828 1829 1830
		if (this._taskSystem) {
			return this._taskSystem instanceof TerminalTaskSystem;
		}
D
Dirk Baeumer 已提交
1831
		return this.executionEngine === ExecutionEngine.Terminal;
1832 1833
	}

1834
	public configureAction(): Action {
A
Alex Ross 已提交
1835
		const thisCapture: AbstractTaskService = this;
1836 1837
		return new class extends Action {
			constructor() {
1838
				super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, () => { thisCapture.runConfigureTasks(); return Promise.resolve(undefined); });
1839 1840
			}
		};
1841 1842
	}

J
Johannes Rieken 已提交
1843
	public beforeShutdown(): boolean | Promise<boolean> {
1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854
		if (!this._taskSystem) {
			return false;
		}
		if (!this._taskSystem.isActiveSync()) {
			return false;
		}
		// The terminal service kills all terminal on shutdown. So there
		// is nothing we can do to prevent this here.
		if (this._taskSystem instanceof TerminalTaskSystem) {
			return false;
		}
1855

J
Johannes Rieken 已提交
1856
		let terminatePromise: Promise<IConfirmationResult>;
1857
		if (this._taskSystem.canAutoTerminate()) {
1858
			terminatePromise = Promise.resolve({ confirmed: true });
1859
		} else {
1860
			terminatePromise = this.dialogService.confirm({
1861 1862 1863 1864
				message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'),
				primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"),
				type: 'question'
			});
E
Erich Gamma 已提交
1865
		}
1866

1867 1868
		return terminatePromise.then(res => {
			if (res.confirmed) {
1869
				return this._taskSystem!.terminateAll().then((responses) => {
1870
					let success = true;
1871
					let code: number | undefined = undefined;
1872 1873 1874 1875
					for (let response of responses) {
						success = success && response.success;
						// We only have a code in the old output runner which only has one task
						// So we can use the first code.
R
Rob Lourens 已提交
1876
						if (code === undefined && response.code !== undefined) {
1877 1878 1879 1880
							code = response.code;
						}
					}
					if (success) {
1881
						this._taskSystem = undefined;
1882 1883 1884
						this.disposeTaskSystemListeners();
						return false; // no veto
					} else if (code && code === TerminateResponseCode.ProcessNotFound) {
1885
						return this.dialogService.confirm({
1886 1887 1888
							message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'),
							primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"),
							type: 'info'
1889
						}).then(res => !res.confirmed);
1890 1891 1892 1893 1894 1895 1896 1897 1898
					}
					return true; // veto
				}, (err) => {
					return true; // veto
				});
			}

			return true; // veto
		});
E
Erich Gamma 已提交
1899 1900
	}

J
Johannes Rieken 已提交
1901
	private handleError(err: any): void {
E
Erich Gamma 已提交
1902 1903 1904
		let showOutput = true;
		if (err instanceof TaskError) {
			let buildError = <TaskError>err;
1905 1906 1907
			let needsConfig = buildError.code === TaskErrors.NotConfigured || buildError.code === TaskErrors.NoBuildTask || buildError.code === TaskErrors.NoTestTask;
			let needsTerminate = buildError.code === TaskErrors.RunningTask;
			if (needsConfig || needsTerminate) {
B
Benjamin Pasero 已提交
1908 1909 1910 1911 1912 1913 1914 1915 1916 1917
				this.notificationService.prompt(buildError.severity, buildError.message, [{
					label: needsConfig ? ConfigureTaskAction.TEXT : nls.localize('TerminateAction.label', "Terminate Task"),
					run: () => {
						if (needsConfig) {
							this.runConfigureTasks();
						} else {
							this.runTerminateCommand();
						}
					}
				}]);
E
Erich Gamma 已提交
1918
			} else {
1919
				this.notificationService.notify({ severity: buildError.severity, message: buildError.message });
E
Erich Gamma 已提交
1920 1921 1922
			}
		} else if (err instanceof Error) {
			let error = <Error>err;
1923
			this.notificationService.error(error.message);
1924
			showOutput = false;
E
Erich Gamma 已提交
1925
		} else if (Types.isString(err)) {
1926
			this.notificationService.error(<string>err);
E
Erich Gamma 已提交
1927
		} else {
1928
			this.notificationService.error(nls.localize('TaskSystem.unknownError', 'An error has occurred while running a task. See task log for details.'));
E
Erich Gamma 已提交
1929 1930
		}
		if (showOutput) {
1931
			this.showOutput();
E
Erich Gamma 已提交
1932 1933
		}
	}
1934 1935

	private canRunCommand(): boolean {
1936
		if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
1937 1938 1939 1940 1941
			this.notificationService.prompt(
				Severity.Info,
				nls.localize('TaskService.noWorkspace', "Tasks are only available on a workspace folder."),
				[{
					label: nls.localize('TaskService.learnMore', "Learn More"),
1942
					run: () => this.openerService.open(URI.parse('https://code.visualstudio.com/docs/editor/tasks'))
1943 1944
				}]
			);
1945 1946 1947 1948 1949
			return false;
		}
		return true;
	}

1950 1951 1952 1953
	private showDetail(): boolean {
		return this.configurationService.getValue<boolean>(QUICKOPEN_DETAIL_CONFIG);
	}

1954
	private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): TaskQuickPickEntry[] {
R
Rob Lourens 已提交
1955
		if (tasks === undefined || tasks === null || tasks.length === 0) {
1956
			return [];
1957
		}
1958
		const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => {
1959
			return { label: task._label, description: this.getTaskDescription(task), task, detail: this.showDetail() ? task.configurationProperties.detail : undefined };
1960
		};
C
Christof Marti 已提交
1961
		function fillEntries(entries: QuickPickInput<TaskQuickPickEntry>[], tasks: Task[], groupLabel: string): void {
C
Christof Marti 已提交
1962
			if (tasks.length) {
C
Christof Marti 已提交
1963
				entries.push({ type: 'separator', label: groupLabel });
1964
			}
1965
			for (let task of tasks) {
1966
				let entry: TaskQuickPickEntry = TaskQuickPickEntry(task);
1967
				entry.buttons = [{ iconClass: 'codicon-gear', tooltip: nls.localize('configureTask', "Configure Task") }];
1968 1969 1970 1971 1972
				if (selectedEntry && (task === selectedEntry.task)) {
					entries.unshift(selectedEntry);
				} else {
					entries.push(entry);
				}
1973 1974
			}
		}
1975
		let entries: TaskQuickPickEntry[];
1976 1977 1978
		if (group) {
			entries = [];
			if (tasks.length === 1) {
1979
				entries.push(TaskQuickPickEntry(tasks[0]));
1980 1981 1982 1983 1984 1985
			} else {
				let recentlyUsedTasks = this.getRecentlyUsedTasks();
				let recent: Task[] = [];
				let configured: Task[] = [];
				let detected: Task[] = [];
				let taskMap: IStringDictionary<Task> = Object.create(null);
1986
				tasks.forEach(task => {
A
Alex Ross 已提交
1987
					let key = task.getRecentlyUsedKey();
1988 1989 1990 1991
					if (key) {
						taskMap[key] = task;
					}
				});
1992
				recentlyUsedTasks.keys().reverse().forEach(key => {
1993 1994 1995 1996 1997 1998
					let task = taskMap[key];
					if (task) {
						recent.push(task);
					}
				});
				for (let task of tasks) {
A
Alex Ross 已提交
1999
					let key = task.getRecentlyUsedKey();
2000
					if (!key || !recentlyUsedTasks.has(key)) {
2001
						if ((task._source.kind === TaskSourceKind.Workspace) || (task._source.kind === TaskSourceKind.User)) {
2002 2003 2004 2005 2006 2007
							configured.push(task);
						} else {
							detected.push(task);
						}
					}
				}
2008
				const sorter = this.createSorter();
2009
				fillEntries(entries, recent, nls.localize('recentlyUsed', 'recently used tasks'));
2010
				configured = configured.sort((a, b) => sorter.compare(a, b));
C
Christof Marti 已提交
2011
				fillEntries(entries, configured, nls.localize('configured', 'configured tasks'));
2012
				detected = detected.sort((a, b) => sorter.compare(a, b));
C
Christof Marti 已提交
2013
				fillEntries(entries, detected, nls.localize('detected', 'detected tasks'));
2014 2015 2016
			}
		} else {
			if (sort) {
2017 2018
				const sorter = this.createSorter();
				tasks = tasks.sort((a, b) => sorter.compare(a, b));
2019
			}
2020
			entries = tasks.map<TaskQuickPickEntry>(task => TaskQuickPickEntry(task));
2021
		}
2022 2023 2024
		return entries;
	}

2025
	private async showQuickPick(tasks: Promise<Task[]> | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise<TaskQuickPickEntry | undefined | null> {
2026 2027
		const tokenSource = new CancellationTokenSource();
		const cancellationToken: CancellationToken = tokenSource.token;
2028
		let _createEntries = new Promise<QuickPickInput<TaskQuickPickEntry>[]>((resolve) => {
2029
			if (Array.isArray(tasks)) {
2030
				resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry));
2031
			} else {
2032
				resolve(tasks.then((tasks) => this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)));
2033
			}
2034 2035
		});

2036
		const timeout: boolean = await Promise.race([new Promise<boolean>(async (resolve) => {
2037 2038
			await _createEntries;
			resolve(false);
2039
		}), new Promise<boolean>((resolve) => {
2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
			const timer = setTimeout(() => {
				clearTimeout(timer);
				resolve(true);
			}, 200);
		})]);

		if (!timeout && ((await _createEntries).length === 1) && this.configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)) {
			return (<TaskQuickPickEntry>(await _createEntries)[0]);
		}

		const pickEntries = _createEntries.then((entries) => {
2051 2052 2053
			if ((entries.length === 1) && this.configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)) {
				tokenSource.cancel();
			} else if ((entries.length === 0) && defaultEntry) {
2054
				entries.push(defaultEntry);
A
Alex Ross 已提交
2055 2056
			} else if (entries.length > 1 && additionalEntries && additionalEntries.length > 0) {
				entries.push({ type: 'separator', label: '' });
2057 2058
				entries.push(additionalEntries[0]);
			}
2059
			return entries;
2060 2061
		});
		return this.quickInputService.pick(pickEntries, {
M
Matt Bierner 已提交
2062 2063 2064 2065 2066 2067 2068 2069 2070
			placeHolder,
			matchOnDescription: true,
			onDidTriggerItemButton: context => {
				let task = context.item.task;
				this.quickInputService.cancel();
				if (ContributedTask.is(task)) {
					this.customize(task, undefined, true);
				} else if (CustomTask.is(task)) {
					this.openConfig(task);
C
Christof Marti 已提交
2071
				}
M
Matt Bierner 已提交
2072
			}
2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084
		}, cancellationToken).then(async (selection) => {
			if (cancellationToken.isCancellationRequested) {
				// canceled when there's only one task
				const task = (await pickEntries)[0];
				if ((<any>task).task) {
					selection = <TaskQuickPickEntry>task;
				}
			}
			if (!selection) {
				return;
			}
			return selection;
M
Matt Bierner 已提交
2085
		});
2086 2087
	}

J
Johannes Rieken 已提交
2088
	private showIgnoredFoldersMessage(): Promise<void> {
D
Dirk Baeumer 已提交
2089
		if (this.ignoredWorkspaceFolders.length === 0 || !this.showIgnoreMessage) {
2090
			return Promise.resolve(undefined);
D
Dirk Baeumer 已提交
2091 2092
		}

2093 2094 2095 2096
		this.notificationService.prompt(
			Severity.Info,
			nls.localize('TaskService.ignoredFolder', 'The following workspace folders are ignored since they use task version 0.1.0: {0}', this.ignoredWorkspaceFolders.map(f => f.name).join(', ')),
			[{
H
Howard Hung 已提交
2097
				label: nls.localize('TaskService.notAgain', "Don't Show Again"),
2098 2099
				isSecondary: true,
				run: () => {
A
Alex Ross 已提交
2100
					this.storageService.store(AbstractTaskService.IgnoreTask010DonotShowAgain_key, true, StorageScope.WORKSPACE);
2101
					this._showIgnoreMessage = false;
2102 2103 2104
				}
			}]
		);
2105

2106
		return Promise.resolve(undefined);
D
Dirk Baeumer 已提交
2107 2108
	}

2109
	private runTaskCommand(arg?: any): void {
2110 2111 2112
		if (!this.canRunCommand()) {
			return;
		}
2113
		let identifier = this.getTaskIdentifier(arg);
R
Rob Lourens 已提交
2114
		if (identifier !== undefined) {
2115 2116 2117 2118
			this.getGroupedTasks().then((grouped) => {
				let resolver = this.createResolver(grouped);
				let folders = this.contextService.getWorkspace().folders;
				for (let folder of folders) {
2119
					let task = resolver.resolve(folder.uri, identifier);
2120
					if (task) {
A
Alex Ross 已提交
2121 2122 2123
						this.run(task).then(undefined, reason => {
							// eat the error, it has already been surfaced to the user and we don't care about it here
						});
2124 2125
						return;
					}
2126
				}
2127
				this.doRunTaskCommand(grouped.all());
2128
			}, () => {
2129
				this.doRunTaskCommand();
2130 2131
			});
		} else {
2132
			this.doRunTaskCommand();
2133 2134 2135
		}
	}

2136
	private doRunTaskCommand(tasks?: Task[]): void {
D
Dirk Baeumer 已提交
2137 2138 2139 2140
		this.showIgnoredFoldersMessage().then(() => {
			this.showQuickPick(tasks ? tasks : this.tasks(),
				nls.localize('TaskService.pickRunTask', 'Select the task to run'),
				{
2141
					label: nls.localize('TaskService.noEntryToRun', 'No task to run found. Configure Tasks...'),
D
Dirk Baeumer 已提交
2142 2143 2144
					task: null
				},
				true).
2145 2146
				then((entry) => {
					let task: Task | undefined | null = entry ? entry.task : undefined;
R
Rob Lourens 已提交
2147
					if (task === undefined) {
D
Dirk Baeumer 已提交
2148 2149 2150 2151 2152
						return;
					}
					if (task === null) {
						this.runConfigureTasks();
					} else {
2153
						this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, reason => {
A
Alex Ross 已提交
2154 2155
							// eat the error, it has already been surfaced to the user and we don't care about it here
						});
D
Dirk Baeumer 已提交
2156 2157 2158
					}
				});
		});
2159 2160
	}

2161
	private reRunTaskCommand(): void {
A
Alex Ross 已提交
2162 2163 2164 2165 2166
		if (!this.canRunCommand()) {
			return;
		}

		ProblemMatcherRegistry.onReady().then(() => {
2167
			return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved
A
Alex Ross 已提交
2168 2169 2170 2171 2172
				let executeResult = this.getTaskSystem().rerun();
				if (executeResult) {
					return this.handleExecuteResult(executeResult);
				} else {
					this.doRunTaskCommand();
2173
					return Promise.resolve(undefined);
A
Alex Ross 已提交
2174 2175 2176 2177 2178
				}
			});
		});
	}

2179 2180 2181 2182 2183
	private splitPerGroupType(tasks: Task[]): { none: Task[], defaults: Task[], users: Task[] } {
		let none: Task[] = [];
		let defaults: Task[] = [];
		let users: Task[] = [];
		for (let task of tasks) {
A
Alex Ross 已提交
2184
			if (task.configurationProperties.groupType === GroupType.default) {
2185
				defaults.push(task);
A
Alex Ross 已提交
2186
			} else if (task.configurationProperties.groupType === GroupType.user) {
2187 2188 2189 2190 2191 2192 2193 2194
				users.push(task);
			} else {
				none.push(task);
			}
		}
		return { none, defaults, users };
	}

2195 2196 2197 2198
	private runBuildCommand(): void {
		if (!this.canRunCommand()) {
			return;
		}
D
Dirk Baeumer 已提交
2199
		if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
2200 2201 2202
			this.build();
			return;
		}
2203 2204 2205 2206 2207
		let options: IProgressOptions = {
			location: ProgressLocation.Window,
			title: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...')
		};
		let promise = this.getTasksForGroup(TaskGroup.Build).then((tasks) => {
2208
			if (tasks.length > 0) {
D
Dirk Baeumer 已提交
2209
				let { defaults, users } = this.splitPerGroupType(tasks);
2210
				if (defaults.length === 1) {
A
Alex Ross 已提交
2211 2212 2213
					this.run(defaults[0]).then(undefined, reason => {
						// eat the error, it has already been surfaced to the user and we don't care about it here
					});
2214
					return;
2215 2216
				} else if (defaults.length + users.length > 0) {
					tasks = defaults.concat(users);
D
Dirk Baeumer 已提交
2217 2218
				}
			}
D
Dirk Baeumer 已提交
2219 2220 2221 2222
			this.showIgnoredFoldersMessage().then(() => {
				this.showQuickPick(tasks,
					nls.localize('TaskService.pickBuildTask', 'Select the build task to run'),
					{
2223
						label: nls.localize('TaskService.noBuildTask', 'No build task to run found. Configure Build Task...'),
D
Dirk Baeumer 已提交
2224 2225
						task: null
					},
2226 2227
					true).then((entry) => {
						let task: Task | undefined | null = entry ? entry.task : undefined;
R
Rob Lourens 已提交
2228
						if (task === undefined) {
D
Dirk Baeumer 已提交
2229 2230 2231
							return;
						}
						if (task === null) {
2232
							this.runConfigureDefaultBuildTask();
D
Dirk Baeumer 已提交
2233 2234
							return;
						}
A
Alex Ross 已提交
2235 2236 2237
						this.run(task, { attachProblemMatcher: true }).then(undefined, reason => {
							// eat the error, it has already been surfaced to the user and we don't care about it here
						});
D
Dirk Baeumer 已提交
2238 2239
					});
			});
2240
		});
2241
		this.progressService.withProgress(options, () => promise);
2242 2243 2244 2245 2246 2247
	}

	private runTestCommand(): void {
		if (!this.canRunCommand()) {
			return;
		}
D
Dirk Baeumer 已提交
2248
		if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
2249
			this.runTest();
2250 2251
			return;
		}
2252 2253 2254 2255 2256
		let options: IProgressOptions = {
			location: ProgressLocation.Window,
			title: nls.localize('TaskService.fetchingTestTasks', 'Fetching test tasks...')
		};
		let promise = this.getTasksForGroup(TaskGroup.Test).then((tasks) => {
2257
			if (tasks.length > 0) {
D
Dirk Baeumer 已提交
2258
				let { defaults, users } = this.splitPerGroupType(tasks);
2259
				if (defaults.length === 1) {
A
Alex Ross 已提交
2260 2261 2262
					this.run(defaults[0]).then(undefined, reason => {
						// eat the error, it has already been surfaced to the user and we don't care about it here
					});
2263
					return;
2264 2265
				} else if (defaults.length + users.length > 0) {
					tasks = defaults.concat(users);
D
Dirk Baeumer 已提交
2266 2267
				}
			}
D
Dirk Baeumer 已提交
2268 2269 2270 2271 2272 2273 2274
			this.showIgnoredFoldersMessage().then(() => {
				this.showQuickPick(tasks,
					nls.localize('TaskService.pickTestTask', 'Select the test task to run'),
					{
						label: nls.localize('TaskService.noTestTaskTerminal', 'No test task to run found. Configure Tasks...'),
						task: null
					}, true
2275 2276
				).then((entry) => {
					let task: Task | undefined | null = entry ? entry.task : undefined;
R
Rob Lourens 已提交
2277
					if (task === undefined) {
D
Dirk Baeumer 已提交
2278 2279 2280 2281 2282 2283
						return;
					}
					if (task === null) {
						this.runConfigureTasks();
						return;
					}
A
Alex Ross 已提交
2284 2285 2286
					this.run(task).then(undefined, reason => {
						// eat the error, it has already been surfaced to the user and we don't care about it here
					});
D
Dirk Baeumer 已提交
2287
				});
2288
			});
2289
		});
2290
		this.progressService.withProgress(options, () => promise);
2291 2292
	}

2293
	private runTerminateCommand(arg?: any): void {
2294 2295 2296
		if (!this.canRunCommand()) {
			return;
		}
2297 2298 2299 2300
		if (arg === 'terminateAll') {
			this.terminateAll();
			return;
		}
J
Johannes Rieken 已提交
2301
		let runQuickPick = (promise?: Promise<Task[]>) => {
2302
			this.showQuickPick(promise || this.getActiveTasks(),
A
Alex Ross 已提交
2303
				nls.localize('TaskService.taskToTerminate', 'Select a task to terminate'),
2304 2305
				{
					label: nls.localize('TaskService.noTaskRunning', 'No task is currently running'),
2306
					task: undefined
2307
				},
2308 2309 2310
				false, true,
				undefined,
				[{
A
Alex Ross 已提交
2311
					label: nls.localize('TaskService.terminateAllRunningTasks', 'All Running Tasks'),
2312 2313 2314 2315 2316 2317 2318 2319
					id: 'terminateAll',
					task: undefined
				}]
			).then(entry => {
				if (entry && entry.id === 'terminateAll') {
					this.terminateAll();
				}
				let task: Task | undefined | null = entry ? entry.task : undefined;
R
Rob Lourens 已提交
2320
				if (task === undefined || task === null) {
2321 2322
					return;
				}
2323
				this.terminate(task);
2324
			});
2325 2326 2327
		};
		if (this.inTerminal()) {
			let identifier = this.getTaskIdentifier(arg);
J
Johannes Rieken 已提交
2328
			let promise: Promise<Task[]>;
R
Rob Lourens 已提交
2329
			if (identifier !== undefined) {
2330 2331 2332
				promise = this.getActiveTasks();
				promise.then((tasks) => {
					for (let task of tasks) {
A
Alex Ross 已提交
2333
						if (task.matches(identifier)) {
2334 2335 2336 2337 2338 2339 2340 2341 2342
							this.terminate(task);
							return;
						}
					}
					runQuickPick(promise);
				});
			} else {
				runQuickPick();
			}
2343 2344 2345
		} else {
			this.isActive().then((active) => {
				if (active) {
2346 2347 2348
					this.terminateAll().then((responses) => {
						// the output runner has only one task
						let response = responses[0];
2349
						if (response.success) {
2350 2351 2352
							return;
						}
						if (response.code && response.code === TerminateResponseCode.ProcessNotFound) {
2353
							this.notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.'));
2354
						} else {
2355
							this.notificationService.error(nls.localize('TerminateAction.failed', 'Failed to terminate running task'));
2356 2357 2358 2359 2360 2361
						}
					});
				}
			});
		}
	}
2362

2363
	private runRestartTaskCommand(arg?: any): void {
2364 2365 2366
		if (!this.canRunCommand()) {
			return;
		}
J
Johannes Rieken 已提交
2367
		let runQuickPick = (promise?: Promise<Task[]>) => {
2368
			this.showQuickPick(promise || this.getActiveTasks(),
2369
				nls.localize('TaskService.taskToRestart', 'Select the task to restart'),
2370 2371 2372 2373 2374
				{
					label: nls.localize('TaskService.noTaskToRestart', 'No task to restart'),
					task: null
				},
				false, true
2375 2376
			).then(entry => {
				let task: Task | undefined | null = entry ? entry.task : undefined;
R
Rob Lourens 已提交
2377
				if (task === undefined || task === null) {
2378 2379
					return;
				}
2380
				this.restart(task);
2381
			});
2382 2383 2384
		};
		if (this.inTerminal()) {
			let identifier = this.getTaskIdentifier(arg);
J
Johannes Rieken 已提交
2385
			let promise: Promise<Task[]>;
R
Rob Lourens 已提交
2386
			if (identifier !== undefined) {
2387 2388 2389
				promise = this.getActiveTasks();
				promise.then((tasks) => {
					for (let task of tasks) {
A
Alex Ross 已提交
2390
						if (task.matches(identifier)) {
2391 2392 2393 2394 2395 2396 2397 2398 2399
							this.restart(task);
							return;
						}
					}
					runQuickPick(promise);
				});
			} else {
				runQuickPick();
			}
2400 2401 2402 2403 2404 2405 2406 2407 2408 2409
		} else {
			this.getActiveTasks().then((activeTasks) => {
				if (activeTasks.length === 0) {
					return;
				}
				let task = activeTasks[0];
				this.restart(task);
			});
		}
	}
2410

2411
	private getTaskIdentifier(arg?: any): string | KeyedTaskIdentifier | undefined {
2412
		let result: string | KeyedTaskIdentifier | undefined = undefined;
2413 2414 2415 2416 2417 2418 2419 2420
		if (Types.isString(arg)) {
			result = arg;
		} else if (arg && Types.isString((arg as TaskIdentifier).type)) {
			result = TaskDefinition.createTaskIdentifier(arg as TaskIdentifier, console);
		}
		return result;
	}

2421
	private openTaskFile(resource: URI, taskSource: string) {
2422
		let configFileCreated = false;
2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436
		this.fileService.resolve(resource).then((stat) => stat, () => undefined).then(async (stat) => {
			const fileExists: boolean = !!stat;
			const configValue = this.configurationService.inspect<TaskConfig.ExternalTaskRunnerConfiguration>('tasks');
			let tasksExistInFile: boolean;
			let target: ConfigurationTarget;
			switch (taskSource) {
				case TaskSourceKind.User: tasksExistInFile = !!configValue.userValue; target = ConfigurationTarget.USER; break;
				case TaskSourceKind.WorkspaceFile: tasksExistInFile = !!configValue.workspaceValue; target = ConfigurationTarget.WORKSPACE; break;
				default: tasksExistInFile = !!configValue.value; target = ConfigurationTarget.WORKSPACE_FOLDER;
			}
			let content;
			if (!tasksExistInFile) {
				const pickTemplateResult = await this.quickInputService.pick(getTaskTemplates(), { placeHolder: nls.localize('TaskService.template', 'Select a Task Template') });
				if (!pickTemplateResult) {
2437 2438
					return Promise.resolve(undefined);
				}
2439
				content = pickTemplateResult.content;
2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452
				let editorConfig = this.configurationService.getValue<any>();
				if (editorConfig.editor.insertSpaces) {
					content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + strings.repeat(' ', s2.length * editorConfig.editor.tabSize));
				}
				configFileCreated = true;
				type TaskServiceTemplateClassification = {
					templateId?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
					autoDetect: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
				};
				type TaskServiceEvent = {
					templateId?: string;
					autoDetect: boolean;
				};
2453 2454 2455 2456 2457 2458 2459
				this.telemetryService.publicLog2<TaskServiceEvent, TaskServiceTemplateClassification>('taskService.template', {
					templateId: pickTemplateResult.id,
					autoDetect: pickTemplateResult.autoDetect
				});
			}

			if (!fileExists && content) {
2460 2461 2462
				return this.textFileService.create(resource, content).then((result): URI => {
					return result.resource;
				});
2463 2464 2465 2466 2467 2468 2469
			} else if (fileExists && (tasksExistInFile || content)) {
				if (content) {
					this.configurationService.updateValue('tasks', json.parse(content), target);
				}
				return stat?.resource;
			}
			return undefined;
2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482
		}).then((resource) => {
			if (!resource) {
				return;
			}
			this.editorService.openEditor({
				resource,
				options: {
					pinned: configFileCreated // pin only if config file is created #8727
				}
			});
		});
	}

A
Alex Ross 已提交
2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504
	private isTaskEntry(value: IQuickPickItem): value is IQuickPickItem & { task: Task } {
		let candidate: IQuickPickItem & { task: Task } = value as any;
		return candidate && !!candidate.task;
	}

	private configureTask(task: Task) {
		if (ContributedTask.is(task)) {
			this.customize(task, undefined, true);
		} else if (CustomTask.is(task)) {
			this.openConfig(task);
		} else if (ConfiguringTask.is(task)) {
			// Do nothing.
		}
	}

	private handleSelection(selection: TaskQuickPickEntryType) {
		if (!selection) {
			return;
		}
		if (this.isTaskEntry(selection)) {
			this.configureTask(selection.task);
		} else {
2505
			this.openTaskFile(selection.folder.toResource('.vscode/tasks.json'), TaskSourceKind.Workspace);
A
Alex Ross 已提交
2506 2507
		}
	}
2508

2509
	public getTaskDescription(task: Task): string | undefined {
2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523
		let description: string | undefined;
		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;
			}
		}
		return description;
	}

2524
	private async runConfigureTasks(): Promise<void> {
2525 2526 2527
		if (!this.canRunCommand()) {
			return undefined;
		}
J
Johannes Rieken 已提交
2528
		let taskPromise: Promise<TaskMap>;
D
Dirk Baeumer 已提交
2529
		if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
2530 2531
			taskPromise = this.getGroupedTasks();
		} else {
2532
			taskPromise = Promise.resolve(new TaskMap());
2533 2534
		}

2535
		let stats = this.contextService.getWorkspace().folders.map<Promise<IFileStat | undefined>>((folder) => {
B
Benjamin Pasero 已提交
2536
			return this.fileService.resolve(folder.toResource('.vscode/tasks.json')).then(stat => stat, () => undefined);
2537 2538 2539 2540
		});

		let createLabel = nls.localize('TaskService.createJsonFile', 'Create tasks.json file from template');
		let openLabel = nls.localize('TaskService.openJsonFile', 'Open tasks.json file');
2541 2542
		const tokenSource = new CancellationTokenSource();
		const cancellationToken: CancellationToken = tokenSource.token;
2543
		let entries = Promise.all(stats).then((stats) => {
2544
			return taskPromise.then((taskMap) => {
A
Alex Ross 已提交
2545
				let entries: QuickPickInput<TaskQuickPickEntryType>[] = [];
2546
				let needsCreateOrOpen: boolean = true;
2547
				if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
2548 2549 2550
					let tasks = taskMap.all();
					if (tasks.length > 0) {
						tasks = tasks.sort((a, b) => a._label.localeCompare(b._label));
2551
						for (let task of tasks) {
2552
							entries.push({ label: task._label, task, description: this.getTaskDescription(task) });
2553 2554 2555 2556 2557 2558
							if (!ContributedTask.is(task)) {
								needsCreateOrOpen = false;
							}
						}
					}
					if (needsCreateOrOpen) {
R
Rob Lourens 已提交
2559
						let label = stats[0] !== undefined ? openLabel : createLabel;
C
Christof Marti 已提交
2560
						if (entries.length) {
C
Christof Marti 已提交
2561
							entries.push({ type: 'separator' });
C
Christof Marti 已提交
2562 2563
						}
						entries.push({ label, folder: this.contextService.getWorkspace().folders[0] });
2564 2565 2566 2567 2568 2569 2570 2571 2572
					}
				} else {
					let folders = this.contextService.getWorkspace().folders;
					let index = 0;
					for (let folder of folders) {
						let tasks = taskMap.get(folder);
						if (tasks.length > 0) {
							tasks = tasks.slice().sort((a, b) => a._label.localeCompare(b._label));
							for (let i = 0; i < tasks.length; i++) {
2573
								let entry: TaskQuickPickEntryType = { label: tasks[i]._label, task: tasks[i], description: this.getTaskDescription(tasks[i]) };
2574
								if (i === 0) {
C
Christof Marti 已提交
2575
									entries.push({ type: 'separator', label: folder.name });
2576 2577 2578 2579
								}
								entries.push(entry);
							}
						} else {
R
Rob Lourens 已提交
2580
							let label = stats[index] !== undefined ? openLabel : createLabel;
A
Alex Ross 已提交
2581
							let entry: TaskQuickPickEntryType = { label, folder: folder };
C
Christof Marti 已提交
2582
							entries.push({ type: 'separator', label: folder.name });
2583 2584 2585 2586 2587
							entries.push(entry);
						}
						index++;
					}
				}
2588
				if ((entries.length === 1) && !needsCreateOrOpen) {
2589 2590
					tokenSource.cancel();
				}
2591 2592 2593 2594
				return entries;
			});
		});

2595
		const timeout: boolean = await Promise.race([new Promise<boolean>(async (resolve) => {
2596 2597
			await entries;
			resolve(false);
2598
		}), new Promise<boolean>((resolve) => {
2599 2600 2601 2602 2603 2604 2605
			const timer = setTimeout(() => {
				clearTimeout(timer);
				resolve(true);
			}, 200);
		})]);

		if (!timeout && ((await entries).length === 1) && this.configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)) {
A
Alex Ross 已提交
2606 2607
			const entry: any = <any>((await entries)[0]);
			if (entry.task) {
A
Alex Ross 已提交
2608
				this.handleSelection(entry);
A
Alex Ross 已提交
2609 2610
				return;
			}
2611 2612
		}

C
Christof Marti 已提交
2613
		this.quickInputService.pick(entries,
2614 2615 2616 2617 2618 2619
			{ placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }, cancellationToken).
			then(async (selection) => {
				if (cancellationToken.isCancellationRequested) {
					// canceled when there's only one task
					const task = (await entries)[0];
					if ((<any>task).task) {
A
Alex Ross 已提交
2620
						selection = <TaskQuickPickEntryType>task;
2621 2622
					}
				}
A
Alex Ross 已提交
2623
				this.handleSelection(selection);
D
Dirk Baeumer 已提交
2624
			});
2625 2626
	}

2627 2628 2629 2630
	private runConfigureDefaultBuildTask(): void {
		if (!this.canRunCommand()) {
			return;
		}
D
Dirk Baeumer 已提交
2631
		if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
2632 2633
			this.tasks().then((tasks => {
				if (tasks.length === 0) {
2634
					this.runConfigureTasks();
2635 2636
					return;
				}
2637
				let selectedTask: Task | undefined;
2638
				let selectedEntry: TaskQuickPickEntry;
2639
				for (let task of tasks) {
A
Alex Ross 已提交
2640
					if (task.configurationProperties.group === TaskGroup.Build && task.configurationProperties.groupType === GroupType.default) {
2641
						selectedTask = task;
2642 2643 2644
						break;
					}
				}
2645 2646
				if (selectedTask) {
					selectedEntry = {
A
Alex Ross 已提交
2647
						label: nls.localize('TaskService.defaultBuildTaskExists', '{0} is already marked as the default build task', selectedTask.getQualifiedLabel()),
2648
						task: selectedTask
2649
					};
2650
				}
D
Dirk Baeumer 已提交
2651 2652
				this.showIgnoredFoldersMessage().then(() => {
					this.showQuickPick(tasks,
2653
						nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), undefined, true, false, selectedEntry).
2654 2655
						then((entry) => {
							let task: Task | undefined | null = entry ? entry.task : undefined;
2656
							if ((task === undefined) || (task === null)) {
D
Dirk Baeumer 已提交
2657 2658
								return;
							}
2659
							if (task === selectedTask && CustomTask.is(task)) {
D
Dirk Baeumer 已提交
2660 2661 2662
								this.openConfig(task);
							}
							if (!InMemoryTask.is(task)) {
2663 2664 2665 2666 2667
								this.customize(task, { group: { kind: 'build', isDefault: true } }, true).then(() => {
									if (selectedTask && (task !== selectedTask) && !InMemoryTask.is(selectedTask)) {
										this.customize(selectedTask, { group: 'build' }, true);
									}
								});
D
Dirk Baeumer 已提交
2668 2669 2670
							}
						});
				});
2671 2672
			}));
		} else {
2673
			this.runConfigureTasks();
2674 2675 2676 2677 2678 2679 2680
		}
	}

	private runConfigureDefaultTestTask(): void {
		if (!this.canRunCommand()) {
			return;
		}
D
Dirk Baeumer 已提交
2681
		if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
2682 2683
			this.tasks().then((tasks => {
				if (tasks.length === 0) {
2684
					this.runConfigureTasks();
2685
					return;
2686
				}
2687
				let selectedTask: Task | undefined;
2688 2689
				let selectedEntry: TaskQuickPickEntry;

2690
				for (let task of tasks) {
A
Alex Ross 已提交
2691
					if (task.configurationProperties.group === TaskGroup.Test && task.configurationProperties.groupType === GroupType.default) {
2692
						selectedTask = task;
2693 2694 2695
						break;
					}
				}
2696 2697
				if (selectedTask) {
					selectedEntry = {
A
Alex Ross 已提交
2698
						label: nls.localize('TaskService.defaultTestTaskExists', '{0} is already marked as the default test task.', selectedTask.getQualifiedLabel()),
2699 2700
						task: selectedTask
					};
2701
				}
2702

D
Dirk Baeumer 已提交
2703
				this.showIgnoredFoldersMessage().then(() => {
2704
					this.showQuickPick(tasks,
2705 2706
						nls.localize('TaskService.pickDefaultTestTask', 'Select the task to be used as the default test task'), undefined, true, false, selectedEntry).then((entry) => {
							let task: Task | undefined | null = entry ? entry.task : undefined;
2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720
							if (!task) {
								return;
							}
							if (task === selectedTask && CustomTask.is(task)) {
								this.openConfig(task);
							}
							if (!InMemoryTask.is(task)) {
								this.customize(task, { group: { kind: 'test', isDefault: true } }, true).then(() => {
									if (selectedTask && (task !== selectedTask) && !InMemoryTask.is(selectedTask)) {
										this.customize(selectedTask, { group: 'test' }, true);
									}
								});
							}
						});
2721 2722 2723
				});
			}));
		} else {
2724
			this.runConfigureTasks();
2725 2726
		}
	}
2727

2728
	public async runShowTasks(): Promise<void> {
2729 2730 2731
		if (!this.canRunCommand()) {
			return;
		}
2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750
		const activeTasks: Task[] = await this.getActiveTasks();
		if (activeTasks.length === 1) {
			this._taskSystem!.revealTask(activeTasks[0]);
		} else {
			this.showQuickPick(this.getActiveTasks(),
				nls.localize('TaskService.pickShowTask', 'Select the task to show its output'),
				{
					label: nls.localize('TaskService.noTaskIsRunning', 'No task is running'),
					task: null
				},
				false, true
			).then((entry) => {
				let task: Task | undefined | null = entry ? entry.task : undefined;
				if (task === undefined || task === null) {
					return;
				}
				this._taskSystem!.revealTask(task);
			});
		}
2751
	}
2752
}