terminalTaskSystem.ts 54.4 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as path from 'vs/base/common/path';
7 8
import * as nls from 'vs/nls';
import * as Objects from 'vs/base/common/objects';
9
import * as Types from 'vs/base/common/types';
10 11
import * as Platform from 'vs/base/common/platform';
import * as Async from 'vs/base/common/async';
12
import * as resources from 'vs/base/common/resources';
13
import { IStringDictionary, values } from 'vs/base/common/collections';
14
import { LinkedMap, Touch } from 'vs/base/common/map';
15
import Severity from 'vs/base/common/severity';
M
Matt Bierner 已提交
16
import { Event, Emitter } from 'vs/base/common/event';
17
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
18
import { isUNC } from 'vs/base/common/extpath';
19

20
import { IFileService } from 'vs/platform/files/common/files';
21
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
22
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
23
import { IModelService } from 'vs/editor/common/services/modelService';
24
import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher';
25
import Constants from 'vs/workbench/contrib/markers/browser/constants';
26

27 28 29
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
30
import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal';
31
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
32
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors';
33 34
import {
	Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind,
35
	TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder
36
} from 'vs/workbench/contrib/tasks/common/tasks';
37
import {
38
	ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
A
Alex Ross 已提交
39
	TelemetryEvent, Triggers, TaskTerminateResponse, TaskSystemInfoResolver, TaskSystemInfo, ResolveSet, ResolvedVariables
40
} from 'vs/workbench/contrib/tasks/common/taskSystem';
A
Alex Ross 已提交
41
import { URI } from 'vs/base/common/uri';
42
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
A
Alex Ross 已提交
43
import { Schemas } from 'vs/base/common/network';
44
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
45
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
46

D
Dirk Baeumer 已提交
47 48 49
interface TerminalData {
	terminal: ITerminalInstance;
	lastTask: string;
50
	group?: string;
D
Dirk Baeumer 已提交
51 52 53 54
}

interface ActiveTerminalData {
	terminal: ITerminalInstance;
55
	task: Task;
56
	promise: Promise<ITaskSummary>;
D
Dirk Baeumer 已提交
57 58
}

59 60 61 62 63 64
class VariableResolver {

	constructor(public workspaceFolder: IWorkspaceFolder, public taskSystemInfo: TaskSystemInfo | undefined, private _values: Map<string, string>, private _service: IConfigurationResolverService | undefined) {
	}
	resolve(value: string): string {
		return value.replace(/\$\{(.*?)\}/g, (match: string, variable: string) => {
A
Alex Ross 已提交
65 66
			// Strip out the ${} because the map contains them variables without those characters.
			let result = this._values.get(match.substring(2, match.length - 1));
67
			if ((result !== undefined) && (result !== null)) {
68 69 70 71 72 73 74 75 76 77
				return result;
			}
			if (this._service) {
				return this._service.resolve(this.workspaceFolder, match);
			}
			return match;
		});
	}
}

A
Alex Ross 已提交
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
export class VerifiedTask {
	readonly task: Task;
	readonly resolver: ITaskResolver;
	readonly trigger: string;
	resolvedVariables?: ResolvedVariables;
	systemInfo?: TaskSystemInfo;
	workspaceFolder?: IWorkspaceFolder;
	shellLaunchConfig?: IShellLaunchConfig;

	constructor(task: Task, resolver: ITaskResolver, trigger: string) {
		this.task = task;
		this.resolver = resolver;
		this.trigger = trigger;
	}

	public verify(): boolean {
94 95 96 97 98
		let verified = false;
		if (this.trigger && this.resolvedVariables && this.workspaceFolder && (this.shellLaunchConfig !== undefined)) {
			verified = true;
		}
		return verified;
A
Alex Ross 已提交
99 100 101 102
	}

	public getVerifiedTask(): { task: Task, resolver: ITaskResolver, trigger: string, resolvedVariables: ResolvedVariables, systemInfo: TaskSystemInfo, workspaceFolder: IWorkspaceFolder, shellLaunchConfig: IShellLaunchConfig } {
		if (this.verify()) {
103
			return { task: this.task, resolver: this.resolver, trigger: this.trigger, resolvedVariables: this.resolvedVariables!, systemInfo: this.systemInfo!, workspaceFolder: this.workspaceFolder!, shellLaunchConfig: this.shellLaunchConfig! };
A
Alex Ross 已提交
104 105 106 107 108 109
		} else {
			throw new Error('VerifiedTask was not checked. verify must be checked before getVerifiedTask.');
		}
	}
}

110
export class TerminalTaskSystem implements ITaskSystem {
111 112 113

	public static TelemetryEventName: string = 'taskService';

A
Alex Ross 已提交
114
	private static ProcessVarName = '__process__';
115

116 117 118 119 120 121 122
	private static shellQuotes: IStringDictionary<ShellQuotingOptions> = {
		'cmd': {
			strong: '"'
		},
		'powershell': {
			escape: {
				escapeChar: '`',
123
				charsToEscape: ' "\'()'
124 125 126 127 128
			},
			strong: '\'',
			weak: '"'
		},
		'bash': {
129 130 131 132
			escape: {
				escapeChar: '\\',
				charsToEscape: ' "\''
			},
133 134 135 136
			strong: '\'',
			weak: '"'
		},
		'zsh': {
137 138 139 140
			escape: {
				escapeChar: '\\',
				charsToEscape: ' "\''
			},
141 142 143 144 145 146 147 148 149 150 151
			strong: '\'',
			weak: '"'
		}
	};

	private static osShellQuotes: IStringDictionary<ShellQuotingOptions> = {
		'linux': TerminalTaskSystem.shellQuotes['bash'],
		'darwin': TerminalTaskSystem.shellQuotes['bash'],
		'win32': TerminalTaskSystem.shellQuotes['powershell']
	};

D
Dirk Baeumer 已提交
152 153
	private activeTasks: IStringDictionary<ActiveTerminalData>;
	private terminals: IStringDictionary<TerminalData>;
154 155
	private idleTaskTerminals: LinkedMap<string, string>;
	private sameTaskTerminals: IStringDictionary<string>;
A
Alex Ross 已提交
156
	private taskSystemInfoResolver: TaskSystemInfoResolver;
A
Alex Ross 已提交
157 158 159
	private lastTask: VerifiedTask;
	private currentTask: VerifiedTask;
	private isRerun: boolean;
160

M
Matt Bierner 已提交
161
	private readonly _onDidStateChange: Emitter<TaskEvent>;
162

163 164 165
	constructor(
		private terminalService: ITerminalService,
		private outputService: IOutputService,
166
		private panelService: IPanelService,
167 168 169
		private markerService: IMarkerService, private modelService: IModelService,
		private configurationResolverService: IConfigurationResolverService,
		private telemetryService: ITelemetryService,
170
		private contextService: IWorkspaceContextService,
171
		private environmentService: IWorkbenchEnvironmentService,
172
		private outputChannelId: string,
173
		private fileService: IFileService,
174
		private terminalInstanceService: ITerminalInstanceService,
A
Alex Ross 已提交
175
		taskSystemInfoResolver: TaskSystemInfoResolver,
176
	) {
177 178

		this.activeTasks = Object.create(null);
D
Dirk Baeumer 已提交
179
		this.terminals = Object.create(null);
180 181
		this.idleTaskTerminals = new LinkedMap<string, string>();
		this.sameTaskTerminals = Object.create(null);
182 183

		this._onDidStateChange = new Emitter();
184
		this.taskSystemInfoResolver = taskSystemInfoResolver;
185 186 187 188
	}

	public get onDidStateChange(): Event<TaskEvent> {
		return this._onDidStateChange.event;
189 190 191
	}

