tasks.ts 25.0 KB
Newer Older
E
Erich Gamma 已提交
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 nls from 'vs/nls';
E
Erich Gamma 已提交
7
import * as Types from 'vs/base/common/types';
8
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
9
import * as Objects from 'vs/base/common/objects';
10
import { UriComponents } from 'vs/base/common/uri';
E
Erich Gamma 已提交
11

12
import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher';
D
Dirk Baeumer 已提交
13
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
D
Dirk Baeumer 已提交
14
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
15
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
16
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
D
Dirk Baeumer 已提交
17 18

export const TASK_RUNNING_STATE = new RawContextKey<boolean>('taskRunning', false);
E
Erich Gamma 已提交
19

20 21
export enum ShellQuoting {
	/**
22
	 * Use character escaping.
23 24 25 26
	 */
	Escape = 1,

	/**
27
	 * Use strong quoting
28 29 30 31
	 */
	Strong = 2,

	/**
32
	 * Use weak quoting.
33
	 */
34
	Weak = 3,
35 36
}

37 38
export const CUSTOMIZED_TASK_TYPE = '$customized';

39 40
export namespace ShellQuoting {
	export function from(this: void, value: string): ShellQuoting {
41 42 43
		if (!value) {
			return ShellQuoting.Strong;
		}
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
		switch (value.toLowerCase()) {
			case 'escape':
				return ShellQuoting.Escape;
			case 'strong':
				return ShellQuoting.Strong;
			case 'weak':
				return ShellQuoting.Weak;
			default:
				return ShellQuoting.Strong;
		}
	}
}

export interface ShellQuotingOptions {
	/**
	 * The character used to do character escaping.
	 */
	escape?: string | {
		escapeChar: string;
		charsToEscape: string;
	};

	/**
	 * The character used for string quoting.
	 */
	strong?: string;

	/**
	 * The character used for weak quoting.
	 */
	weak?: string;
}

77 78 79 80
export interface ShellConfiguration {
	/**
	 * The shell executable.
	 */
81
	executable?: string;
82

83 84 85 86
	/**
	 * The arguments to be passed to the shell executable.
	 */
	args?: string[];
87 88 89 90 91

	/**
	 * Which kind of quotes the shell supports.
	 */
	quoting?: ShellQuotingOptions;
92
}
E
Erich Gamma 已提交
93

D
Dirk Baeumer 已提交
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
export interface CommandOptions {

	/**
	 * The shell to use if the task is a shell command.
	 */
	shell?: ShellConfiguration;

	/**
	 * The current working directory of the executed program or shell.
	 * If omitted VSCode's current workspace root is used.
	 */
	cwd?: string;

	/**
	 * The environment of the executed program or shell. If omitted
	 * the parent process' environment is used.
	 */
	env?: { [key: string]: string; };
}

114 115 116 117
export namespace CommandOptions {
	export const defaults: CommandOptions = { cwd: '${workspaceFolder}' };
}

D
Dirk Baeumer 已提交
118 119 120 121 122 123 124 125
export enum RevealKind {
	/**
	 * Always brings the terminal to front if the task is executed.
	 */
	Always = 1,

	/**
	 * Only brings the terminal to front if a problem is detected executing the task
126 127 128
	 * e.g. the task couldn't be started,
	 * the task ended with an exit code other than zero,
	 * or the problem matcher found an error.
D
Dirk Baeumer 已提交
129 130 131 132 133 134 135 136 137 138
	 */
	Silent = 2,

	/**
	 * The terminal never comes to front when the task is executed.
	 */
	Never = 3
}

export namespace RevealKind {
139
	export function fromString(this: void, value: string): RevealKind {
D
Dirk Baeumer 已提交
140 141 142 143 144 145 146 147 148 149 150 151 152
		switch (value.toLowerCase()) {
			case 'always':
				return RevealKind.Always;
			case 'silent':
				return RevealKind.Silent;
			case 'never':
				return RevealKind.Never;
			default:
				return RevealKind.Always;
		}
	}
}

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
export enum RevealProblemKind {
	/**
	 * Never reveals the problems panel when this task is executed.
	 */
	Never = 1,


	/**
	 * Only reveals the problems panel if a problem is found.
	 */
	OnProblem = 2,

	/**
	 * Never reveals the problems panel when this task is executed.
	 */
	Always = 3
}

