Restore waitOnExit, type, hideFromUser, reconnectionOwner terminal properties on reconnect (#155346)

This commit is contained in:
Megan Rogge
2022-07-25 13:44:47 -07:00
committed by GitHub
parent c7a138df0e
commit 1a8d4bf61d
8 changed files with 124 additions and 64 deletions
+13 -3
View File
@@ -169,8 +169,14 @@ export interface IPtyHostAttachTarget {
environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined;
reconnectionOwner?: string;
task?: { label: string; id: string; lastTask: string; group?: string };
waitOnExit?: WaitOnExitValue;
hideFromUser?: boolean;
isFeatureTerminal?: boolean;
type?: TerminalType;
}
export type TerminalType = 'Task' | 'Local' | undefined;
export enum TitleEventSource {
/** From the API or the rename command that overrides any other type */
Api,
@@ -446,7 +452,7 @@ export interface IShellLaunchConfig {
reconnectionOwner?: string;
/** Whether to wait for a key press before closing the terminal. */
waitOnExit?: boolean | string | ((exitCode: number) => string);
waitOnExit?: WaitOnExitValue;
/**
* A string including ANSI escape sequences that will be written to the terminal emulator
@@ -469,7 +475,9 @@ export interface IShellLaunchConfig {
/**
* This is a terminal that attaches to an already running terminal.
*/
attachPersistentProcess?: { id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections; reconnectionOwner?: string; task?: { label: string; id: string; lastTask: string; group?: string } };
attachPersistentProcess?: {
id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections; reconnectionOwner?: string; task?: { label: string; id: string; lastTask: string; group?: string }; type?: TerminalType; waitOnExit?: WaitOnExitValue; hideFromUser?: boolean; isFeatureTerminal?: boolean;
};
/**
* Whether the terminal process environment should be exactly as provided in
@@ -544,9 +552,11 @@ export interface IShellLaunchConfig {
/**
* The task associated with this terminal
*/
task?: { lastTask: string; group?: string; label: string; id: string };
task?: { label: string; id: string; lastTask: string; group?: string };
}
export type WaitOnExitValue = boolean | string | ((exitCode: number) => string);
export interface ICreateContributedTerminalProfileOptions {
icon?: URI | string | { light: URI; dark: URI };
color?: string;
@@ -5,7 +5,7 @@
import { UriComponents } from 'vs/base/common/uri';
import { ISerializableEnvironmentVariableCollection, ISerializableEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariable';
import { IFixedTerminalDimensions, IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IFixedTerminalDimensions, IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TerminalType, TitleEventSource, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
export interface ISingleTerminalConfiguration<T> {
userValue: T | undefined;
@@ -62,6 +62,10 @@ export interface IProcessDetails {
environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined;
reconnectionOwner?: string;
task?: { label: string; id: string; lastTask: string; group?: string };
waitOnExit?: WaitOnExitValue;
hideFromUser?: boolean;
isFeatureTerminal?: boolean;
type?: TerminalType;
}
export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo<IProcessDetails>;
+5 -1
View File
@@ -411,7 +411,11 @@ export class PtyService extends Disposable implements IPtyService {
fixedDimensions: persistentProcess.fixedDimensions,
environmentVariableCollections: persistentProcess.processLaunchOptions.options.environmentVariableCollections,
reconnectionOwner: persistentProcess.shellLaunchConfig.reconnectionOwner,
task: persistentProcess.shellLaunchConfig.task
task: persistentProcess.shellLaunchConfig.task,
waitOnExit: persistentProcess.shellLaunchConfig.waitOnExit,
hideFromUser: persistentProcess.shellLaunchConfig.hideFromUser,
isFeatureTerminal: persistentProcess.shellLaunchConfig.isFeatureTerminal,
type: persistentProcess.shellLaunchConfig.type
};
}
@@ -30,14 +30,14 @@ import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { IShellLaunchConfig, TerminalLocation, TerminalSettingId, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus';
import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors';
import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration';
import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IExtensionTaskSource, InMemoryTask, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks';
import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IConfigurationProperties, IExtensionTaskSource, InMemoryTask, IPresentationOptions, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
import { IResolvedVariables, IResolveSet, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from 'vs/workbench/contrib/tasks/common/taskSystem';
import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
@@ -284,6 +284,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
if (this._tasksToReconnect.includes(task._id)) {
this._terminalForTask = terminals.find(t => t.shellLaunchConfig.attachPersistentProcess?.task?.id === task._id);
// Restore the waitOnExit value of the terminal because it may have been a function
// that cannot be persisted in the pty host
if ('command' in task && task.command.presentation && this._terminalForTask) {
this._terminalForTask.waitOnExit = getWaitOnExitValue(task.command.presentation, task.configurationProperties);
}
this.run(task, resolver, trigger);
}
return undefined;
@@ -541,35 +546,38 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
alreadyResolved = alreadyResolved ?? new Map<string, string>();
const promises: Promise<ITaskSummary>[] = [];
if (task.configurationProperties.dependsOn) {
for (const dependency of task.configurationProperties.dependsOn) {
const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
if (dependencyTask) {
this._adoptConfigurationForDependencyTask(dependencyTask, task);
const key = dependencyTask.getMapKey();
let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined;
if (!promise) {
this._fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
encounteredDependencies.add(task.getCommonTaskId());
promise = this._executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved);
}
promises.push(promise);
if (task.configurationProperties.dependsOrder === DependsOrder.sequence) {
const promiseResult = await promise;
if (promiseResult.exitCode === 0) {
promise = Promise.resolve(promiseResult);
} else {
promise = Promise.reject(promiseResult);
break;
if (!this._terminalForTask) {
// we already handle dependent tasks when reconnecting, don't create extras
for (const dependency of task.configurationProperties.dependsOn) {
const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
if (dependencyTask) {
this._adoptConfigurationForDependencyTask(dependencyTask, task);
const key = dependencyTask.getMapKey();
let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined;
if (!promise) {
this._fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
encounteredDependencies.add(task.getCommonTaskId());
promise = this._executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved);
}
promises.push(promise);
if (task.configurationProperties.dependsOrder === DependsOrder.sequence) {
const promiseResult = await promise;
if (promiseResult.exitCode === 0) {
promise = Promise.resolve(promiseResult);
} else {
promise = Promise.reject(promiseResult);
break;
}
}
promises.push(promise);
} else {
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.uri.toString()
));
this._showOutput();
}
promises.push(promise);
} else {
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.uri.toString()
));
this._showOutput();
}
}
}
@@ -1065,7 +1073,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return needsFolderQualification ? task.getQualifiedLabel() : (task.configurationProperties.name || '');
}
private async _createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string | ((exitCode: number) => string)): Promise<IShellLaunchConfig | undefined> {
private async _createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: WaitOnExitValue): Promise<IShellLaunchConfig | undefined> {
let shellLaunchConfig: IShellLaunchConfig;
const isShellCommand = task.command.runtime === RuntimeType.Shell;
const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
@@ -1338,24 +1346,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
const options = await this._resolveOptions(resolver, task.command.options);
const presentationOptions = task.command.presentation;
let waitOnExit: boolean | string | ((exitCode: number) => string) = false;
if (!presentationOptions) {
throw new Error('Task presentation options should not be undefined here.');
}
if ((presentationOptions.close === undefined) || (presentationOptions.close === false)) {
if ((presentationOptions.reveal !== RevealKind.Never) || !task.configurationProperties.isBackground || (presentationOptions.close === false)) {
if (presentationOptions.panel === PanelKind.New) {
waitOnExit = taskShellIntegrationWaitOnExitSequence(nls.localize('closeTerminal', 'Press any key to close the terminal.'));
} else if (presentationOptions.showReuseMessage) {
waitOnExit = taskShellIntegrationWaitOnExitSequence(nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.'));
} else {
waitOnExit = true;
}
}
} else {
waitOnExit = !presentationOptions.close;
}
const waitOnExit = getWaitOnExitValue(presentationOptions, task.configurationProperties);
let command: CommandString | undefined;
let args: CommandString[] | undefined;
@@ -1384,7 +1378,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return [undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)];
}
}
const prefersSameTerminal = presentationOptions.panel === PanelKind.Dedicated;
const allowsSharedTerminal = presentationOptions.panel === PanelKind.Shared;
const group = presentationOptions.group;
@@ -1787,6 +1780,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
}
function getWaitOnExitValue(presentationOptions: IPresentationOptions, configurationProperties: IConfigurationProperties) {
if ((presentationOptions.close === undefined) || (presentationOptions.close === false)) {
if ((presentationOptions.reveal !== RevealKind.Never) || !configurationProperties.isBackground || (presentationOptions.close === false)) {
if (presentationOptions.panel === PanelKind.New) {
return taskShellIntegrationWaitOnExitSequence(nls.localize('closeTerminal', 'Press any key to close the terminal.'));
} else if (presentationOptions.showReuseMessage) {
return taskShellIntegrationWaitOnExitSequence(nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.'));
} else {
return true;
}
}
}
return !presentationOptions.close;
}
function taskShellIntegrationWaitOnExitSequence(message: string): (exitCode: number) => string {
return (exitCode) => {
return `${VSCodeSequence(VSCodeOscPt.CommandFinished, exitCode.toString())}${message}`;
@@ -11,7 +11,7 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TerminalType, TitleEventSource, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -257,6 +257,11 @@ interface ITerminalEditorInputObject {
readonly icon: TerminalIcon | undefined;
readonly color: string | undefined;
readonly hasChildProcesses?: boolean;
readonly task?: { label: string; id: string; lastTask: string; group?: string; waitOnExit?: WaitOnExitValue };
readonly type?: TerminalType;
readonly isFeatureTerminal?: boolean;
readonly hideFromUser?: boolean;
readonly reconnectionOwner?: string;
}
export interface ISerializedTerminalEditorInput extends ITerminalEditorInputObject {
@@ -513,6 +518,12 @@ export interface ITerminalInstance {
*/
readonly hasFocus: boolean;
/**
* Get or set the behavior of the terminal when it closes. This was indented only to be called
* after reconnecting to a terminal.
*/
waitOnExit: WaitOnExitValue | undefined;
/**
* An event that fires when the terminal instance's title changes.
*/
@@ -43,7 +43,11 @@ export class TerminalInputSerializer implements IEditorSerializer {
icon: instance.icon,
color: instance.color,
resource: instance.resource.toString(),
hasChildProcesses: instance.hasChildProcesses
hasChildProcesses: instance.hasChildProcesses,
task: instance.shellLaunchConfig.task,
type: instance.shellLaunchConfig.type,
isFeatureTerminal: instance.shellLaunchConfig.isFeatureTerminal,
hideFromUser: instance.shellLaunchConfig.hideFromUser,
};
}
}
@@ -226,6 +226,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
xterm?: XtermTerminal;
disableLayout: boolean = false;
get waitOnExit(): ITerminalInstance['waitOnExit'] { return this._shellLaunchConfig.attachPersistentProcess?.waitOnExit || this._shellLaunchConfig.waitOnExit; }
set waitOnExit(value: ITerminalInstance['waitOnExit']) {
this._shellLaunchConfig.waitOnExit = value;
}
get target(): TerminalLocation | undefined { return this._target; }
set target(value: TerminalLocation | undefined) {
if (this.xterm) {
@@ -303,12 +308,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
get description(): string | undefined {
if (this._description) {
return this._description;
} else if (this._shellLaunchConfig.type) {
if (this._shellLaunchConfig.type === 'Task') {
}
const type = this.shellLaunchConfig.attachPersistentProcess?.type || this.shellLaunchConfig.type;
if (type) {
if (type === 'Task') {
return nls.localize('terminalTypeTask', "Task");
} else {
return nls.localize('terminalTypeLocal', "Local");
}
return nls.localize('terminalTypeLocal', "Local");
}
return undefined;
}
@@ -407,6 +413,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// the resource is already set when it's been moved from another window
this._resource = resource || getTerminalUri(this._workspaceContextService.getWorkspace().id, this.instanceId, this.title);
if (this._shellLaunchConfig.attachPersistentProcess?.hideFromUser) {
this._shellLaunchConfig.hideFromUser = this._shellLaunchConfig.attachPersistentProcess.hideFromUser;
}
if (this._shellLaunchConfig.attachPersistentProcess?.isFeatureTerminal) {
this._shellLaunchConfig.isFeatureTerminal = this._shellLaunchConfig.attachPersistentProcess.isFeatureTerminal;
}
if (this._shellLaunchConfig.attachPersistentProcess?.type) {
this._shellLaunchConfig.type = this._shellLaunchConfig.attachPersistentProcess.type;
}
if (this.shellLaunchConfig.cwd) {
const cwdUri = typeof this._shellLaunchConfig.cwd === 'string' ? URI.from({
scheme: Schemas.file,
@@ -1764,18 +1782,19 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Only trigger wait on exit when the exit was *not* triggered by the
// user (via the `workbench.action.terminal.kill` command).
if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) {
const waitOnExit = this.waitOnExit;
if (waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) {
this._xtermReadyPromise.then(xterm => {
if (exitMessage) {
xterm.raw.write(formatMessageForTerminal(exitMessage));
}
switch (typeof this._shellLaunchConfig.waitOnExit) {
switch (typeof waitOnExit) {
case 'string':
xterm.raw.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit, { excludeLeadingNewLine: true }));
xterm.raw.write(formatMessageForTerminal(waitOnExit, { excludeLeadingNewLine: true }));
break;
case 'function':
if (this.exitCode !== undefined) {
xterm.raw.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit(this.exitCode), { excludeLeadingNewLine: true }));
xterm.raw.write(formatMessageForTerminal(waitOnExit(this.exitCode), { excludeLeadingNewLine: true }));
}
break;
}
@@ -2732,14 +2751,15 @@ export class TerminalLabelComputer extends Disposable {
labelType: TerminalLabelType,
reset?: boolean
) {
const type = this._instance.shellLaunchConfig.attachPersistentProcess?.type || this._instance.shellLaunchConfig.type;
const templateProperties: ITerminalLabelTemplateProperties = {
cwd: this._instance.cwd || this._instance.initialCwd || '',
cwdFolder: '',
workspaceFolder: this._instance.workspaceFolder ? path.basename(this._instance.workspaceFolder.uri.fsPath) : undefined,
local: this._instance.shellLaunchConfig.type === 'Local' ? this._instance.shellLaunchConfig.type : undefined,
local: type === 'Local' ? type : undefined,
process: this._instance.processName,
sequence: this._instance.sequence,
task: this._instance.shellLaunchConfig.type === 'Task' ? this._instance.shellLaunchConfig.type : undefined,
task: type === 'Task' ? type : undefined,
fixedDimensions: this._instance.fixedCols
? (this._instance.fixedRows ? `\u2194${this._instance.fixedCols} \u2195${this._instance.fixedRows}` : `\u2194${this._instance.fixedCols}`)
: (this._instance.fixedRows ? `\u2195${this._instance.fixedRows}` : ''),
@@ -441,7 +441,6 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
remoteAuthority: undefined,
os: OS
});
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
const initialCwd = await terminalEnvironment.getCwd(