	public log(value: string): void {
192
		this.appendOutput(value + '\n');
193 194
	}

195
	protected showOutput(): void {
196
		this.outputService.showChannel(this.outputChannelId, true);
197 198
	}

199
	public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult {
A
Alex Ross 已提交
200
		this.currentTask = new VerifiedTask(task, resolver, trigger);
A
Alex Ross 已提交
201
		let terminalData = this.activeTasks[task.getMapKey()];
D
Dirk Baeumer 已提交
202
		if (terminalData && terminalData.promise) {
203 204 205
			let reveal = RevealKind.Always;
			let focus = false;
			if (CustomTask.is(task) || ContributedTask.is(task)) {
206 207
				reveal = task.command.presentation!.reveal;
				focus = task.command.presentation!.focus;
208
			}
209 210 211
			if (reveal === RevealKind.Always || focus) {
				this.terminalService.setActiveInstance(terminalData.terminal);
				this.terminalService.showPanel(focus);
D
Dirk Baeumer 已提交
212
			}
A
Alex Ross 已提交
213
			this.lastTask = this.currentTask;
214
			return { kind: TaskExecuteKind.Active, task, active: { same: true, background: task.configurationProperties.isBackground! }, promise: terminalData.promise };
D
Dirk Baeumer 已提交
215 216
		}

217
		try {
A
Alex Ross 已提交
218
			const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this.executeTask(task, resolver, trigger) };
219 220 221
			executeResult.promise.then(summary => {
				this.lastTask = this.currentTask;
			});
A
Alex Ross 已提交
222
			return executeResult;
223 224 225 226 227 228 229 230 231 232 233 234 235
		} catch (error) {
			if (error instanceof TaskError) {
				throw error;
			} else if (error instanceof Error) {
				this.log(error.message);
				throw new TaskError(Severity.Error, error.message, TaskErrors.UnknownError);
			} else {
				this.log(error.toString());
				throw new TaskError(Severity.Error, nls.localize('TerminalTaskSystem.unknownError', 'A unknown error has occurred while executing a task. See task output log for details.'), TaskErrors.UnknownError);
			}
		}
	}

A
Alex Ross 已提交
236 237
	public rerun(): ITaskExecuteResult | undefined {
		if (this.lastTask && this.lastTask.verify()) {
R
Rob Lourens 已提交
238
			if ((this.lastTask.task.runOptions.reevaluateOnRerun !== undefined) && !this.lastTask.task.runOptions.reevaluateOnRerun) {
A
Alex Ross 已提交
239 240 241
				this.isRerun = true;
			}
			const result = this.run(this.lastTask.task, this.lastTask.resolver);
242 243 244
			result.promise.then(summary => {
				this.isRerun = false;
			});
A
Alex Ross 已提交
245 246 247 248 249
			return result;
		} else {
			return undefined;
		}
	}
250 251

	public revealTask(task: Task): boolean {
A
Alex Ross 已提交
252
		let terminalData = this.activeTasks[task.getMapKey()];
253 254 255 256
		if (!terminalData) {
			return false;
		}
		this.terminalService.setActiveInstance(terminalData.terminal);
257
		if (CustomTask.is(task) || ContributedTask.is(task)) {
258
			this.terminalService.showPanel(task.command.presentation!.focus);
259
		}
260 261 262
		return true;
	}

263 264
	public isActive(): Promise<boolean> {
		return Promise.resolve(this.isActiveSync());
265 266 267
	}

	public isActiveSync(): boolean {
D
Dirk Baeumer 已提交
268
		return Object.keys(this.activeTasks).length > 0;
269 270 271
	}

	public canAutoTerminate(): boolean {
A
Alex Ross 已提交
272
		return Object.keys(this.activeTasks).every(key => !this.activeTasks[key].task.configurationProperties.promptOnClose);
273 274
	}

275 276 277 278
	public getActiveTasks(): Task[] {
		return Object.keys(this.activeTasks).map(key => this.activeTasks[key].task);
	}

G
Gabriel DeBacker 已提交
279
	public customExecutionComplete(task: Task, result: number): Promise<void> {
280 281
		let activeTerminal = this.activeTasks[task.getMapKey()];
		if (!activeTerminal) {
282
			return Promise.reject(new Error('Expected to have a terminal for an custom execution task'));
283 284 285
		}

		return new Promise<void>((resolve) => {
286
			activeTerminal.terminal.rendererExit(result);
287 288 289 290
			resolve();
		});
	}

291
	public terminate(task: Task): Promise<TaskTerminateResponse> {
A
Alex Ross 已提交
292
		let activeTerminal = this.activeTasks[task.getMapKey()];
293
		if (!activeTerminal) {
294
			return Promise.resolve<TaskTerminateResponse>({ success: false, task: undefined });
295
		}
296
		return new Promise<TaskTerminateResponse>((resolve, reject) => {
297
			let terminal = activeTerminal.terminal;
298

299
			const onExit = terminal.onExit(() => {
300 301 302
				let task = activeTerminal.task;
				try {
					onExit.dispose();
303
					this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Terminated, task));
304 305 306 307
				} catch (error) {
					// Do nothing.
				}
				resolve({ success: true, task: task });
308 309 310
			});
			terminal.dispose();
		});
311 312
	}

313 314
	public terminateAll(): Promise<TaskTerminateResponse[]> {
		let promises: Promise<TaskTerminateResponse>[] = [];
D
Dirk Baeumer 已提交
315
		Object.keys(this.activeTasks).forEach((key) => {
316 317
			let terminalData = this.activeTasks[key];
			let terminal = terminalData.terminal;
318
			promises.push(new Promise<TaskTerminateResponse>((resolve, reject) => {
319 320 321 322
				const onExit = terminal.onExit(() => {
					let task = terminalData.task;
					try {
						onExit.dispose();
323
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Terminated, task));
324 325 326 327 328 329 330
					} catch (error) {
						// Do nothing.
					}
					resolve({ success: true, task: terminalData.task });
				});
			}));
			terminal.dispose();
D
Dirk Baeumer 已提交
331 332
		});
		this.activeTasks = Object.create(null);
333
		return Promise.all<TaskTerminateResponse>(promises);
334 335
	}

336
	private async executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise<ITaskSummary> {
337
		let promises: Promise<ITaskSummary>[] = [];
A
Alex Ross 已提交
338
		if (task.configurationProperties.dependsOn) {
339 340
			for (let index in task.configurationProperties.dependsOn) {
				const dependency = task.configurationProperties.dependsOn[index];
341 342 343
				let dependencyTask = resolver.resolve(dependency.workspaceFolder, dependency.task!);
				if (dependencyTask) {
					let key = dependencyTask.getMapKey();
344
					let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined;
D
Dirk Baeumer 已提交
345
					if (!promise) {
346 347
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
						promise = this.executeTask(dependencyTask, resolver, trigger);
D
Dirk Baeumer 已提交
348
					}
349 350 351
					if (task.configurationProperties.dependsOrder === DependsOrder.sequence) {
						promise = Promise.resolve(await promise);
					}
D
Dirk Baeumer 已提交
352
					promises.push(promise);
353
				} else {
354 355 356 357 358
					this.log(nls.localize('dependencyFailed',
						'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'',
						Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0),
						dependency.workspaceFolder.name
					));
359
					this.showOutput();
D
Dirk Baeumer 已提交
360
				}
361
			}
D
Dirk Baeumer 已提交
362 363
		}

