Skip to content

Commit c46768c

Browse files
authored
Merge pull request microsoft#185255 from microsoft/tyriar/ptyhost_status_bar
Change the pty host responsiveness notification to a status bar item
2 parents 6924627 + dc56d19 commit c46768c

File tree

8 files changed

+90
-33
lines changed

8 files changed

+90
-33
lines changed

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

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@ import { Schemas } from 'vs/base/common/network';
99
import { withNullAsUndefined } from 'vs/base/common/types';
1010
import { localize } from 'vs/nls';
1111
import { ILogService } from 'vs/platform/log/common/log';
12-
import { INotificationHandle, INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
1312
import { ICrossVersionSerializedTerminalState, IPtyHostController, ISerializedTerminalState } from 'vs/platform/terminal/common/terminal';
13+
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
1414
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
15+
import { STATUS_BAR_WARNING_ITEM_BACKGROUND, STATUS_BAR_WARNING_ITEM_FOREGROUND } from 'vs/workbench/common/theme';
16+
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
1517
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
1618
import { IHistoryService } from 'vs/workbench/services/history/common/history';
19+
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
1720

1821
export abstract class BaseTerminalBackend extends Disposable {
1922
private _isPtyHostUnresponsive: boolean = false;
2023

24+
get isResponsive(): boolean { return !this._isPtyHostUnresponsive; }
25+
2126
protected readonly _onPtyHostRestart = this._register(new Emitter<void>());
2227
readonly onPtyHostRestart = this._onPtyHostRestart.event;
2328
protected readonly _onPtyHostUnresponsive = this._register(new Emitter<void>());
@@ -26,55 +31,63 @@ export abstract class BaseTerminalBackend extends Disposable {
2631
readonly onPtyHostResponsive = this._onPtyHostResponsive.event;
2732

2833
constructor(
29-
eventSource: IPtyHostController,
34+
private readonly _ptyHostController: IPtyHostController,
3035
protected readonly _logService: ILogService,
31-
notificationService: INotificationService,
3236
historyService: IHistoryService,
3337
configurationResolverService: IConfigurationResolverService,
38+
statusBarService: IStatusbarService,
3439
protected readonly _workspaceContextService: IWorkspaceContextService
3540
) {
3641
super();
3742

43+
let unresponsiveStatusBarEntry: IStatusbarEntry;
44+
let statusBarAccessor: IStatusbarEntryAccessor;
45+
3846
// Attach pty host listeners
39-
if (eventSource.onPtyHostExit) {
40-
this._register(eventSource.onPtyHostExit(() => {
47+
if (this._ptyHostController.onPtyHostExit) {
48+
this._register(this._ptyHostController.onPtyHostExit(() => {
4149
this._logService.error(`The terminal's pty host process exited, the connection to all terminal processes was lost`);
4250
}));
4351
}
44-
let unresponsiveNotification: INotificationHandle | undefined;
45-
if (eventSource.onPtyHostStart) {
46-
this._register(eventSource.onPtyHostStart(() => {
52+
if (this._ptyHostController.onPtyHostStart) {
53+
this._register(this._ptyHostController.onPtyHostStart(() => {
4754
this._onPtyHostRestart.fire();
48-
unresponsiveNotification?.close();
49-
unresponsiveNotification = undefined;
55+
statusBarAccessor?.dispose();
5056
this._isPtyHostUnresponsive = false;
5157
}));
5258
}
53-
if (eventSource.onPtyHostUnresponsive) {
54-
this._register(eventSource.onPtyHostUnresponsive(() => {
55-
const choices: IPromptChoice[] = [{
56-
label: localize('restartPtyHost', "Restart pty host"),
57-
run: () => eventSource.restartPtyHost!()
58-
}];
59-
unresponsiveNotification = notificationService.prompt(Severity.Error, localize('nonResponsivePtyHost', "The connection to the terminal's pty host process is unresponsive, the terminals may stop working."), choices);
59+
if (this._ptyHostController.onPtyHostUnresponsive) {
60+
this._register(this._ptyHostController.onPtyHostUnresponsive(() => {
61+
statusBarAccessor?.dispose();
62+
if (!unresponsiveStatusBarEntry) {
63+
unresponsiveStatusBarEntry = {
64+
name: localize('ptyHostStatus', 'Pty Host Status'),
65+
text: `$(debug-disconnect) ${localize('ptyHostStatus.short', 'Pty Host')}`,
66+
tooltip: localize('nonResponsivePtyHost', "The connection to the terminal's pty host process is unresponsive, terminals may stop working. Click to manually restart the pty host."),
67+
ariaLabel: localize('ptyHostStatus.ariaLabel', 'Pty Host is unresponsive'),
68+
command: TerminalCommandId.RestartPtyHost,
69+
backgroundColor: themeColorFromId(STATUS_BAR_WARNING_ITEM_BACKGROUND),
70+
color: themeColorFromId(STATUS_BAR_WARNING_ITEM_FOREGROUND),
71+
};
72+
}
73+
statusBarAccessor = statusBarService.addEntry(unresponsiveStatusBarEntry, 'ptyHostStatus', StatusbarAlignment.LEFT);
6074
this._isPtyHostUnresponsive = true;
6175
this._onPtyHostUnresponsive.fire();
6276
}));
6377
}
64-
if (eventSource.onPtyHostResponsive) {
65-
this._register(eventSource.onPtyHostResponsive(() => {
78+
if (this._ptyHostController.onPtyHostResponsive) {
79+
this._register(this._ptyHostController.onPtyHostResponsive(() => {
6680
if (!this._isPtyHostUnresponsive) {
6781
return;
6882
}
6983
this._logService.info('The pty host became responsive again');
70-
unresponsiveNotification?.close();
71-
unresponsiveNotification = undefined;
84+
statusBarAccessor?.dispose();
7285
this._isPtyHostUnresponsive = false;
7386
this._onPtyHostResponsive.fire();
7487
}));
7588
}
76-
if (eventSource.onPtyHostRequestResolveVariables) {
77-
this._register(eventSource.onPtyHostRequestResolveVariables(async e => {
89+
if (this._ptyHostController.onPtyHostRequestResolveVariables) {
90+
this._register(this._ptyHostController.onPtyHostRequestResolveVariables(async e => {
7891
// Only answer requests for this workspace
7992
if (e.workspaceId !== this._workspaceContextService.getWorkspace().id) {
8093
return;
@@ -85,11 +98,15 @@ export abstract class BaseTerminalBackend extends Disposable {
8598
return configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, t);
8699
});
87100
const result = await Promise.all(resolveCalls);
88-
eventSource.acceptPtyHostResolvedVariables?.(e.requestId, result);
101+
this._ptyHostController.acceptPtyHostResolvedVariables?.(e.requestId, result);
89102
}));
90103
}
91104
}
92105

106+
restartPtyHost(): void {
107+
this._ptyHostController.restartPtyHost?.();
108+
}
109+
93110
protected _deserializeTerminalState(serializedState: string | undefined): ISerializedTerminalState[] | undefined {
94111
if (serializedState === undefined) {
95112
return undefined;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
1010
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1111
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1212
import { ILogService } from 'vs/platform/log/common/log';
13-
import { INotificationService } from 'vs/platform/notification/common/notification';
1413
import { Registry } from 'vs/platform/registry/common/platform';
1514
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
1615
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@@ -28,6 +27,7 @@ import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/termin
2827
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
2928
import { IHistoryService } from 'vs/workbench/services/history/common/history';
3029
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
30+
import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar';
3131

3232
export class RemoteTerminalBackendContribution implements IWorkbenchContribution {
3333
constructor(
@@ -60,14 +60,14 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack
6060
@ILogService logService: ILogService,
6161
@ICommandService private readonly _commandService: ICommandService,
6262
@IStorageService private readonly _storageService: IStorageService,
63-
@INotificationService notificationService: INotificationService,
6463
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
6564
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
6665
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
6766
@IHistoryService private readonly _historyService: IHistoryService,
6867
@IConfigurationService private readonly _configurationService: IConfigurationService,
68+
@IStatusbarService statusBarService: IStatusbarService,
6969
) {
70-
super(_remoteTerminalChannel, logService, notificationService, _historyService, configurationResolverService, workspaceContextService);
70+
super(_remoteTerminalChannel, logService, _historyService, configurationResolverService, statusBarService, workspaceContextService);
7171

7272
this._remoteTerminalChannel.onProcessData(e => this._ptys.get(e.id)?.handleData(e.event));
7373
this._remoteTerminalChannel.onProcessReplay(e => {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export interface ITerminalInstanceService {
7777
*/
7878
getBackend(remoteAuthority?: string): Promise<ITerminalBackend | undefined>;
7979

80+
getRegisteredBackends(): IterableIterator<ITerminalBackend>;
8081
didRegisterBackend(remoteAuthority?: string): void;
8182
}
8283

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
104104
return backend;
105105
}
106106

107+
getRegisteredBackends(): IterableIterator<ITerminalBackend> {
108+
return Registry.as<ITerminalBackendRegistry>(TerminalExtensions.Backend).backends.values();
109+
}
110+
107111
didRegisterBackend(remoteAuthority?: string) {
108112
this._backendRegistration.get(remoteAuthority)?.resolve();
109113
}

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export interface IShellLaunchConfigResolveOptions {
100100
export interface ITerminalBackend {
101101
readonly remoteAuthority: string | undefined;
102102

103+
readonly isResponsive: boolean;
104+
103105
/**
104106
* Fired when the ptyHost process becomes non-responsive, this should disable stdin for all
105107
* terminals using this pty host connection and mark them as disconnected.
@@ -145,13 +147,20 @@ export interface ITerminalBackend {
145147
options: ITerminalProcessOptions,
146148
shouldPersist: boolean
147149
): Promise<ITerminalChildProcess>;
150+
151+
restartPtyHost(): void;
148152
}
149153

150154
export const TerminalExtensions = {
151155
Backend: 'workbench.contributions.terminal.processBackend'
152156
};
153157

154158
export interface ITerminalBackendRegistry {
159+
/**
160+
* Gets all backends in the registry.
161+
*/
162+
backends: ReadonlyMap<string, ITerminalBackend>;
163+
155164
/**
156165
* Registers a terminal backend for a remote authority.
157166
*/
@@ -166,6 +175,8 @@ export interface ITerminalBackendRegistry {
166175
class TerminalBackendRegistry implements ITerminalBackendRegistry {
167176
private readonly _backends = new Map<string, ITerminalBackend>();
168177

178+
get backends(): ReadonlyMap<string, ITerminalBackend> { return this._backends; }
179+
169180
registerTerminalBackend(backend: ITerminalBackend): void {
170181
const key = this._sanitizeRemoteAuthority(backend.remoteAuthority);
171182
if (this._backends.has(key)) {
@@ -578,8 +589,6 @@ export const enum TerminalCommandId {
578589
MoveToTerminalPanel = 'workbench.action.terminal.moveToTerminalPanel',
579590
SetDimensions = 'workbench.action.terminal.setDimensions',
580591
ClearPreviousSessionHistory = 'workbench.action.terminal.clearPreviousSessionHistory',
581-
WriteDataToTerminal = 'workbench.action.terminal.writeDataToTerminal',
582-
ShowTextureAtlas = 'workbench.action.terminal.showTextureAtlas',
583592
ShowTerminalAccessibilityHelp = 'workbench.action.terminal.showAccessibilityHelp',
584593
SelectPrevSuggestion = 'workbench.action.terminal.selectPrevSuggestion',
585594
SelectPrevPageSuggestion = 'workbench.action.terminal.selectPrevPageSuggestion',
@@ -589,6 +598,12 @@ export const enum TerminalCommandId {
589598
HideSuggestWidget = 'workbench.action.terminal.hideSuggestWidget',
590599
FocusHover = 'workbench.action.terminal.focusHover',
591600
ShowEnvironmentContributions = 'workbench.action.terminal.showEnvironmentContributions',
601+
602+
// Developer commands
603+
604+
WriteDataToTerminal = 'workbench.action.terminal.writeDataToTerminal',
605+
ShowTextureAtlas = 'workbench.action.terminal.showTextureAtlas',
606+
RestartPtyHost = 'workbench.action.terminal.restartPtyHost',
592607
}
593608

594609
export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [

src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
1111
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1212
import { ILabelService } from 'vs/platform/label/common/label';
1313
import { ILogService } from 'vs/platform/log/common/log';
14-
import { INotificationService } from 'vs/platform/notification/common/notification';
1514
import { Registry } from 'vs/platform/registry/common/platform';
1615
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
1716
import { ILocalPtyService, IProcessPropertyMap, IPtyService, IShellLaunchConfig, ITerminalChildProcess, ITerminalEnvironment, ITerminalProcessOptions, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, ProcessPropertyType, TerminalIpcChannels, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
@@ -36,6 +35,7 @@ import { getDelayedChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
3635
import { mark } from 'vs/base/common/performance';
3736
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
3837
import { DeferredPromise } from 'vs/base/common/async';
38+
import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar';
3939

4040
export class LocalTerminalBackendContribution implements IWorkbenchContribution {
4141
constructor(
@@ -74,11 +74,11 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
7474
@IHistoryService private readonly _historyService: IHistoryService,
7575
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
7676
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
77-
@INotificationService notificationService: INotificationService,
7877
@IHistoryService historyService: IHistoryService,
79-
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService
78+
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
79+
@IStatusbarService statusBarService: IStatusbarService,
8080
) {
81-
super(_localPtyService, logService, notificationService, historyService, _configurationResolverService, workspaceContextService);
81+
super(_localPtyService, logService, historyService, _configurationResolverService, statusBarService, workspaceContextService);
8282

8383
this._proxy = ProxyChannel.toService<IPtyService>(getDelayedChannel(this._clientEventually.p.then(client => client.getChannel(TerminalIpcChannels.PtyHostWindow))));
8484

src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { localize } from 'vs/nls';
99
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
1010
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
1111
import { IFileService } from 'vs/platform/files/common/files';
12+
import { ILogService } from 'vs/platform/log/common/log';
1213
import { IOpenerService } from 'vs/platform/opener/common/opener';
1314
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
1415
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -83,3 +84,21 @@ registerTerminalAction({
8384
xterm._writeText(escapedData);
8485
}
8586
});
87+
88+
89+
registerTerminalAction({
90+
id: TerminalCommandId.RestartPtyHost,
91+
title: { value: localize('workbench.action.terminal.restartPtyHost', "Restart Pty Host"), original: 'Restart Pty Host' },
92+
category: Categories.Developer,
93+
run: async (c, accessor) => {
94+
const logService = accessor.get(ILogService);
95+
const backends = Array.from(c.instanceService.getRegisteredBackends());
96+
const unresponsiveBackends = backends.filter(e => !e.isResponsive);
97+
// Restart only unresponsive backends if there are any
98+
const restartCandidates = unresponsiveBackends.length > 0 ? unresponsiveBackends : backends;
99+
for (const backend of restartCandidates) {
100+
logService.warn(`Restarting pty host for authority "${backend.remoteAuthority}"`);
101+
backend.restartPtyHost();
102+
}
103+
}
104+
});

src/vs/workbench/test/browser/workbenchTestServices.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,7 @@ export class TestTerminalInstanceService implements ITerminalInstanceService {
17951795
createInstance(options: ICreateTerminalOptions, target: TerminalLocation): ITerminalInstance { throw new Error('Method not implemented.'); }
17961796
async getBackend(remoteAuthority?: string): Promise<ITerminalBackend | undefined> { throw new Error('Method not implemented.'); }
17971797
didRegisterBackend(remoteAuthority?: string): void { throw new Error('Method not implemented.'); }
1798+
getRegisteredBackends(): IterableIterator<ITerminalBackend> { throw new Error('Method not implemented.'); }
17981799
}
17991800

18001801
export class TestTerminalEditorService implements ITerminalEditorService {

0 commit comments

Comments
 (0)