Skip to content

Commit 788b95b

Browse files
author
Benjamin Pasero
committed
web - enable the remote status indicator
1 parent 5d9108d commit 788b95b

4 files changed

Lines changed: 234 additions & 209 deletions

File tree

src/vs/workbench/contrib/remote/browser/remoteViewlet.css renamed to src/vs/workbench/contrib/remote/browser/media/remoteViewlet.css

File renamed without changes.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import 'vs/css!./remoteViewlet';
6+
import 'vs/css!./media/remoteViewlet';
77
import * as nls from 'vs/nls';
88
import * as dom from 'vs/base/browser/dom';
99
import { URI } from 'vs/base/common/uri';
@@ -55,6 +55,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
5555
import { Event } from 'vs/base/common/event';
5656
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
5757
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
58+
import { RemoteWindowActiveIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator';
5859

5960
export interface HelpInformation {
6061
extensionDescription: IExtensionDescription;
@@ -803,3 +804,5 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
803804

804805
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
805806
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
807+
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
808+
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as nls from 'vs/nls';
7+
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
8+
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
9+
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
10+
import { Disposable } from 'vs/base/common/lifecycle';
11+
import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
12+
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
13+
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
14+
import { ILabelService } from 'vs/platform/label/common/label';
15+
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
16+
import { ICommandService } from 'vs/platform/commands/common/commands';
17+
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
18+
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
19+
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
20+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
21+
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
22+
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
23+
import { IHostService } from 'vs/workbench/services/host/browser/host';
24+
import { RemoteConnectionState, Deprecated_RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys';
25+
import { isWeb } from 'vs/base/common/platform';
26+
import { once } from 'vs/base/common/functional';
27+
28+
const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu';
29+
const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close';
30+
const SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command
31+
32+
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
33+
34+
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
35+
private windowCommandMenu: IMenu;
36+
private hasWindowActions: boolean = false;
37+
private remoteAuthority: string | undefined;
38+
private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined;
39+
40+
constructor(
41+
@IStatusbarService private readonly statusbarService: IStatusbarService,
42+
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
43+
@ILabelService private readonly labelService: ILabelService,
44+
@IContextKeyService private contextKeyService: IContextKeyService,
45+
@IMenuService private menuService: IMenuService,
46+
@IQuickInputService private readonly quickInputService: IQuickInputService,
47+
@ICommandService private readonly commandService: ICommandService,
48+
@IExtensionService extensionService: IExtensionService,
49+
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
50+
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
51+
@IHostService hostService: IHostService
52+
) {
53+
super();
54+
55+
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
56+
this._register(this.windowCommandMenu);
57+
58+
const category = nls.localize('remote.category', "Remote");
59+
const that = this;
60+
registerAction2(class extends Action2 {
61+
constructor() {
62+
super({
63+
id: WINDOW_ACTIONS_COMMAND_ID,
64+
category,
65+
title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' },
66+
f1: true,
67+
});
68+
}
69+
run = () => that.showIndicatorActions(that.windowCommandMenu);
70+
});
71+
72+
this.remoteAuthority = environmentService.configuration.remoteAuthority;
73+
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || '');
74+
75+
if (this.remoteAuthority) {
76+
77+
if (SHOW_CLOSE_REMOTE_COMMAND_ID) {
78+
registerAction2(class extends Action2 {
79+
constructor() {
80+
super({
81+
id: CLOSE_REMOTE_COMMAND_ID,
82+
category,
83+
title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' },
84+
f1: true
85+
});
86+
}
87+
run = () => that.remoteAuthority && hostService.openWindow({ forceReuseWindow: true });
88+
});
89+
90+
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
91+
group: '6_close',
92+
command: {
93+
id: CLOSE_REMOTE_COMMAND_ID,
94+
title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection")
95+
},
96+
order: 3.5
97+
});
98+
}
99+
100+
// Pending entry until extensions are ready
101+
this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID);
102+
this.connectionState = 'initializing';
103+
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
104+
105+
const connection = remoteAgentService.getConnection();
106+
if (connection) {
107+
this._register(connection.onDidStateChange((e) => {
108+
switch (e.type) {
109+
case PersistentConnectionEventType.ConnectionLost:
110+
case PersistentConnectionEventType.ReconnectionPermanentFailure:
111+
case PersistentConnectionEventType.ReconnectionRunning:
112+
case PersistentConnectionEventType.ReconnectionWait:
113+
this.setDisconnected(true);
114+
break;
115+
case PersistentConnectionEventType.ConnectionGain:
116+
this.setDisconnected(false);
117+
break;
118+
}
119+
}));
120+
}
121+
}
122+
123+
extensionService.whenInstalledExtensionsRegistered().then(_ => {
124+
if (this.remoteAuthority) {
125+
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
126+
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
127+
}
128+
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
129+
this.updateWindowIndicator();
130+
});
131+
}
132+
133+
private setDisconnected(isDisconnected: boolean): void {
134+
const newState = isDisconnected ? 'disconnected' : 'connected';
135+
if (this.connectionState !== newState) {
136+
this.connectionState = newState;
137+
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
138+
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? `disconnected/${this.remoteAuthority!}` : this.remoteAuthority!);
139+
this.updateWindowIndicator();
140+
}
141+
}
142+
143+
private updateWindowIndicator(): void {
144+
const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined;
145+
if (this.remoteAuthority) {
146+
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
147+
if (this.connectionState !== 'disconnected') {
148+
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
149+
} else {
150+
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand);
151+
}
152+
} else {
153+
if (windowActionCommand) {
154+
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
155+
} else if (this.windowIndicatorEntry) {
156+
this.windowIndicatorEntry.dispose();
157+
this.windowIndicatorEntry = undefined;
158+
}
159+
}
160+
}
161+
162+
private updateWindowActions() {
163+
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
164+
if (newHasWindowActions !== this.hasWindowActions) {
165+
this.hasWindowActions = newHasWindowActions;
166+
this.updateWindowIndicator();
167+
}
168+
}
169+
170+
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
171+
const properties: IStatusbarEntry = {
172+
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
173+
};
174+
if (this.windowIndicatorEntry) {
175+
this.windowIndicatorEntry.update(properties);
176+
} else {
177+
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
178+
}
179+
}
180+
181+
private showIndicatorActions(menu: IMenu) {
182+
183+
const actions = menu.getActions();
184+
185+
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
186+
for (let actionGroup of actions) {
187+
if (items.length) {
188+
items.push({ type: 'separator' });
189+
}
190+
for (let action of actionGroup[1]) {
191+
if (action instanceof MenuItemAction) {
192+
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
193+
if (action.item.category) {
194+
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
195+
label = nls.localize('cat.title', "{0}: {1}", category, label);
196+
}
197+
items.push({
198+
type: 'item',
199+
id: action.item.id,
200+
label
201+
});
202+
}
203+
}
204+
}
205+
206+
if (SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) {
207+
if (items.length) {
208+
items.push({ type: 'separator' });
209+
}
210+
items.push({
211+
type: 'item',
212+
id: CLOSE_REMOTE_COMMAND_ID,
213+
label: nls.localize('closeRemote.title', 'Close Remote Connection')
214+
});
215+
}
216+
217+
const quickPick = this.quickInputService.createQuickPick();
218+
quickPick.items = items;
219+
quickPick.canSelectMany = false;
220+
once(quickPick.onDidAccept)((_ => {
221+
const selectedItems = quickPick.selectedItems;
222+
if (selectedItems.length === 1) {
223+
this.commandService.executeCommand(selectedItems[0].id!);
224+
}
225+
quickPick.hide();
226+
}));
227+
quickPick.show();
228+
}
229+
}

0 commit comments

Comments
 (0)