364
		if ((ContributedTask.is(task) || CustomTask.is(task)) && (task.command)) {
365
			return Promise.all(promises).then((summaries): Promise<ITaskSummary> | ITaskSummary => {
D
Dirk Baeumer 已提交
366 367 368 369 370
				for (let summary of summaries) {
					if (summary.exitCode !== 0) {
						return { exitCode: summary.exitCode };
					}
				}
A
Alex Ross 已提交
371 372 373 374 375
				if (this.isRerun) {
					return this.reexecuteCommand(task, trigger);
				} else {
					return this.executeCommand(task, trigger);
				}
D
Dirk Baeumer 已提交
376
			});
377
		} else {
378
			return Promise.all(promises).then((summaries): ITaskSummary => {
D
Dirk Baeumer 已提交
379 380 381 382 383 384 385 386 387 388
				for (let summary of summaries) {
					if (summary.exitCode !== 0) {
						return { exitCode: summary.exitCode };
					}
				}
				return { exitCode: 0 };
			});
		}
	}

389
	private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder, task: CustomTask | ContributedTask, variables: Set<string>): Promise<ResolvedVariables> {
390 391 392 393 394 395 396 397 398 399 400 401 402 403
		let isProcess = task.command && task.command.runtime === RuntimeType.Process;
		let options = task.command && task.command.options ? task.command.options : undefined;
		let cwd = options ? options.cwd : undefined;
		let envPath: string | undefined = undefined;
		if (options && options.env) {
			for (let key of Object.keys(options.env)) {
				if (key.toLowerCase() === 'path') {
					if (Types.isString(options.env[key])) {
						envPath = options.env[key];
					}
					break;
				}
			}
		}
A
Alex Ross 已提交
404

405
		let resolvedVariables: Promise<ResolvedVariables>;
406
		if (taskSystemInfo) {
407 408 409
			let resolveSet: ResolveSet = {
				variables
			};
A
Alex Ross 已提交
410

411
			if (taskSystemInfo.platform === Platform.Platform.Windows && isProcess) {
412
				resolveSet.process = { name: CommandString.value(task.command.name!) };
413 414 415 416 417 418 419
				if (cwd) {
					resolveSet.process.cwd = cwd;
				}
				if (envPath) {
					resolveSet.process.path = envPath;
				}
			}
A
Alex Ross 已提交
420 421
			resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(resolved => {
				if ((taskSystemInfo.platform !== Platform.Platform.Windows) && isProcess) {
422
					resolved.variables.set(TerminalTaskSystem.ProcessVarName, CommandString.value(task.command.name!));
A
Alex Ross 已提交
423 424 425
				}
				return Promise.resolve(resolved);
			});
A
Alex Ross 已提交
426
			return resolvedVariables;
427
		} else {
A
Alex Ross 已提交
428 429 430 431
			let variablesArray = new Array<string>();
			variables.forEach(variable => variablesArray.push(variable));

			return new Promise((resolve, reject) => {
A
Alex Ross 已提交
432
				this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async resolvedVariablesMap => {
433 434 435 436
					if (resolvedVariablesMap) {
						if (isProcess) {
							let processVarValue: string;
							if (Platform.isWindows) {
437
								processVarValue = await this.findExecutable(
438
									this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)),
439 440 441 442
									cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined,
									envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined
								);
							} else {
443
								processVarValue = this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!));
444 445
							}
							resolvedVariablesMap.set(TerminalTaskSystem.ProcessVarName, processVarValue);
A
Alex Ross 已提交
446
						}
447 448 449 450 451 452
						let resolvedVariablesResult: ResolvedVariables = {
							variables: resolvedVariablesMap,
						};
						resolve(resolvedVariablesResult);
					} else {
						resolve(undefined);
A
Alex Ross 已提交
453 454 455 456
					}
				}, reason => {
					reject(reason);
				});
457 458
			});
		}
A
Alex Ross 已提交
459 460
	}

461
	private executeCommand(task: CustomTask | ContributedTask, trigger: string): Promise<ITaskSummary> {
A
Alex Ross 已提交
462
		this.currentTask.workspaceFolder = task.getWorkspaceFolder();
A
Alex Ross 已提交
463 464 465 466 467 468
		if (this.currentTask.workspaceFolder) {
			this.currentTask.systemInfo = this.taskSystemInfoResolver(this.currentTask.workspaceFolder);
		}

		let variables = new Set<string>();
		this.collectTaskVariables(variables, task);
469
		const resolvedVariables = this.resolveVariablesFromSet(this.currentTask.systemInfo, this.currentTask.workspaceFolder!, task, variables);
A
Alex Ross 已提交
470 471

		return resolvedVariables.then((resolvedVariables) => {
472
			if (resolvedVariables && task.command && task.command.runtime) {
473
				this.currentTask.resolvedVariables = resolvedVariables;
474
				return this.executeInTerminal(task, trigger, new VariableResolver(this.currentTask.workspaceFolder!, this.currentTask.systemInfo, resolvedVariables.variables, this.configurationResolverService));
475 476 477
			} else {
				return Promise.resolve({ exitCode: 0 });
			}
A
Alex Ross 已提交
478 479
		}, reason => {
			return Promise.reject(reason);
480 481 482
		});
	}

483
	private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string): Promise<ITaskSummary> {
A
Alex Ross 已提交
484 485 486 487 488 489 490
		this.currentTask.workspaceFolder = this.lastTask.workspaceFolder;
		let variables = new Set<string>();
		this.collectTaskVariables(variables, task);

		// Check that the task hasn't changed to include new variables
		let hasAllVariables = true;
		variables.forEach(value => {
491
			if (value.substring(2, value.length - 1) in this.lastTask.getVerifiedTask().resolvedVariables) {
A
Alex Ross 已提交
492 493 494 495 496 497 498 499
				hasAllVariables = false;
			}
		});

		if (!hasAllVariables) {
			return this.resolveVariablesFromSet(this.lastTask.getVerifiedTask().systemInfo, this.lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => {
				this.currentTask.resolvedVariables = resolvedVariables;
				return this.executeInTerminal(task, trigger, new VariableResolver(this.lastTask.getVerifiedTask().workspaceFolder, this.lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService));
A
Alex Ross 已提交
500 501
			}, reason => {
				return Promise.reject(reason);
A
Alex Ross 已提交
502 503 504 505 506 507 508
			});
		} else {
			this.currentTask.resolvedVariables = this.lastTask.getVerifiedTask().resolvedVariables;
			return this.executeInTerminal(task, trigger, new VariableResolver(this.lastTask.getVerifiedTask().workspaceFolder, this.lastTask.getVerifiedTask().systemInfo, this.lastTask.getVerifiedTask().resolvedVariables.variables, this.configurationResolverService));
		}
	}

509
	private executeInTerminal(task: CustomTask | ContributedTask, trigger: string, resolver: VariableResolver): Promise<ITaskSummary> {
510 511 512
		let terminal: ITerminalInstance | undefined = undefined;
		let executedCommand: string | undefined = undefined;
		let error: TaskError | undefined = undefined;
513
		let promise: Promise<ITaskSummary> | undefined = undefined;
A
Alex Ross 已提交
514
		if (task.configurationProperties.isBackground) {
515
			promise = new Promise<ITaskSummary>((resolve, reject) => {
A
Alex Ross 已提交
516
				const problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers);
517
				let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService, this.fileService);
518
				let toDispose: IDisposable[] | undefined = [];
D
Dirk Baeumer 已提交
519
				let eventCounter: number = 0;
B
Benjamin Pasero 已提交
520
				toDispose.push(watchingProblemMatcher.onDidStateChange((event) => {
521 522 523 524 525 526
					if (event.kind === ProblemCollectorEventKind.BackgroundProcessingBegins) {
						eventCounter++;
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task));
					} else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) {
						eventCounter--;
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task));
527
						if (eventCounter === 0) {
528
							if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity &&
529
								(watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) {
530
								let reveal = task.command.presentation!.reveal;
531 532
								let revealProblems = task.command.presentation!.revealProblems;
								if (revealProblems === RevealProblemKind.OnProblem) {
533 534 535 536 537
									this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true);
								} else if (reveal === RevealKind.Silent) {
									this.terminalService.setActiveInstance(terminal!);
									this.terminalService.showPanel(false);
								}
538 539
							}
						}