export namespace RevealProblemKind {
	export function fromString(this: void, value: string): RevealProblemKind {
		switch (value.toLowerCase()) {
			case 'always':
				return RevealProblemKind.Always;
			case 'never':
				return RevealProblemKind.Never;
			case 'onproblem':
				return RevealProblemKind.OnProblem;
			default:
				return RevealProblemKind.OnProblem;
		}
	}
}

186
export enum PanelKind {
187 188

	/**
189
	 * Shares a panel with other tasks. This is the default.
190 191 192 193
	 */
	Shared = 1,

	/**
194
	 * Uses a dedicated panel for this tasks. The panel is not
195 196
	 * shared with other tasks.
	 */
197
	Dedicated = 2,
198 199

	/**
200
	 * Creates a new panel whenever this task is executed.
201 202 203 204
	 */
	New = 3
}

205 206
export namespace PanelKind {
	export function fromString(value: string): PanelKind {
207 208
		switch (value.toLowerCase()) {
			case 'shared':
209 210 211
				return PanelKind.Shared;
			case 'dedicated':
				return PanelKind.Dedicated;
212
			case 'new':
213
				return PanelKind.New;
214
			default:
215
				return PanelKind.Shared;
216 217 218 219
		}
	}
}

220
export interface PresentationOptions {
D
Dirk Baeumer 已提交
221
	/**
222
	 * Controls whether the task output is reveal in the user interface.
D
Dirk Baeumer 已提交
223 224 225 226
	 * Defaults to `RevealKind.Always`.
	 */
	reveal: RevealKind;

227 228 229 230
	/**
	 * Controls whether the problems pane is revealed when running this task or not.
	 * Defaults to `RevealProblemKind.Never`.
	 */
231
	revealProblems: RevealProblemKind;
232

D
Dirk Baeumer 已提交
233
	/**
234 235
	 * Controls whether the command associated with the task is echoed
	 * in the user interface.
D
Dirk Baeumer 已提交
236 237
	 */
	echo: boolean;
238 239

	/**
240
	 * Controls whether the panel showing the task output is taking focus.
241 242 243 244
	 */
	focus: boolean;

	/**
245 246 247
	 * Controls if the task panel is used for this task only (dedicated),
	 * shared between tasks (shared) or if a new panel is created on
	 * every task execution (new). Defaults to `TaskInstanceKind.Shared`
248
	 */
249
	panel: PanelKind;
250 251 252 253

	/**
	 * Controls whether to show the "Terminal will be reused by tasks, press any key to close it" message.
	 */
D
Dirk Baeumer 已提交
254
	showReuseMessage: boolean;
255 256 257 258

	/**
	 * Controls whether to clear the terminal before executing the task.
	 */
259
	clear: boolean;
260 261 262 263 264

	/**
	 * Controls whether the task is executed in a specific terminal group using split panes.
	 */
	group?: string;
D
Dirk Baeumer 已提交
265 266
}

267 268
export namespace PresentationOptions {
	export const defaults: PresentationOptions = {
269
		echo: true, reveal: RevealKind.Always, revealProblems: RevealProblemKind.Never, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false
270 271 272
	};
}

273
export enum RuntimeType {
D
Dirk Baeumer 已提交
274
	Shell = 1,
275
	Process = 2,
A
Alex Ross 已提交
276
	CustomExecution2 = 3
D
Dirk Baeumer 已提交
277 278
}

279 280
export namespace RuntimeType {
	export function fromString(value: string): RuntimeType {
D
Dirk Baeumer 已提交
281 282
		switch (value.toLowerCase()) {
			case 'shell':
283
				return RuntimeType.Shell;
D
Dirk Baeumer 已提交
284
			case 'process':
285
				return RuntimeType.Process;
286 287
			case 'customExecution2':
				return RuntimeType.CustomExecution2;
D
Dirk Baeumer 已提交
288
			default:
289
				return RuntimeType.Process;
D
Dirk Baeumer 已提交
290 291 292 293
		}
	}
}

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
export interface QuotedString {
	value: string;
	quoting: ShellQuoting;
}

export type CommandString = string | QuotedString;

export namespace CommandString {
	export function value(value: CommandString): string {
		if (Types.isString(value)) {
			return value;
		} else {
			return value.value;
		}
	}
}

