Skip to content

Commit 1a8d4bf

Browse files
authored
Restore waitOnExit, type, hideFromUser, reconnectionOwner terminal properties on reconnect (microsoft#155346)
1 parent c7a138d commit 1a8d4bf

File tree

8 files changed

+124
-64
lines changed

8 files changed

+124
-64
lines changed

src/vs/platform/terminal/common/terminal.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,14 @@ export interface IPtyHostAttachTarget {
169169
environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined;
170170
reconnectionOwner?: string;
171171
task?: { label: string; id: string; lastTask: string; group?: string };
172+
waitOnExit?: WaitOnExitValue;
173+
hideFromUser?: boolean;
174+
isFeatureTerminal?: boolean;
175+
type?: TerminalType;
172176
}
173177

178+
export type TerminalType = 'Task' | 'Local' | undefined;
179+
174180
export enum TitleEventSource {
175181
/** From the API or the rename command that overrides any other type */
176182
Api,
@@ -446,7 +452,7 @@ export interface IShellLaunchConfig {
446452
reconnectionOwner?: string;
447453

448454
/** Whether to wait for a key press before closing the terminal. */
449-
waitOnExit?: boolean | string | ((exitCode: number) => string);
455+
waitOnExit?: WaitOnExitValue;
450456

451457
/**
452458
* A string including ANSI escape sequences that will be written to the terminal emulator
@@ -469,7 +475,9 @@ export interface IShellLaunchConfig {
469475
/**
470476
* This is a terminal that attaches to an already running terminal.
471477
*/
472-
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 } };
478+
attachPersistentProcess?: {
479+
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;
480+
};
473481

474482
/**
475483
* Whether the terminal process environment should be exactly as provided in
@@ -544,9 +552,11 @@ export interface IShellLaunchConfig {
544552
/**
545553
* The task associated with this terminal
546554
*/
547-
task?: { lastTask: string; group?: string; label: string; id: string };
555+
task?: { label: string; id: string; lastTask: string; group?: string };
548556
}
549557