540
					}
D
Dirk Baeumer 已提交
541 542
				}));
				watchingProblemMatcher.aboutToStart();
543
				let delayer: Async.Delayer<any> | undefined = undefined;
544
				[terminal, executedCommand, error] = this.createTerminal(task, resolver);
545 546 547
				if (error || !terminal) {
					return;
				}
548
				let processStartedSignaled = false;
549
				terminal.processReady.then(() => {
550
					if (!processStartedSignaled) {
G
Gabriel DeBacker 已提交
551
						if (task.command.runtime !== RuntimeType.CustomExecution) {
552 553
							this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
						}
554 555
						processStartedSignaled = true;
					}
556 557 558
				}, (_error) => {
					// The process never got ready. Need to think how to handle this.
				});
559
				this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id));
D
Dirk Baeumer 已提交
560
				const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers);
D
Dirk Baeumer 已提交
561
				const onData = terminal.onLineData((line) => {
562 563 564 565 566 567 568
					watchingProblemMatcher.processLine(line);
					if (!delayer) {
						delayer = new Async.Delayer(3000);
					}
					delayer.trigger(() => {
						watchingProblemMatcher.forceDelivery();
						delayer = undefined;
569
					});
D
Dirk Baeumer 已提交
570 571 572 573
				});
				const onExit = terminal.onExit((exitCode) => {
					onData.dispose();
					onExit.dispose();
A
Alex Ross 已提交
574
					let key = task.getMapKey();
575
					delete this.activeTasks[key];
576
					this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
577 578 579 580 581 582 583 584 585 586
					if (exitCode !== undefined) {
						// Only keep a reference to the terminal if it is not being disposed.
						switch (task.command.presentation!.panel) {
							case PanelKind.Dedicated:
								this.sameTaskTerminals[key] = terminal!.id.toString();
								break;
							case PanelKind.Shared:
								this.idleTaskTerminals.set(key, terminal!.id.toString(), Touch.AsOld);
								break;
						}
D
Dirk Baeumer 已提交
587
					}
588 589 590 591
					let reveal = task.command.presentation!.reveal;
					if ((reveal === RevealKind.Silent) && ((exitCode !== 0) || (watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity &&
						(watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error))) {
						this.terminalService.setActiveInstance(terminal!);
592 593
						this.terminalService.showPanel(false);
					}
D
Dirk Baeumer 已提交
594
					watchingProblemMatcher.done();
D
Dirk Baeumer 已提交
595
					watchingProblemMatcher.dispose();
596
					registeredLinkMatchers.forEach(handle => terminal!.deregisterLinkMatcher(handle));
597
					if (!processStartedSignaled) {
598
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
599
						processStartedSignaled = true;
600
					}
G
Gabriel DeBacker 已提交
601 602 603 604 605

					if (task.command.runtime !== RuntimeType.CustomExecution) {
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode));
					}

D
Dirk Baeumer 已提交
606
					for (let i = 0; i < eventCounter; i++) {
607 608
						let event = TaskEvent.create(TaskEventKind.Inactive, task);
						this._onDidStateChange.fire(event);
D
Dirk Baeumer 已提交
609 610
					}
					eventCounter = 0;
611
					this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
612 613
					toDispose = dispose(toDispose!);
					toDispose = undefined;
D
Dirk Baeumer 已提交
614 615 616 617
					resolve({ exitCode });
				});
			});
		} else {
618
			promise = new Promise<ITaskSummary>((resolve, reject) => {
619
				[terminal, executedCommand, error] = this.createTerminal(task, resolver);
620
				if (!terminal || error) {
621 622
					return;
				}
623 624

				let processStartedSignaled = false;
625
				terminal.processReady.then(() => {
626
					if (!processStartedSignaled) {
G
Gabriel DeBacker 已提交
627
						if (task.command.runtime !== RuntimeType.CustomExecution) {
628 629
							this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!));
						}
630 631
						processStartedSignaled = true;
					}
632 633 634
				}, (_error) => {
					// The process never got ready. Need to think how to handle this.
				});
635
				this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id));
636
				this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task));
A
Alex Ross 已提交
637
				let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers);
638
				let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService);
D
Dirk Baeumer 已提交
639
				const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers);
D
Dirk Baeumer 已提交
640
				const onData = terminal.onLineData((line) => {
641
					startStopProblemMatcher.processLine(line);
642
				});
D
Dirk Baeumer 已提交
643 644 645
				const onExit = terminal.onExit((exitCode) => {
					onData.dispose();
					onExit.dispose();
A
Alex Ross 已提交
646
					let key = task.getMapKey();
647
					delete this.activeTasks[key];
648
					this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
649 650 651 652 653 654 655 656 657 658
					if (exitCode !== undefined) {
						// Only keep a reference to the terminal if it is not being disposed.
						switch (task.command.presentation!.panel) {
							case PanelKind.Dedicated:
								this.sameTaskTerminals[key] = terminal!.id.toString();
								break;
							case PanelKind.Shared:
								this.idleTaskTerminals.set(key, terminal!.id.toString(), Touch.AsOld);
								break;
						}
D
Dirk Baeumer 已提交
659
					}
660
					let reveal = task.command.presentation!.reveal;
661 662
					let revealProblems = task.command.presentation!.revealProblems;
					let revealProblemPanel = terminal && (revealProblems === RevealProblemKind.OnProblem) && (startStopProblemMatcher.numberOfMatches > 0);
663 664 665
					if (revealProblemPanel) {
						this.panelService.openPanel(Constants.MARKERS_PANEL_ID);
					} else if (terminal && (reveal === RevealKind.Silent) && ((exitCode !== 0) || (startStopProblemMatcher.numberOfMatches > 0) && startStopProblemMatcher.maxMarkerSeverity &&
666
						(startStopProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error))) {
667 668 669
						this.terminalService.setActiveInstance(terminal);
						this.terminalService.showPanel(false);
					}
D
Dirk Baeumer 已提交
670 671
					startStopProblemMatcher.done();
					startStopProblemMatcher.dispose();
672 673 674 675 676 677 678
					registeredLinkMatchers.forEach(handle => {
						if (terminal) {
							terminal.deregisterLinkMatcher(handle);
						}
					});
					if (!processStartedSignaled && terminal) {
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!));
679
						processStartedSignaled = true;
680
					}
G
Gabriel DeBacker 已提交
681 682 683
					if (task.command.runtime !== RuntimeType.CustomExecution) {
						this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode));
					}
684
					this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task));
685
					this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
D
Dirk Baeumer 已提交
686 687 688 689
					resolve({ exitCode });
				});
			});
		}
690
		if (error) {
691
			return Promise.reject(new Error((<TaskError>error).message));
692
		}
693
		if (!terminal) {
694
			return Promise.reject(new Error(`Failed to create terminal for task ${task._label}`));
695
		}
696
		let showProblemPanel = task.command.presentation && (task.command.presentation.revealProblems === RevealProblemKind.Always);
697 698 699
		if (showProblemPanel) {
			this.panelService.openPanel(Constants.MARKERS_PANEL_ID);
		} else if (task.command.presentation && (task.command.presentation.reveal === RevealKind.Always)) {
700
			this.terminalService.setActiveInstance(terminal);
701
			this.terminalService.showPanel(task.command.presentation.focus);
D
Dirk Baeumer 已提交
702
		}
A
Alex Ross 已提交
703
		this.activeTasks[task.getMapKey()] = { terminal, task, promise };
704
		this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed));