311
export interface CommandConfiguration {
D
Dirk Baeumer 已提交
312

313
	/**
D
Dirk Baeumer 已提交
314
	 * The task type
315
	 */
A
Alex Ross 已提交
316
	runtime?: RuntimeType;
E
Erich Gamma 已提交
317

318
	/**
D
Dirk Baeumer 已提交
319
	 * The command to execute
320
	 */
A
Alex Ross 已提交
321
	name?: CommandString;
E
Erich Gamma 已提交
322

323 324 325 326
	/**
	 * Additional command options.
	 */
	options?: CommandOptions;
E
Erich Gamma 已提交
327

328 329 330
	/**
	 * Command arguments.
	 */
331
	args?: CommandString[];
E
Erich Gamma 已提交
332

333 334 335 336
	/**
	 * The task selector if needed.
	 */
	taskSelector?: string;
E
Erich Gamma 已提交
337

338 339 340 341 342 343
	/**
	 * Whether to suppress the task name when merging global args
	 *
	 */
	suppressTaskName?: boolean;

344
	/**
345
	 * Describes how the task is presented in the UI.
346
	 */
A
Alex Ross 已提交
347
	presentation?: PresentationOptions;
E
Erich Gamma 已提交
348 349
}

350 351 352 353 354
export namespace TaskGroup {
	export const Clean: 'clean' = 'clean';

	export const Build: 'build' = 'build';

355
	export const Rebuild: 'rebuild' = 'rebuild';
356 357 358

	export const Test: 'test' = 'test';

359
	export function is(value: string): value is string {
360
		return value === Clean || value === Build || value === Rebuild || value === Test;
361 362 363
	}
}

364
export type TaskGroup = 'clean' | 'build' | 'rebuild' | 'test';
365

366

367
export const enum TaskScope {
D
Dirk Baeumer 已提交
368 369 370 371 372
	Global = 1,
	Workspace = 2,
	Folder = 3
}

373 374 375
export namespace TaskSourceKind {
	export const Workspace: 'workspace' = 'workspace';
	export const Extension: 'extension' = 'extension';
376
	export const InMemory: 'inMemory' = 'inMemory';
377 378 379
}

export interface TaskSourceConfigElement {
S
Sandeep Somavarapu 已提交
380
	workspaceFolder: IWorkspaceFolder;
381 382 383 384 385
	file: string;
	index: number;
	element: any;
}

A
Alex Ross 已提交
386
interface BaseTaskSource {
M
Matt Bierner 已提交
387
	readonly kind: string;
D
Dirk Baeumer 已提交
388
	readonly label: string;
A
Alex Ross 已提交
389 390 391 392
}

export interface WorkspaceTaskSource extends BaseTaskSource {
	readonly kind: 'workspace';
D
Dirk Baeumer 已提交
393
	readonly config: TaskSourceConfigElement;
394
	readonly customizes?: KeyedTaskIdentifier;
395 396
}

A
Alex Ross 已提交
397
export interface ExtensionTaskSource extends BaseTaskSource {
D
Dirk Baeumer 已提交
398
	readonly kind: 'extension';
399
	readonly extension?: string;
D
Dirk Baeumer 已提交
400
	readonly scope: TaskScope;
S
Sandeep Somavarapu 已提交
401
	readonly workspaceFolder: IWorkspaceFolder | undefined;
402 403
}

404
export interface ExtensionTaskSourceTransfer {
405
	__workspaceFolder: UriComponents;
406
	__definition: { type: string;[name: string]: any };
407 408
}

A
Alex Ross 已提交
409
export interface InMemoryTaskSource extends BaseTaskSource {
410
	readonly kind: 'inMemory';
411 412
}

413
export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | InMemoryTaskSource;
414

415 416
export interface TaskIdentifier {
	type: string;
D
Dirk Baeumer 已提交
417
	[name: string]: any;
418
}
E
Erich Gamma 已提交
419

420 421 422 423
export interface KeyedTaskIdentifier extends TaskIdentifier {
	_key: string;
}

424
export interface TaskDependency {
S
Sandeep Somavarapu 已提交
425
	workspaceFolder: IWorkspaceFolder;
A
Alex Ross 已提交
426
	task: string | KeyedTaskIdentifier | undefined;
427 428
}

429
export const enum GroupType {
430 431 432 433
	default = 'default',
	user = 'user'
}

434 435 436 437 438
export const enum DependsOrder {
	parallel = 'parallel',
	sequence = 'sequence'
}