558+
export type WaitOnExitValue = boolean | string | ((exitCode: number) => string);
559+
550560
export interface ICreateContributedTerminalProfileOptions {
551561
icon?: URI | string | { light: URI; dark: URI };
552562
color?: string;

src/vs/platform/terminal/common/terminalProcess.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { UriComponents } from 'vs/base/common/uri';
77
import { ISerializableEnvironmentVariableCollection, ISerializableEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariable';
8-
import { IFixedTerminalDimensions, IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource } from 'vs/platform/terminal/common/terminal';
8+
import { IFixedTerminalDimensions, IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TerminalType, TitleEventSource, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
99

1010
export interface ISingleTerminalConfiguration<T> {
1111
userValue: T | undefined;
@@ -62,6 +62,10 @@ export interface IProcessDetails {
6262
environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined;
6363
reconnectionOwner?: string;
6464
task?: { label: string; id: string; lastTask: string; group?: string };
65+
waitOnExit?: WaitOnExitValue;
66+
hideFromUser?: boolean;
67+
isFeatureTerminal?: boolean;
68+
type?: TerminalType;
6569
}
6670

6771
export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo<IProcessDetails>;

src/vs/platform/terminal/node/ptyService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,11 @@ export class PtyService extends Disposable implements IPtyService {
411411
fixedDimensions: persistentProcess.fixedDimensions,
412412
environmentVariableCollections: persistentProcess.processLaunchOptions.options.environmentVariableCollections,
413413
reconnectionOwner: persistentProcess.shellLaunchConfig.reconnectionOwner,
414-
task: persistentProcess.shellLaunchConfig.task
414+
task: persistentProcess.shellLaunchConfig.task,
415+
waitOnExit: persistentProcess.shellLaunchConfig.waitOnExit,
416+
hideFromUser: persistentProcess.shellLaunchConfig.hideFromUser,
417+
isFeatureTerminal: persistentProcess.shellLaunchConfig.isFeatureTerminal,
418+
type: persistentProcess.shellLaunchConfig.type
415419
};
416420
}
417421

src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ import { URI } from 'vs/base/common/uri';
3030
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
3131
import { ILogService } from 'vs/platform/log/common/log';
3232
import { INotificationService } from 'vs/platform/notification/common/notification';
33-
import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
33+
import { IShellLaunchConfig, TerminalLocation, TerminalSettingId, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
3434
import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
3535
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
3636
import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
3737
import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus';
3838
import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors';
3939
import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration';
40-
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';
40+
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';
4141
import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
4242
import { IResolvedVariables, IResolveSet, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from 'vs/workbench/contrib/tasks/common/taskSystem';
4343
import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
@@ -284,6 +284,11 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
284284
}
285285
if (this._tasksToReconnect.includes(task._id)) {
286286
this._terminalForTask = terminals.find(t => t.shellLaunchConfig.attachPersistentProcess?.task?.id === task._id);
287+
// Restore the waitOnExit value of the terminal because it may have been a function
288+
// that cannot be persisted in the pty host
289+
if ('command' in task && task.command.presentation && this._terminalForTask) {
290+
this._terminalForTask.waitOnExit = getWaitOnExitValue(task.command.presentation, task.configurationProperties);
291+
}
287292
this.run(task, resolver, trigger);
288293
}
289294
return undefined;
@@ -541,35 +546,38 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
541546
alreadyResolved = alreadyResolved ?? new Map<string, string>();
542547
const promises: Promise<ITaskSummary>[] = [];
543548
if (task.configurationProperties.dependsOn) {
544-
for (const dependency of task.configurationProperties.dependsOn) {
545-
const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
546-
if (dependencyTask) {
547-
this._adoptConfigurationForDependencyTask(dependencyTask, task);
548-
const key = dependencyTask.getMapKey();
549-
let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined;
550-
if (!promise) {
551-
this._fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
552-
encounteredDependencies.add(task.getCommonTaskId());
553-
promise = this._executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved);
554-
}
555-
promises.push(promise);
556-
if (task.configurationProperties.dependsOrder === DependsOrder.sequence) {
557-
const promiseResult = await promise;
558-
if (promiseResult.exitCode === 0) {
559-
promise = Promise.resolve(promiseResult);
560-
} else {
561-
promise = Promise.reject(promiseResult);
562-
break;
549+
if (!this._terminalForTask) {
550+
// we already handle dependent tasks when reconnecting, don't create extras
551+
for (const dependency of task.configurationProperties.dependsOn) {
552+
const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
553+
if (dependencyTask) {
554+
this._adoptConfigurationForDependencyTask(dependencyTask, task);
555+
const key = dependencyTask.getMapKey();
556+
let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined;
557+
if (!promise) {
558+
this._fireTaskEvent(TaskEvent.create(TaskEventKind.DependsOnStarted, task));
559+
encounteredDependencies.add(task.getCommonTaskId());
560+
promise = this._executeDependencyTask(dependencyTask, resolver, trigger, encounteredDependencies, alreadyResolved);
561+
}
562+
promises.push(promise);
563+
if (task.configurationProperties.dependsOrder === DependsOrder.sequence) {
564+
const promiseResult = await promise;
565+
if (promiseResult.exitCode === 0) {
566+
promise = Promise.resolve(promiseResult);
567+
} else {
568+
promise = Promise.reject(promiseResult);
569+
break;
570+
}
563571
}
572+
promises.push(promise);
573+
} else {
574+
this._log(nls.localize('dependencyFailed',
575+
'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'',
576+
Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0),
577+
dependency.uri.toString()
578+
));
579+
this._showOutput();
564580
}
565-
promises.push(promise);
566-
} else {
567-
this._log(nls.localize('dependencyFailed',
568-
'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'',
569-
Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0),
570-
dependency.uri.toString()
571-
));
572-
this._showOutput();
573581
}
574582
}
575583
}
@@ -1065,7 +1073,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
10651073
return needsFolderQualification ? task.getQualifiedLabel() : (task.configurationProperties.name || '');
10661074
}
10671075

1068-
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> {
1076+
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> {
10691077
let shellLaunchConfig: IShellLaunchConfig;
10701078
const isShellCommand = task.command.runtime === RuntimeType.Shell;
10711079
const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
@@ -1338,24 +1346,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
13381346
const options = await this._resolveOptions(resolver, task.command.options);
13391347
const presentationOptions = task.command.presentation;
13401348

1341-
let waitOnExit: boolean | string | ((exitCode: number) => string) = false;
13421349
if (!presentationOptions) {
13431350
throw new Error('Task presentation options should not be undefined here.');
13441351
}
1345-
1346-
if ((presentationOptions.close === undefined) || (presentationOptions.close === false)) {
1347-
if ((presentationOptions.reveal !== RevealKind.Never) || !task.configurationProperties.isBackground || (presentationOptions.close === false)) {
1348-
if (presentationOptions.panel === PanelKind.New) {
1349-
waitOnExit = taskShellIntegrationWaitOnExitSequence(nls.localize('closeTerminal', 'Press any key to close the terminal.'));
1350-
} else if (presentationOptions.showReuseMessage) {
1351-
waitOnExit = taskShellIntegrationWaitOnExitSequence(nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.'));
1352-
} else {
1353-
waitOnExit = true;
1354-
}
1355-
}
1356-
} else {
1357-
waitOnExit = !presentationOptions.close;
1358-
}
1352+
const waitOnExit = getWaitOnExitValue(presentationOptions, task.configurationProperties);
13591353

13601354
let command: CommandString | undefined;
13611355
let args: CommandString[] | undefined;
@@ -1384,7 +1378,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
13841378
return [undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)];
13851379
}
13861380
}
1387-
13881381
const prefersSameTerminal = presentationOptions.panel === PanelKind.Dedicated;
13891382
const allowsSharedTerminal = presentationOptions.panel === PanelKind.Shared;
13901383
const group = presentationOptions.group;
@@ -1787,6 +1780,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
17871780
}
17881781
}
17891782

1783+
function getWaitOnExitValue(presentationOptions: IPresentationOptions, configurationProperties: IConfigurationProperties) {
1784+
if ((presentationOptions.close === undefined) || (presentationOptions.close === false)) {
1785+
if ((presentationOptions.reveal !== RevealKind.Never) || !configurationProperties.isBackground || (presentationOptions.close === false)) {
1786+
if (presentationOptions.panel === PanelKind.New) {
1787+
return taskShellIntegrationWaitOnExitSequence(nls.localize('closeTerminal', 'Press any key to close the terminal.'));
1788+
} else if (presentationOptions.showReuseMessage) {
1789+
return taskShellIntegrationWaitOnExitSequence(nls.localize('reuseTerminal', 'Terminal will be reused by tasks, press any key to close it.'));
1790+
} else {
1791+
return true;
1792+
}
1793+
}
1794+
}
1795+
return !presentationOptions.close;
1796+
}
1797+
17901798
function taskShellIntegrationWaitOnExitSequence(message: string): (exitCode: number) => string {
17911799
return (exitCode) => {
17921800
return `${VSCodeSequence(VSCodeOscPt.CommandFinished, exitCode.toString())}${message}`;

src/vs/workbench/contrib/terminal/browser/terminal.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
1111
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1212
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
1313
import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
14-
import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal';
14+
import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TerminalType, TitleEventSource, WaitOnExitValue } from 'vs/platform/terminal/common/terminal';
1515
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
1616
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
1717
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -257,6 +257,11 @@ interface ITerminalEditorInputObject {
257257
readonly icon: TerminalIcon | undefined;
258258
readonly color: string | undefined;
259259
readonly hasChildProcesses?: boolean;
260+
readonly task?: { label: string; id: string; lastTask: string; group?: string; waitOnExit?: WaitOnExitValue };
261+
readonly type?: TerminalType;
262+
readonly isFeatureTerminal?: boolean;
263+
readonly hideFromUser?: boolean;
264+
readonly reconnectionOwner?: string;
260265
}
261266

262267
export interface ISerializedTerminalEditorInput extends ITerminalEditorInputObject {
@@ -513,6 +518,12 @@ export interface ITerminalInstance {
513518
*/
514519
readonly hasFocus: boolean;
515520

521+
/**
522+
* Get or set the behavior of the terminal when it closes. This was indented only to be called
523+
* after reconnecting to a terminal.
524+
*/
525+
waitOnExit: WaitOnExitValue | undefined;
526+
516527
/**
517528
* An event that fires when the terminal instance's title changes.
518529
*/

src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ export class TerminalInputSerializer implements IEditorSerializer {
4343
icon: instance.icon,
4444
color: instance.color,
4545
resource: instance.resource.toString(),
46-
hasChildProcesses: instance.hasChildProcesses
46+
hasChildProcesses: instance.hasChildProcesses,
47+
task: instance.shellLaunchConfig.task,
48+
type: instance.shellLaunchConfig.type,
49+
isFeatureTerminal: instance.shellLaunchConfig.isFeatureTerminal,
50+
hideFromUser: instance.shellLaunchConfig.hideFromUser,
4751
};
4852
}
4953
}

0 commit comments

Comments
 (0)