D
Dirk Baeumer 已提交
705 706 707 708
		return promise.then((summary) => {
			try {
				let telemetryEvent: TelemetryEvent = {
					trigger: trigger,
709
					runner: 'terminal',
A
Alex Ross 已提交
710
					taskKind: task.getTelemetryKind(),
711
					command: this.getSanitizedCommand(executedCommand!),
D
Dirk Baeumer 已提交
712 713 714
					success: true,
					exitCode: summary.exitCode
				};
K
kieferrm 已提交
715
				/* __GDPR__
K
kieferrm 已提交
716 717 718 719
					"taskService" : {
						"${include}": [
							"${TelemetryEvent}"
						]
K
kieferrm 已提交
720
					}
K
kieferrm 已提交
721
				*/
D
Dirk Baeumer 已提交
722 723
				this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
			} catch (error) {
724
			}
D
Dirk Baeumer 已提交
725 726 727 728 729
			return summary;
		}, (error) => {
			try {
				let telemetryEvent: TelemetryEvent = {
					trigger: trigger,
730
					runner: 'terminal',
A
Alex Ross 已提交
731
					taskKind: task.getTelemetryKind(),
732
					command: this.getSanitizedCommand(executedCommand!),
D
Dirk Baeumer 已提交
733 734
					success: false
				};
K
kieferrm 已提交
735
				/* __GDPR__
K
kieferrm 已提交
736 737 738 739 740 741
					"taskService" : {
						"${include}": [
							"${TelemetryEvent}"
						]
					}
				*/
D
Dirk Baeumer 已提交
742 743
				this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
			} catch (error) {
744
			}
745
			return Promise.reject<ITaskSummary>(error);
D
Dirk Baeumer 已提交
746
		});
747 748
	}

749 750 751 752 753
	private createTerminalName(task: CustomTask | ContributedTask): string {
		const needsFolderQualification = this.currentTask.workspaceFolder && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
		return nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', needsFolderQualification ? task.getQualifiedLabel() : task.configurationProperties.name);
	}

754 755 756 757 758 759 760 761 762 763 764 765
	private getDefaultShell(platform: Platform.Platform): string {
		let defaultShell: string | undefined = undefined;
		try {
			defaultShell = this.terminalInstanceService.getDefaultShell(platform);
		} catch {
			// Do nothing
		}
		if (!defaultShell) {
			// Make up a guess for the default shell.
			if (platform === Platform.Platform.Windows) {
				defaultShell = 'cmd.exe';
			} else {
A
Alex Ross 已提交
766
				defaultShell = 'bash';
767 768 769 770 771 772
			}
			console.warn('Cannot get the default shell.');
		}
		return defaultShell;
	}

A
Alex Ross 已提交
773
	private createShellLaunchConfig(task: CustomTask | ContributedTask, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string): IShellLaunchConfig | undefined {
774
		let shellLaunchConfig: IShellLaunchConfig;
775
		let isShellCommand = task.command.runtime === RuntimeType.Shell;
A
Alex Ross 已提交
776
		let needsFolderQualification = this.currentTask.workspaceFolder && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
777
		let terminalName = this.createTerminalName(task);
A
Alex Ross 已提交
778
		let originalCommand = task.command.name;
D
Dirk Baeumer 已提交
779
		if (isShellCommand) {
780
			shellLaunchConfig = { name: terminalName, executable: undefined, args: undefined, waitOnExit };
781
			this.terminalInstanceService.mergeDefaultShellPathAndArgs(shellLaunchConfig, this.getDefaultShell(platform), this.terminalService.configHelper, platform);
782
			let shellSpecified: boolean = false;
783
			let shellOptions: ShellConfiguration | undefined = task.command.options && task.command.options.shell;
784 785
			if (shellOptions) {
				if (shellOptions.executable) {
A
Alex Ross 已提交
786
					shellLaunchConfig.executable = this.resolveVariable(variableResolver, shellOptions.executable);
787 788
					shellSpecified = true;
				}
D
Dirk Baeumer 已提交
789
				if (shellOptions.args) {
A
Alex Ross 已提交
790
					shellLaunchConfig.args = this.resolveVariables(variableResolver, shellOptions.args.slice());
791
				} else {
792
					shellLaunchConfig.args = [];
793 794
				}
			}
795
			let shellArgs = <string[]>shellLaunchConfig.args!.slice(0);
796
			let toAdd: string[] = [];
797
			let commandLine = this.buildShellCommandLine(platform, shellLaunchConfig.executable!, shellOptions, command, originalCommand, args);
798
			let windowsShellArgs: boolean = false;
799
			if (platform === Platform.Platform.Windows) {
800
				windowsShellArgs = true;
801
				let basename = path.basename(shellLaunchConfig.executable!).toLowerCase();
B
Benjamin Pasero 已提交
802
				if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(process.cwd())))) {
A
Alex Ross 已提交
803
					return undefined;
804
				}
A
Alex Ross 已提交
805
				if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) {
806 807 808
					if (!shellSpecified) {
						toAdd.push('-Command');
					}
809
				} else if ((basename === 'bash.exe') || (basename === 'zsh.exe')) {
810
					windowsShellArgs = false;
811 812 813
					if (!shellSpecified) {
						toAdd.push('-c');
					}
A
Alex Ross 已提交
814
				} else if (basename === 'wsl.exe') {
815
					if (!shellSpecified) {
A
Alex Ross 已提交
816 817
						toAdd.push('-e');
					}
818
				} else {
819 820 821
					if (!shellSpecified) {
						toAdd.push('/d', '/c');
					}
822 823
				}
			} else {
824
				if (!shellSpecified) {
D
Dirk Baeumer 已提交
825
					// Under Mac remove -l to not start it as a login shell.
826
					if (platform === Platform.Platform.Mac) {
D
Dirk Baeumer 已提交
827 828 829 830 831
						let index = shellArgs.indexOf('-l');
						if (index !== -1) {
							shellArgs.splice(index, 1);
						}
					}
832 833
					toAdd.push('-c');
				}
834 835 836 837 838 839 840
			}
			toAdd.forEach(element => {
				if (!shellArgs.some(arg => arg.toLowerCase() === element)) {
					shellArgs.push(element);
				}
			});
			shellArgs.push(commandLine);
841
			shellLaunchConfig.args = windowsShellArgs ? shellArgs.join(' ') : shellArgs;
842
			if (task.command.presentation && task.command.presentation.echo) {
843
				if (needsFolderQualification) {
844
					shellLaunchConfig.initialText = `\x1b[1m> Executing task in folder ${this.currentTask.workspaceFolder!.name}: ${commandLine} <\x1b[0m\n`;
845 846 847
				} else {
					shellLaunchConfig.initialText = `\x1b[1m> Executing task: ${commandLine} <\x1b[0m\n`;
				}
D
Dirk Baeumer 已提交
848
			}
849
		} else {
G
Gabriel DeBacker 已提交
850
			let commandExecutable = task.command.runtime !== RuntimeType.CustomExecution ? CommandString.value(command) : undefined;
851
			let executable = !isShellCommand
A
Alex Ross 已提交
852
				? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}')
853
				: commandExecutable;
854 855

			// When we have a process task there is no need to quote arguments. So we go ahead and take the string value.
D
Dirk Baeumer 已提交
856
			shellLaunchConfig = {
857
				name: terminalName,
858
				executable: executable,
859
				args: args.map(a => Types.isString(a) ? a : a.value),
860 861
				waitOnExit
			};
862 863
			if (task.command.presentation && task.command.presentation.echo) {
				let getArgsToEcho = (args: string | string[] | undefined): string => {
D
Dirk Baeumer 已提交
864 865 866 867 868 869 870 871
					if (!args || args.length === 0) {
						return '';
					}
					if (Types.isString(args)) {
						return args;
					}
					return args.join(' ');
				};
872
				if (needsFolderQualification) {
873
					shellLaunchConfig.initialText = `\x1b[1m> Executing task in folder ${this.currentTask.workspaceFolder!.name}: ${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)} <\x1b[0m\n`;
874 875 876
				} else {
					shellLaunchConfig.initialText = `\x1b[1m> Executing task: ${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)} <\x1b[0m\n`;
				}
D
Dirk Baeumer 已提交
877
			}