439
export interface ConfigurationProperties {
440

E
Erich Gamma 已提交
441 442 443
	/**
	 * The task's name
	 */
444
	name?: string;
E
Erich Gamma 已提交
445

446
	/**
447
	 * The task's name
448
	 */
449
	identifier?: string;
450

451 452 453
	/**
	 * the task's group;
	 */
454
	group?: string;
455

D
Dirk Baeumer 已提交
456
	/**
457
	 * The group type
D
Dirk Baeumer 已提交
458
	 */
459
	groupType?: GroupType;
D
Dirk Baeumer 已提交
460

E
Erich Gamma 已提交
461
	/**
462
	 * The presentation options
E
Erich Gamma 已提交
463
	 */
464
	presentation?: PresentationOptions;
E
Erich Gamma 已提交
465

466 467 468 469 470
	/**
	 * The command options;
	 */
	options?: CommandOptions;

E
Erich Gamma 已提交
471
	/**
472
	 * Whether the task is a background task or not.
E
Erich Gamma 已提交
473
	 */
474
	isBackground?: boolean;
E
Erich Gamma 已提交
475 476

	/**
477
	 * Whether the task should prompt on close for confirmation if running.
E
Erich Gamma 已提交
478
	 */
479
	promptOnClose?: boolean;
E
Erich Gamma 已提交
480 481

	/**
482
	 * The other tasks this task depends on.
E
Erich Gamma 已提交
483
	 */
484
	dependsOn?: TaskDependency[];
E
Erich Gamma 已提交
485

486 487 488 489 490
	/**
	 * The order the dependsOn tasks should be executed in.
	 */
	dependsOrder?: DependsOrder;

491 492 493
	/**
	 * The problem watchers to use for this task
	 */
494
	problemMatchers?: Array<string | ProblemMatcher>;
E
Erich Gamma 已提交
495 496
}

A
Alex Ross 已提交
497 498 499 500 501
export enum RunOnOptions {
	default = 1,
	folderOpen = 2
}

A
Alex Ross 已提交
502
export interface RunOptions {
503
	reevaluateOnRerun?: boolean;
A
Alex Ross 已提交
504
	runOn?: RunOnOptions;
A
Alex Ross 已提交
505
}
506 507 508 509 510

export namespace RunOptions {
	export const defaults: RunOptions = { reevaluateOnRerun: true, runOn: RunOnOptions.default };
}

A
Alex Ross 已提交
511
export abstract class CommonTask {
512 513 514 515 516 517 518 519 520

	/**
	 * The task's internal id
	 */
	_id: string;

	/**
	 * The cached label.
	 */
521
	_label: string = '';
522

M
Matt Bierner 已提交
523
	type?: string;
A
Alex Ross 已提交
524 525

	runOptions: RunOptions;
A
Alex Ross 已提交
526 527 528 529 530

	configurationProperties: ConfigurationProperties;

	_source: BaseTaskSource;

531 532
	private _taskLoadMessages: string[] | undefined;

M
Matt Bierner 已提交
533
	protected constructor(id: string, label: string | undefined, type: string | undefined, runOptions: RunOptions,
A
Alex Ross 已提交
534 535
		configurationProperties: ConfigurationProperties, source: BaseTaskSource) {
		this._id = id;
A
Alex Ross 已提交
536 537 538
		if (label) {
			this._label = label;
		}
A
Alex Ross 已提交
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
		if (type) {
			this.type = type;
		}
		this.runOptions = runOptions;
		this.configurationProperties = configurationProperties;
		this._source = source;
	}

	public getDefinition(useSource?: boolean): KeyedTaskIdentifier | undefined {
		return undefined;
	}

	public getMapKey(): string {
		return this._id;
	}

	public getRecentlyUsedKey(): string | undefined {
		return undefined;
	}

	public clone(): Task {
A
Alex Ross 已提交
560
		return this.fromObject(Objects.assign({}, <any>this));
A
Alex Ross 已提交
561 562
	}

A
Alex Ross 已提交
563 564
	protected abstract fromObject(object: any): Task;

A
Alex Ross 已提交
565 566 567 568 569 570 571 572
	public getWorkspaceFolder(): IWorkspaceFolder | undefined {
		return undefined;
	}

	public getTelemetryKind(): string {
		return 'unknown';
	}

573
	public matches(key: string | KeyedTaskIdentifier | undefined, compareId: boolean = false): boolean {
R
Rob Lourens 已提交
574
		if (key === undefined) {
A
Alex Ross 已提交
575 576 577 578 579 580
			return false;
		}
		if (Types.isString(key)) {
			return key === this._label || key === this.configurationProperties.identifier || (compareId && key === this._id);
		}
		let identifier = this.getDefinition(true);
R
Rob Lourens 已提交
581
		return identifier !== undefined && identifier._key === key._key;
A
Alex Ross 已提交
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
	}

