in src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts [895:1027]
private async createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver, workspaceFolder: IWorkspaceFolder): Promise<[ITerminalInstance | undefined, string | undefined, TaskError | undefined]> {
let platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform;
let options = this.resolveOptions(resolver, task.command.options);
let waitOnExit: boolean | string = false;
const presentationOptions = task.command.presentation;
if (!presentationOptions) {
throw new Error('Task presentation options should not be undefined here.');
}
if (presentationOptions.reveal !== RevealKind.Never || !task.configurationProperties.isBackground) {
if (presentationOptions.panel === PanelKind.New) {
waitOnExit = nls.localize('closeTerminal', 'Press any key to close the terminal.');
} else if (presentationOptions.showReuseMessage) {
waitOnExit = nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.');
} else {
waitOnExit = true;
}
}
let commandExecutable: string | undefined;
let command: CommandString | undefined;
let args: CommandString[] | undefined;
let launchConfigs: IShellLaunchConfig | undefined;
if (task.command.runtime === RuntimeType.CustomExecution) {
this.currentTask.shellLaunchConfig = launchConfigs = {
isRendererOnly: true,
waitOnExit,
name: this.createTerminalName(task, workspaceFolder),
initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined
};
} else if (task.command.runtime === RuntimeType.CustomExecution2) {
this.currentTask.shellLaunchConfig = launchConfigs = {
isVirtualProcess: true,
waitOnExit,
name: this.createTerminalName(task, workspaceFolder),
initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined
};
} else {
let resolvedResult: { command: CommandString, args: CommandString[] } = this.resolveCommandAndArgs(resolver, task.command);
command = resolvedResult.command;
args = resolvedResult.args;
commandExecutable = CommandString.value(command);
this.currentTask.shellLaunchConfig = launchConfigs = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : await this.createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit);
if (launchConfigs === 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)];
}
}
let prefersSameTerminal = presentationOptions.panel === PanelKind.Dedicated;
let allowsSharedTerminal = presentationOptions.panel === PanelKind.Shared;
let group = presentationOptions.group;
let taskKey = task.getMapKey();
let terminalToReuse: TerminalData | undefined;
if (prefersSameTerminal) {
let terminalId = this.sameTaskTerminals[taskKey];
if (terminalId) {
terminalToReuse = this.terminals[terminalId];
delete this.sameTaskTerminals[taskKey];
}
} else if (allowsSharedTerminal) {
// 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)!;
if (idleTerminalId && this.terminals[idleTerminalId] && this.terminals[idleTerminalId].group === group) {
terminalId = this.idleTaskTerminals.remove(taskId);
break;
}
}
}
if (terminalId) {
terminalToReuse = this.terminals[terminalId];
}
}
if (terminalToReuse) {
if (!launchConfigs) {
throw new Error('Task shell launch configuration should not be undefined here.');
}
terminalToReuse.terminal.reuseTerminal(launchConfigs);
if (task.command.presentation && task.command.presentation.clear) {
terminalToReuse.terminal.clear();
}
this.terminals[terminalToReuse.terminal.id.toString()].lastTask = taskKey;
return [terminalToReuse.terminal, commandExecutable, undefined];
}
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;
await originalInstance.waitForTitle();
result = this.terminalService.splitInstance(originalInstance, launchConfigs);
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(launchConfigs);
}
const terminalKey = result.id.toString();
result.onDisposed((terminal) => {
let terminalData = this.terminals[terminalKey];
if (terminalData) {
delete this.terminals[terminalKey];
delete this.sameTaskTerminals[terminalData.lastTask];
this.idleTaskTerminals.delete(terminalData.lastTask);
// 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.
delete this.activeTasks[task.getMapKey()];
}
});
this.terminals[terminalKey] = { terminal: result, lastTask: taskKey, group };
return [result, commandExecutable, undefined];
}