878
		}
A
Alex Ross 已提交
879

880
		if (options.cwd) {
881
			let cwd = options.cwd;
B
Benjamin Pasero 已提交
882
			if (!path.isAbsolute(cwd)) {
A
Alex Ross 已提交
883
				let workspaceFolder = task.getWorkspaceFolder();
884
				if (workspaceFolder && (workspaceFolder.uri.scheme === 'file')) {
B
Benjamin Pasero 已提交
885
					cwd = path.join(workspaceFolder.uri.fsPath, cwd);
886 887 888
				}
			}
			// This must be normalized to the OS
A
Alex Ross 已提交
889
			shellLaunchConfig.cwd = resources.toLocalResource(URI.from({ scheme: Schemas.file, path: cwd }), this.environmentService.configuration.remoteAuthority);
890 891
		}
		if (options.env) {
892
			shellLaunchConfig.env = options.env;
893
		}
A
Alex Ross 已提交
894 895 896
		return shellLaunchConfig;
	}

897
	private createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver): [ITerminalInstance | undefined, string | undefined, TaskError | undefined] {
A
Alex Ross 已提交
898 899
		let platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform;
		let options = this.resolveOptions(resolver, task.command.options);
900

A
Alex Ross 已提交
901
		let waitOnExit: boolean | string = false;
902 903 904 905
		const presentationOptions = task.command.presentation;
		if (!presentationOptions) {
			throw new Error('Task presentation options should not be undefined here.');
		}
A
Alex Ross 已提交
906

907 908
		if (presentationOptions.reveal !== RevealKind.Never || !task.configurationProperties.isBackground) {
			if (presentationOptions.panel === PanelKind.New) {
A
Alex Ross 已提交
909
				waitOnExit = nls.localize('closeTerminal', 'Press any key to close the terminal.');
910
			} else if (presentationOptions.showReuseMessage) {
A
Alex Ross 已提交
911 912 913 914 915
				waitOnExit = nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.');
			} else {
				waitOnExit = true;
			}
		}
916 917 918 919 920

		let commandExecutable: string | undefined;
		let command: CommandString | undefined;
		let args: CommandString[] | undefined;

G
Gabriel DeBacker 已提交
921
		if (task.command.runtime === RuntimeType.CustomExecution) {
922 923 924
			this.currentTask.shellLaunchConfig = {
				isRendererOnly: true,
				waitOnExit,
A
Alex Ross 已提交
925 926
				name: this.createTerminalName(task),
				initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined
927 928
			};
		} else {
929 930 931 932 933 934 935 936 937
			let resolvedResult: { command: CommandString, args: CommandString[] } = this.resolveCommandAndArgs(resolver, task.command);
			command = resolvedResult.command;
			args = resolvedResult.args;
			commandExecutable = CommandString.value(command);

			this.currentTask.shellLaunchConfig = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : this.createShellLaunchConfig(task, resolver, platform, options, command, args, waitOnExit);
			if (this.currentTask.shellLaunchConfig === undefined) {
				return [undefined, undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)];
			}
A
Alex Ross 已提交
938
		}
939

940 941
		let prefersSameTerminal = presentationOptions.panel === PanelKind.Dedicated;
		let allowsSharedTerminal = presentationOptions.panel === PanelKind.Shared;
942
		let group = presentationOptions.group;
943

A
Alex Ross 已提交
944
		let taskKey = task.getMapKey();
945
		let terminalToReuse: TerminalData | undefined;
946
		if (prefersSameTerminal) {
947
			let terminalId = this.sameTaskTerminals[taskKey];
948 949
			if (terminalId) {
				terminalToReuse = this.terminals[terminalId];
950
				delete this.sameTaskTerminals[taskKey];
D
Dirk Baeumer 已提交
951
			}
952
		} else if (allowsSharedTerminal) {
953 954 955 956 957 958 959 960
			// Always allow to reuse the terminal previously used by the same task.
			let terminalId = this.idleTaskTerminals.remove(taskKey);
			if (!terminalId) {
				// There is no idle terminal which was used by the same task.
				// Search for any idle terminal used previously by a task of the same group
				// (or, if the task has no group, a terminal used by a task without group).
				for (const taskId of this.idleTaskTerminals.keys()) {
					const idleTerminalId = this.idleTaskTerminals.get(taskId)!;
961
					if (idleTerminalId && this.terminals[idleTerminalId] && this.terminals[idleTerminalId].group === group) {
962 963 964 965 966
						terminalId = this.idleTaskTerminals.remove(taskId);
						break;
					}
				}
			}
967 968
			if (terminalId) {
				terminalToReuse = this.terminals[terminalId];
969
			}
D
Dirk Baeumer 已提交
970
		}
971
		if (terminalToReuse) {
972 973
			if (!this.currentTask.shellLaunchConfig) {
				throw new Error('Task shell launch configuration should not be undefined here.');
974 975
			}

976 977
			terminalToReuse.terminal.reuseTerminal(this.currentTask.shellLaunchConfig);

978
			if (task.command.presentation && task.command.presentation.clear) {
979 980
				terminalToReuse.terminal.clear();
			}
981
			this.terminals[terminalToReuse.terminal.id.toString()].lastTask = taskKey;
982
			return [terminalToReuse.terminal, commandExecutable, undefined];
983 984
		}

985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004
		let result: ITerminalInstance | null = null;
		if (group) {
			// Try to find an existing terminal to split.
			// Even if an existing terminal is found, the split can fail if the terminal width is too small.
			for (const terminal of values(this.terminals)) {
				if (terminal.group === group) {
					const originalInstance = terminal.terminal;
					const config = this.currentTask.shellLaunchConfig;
					result = this.terminalService.splitInstance(originalInstance, config);
					if (result) {
						break;
					}
				}
			}
		}
		if (!result) {
			// Either no group is used, no terminal with the group exists or splitting an existing terminal failed.
			result = this.terminalService.createTerminal(this.currentTask.shellLaunchConfig);
		}

1005
		const terminalKey = result.id.toString();
D
Dirk Baeumer 已提交
1006
		result.onDisposed((terminal) => {
1007
			let terminalData = this.terminals[terminalKey];
D
Dirk Baeumer 已提交
1008
			if (terminalData) {
1009
				delete this.terminals[terminalKey];
1010 1011
				delete this.sameTaskTerminals[terminalData.lastTask];
				this.idleTaskTerminals.delete(terminalData.lastTask);
1012 1013 1014 1015
				// Delete the task now as a work around for cases when the onExit isn't fired.
				// This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected.
				// For correct terminal re-use, the task needs to be deleted immediately.
				// Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate.
A
Alex Ross 已提交
1016
				delete this.activeTasks[task.getMapKey()];
D
Dirk Baeumer 已提交
1017 1018
			}
		});
1019
		this.terminals[terminalKey] = { terminal: result, lastTask: taskKey, group };
1020
		return [result, commandExecutable, undefined];
1021 1022
	}