	public getQualifiedLabel(): string {
		let workspaceFolder = this.getWorkspaceFolder();
		if (workspaceFolder) {
			return `${this._label} (${workspaceFolder.name})`;
		} else {
			return this._label;
		}
	}

	public getTaskExecution(): TaskExecution {
		let result: TaskExecution = {
			id: this._id,
			task: <any>this
		};
		return result;
	}
600 601

	public addTaskLoadMessages(messages: string[] | undefined) {
R
Rob Lourens 已提交
602
		if (this._taskLoadMessages === undefined) {
603 604 605 606 607 608 609 610 611 612
			this._taskLoadMessages = [];
		}
		if (messages) {
			this._taskLoadMessages = this._taskLoadMessages.concat(messages);
		}
	}

	get taskLoadMessages(): string[] | undefined {
		return this._taskLoadMessages;
	}
613 614
}

A
Alex Ross 已提交
615
export class CustomTask extends CommonTask {
616

617
	type!: '$customized'; // CUSTOMIZED_TASK_TYPE
618

619
	/**
620
	 * Indicated the source of the task (e.g. tasks.json or extension)
621 622 623
	 */
	_source: WorkspaceTaskSource;

624 625
	hasDefinedMatchers: boolean;

626 627 628
	/**
	 * The command configuration
	 */
629
	command: CommandConfiguration = {};
630

M
Matt Bierner 已提交
631
	public constructor(id: string, source: WorkspaceTaskSource, label: string, type: string, command: CommandConfiguration | undefined,
A
Alex Ross 已提交
632 633 634 635
		hasDefinedMatchers: boolean, runOptions: RunOptions, configurationProperties: ConfigurationProperties) {
		super(id, label, undefined, runOptions, configurationProperties, source);
		this._source = source;
		this.hasDefinedMatchers = hasDefinedMatchers;
A
Alex Ross 已提交
636 637 638
		if (command) {
			this.command = command;
		}
639
	}
A
Alex Ross 已提交
640 641 642 643 644 645 646 647 648

	public customizes(): KeyedTaskIdentifier | undefined {
		if (this._source && this._source.customizes) {
			return this._source.customizes;
		}
		return undefined;
	}

	public getDefinition(useSource: boolean = false): KeyedTaskIdentifier {
R
Rob Lourens 已提交
649
		if (useSource && this._source.customizes !== undefined) {
A
Alex Ross 已提交
650
			return this._source.customizes;
D
Dirk Baeumer 已提交
651
		} else {
A
Alex Ross 已提交
652
			let type: string;
A
Alex Ross 已提交
653 654 655 656 657 658 659 660 661 662
			const commandRuntime = this.command ? this.command.runtime : undefined;
			switch (commandRuntime) {
				case RuntimeType.Shell:
					type = 'shell';
					break;

				case RuntimeType.Process:
					type = 'process';
					break;

663 664 665 666
				case RuntimeType.CustomExecution2:
					type = 'customExecution2';
					break;

A
Alex Ross 已提交
667 668 669 670 671 672
				case undefined:
					type = '$composite';
					break;

				default:
					throw new Error('Unexpected task runtime');
A
Alex Ross 已提交
673
			}
A
Alex Ross 已提交
674

A
Alex Ross 已提交
675 676 677 678 679 680
			let result: KeyedTaskIdentifier = {
				type,
				_key: this._id,
				id: this._id
			};
			return result;
D
Dirk Baeumer 已提交
681 682
		}
	}
A
Alex Ross 已提交
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706

	public static is(value: any): value is CustomTask {
		return value instanceof CustomTask;
	}

	public getMapKey(): string {
		let workspaceFolder = this._source.config.workspaceFolder;
		return workspaceFolder ? `${workspaceFolder.uri.toString()}|${this._id}` : this._id;
	}

	public getRecentlyUsedKey(): string | undefined {
		interface CustomKey {
			type: string;
			folder: string;
			id: string;
		}
		let workspaceFolder = this._source.config.workspaceFolder;
		if (!workspaceFolder) {
			return undefined;
		}
		let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder.uri.toString(), id: this.configurationProperties.identifier! };
		return JSON.stringify(key);
	}