1023
	private buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: ShellConfiguration | undefined, command: CommandString, originalCommand: CommandString | undefined, args: CommandString[]): string {
1024
		let basename = path.parse(shellExecutable).name.toLowerCase();
Z
Zev Isert 已提交
1025
		let shellQuoteOptions = this.getQuotingOptions(basename, shellOptions);
1026 1027 1028 1029 1030 1031 1032 1033

		function needsQuotes(value: string): boolean {
			if (value.length >= 2) {
				let first = value[0] === shellQuoteOptions.strong ? shellQuoteOptions.strong : value[0] === shellQuoteOptions.weak ? shellQuoteOptions.weak : undefined;
				if (first === value[value.length - 1]) {
					return false;
				}
			}
1034
			let quote: string | undefined;
1035
			for (let i = 0; i < value.length; i++) {
1036 1037 1038 1039
				// We found the end quote.
				let ch = value[i];
				if (ch === quote) {
					quote = undefined;
R
Rob Lourens 已提交
1040
				} else if (quote !== undefined) {
1041 1042 1043 1044 1045 1046 1047 1048
					// skip the character. We are quoted.
					continue;
				} else if (ch === shellQuoteOptions.escape) {
					// Skip the next character
					i++;
				} else if (ch === shellQuoteOptions.strong || ch === shellQuoteOptions.weak) {
					quote = ch;
				} else if (ch === ' ') {
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
					return true;
				}
			}
			return false;
		}

		function quote(value: string, kind: ShellQuoting): [string, boolean] {
			if (kind === ShellQuoting.Strong && shellQuoteOptions.strong) {
				return [shellQuoteOptions.strong + value + shellQuoteOptions.strong, true];
			} else if (kind === ShellQuoting.Weak && shellQuoteOptions.weak) {
				return [shellQuoteOptions.weak + value + shellQuoteOptions.weak, true];
			} else if (kind === ShellQuoting.Escape && shellQuoteOptions.escape) {
				if (Types.isString(shellQuoteOptions.escape)) {
					return [value.replace(/ /g, shellQuoteOptions.escape + ' '), true];
				} else {
					let buffer: string[] = [];
					for (let ch of shellQuoteOptions.escape.charsToEscape) {
						buffer.push(`\\${ch}`);
					}
					let regexp: RegExp = new RegExp('[' + buffer.join(',') + ']', 'g');
					let escapeChar = shellQuoteOptions.escape.escapeChar;
					return [value.replace(regexp, (match) => escapeChar + match), true];
				}
			}
			return [value, false];
		}

		function quoteIfNecessary(value: CommandString): [string, boolean] {
			if (Types.isString(value)) {
				if (needsQuotes(value)) {
					return quote(value, ShellQuoting.Strong);
				} else {
					return [value, false];
				}
			} else {
				return quote(value.value, value.quoting);
			}
		}

1088 1089 1090 1091 1092 1093 1094
		// If we have no args and the command is a string then use the command to stay backwards compatible with the old command line
		// model. To allow variable resolving with spaces we do continue if the resolved value is different than the original one
		// and the resolved one needs quoting.
		if ((!args || args.length === 0) && Types.isString(command) && (command === originalCommand as string || needsQuotes(originalCommand as string))) {
			return command;
		}

1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
		let result: string[] = [];
		let commandQuoted = false;
		let argQuoted = false;
		let value: string;
		let quoted: boolean;
		[value, quoted] = quoteIfNecessary(command);
		result.push(value);
		commandQuoted = quoted;
		for (let arg of args) {
			[value, quoted] = quoteIfNecessary(arg);
			result.push(value);
			argQuoted = argQuoted || quoted;
		}

		let commandLine = result.join(' ');
		// There are special rules quoted command line in cmd.exe
1111
		if (platform === Platform.Platform.Windows) {
1112 1113 1114 1115 1116 1117 1118
			if (basename === 'cmd' && commandQuoted && argQuoted) {
				commandLine = '"' + commandLine + '"';
			} else if (basename === 'powershell' && commandQuoted) {
				commandLine = '& ' + commandLine;
			}
		}

1119
		if (basename === 'cmd' && platform === Platform.Platform.Windows && commandQuoted && argQuoted) {
1120 1121 1122
			commandLine = '"' + commandLine + '"';
		}
		return commandLine;
1123 1124
	}

1125
	private getQuotingOptions(shellBasename: string, shellOptions: ShellConfiguration | undefined): ShellQuotingOptions {
1126 1127 1128 1129 1130 1131
		if (shellOptions && shellOptions.quoting) {
			return shellOptions.quoting;
		}
		return TerminalTaskSystem.shellQuotes[shellBasename] || TerminalTaskSystem.osShellQuotes[process.platform];
	}

1132
	private collectTaskVariables(variables: Set<string>, task: CustomTask | ContributedTask): void {
A
Alex Ross 已提交
1133
		if (task.command && task.command.name) {
1134
			this.collectCommandVariables(variables, task.command, task);
1135
		}
A
Alex Ross 已提交
1136
		this.collectMatcherVariables(variables, task.configurationProperties.problemMatchers);
1137 1138
	}

1139
	private collectCommandVariables(variables: Set<string>, command: CommandConfiguration, task: CustomTask | ContributedTask): void {
1140
		// The custom execution should have everything it needs already as it provided
1141
		// the callback.
G
Gabriel DeBacker 已提交
1142
		if (command.runtime === RuntimeType.CustomExecution) {
1143 1144 1145
			return;
		}

R
Rob Lourens 已提交
1146
		if (command.name === undefined) {
1147 1148
			throw new Error('Command name should never be undefined here.');
		}
1149 1150 1151 1152
		this.collectVariables(variables, command.name);
		if (command.args) {
			command.args.forEach(arg => this.collectVariables(variables, arg));
		}
1153 1154 1155 1156 1157
		// Try to get a scope.
		const scope = (<ExtensionTaskSource>task._source).scope;
		if (scope !== TaskScope.Global) {
			variables.add('${workspaceFolder}');
		}
1158 1159 1160 1161 1162
		if (command.options) {
			let options = command.options;
			if (options.cwd) {
				this.collectVariables(variables, options.cwd);
			}
1163 1164 1165 1166
			const optionsEnv = options.env;
			if (optionsEnv) {
				Object.keys(optionsEnv).forEach((key) => {
					let value: any = optionsEnv[key];
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
					if (Types.isString(value)) {
						this.collectVariables(variables, value);
					}
				});
			}
			if (options.shell) {
				if (options.shell.executable) {
					this.collectVariables(variables, options.shell.executable);
				}
				if (options.shell.args) {
					options.shell.args.forEach(arg => this.collectVariables(variables, arg));
				}
			}
		}
	}

1183
	private collectMatcherVariables(variables: Set<string>, values: Array<string | ProblemMatcher> | undefined): void {
R
Rob Lourens 已提交
1184
		if (values === undefined || values === null || values.length === 0) {
1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206
			return;
		}
		values.forEach((value) => {
			let matcher: ProblemMatcher;
			if (Types.isString(value)) {
				if (value[0] === '$') {
					matcher = ProblemMatcherRegistry.get(value.substring(1));
				} else {
					matcher = ProblemMatcherRegistry.get(value);
				}
			} else {
				matcher = value;
			}
			if (matcher && matcher.filePrefix) {
				this.collectVariables(variables, matcher.filePrefix);
			}
		});
	}

	private collectVariables(variables: Set<string>, value: string | CommandString): void {
		let string: string = Types.isString(value) ? value : value.value;
		let r = /\$\{(.*?)\}/g;
M
Matt Bierner 已提交
1207
		let matches: RegExpExecArray | null;
1208 1209 1210 1211 1212 1213 1214 1215 1216
		do {
			matches = r.exec(string);
			if (matches) {
				variables.add(matches[0]);
			}
		} while (matches);
	}

	private resolveCommandAndArgs(resolver: VariableResolver, commandConfig: CommandConfiguration): { command: CommandString, args: CommandString[] } {
1217
		// First we need to use the command args:
1218 1219 1220
		let args: CommandString[] = commandConfig.args ? commandConfig.args.slice() : [];
		args = this.resolveVariables(resolver, args);
		let command: CommandString = this.resolveVariable(resolver, commandConfig.name);
1221 1222 1223
		return { command, args };
	}