707
	public getWorkspaceFolder(): IWorkspaceFolder {
A
Alex Ross 已提交
708 709 710 711 712 713 714 715
		return this._source.config.workspaceFolder;
	}

	public getTelemetryKind(): string {
		if (this._source.customizes) {
			return 'workspace>extension';
		} else {
			return 'workspace';
716 717
		}
	}
A
Alex Ross 已提交
718 719 720 721

	protected fromObject(object: CustomTask): CustomTask {
		return new CustomTask(object._id, object._source, object._label, object.type, object.command, object.hasDefinedMatchers, object.runOptions, object.configurationProperties);
	}
722 723
}

A
Alex Ross 已提交
724
export class ConfiguringTask extends CommonTask {
725

726
	/**
727
	 * Indicated the source of the task (e.g. tasks.json or extension)
728 729 730
	 */
	_source: WorkspaceTaskSource;

731
	configures: KeyedTaskIdentifier;
732

M
Matt Bierner 已提交
733
	public constructor(id: string, source: WorkspaceTaskSource, label: string | undefined, type: string | undefined,
A
Alex Ross 已提交
734 735 736 737 738 739 740 741
		configures: KeyedTaskIdentifier, runOptions: RunOptions, configurationProperties: ConfigurationProperties) {
		super(id, label, type, runOptions, configurationProperties, source);
		this._source = source;
		this.configures = configures;
	}

	public static is(value: any): value is ConfiguringTask {
		return value instanceof ConfiguringTask;
742
	}
A
Alex Ross 已提交
743 744 745 746 747

	protected fromObject(object: any): Task {
		return object;
	}

748 749 750
	public getDefinition(): KeyedTaskIdentifier {
		return this.configures;
	}
751 752
}

A
Alex Ross 已提交
753
export class ContributedTask extends CommonTask {
754

755
	/**
756
	 * Indicated the source of the task (e.g. tasks.json or extension)
757
	 * Set in the super constructor
758
	 */
759
	_source!: ExtensionTaskSource;
760

761
	defines: KeyedTaskIdentifier;
762

763 764
	hasDefinedMatchers: boolean;

765 766 767 768 769
	/**
	 * The command configuration
	 */
	command: CommandConfiguration;

M
Matt Bierner 已提交
770
	public constructor(id: string, source: ExtensionTaskSource, label: string, type: string | undefined, defines: KeyedTaskIdentifier,
A
Alex Ross 已提交
771 772 773 774 775 776
		command: CommandConfiguration, hasDefinedMatchers: boolean, runOptions: RunOptions,
		configurationProperties: ConfigurationProperties) {
		super(id, label, type, runOptions, configurationProperties, source);
		this.defines = defines;
		this.hasDefinedMatchers = hasDefinedMatchers;
		this.command = command;
777 778
	}

A
Alex Ross 已提交
779 780 781
	public getDefinition(): KeyedTaskIdentifier {
		return this.defines;
	}
782

A
Alex Ross 已提交
783 784
	public static is(value: any): value is ContributedTask {
		return value instanceof ContributedTask;
785 786
	}

A
Alex Ross 已提交
787 788 789 790 791 792
	public getMapKey(): string {
		let workspaceFolder = this._source.workspaceFolder;
		return workspaceFolder
			? `${this._source.scope.toString()}|${workspaceFolder.uri.toString()}|${this._id}`
			: `${this._source.scope.toString()}|${this._id}`;
	}
793

A
Alex Ross 已提交
794
	public getRecentlyUsedKey(): string | undefined {
795 796 797 798 799 800
		interface ContributedKey {
			type: string;
			scope: number;
			folder?: string;
			id: string;
		}
801

A
Alex Ross 已提交
802 803 804
		let key: ContributedKey = { type: 'contributed', scope: this._source.scope, id: this._id };
		if (this._source.scope === TaskScope.Folder && this._source.workspaceFolder) {
			key.folder = this._source.workspaceFolder.uri.toString();
805
		}
A
Alex Ross 已提交
806
		return JSON.stringify(key);
807 808
	}

A
Alex Ross 已提交
809 810
	public getWorkspaceFolder(): IWorkspaceFolder | undefined {
		return this._source.workspaceFolder;
D
Dirk Baeumer 已提交
811 812
	}

A
Alex Ross 已提交
813 814
	public getTelemetryKind(): string {
		return 'extension';
815
	}
A
Alex Ross 已提交
816 817 818 819

	protected fromObject(object: ContributedTask): ContributedTask {
		return new ContributedTask(object._id, object._source, object._label, object.type, object.defines, object.command, object.hasDefinedMatchers, object.runOptions, object.configurationProperties);
	}
A
Alex Ross 已提交
820
}
821

A
Alex Ross 已提交
822 823
export class InMemoryTask extends CommonTask {
	/**
824
	 * Indicated the source of the task (e.g. tasks.json or extension)
A
Alex Ross 已提交
825 826
	 */
	_source: InMemoryTaskSource;
827

828
	type!: 'inMemory';
829

M
Matt Bierner 已提交
830
	public constructor(id: string, source: InMemoryTaskSource, label: string, type: string,
A
Alex Ross 已提交
831 832 833
		runOptions: RunOptions, configurationProperties: ConfigurationProperties) {
		super(id, label, type, runOptions, configurationProperties, source);
		this._source = source;
834
	}
835

A
Alex Ross 已提交
836 837
	public static is(value: any): value is InMemoryTask {
		return value instanceof InMemoryTask;
838 839
	}

A
Alex Ross 已提交
840 841
	public getTelemetryKind(): string {
		return 'composite';
842
	}
A
Alex Ross 已提交
843 844 845 846

	protected fromObject(object: InMemoryTask): InMemoryTask {
		return new InMemoryTask(object._id, object._source, object._label, object.type, object.runOptions, object.configurationProperties);
	}
847 848
}

A
Alex Ross 已提交
849 850
export type Task = CustomTask | ContributedTask | InMemoryTask;

851 852
export interface TaskExecution {
	id: string;
D
Dirk Baeumer 已提交
853
	task: Task;
854
}
855

856
export enum ExecutionEngine {
857 858 859 860
	Process = 1,
	Terminal = 2
}

861 862 863 864
export namespace ExecutionEngine {
	export const _default: ExecutionEngine = ExecutionEngine.Terminal;
}

865
export const enum JsonSchemaVersion {
866 867
	V0_1_0 = 1,
	V2_0_0 = 2
868 869 870 871 872
}

export interface TaskSet {
	tasks: Task[];
	extension?: IExtensionDescription;
873 874
}

D
Dirk Baeumer 已提交
875
export interface TaskDefinition {
876
	extensionId: string;
877 878 879
	taskType: string;
	required: string[];
	properties: IJSONSchemaMap;
880 881 882 883 884 885
}

export class TaskSorter {

	private _order: Map<string, number> = new Map();

S
Sandeep Somavarapu 已提交
886
	constructor(workspaceFolders: IWorkspaceFolder[]) {
887 888 889 890 891 892
		for (let i = 0; i < workspaceFolders.length; i++) {
			this._order.set(workspaceFolders[i].uri.toString(), i);
		}
	}

	public compare(a: Task, b: Task): number {
A
Alex Ross 已提交
893 894
		let aw = a.getWorkspaceFolder();
		let bw = b.getWorkspaceFolder();
895 896
		if (aw && bw) {
			let ai = this._order.get(aw.uri.toString());
R
Rob Lourens 已提交
897
			ai = ai === undefined ? 0 : ai + 1;
898
			let bi = this._order.get(bw.uri.toString());
R
Rob Lourens 已提交
899
			bi = bi === undefined ? 0 : bi + 1;
900 901 902 903 904 905 906 907 908 909 910 911 912
			if (ai === bi) {
				return a._label.localeCompare(b._label);
			} else {
				return ai - bi;
			}
		} else if (!aw && bw) {
			return -1;
		} else if (aw && !bw) {
			return +1;
		} else {
			return 0;
		}
	}
913 914
}

915
export const enum TaskEventKind {
916
	DependsOnStarted = 'dependsOnStarted',
917
	Start = 'start',
918
	ProcessStarted = 'processStarted',
919 920 921
	Active = 'active',
	Inactive = 'inactive',
	Changed = 'changed',
922
	Terminated = 'terminated',
923
	ProcessEnded = 'processEnded',
924
	End = 'end'
925 926 927
}


928
export const enum TaskRunType {
929 930 931 932 933 934 935 936 937 938
	SingleRun = 'singleRun',
	Background = 'background'
}

export interface TaskEvent {
	kind: TaskEventKind;
	taskId?: string;
	taskName?: string;
	runType?: TaskRunType;
	group?: string;
939 940
	processId?: number;
	exitCode?: number;
941
	terminalId?: number;
942 943 944
	__task?: Task;
}