1224 1225 1226 1227
	private resolveVariables(resolver: VariableResolver, value: string[]): string[];
	private resolveVariables(resolver: VariableResolver, value: CommandString[]): CommandString[];
	private resolveVariables(resolver: VariableResolver, value: CommandString[]): CommandString[] {
		return value.map(s => this.resolveVariable(resolver, s));
1228 1229
	}

1230
	private resolveMatchers(resolver: VariableResolver, values: Array<string | ProblemMatcher> | undefined): ProblemMatcher[] {
R
Rob Lourens 已提交
1231
		if (values === undefined || values === null || values.length === 0) {
1232 1233
			return [];
		}
1234 1235 1236 1237
		let result: ProblemMatcher[] = [];
		values.forEach((value) => {
			let matcher: ProblemMatcher;
			if (Types.isString(value)) {
1238 1239 1240 1241 1242
				if (value[0] === '$') {
					matcher = ProblemMatcherRegistry.get(value.substring(1));
				} else {
					matcher = ProblemMatcherRegistry.get(value);
				}
1243 1244 1245 1246
			} else {
				matcher = value;
			}
			if (!matcher) {
A
Alex Ross 已提交
1247
				this.appendOutput(nls.localize('unknownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored'));
1248 1249
				return;
			}
1250
			let taskSystemInfo: TaskSystemInfo | undefined = resolver.taskSystemInfo;
R
Rob Lourens 已提交
1251 1252
			let hasFilePrefix = matcher.filePrefix !== undefined;
			let hasUriProvider = taskSystemInfo !== undefined && taskSystemInfo.uriProvider !== undefined;
1253
			if (!hasFilePrefix && !hasUriProvider) {
1254 1255
				result.push(matcher);
			} else {
J
Johannes Rieken 已提交
1256
				let copy = Objects.deepClone(matcher);
R
Rob Lourens 已提交
1257
				if (hasUriProvider && (taskSystemInfo !== undefined)) {
1258
					copy.uriProvider = taskSystemInfo.uriProvider;
1259 1260
				}
				if (hasFilePrefix) {
1261
					copy.filePrefix = this.resolveVariable(resolver, copy.filePrefix);
1262
				}
1263 1264 1265 1266 1267 1268
				result.push(copy);
			}
		});
		return result;
	}

1269 1270 1271
	private resolveVariable(resolver: VariableResolver, value: string | undefined): string;
	private resolveVariable(resolver: VariableResolver, value: CommandString | undefined): CommandString;
	private resolveVariable(resolver: VariableResolver, value: CommandString | undefined): CommandString {
1272
		// TODO@Dirk Task.getWorkspaceFolder should return a WorkspaceFolder that is defined in workspace.ts
1273
		if (Types.isString(value)) {
1274
			return resolver.resolve(value);
R
Rob Lourens 已提交
1275
		} else if (value !== undefined) {
1276
			return {
1277
				value: resolver.resolve(value.value),
1278 1279
				quoting: value.quoting
			};
1280 1281
		} else { // This should never happen
			throw new Error('Should never try to resolve undefined.');
1282
		}
1283 1284
	}

1285
	private resolveOptions(resolver: VariableResolver, options: CommandOptions | undefined): CommandOptions {
R
Rob Lourens 已提交
1286
		if (options === undefined || options === null) {
1287
			return { cwd: this.resolveVariable(resolver, '${workspaceFolder}') };
1288 1289
		}
		let result: CommandOptions = Types.isString(options.cwd)
1290 1291
			? { cwd: this.resolveVariable(resolver, options.cwd) }
			: { cwd: this.resolveVariable(resolver, '${workspaceFolder}') };
1292 1293 1294
		if (options.env) {
			result.env = Object.create(null);
			Object.keys(options.env).forEach((key) => {
1295
				let value: any = options.env![key];
1296
				if (Types.isString(value)) {
1297
					result.env![key] = this.resolveVariable(resolver, value);
1298
				} else {
1299
					result.env![key] = value.toString();
1300 1301 1302 1303
				}
			});
		}
		return result;
1304 1305
	}

D
Dirk Baeumer 已提交
1306
	private registerLinkMatchers(terminal: ITerminalInstance, problemMatchers: ProblemMatcher[]): number[] {
1307
		let result: number[] = [];
D
Dirk Baeumer 已提交
1308
		/*
1309 1310 1311 1312 1313 1314 1315 1316 1317
		let handlePattern = (matcher: ProblemMatcher, pattern: ProblemPattern): void => {
			if (pattern.regexp instanceof RegExp && Types.isNumber(pattern.file)) {
				result.push(terminal.registerLinkMatcher(pattern.regexp, (match: string) => {
					let resource: URI = getResource(match, matcher);
					if (resource) {
						this.workbenchEditorService.openEditor({
							resource: resource
						});
					}
D
Dirk Baeumer 已提交
1318
				}, 0));
1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330
			}
		};

		for (let problemMatcher of problemMatchers) {
			if (Array.isArray(problemMatcher.pattern)) {
				for (let pattern of problemMatcher.pattern) {
					handlePattern(problemMatcher, pattern);
				}
			} else if (problemMatcher.pattern) {
				handlePattern(problemMatcher, problemMatcher.pattern);
			}
		}
D
Dirk Baeumer 已提交
1331
		*/
1332
		return result;
1333 1334
	}

1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354
	private static WellKnowCommands: IStringDictionary<boolean> = {
		'ant': true,
		'cmake': true,
		'eslint': true,
		'gradle': true,
		'grunt': true,
		'gulp': true,
		'jake': true,
		'jenkins': true,
		'jshint': true,
		'make': true,
		'maven': true,
		'msbuild': true,
		'msc': true,
		'nmake': true,
		'npm': true,
		'rake': true,
		'tsc': true,
		'xbuild': true
	};
1355

1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366
	public getSanitizedCommand(cmd: string): string {
		let result = cmd.toLowerCase();
		let index = result.lastIndexOf(path.sep);
		if (index !== -1) {
			result = result.substring(index + 1);
		}
		if (TerminalTaskSystem.WellKnowCommands[result]) {
			return result;
		}
		return 'other';
	}
1367 1368 1369 1370 1371 1372 1373

	private appendOutput(output: string): void {
		const outputChannel = this.outputService.getChannel(this.outputChannelId);
		if (outputChannel) {
			outputChannel.append(output);
		}
	}
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420

	private async findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string> {
		// If we have an absolute path then we take it.
		if (path.isAbsolute(command)) {
			return command;
		}
		if (cwd === undefined) {
			cwd = process.cwd();
		}
		const dir = path.dirname(command);
		if (dir !== '.') {
			// We have a directory and the directory is relative (see above). Make the path absolute
			// to the current working directory.
			return path.join(cwd, command);
		}
		if (paths === undefined && Types.isString(process.env.PATH)) {
			paths = process.env.PATH.split(path.delimiter);
		}
		// No PATH environment. Make path absolute to the cwd.
		if (paths === undefined || paths.length === 0) {
			return path.join(cwd, command);
		}
		// We have a simple file name. We get the path variable from the env
		// and try to find the executable on the path.
		for (let pathEntry of paths) {
			// The path entry is absolute.
			let fullPath: string;
			if (path.isAbsolute(pathEntry)) {
				fullPath = path.join(pathEntry, command);
			} else {
				fullPath = path.join(cwd, pathEntry, command);
			}

			if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: fullPath }), this.environmentService.configuration.remoteAuthority))) {
				return fullPath;
			}
			let withExtension = fullPath + '.com';
			if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: withExtension }), this.environmentService.configuration.remoteAuthority))) {
				return withExtension;
			}
			withExtension = fullPath + '.exe';
			if (await this.fileService.exists(resources.toLocalResource(URI.from({ scheme: Schemas.file, path: withExtension }), this.environmentService.configuration.remoteAuthority))) {
				return withExtension;
			}
		}
		return path.join(cwd, command);
	}
J
Johannes Rieken 已提交
1421
}