945
export const enum TaskRunSource {
946 947
	System,
	User,
948 949 950 951
	FolderOpen,
	ConfigurationChange
}

952
export namespace TaskEvent {
953
	export function create(kind: TaskEventKind.ProcessStarted | TaskEventKind.ProcessEnded, task: Task, processIdOrExitCode?: number): TaskEvent;
954
	export function create(kind: TaskEventKind.Start, task: Task, terminalId?: number): TaskEvent;
955
	export function create(kind: TaskEventKind.DependsOnStarted | TaskEventKind.Start | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.Terminated | TaskEventKind.End, task: Task): TaskEvent;
956
	export function create(kind: TaskEventKind.Changed): TaskEvent;
957
	export function create(kind: TaskEventKind, task?: Task, processIdOrExitCodeOrTerminalId?: number): TaskEvent {
958
		if (task) {
959
			let result: TaskEvent = {
960 961
				kind: kind,
				taskId: task._id,
A
Alex Ross 已提交
962 963 964
				taskName: task.configurationProperties.name,
				runType: task.configurationProperties.isBackground ? TaskRunType.Background : TaskRunType.SingleRun,
				group: task.configurationProperties.group,
M
Matt Bierner 已提交
965 966
				processId: undefined as number | undefined,
				exitCode: undefined as number | undefined,
967
				terminalId: undefined as number | undefined,
968
				__task: task,
969
			};
970 971 972 973
			if (kind === TaskEventKind.Start) {
				result.terminalId = processIdOrExitCodeOrTerminalId;
			} else if (kind === TaskEventKind.ProcessStarted) {
				result.processId = processIdOrExitCodeOrTerminalId;
974
			} else if (kind === TaskEventKind.ProcessEnded) {
975
				result.exitCode = processIdOrExitCodeOrTerminalId;
976 977
			}
			return Object.freeze(result);
978 979 980 981
		} else {
			return Object.freeze({ kind: TaskEventKind.Changed });
		}
	}
982 983 984 985 986 987 988 989 990
}

export namespace KeyedTaskIdentifier {
	function sortedStringify(literal: any): string {
		const keys = Object.keys(literal).sort();
		let result: string = '';
		for (let position in keys) {
			let stringified = literal[keys[position]];
			if (stringified instanceof Object) {
A
Alex Ross 已提交
991
				stringified = sortedStringify(stringified);
992 993 994 995 996 997 998 999 1000
			} else if (typeof stringified === 'string') {
				stringified = stringified.replace(/,/g, ',,');
			}
			result += keys[position] + ',' + stringified + ',';
		}
		return result;
	}
	export function create(value: TaskIdentifier): KeyedTaskIdentifier {
		const resultKey = sortedStringify(value);
1001 1002 1003
		let result = { _key: resultKey, type: value.taskType };
		Objects.assign(result, value);
		return result;
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
	}
}

export namespace TaskDefinition {
	export function createTaskIdentifier(external: TaskIdentifier, reporter: { error(message: string): void; }): KeyedTaskIdentifier | undefined {
		let definition = TaskDefinitionRegistry.get(external.type);
		if (definition === undefined) {
			// We have no task definition so we can't sanitize the literal. Take it as is
			let copy = Objects.deepClone(external);
			delete copy._key;
			return KeyedTaskIdentifier.create(copy);
		}

		let literal: { type: string;[name: string]: any } = Object.create(null);
		literal.type = definition.taskType;
		let required: Set<string> = new Set();
		definition.required.forEach(element => required.add(element));

		let properties = definition.properties;
		for (let property of Object.keys(properties)) {
			let value = external[property];
			if (value !== undefined && value !== null) {
				literal[property] = value;
			} else if (required.has(property)) {
				let schema = properties[property];
				if (schema.default !== undefined) {
					literal[property] = Objects.deepClone(schema.default);
				} else {
					switch (schema.type) {
						case 'boolean':
							literal[property] = false;
							break;
						case 'number':
						case 'integer':
							literal[property] = 0;
							break;
						case 'string':
							literal[property] = '';
							break;
						default:
							reporter.error(nls.localize(
								'TaskDefinition.missingRequiredProperty',
								'Error: the task identifier \'{0}\' is missing the required property \'{1}\'. The task identifier will be ignored.', JSON.stringify(external, undefined, 0), property
							));
							return undefined;
					}
				}
			}
		}
		return KeyedTaskIdentifier.create(literal);
	}
}