remote.ts 29.9 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import 'vs/css!./media/remoteViewlet';
7
import * as nls from 'vs/nls';
8
import * as dom from 'vs/base/browser/dom';
9 10 11 12 13 14 15
import { URI } from 'vs/base/common/uri';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
16
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
17 18
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
S
SteVen Batten 已提交
19
import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet';
20
import { AutomaticPortForwarding, ForwardedPortsView, VIEWLET_ID } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
A
Alex Ross 已提交
21
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
22
import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views';
23 24 25
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
26
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
27
import { ICommandService } from 'vs/platform/commands/common/commands';
S
Sandeep Somavarapu 已提交
28
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
29 30
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
31
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
A
Alex Ross 已提交
32
import { registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
A
Alex Dima 已提交
33 34 35 36 37 38 39
import { IProgress, IProgressStep, IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ReconnectionWaitEvent, PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import Severity from 'vs/base/common/severity';
import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions';
40
import { IDisposable } from 'vs/base/common/lifecycle';
41
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
42
import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems';
A
Alex Ross 已提交
43
import { Action, IActionViewItem } from 'vs/base/common/actions';
44
import { isStringArray } from 'vs/base/common/types';
45
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
46
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
47
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
48 49
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
50
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
51 52 53
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Event } from 'vs/base/common/event';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
S
Sandeep Somavarapu 已提交
54
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
55
import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator';
56
import { inQuickPickContextKeyValue } from 'vs/workbench/browser/quickaccess';
M
Martin Aeschlimann 已提交
57 58
import * as icons from 'vs/workbench/contrib/remote/browser/remoteIcons';

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

export interface HelpInformation {
	extensionDescription: IExtensionDescription;
	getStarted?: string;
	documentation?: string;
	feedback?: string;
	issues?: string;
	remoteName?: string[] | string;
}

const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint<HelpInformation>({
	extensionPoint: 'remoteHelp',
	jsonSchema: {
		description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'),
		type: 'object',
		properties: {
			'getStarted': {
76
				description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url, or a command that returns the url, to your project's Getting Started page"),
77 78 79
				type: 'string'
			},
			'documentation': {
80
				description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url, or a command that returns the url, to your project's documentation page"),
81 82 83
				type: 'string'
			},
			'feedback': {
84
				description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url, or a command that returns the url, to your project's feedback reporter"),
85 86 87
				type: 'string'
			},
			'issues': {
88
				description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url, or a command that returns the url, to your project's issues list"),
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
				type: 'string'
			}
		}
	}
});

interface IViewModel {
	helpInformation: HelpInformation[];
}

class HelpTreeVirtualDelegate implements IListVirtualDelegate<IHelpItem> {
	getHeight(element: IHelpItem): number {
		return 22;
	}

	getTemplateId(element: IHelpItem): string {
		return 'HelpItemTemplate';
	}
}

interface IHelpItemTemplateData {
	parent: HTMLElement;
	icon: HTMLElement;
}

class HelpTreeRenderer implements ITreeRenderer<HelpModel | IHelpItem, IHelpItem, IHelpItemTemplateData> {
	templateId: string = 'HelpItemTemplate';

	renderTemplate(container: HTMLElement): IHelpItemTemplateData {
118
		container.classList.add('remote-help-tree-node-item');
119 120 121 122 123 124 125 126 127 128
		const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon'));
		const data = <IHelpItemTemplateData>Object.create(null);
		data.parent = container;
		data.icon = icon;
		return data;
	}

	renderElement(element: ITreeNode<IHelpItem, IHelpItem>, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void {
		const container = templateData.parent;
		dom.append(container, templateData.icon);
129
		templateData.icon.classList.add(...element.element.iconClasses);
130 131 132 133 134 135 136 137 138
		const labelContainer = dom.append(container, dom.$('.help-item-label'));
		labelContainer.innerText = element.element.label;
	}

	disposeTemplate(templateData: IHelpItemTemplateData): void {

	}
}

139 140
class HelpDataSource implements IAsyncDataSource<HelpModel, IHelpItem> {
	hasChildren(element: HelpModel) {
141 142 143
		return element instanceof HelpModel;
	}

144
	getChildren(element: HelpModel) {
145 146 147 148 149 150 151 152
		if (element instanceof HelpModel && element.items) {
			return element.items;
		}

		return [];
	}
}
interface IHelpItem {
153
	icon: ThemeIcon,
154 155 156 157
	iconClasses: string[];
	label: string;
	handleClick(): Promise<void>;
}
158 159

class HelpModel {
B
Benjamin Pasero 已提交
160
	items: IHelpItem[] | undefined;
161 162

	constructor(
163
		viewModel: IViewModel,
164 165
		openerService: IOpenerService,
		quickInputService: IQuickInputService,
166 167
		commandService: ICommandService,
		remoteExplorerService: IRemoteExplorerService,
168
		environmentService: IWorkbenchEnvironmentService
169 170
	) {
		let helpItems: IHelpItem[] = [];
171
		const getStarted = viewModel.helpInformation.filter(info => info.getStarted);
172 173 174

		if (getStarted.length) {
			helpItems.push(new HelpItem(
M
Martin Aeschlimann 已提交
175
				icons.getStartedIcon,
176
				nls.localize('remote.help.getStarted', "Get Started"),
177 178 179 180 181
				getStarted.map((info: HelpInformation) => (new HelpItemValue(commandService,
					info.extensionDescription,
					(typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName,
					info.getStarted!)
				)),
182 183
				quickInputService,
				environmentService,
184 185
				openerService,
				remoteExplorerService
186 187 188
			));
		}

189
		const documentation = viewModel.helpInformation.filter(info => info.documentation);
190 191 192

		if (documentation.length) {
			helpItems.push(new HelpItem(
M
Martin Aeschlimann 已提交
193
				icons.documentationIcon,
194
				nls.localize('remote.help.documentation', "Read Documentation"),
195 196 197 198 199
				documentation.map((info: HelpInformation) => (new HelpItemValue(commandService,
					info.extensionDescription,
					(typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName,
					info.documentation!)
				)),
200 201
				quickInputService,
				environmentService,
202 203
				openerService,
				remoteExplorerService
204 205 206
			));
		}

207
		const feedback = viewModel.helpInformation.filter(info => info.feedback);
208 209 210

		if (feedback.length) {
			helpItems.push(new HelpItem(
M
Martin Aeschlimann 已提交
211
				icons.feedbackIcon,
212
				nls.localize('remote.help.feedback', "Provide Feedback"),
213 214 215 216 217
				feedback.map((info: HelpInformation) => (new HelpItemValue(commandService,
					info.extensionDescription,
					(typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName,
					info.feedback!)
				)),
218 219
				quickInputService,
				environmentService,
220 221
				openerService,
				remoteExplorerService
222 223 224
			));
		}

225
		const issues = viewModel.helpInformation.filter(info => info.issues);
226 227 228

		if (issues.length) {
			helpItems.push(new HelpItem(
M
Martin Aeschlimann 已提交
229
				icons.reviewIssuesIcon,
230
				nls.localize('remote.help.issues', "Review Issues"),
231 232 233 234 235
				issues.map((info: HelpInformation) => (new HelpItemValue(commandService,
					info.extensionDescription,
					(typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName,
					info.issues!)
				)),
236 237
				quickInputService,
				environmentService,
238 239
				openerService,
				remoteExplorerService
240 241 242 243 244
			));
		}

		if (helpItems.length) {
			helpItems.push(new IssueReporterItem(
M
Martin Aeschlimann 已提交
245
				icons.reportIssuesIcon,
246
				nls.localize('remote.help.report', "Report Issue"),
247 248 249 250
				viewModel.helpInformation.map(info => (new HelpItemValue(commandService,
					info.extensionDescription,
					(typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName
				))),
251
				quickInputService,
252
				environmentService,
253 254
				commandService,
				remoteExplorerService
255 256 257 258 259 260 261 262 263
			));
		}

		if (helpItems.length) {
			this.items = helpItems;
		}
	}
}

264 265 266 267 268
class HelpItemValue {
	private _url: string | undefined;
	constructor(private commandService: ICommandService, public extensionDescription: IExtensionDescription, public remoteAuthority: string[] | undefined, private urlOrCommand?: string) { }

	get url(): Promise<string> {
269
		return new Promise<string>(async (resolve) => {
270 271 272 273 274 275
			if (this._url === undefined) {
				if (this.urlOrCommand) {
					let url = URI.parse(this.urlOrCommand);
					if (url.authority) {
						this._url = this.urlOrCommand;
					} else {
276 277 278 279
						const urlCommand: Promise<string | undefined> = this.commandService.executeCommand(this.urlOrCommand);
						// We must be defensive. The command may never return, meaning that no help at all is ever shown!
						const emptyString: Promise<string> = new Promise(resolve => setTimeout(() => resolve(''), 500));
						this._url = await Promise.race([urlCommand, emptyString]);
280 281 282
					}
				}
			}
283 284 285
			if (this._url === undefined) {
				this._url = '';
			}
286 287 288 289 290
			resolve(this._url);
		});
	}
}

291
abstract class HelpItemBase implements IHelpItem {
292
	public iconClasses: string[] = [];
293
	constructor(
294
		public icon: ThemeIcon,
295
		public label: string,
296
		public values: HelpItemValue[],
297
		private quickInputService: IQuickInputService,
298 299
		private environmentService: IWorkbenchEnvironmentService,
		private remoteExplorerService: IRemoteExplorerService
300
	) {
301
		this.iconClasses.push(...ThemeIcon.asClassNameArray(icon));
302
		this.iconClasses.push('remote-help-tree-node-item-icon');
303 304
	}

305
	async handleClick() {
306
		const remoteAuthority = this.environmentService.remoteAuthority;
307 308
		if (remoteAuthority) {
			for (let i = 0; i < this.remoteExplorerService.targetType.length; i++) {
309
				if (remoteAuthority.startsWith(this.remoteExplorerService.targetType[i])) {
310 311 312
					for (let value of this.values) {
						if (value.remoteAuthority) {
							for (let authority of value.remoteAuthority) {
313
								if (remoteAuthority.startsWith(authority)) {
314 315 316
									await this.takeAction(value.extensionDescription, await value.url);
									return;
								}
317
							}
318 319 320 321 322 323
						}
					}
				}
			}
		}

324
		if (this.values.length > 1) {
325
			let actions = (await Promise.all(this.values.map(async (value) => {
326 327
				return {
					label: value.extensionDescription.displayName || value.extensionDescription.identifier.value,
328
					description: await value.url,
329
					extensionDescription: value.extensionDescription
330
				};
331
			}))).filter(item => item.description);
332

333
			const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") });
334

335
			if (action) {
336
				await this.takeAction(action.extensionDescription, action.description);
337 338
			}
		} else {
339
			await this.takeAction(this.values[0].extensionDescription, await this.values[0].url);
340 341
		}
	}
342 343

	protected abstract takeAction(extensionDescription: IExtensionDescription, url?: string): Promise<void>;
344
}
345

346
class HelpItem extends HelpItemBase {
347
	constructor(
348
		icon: ThemeIcon,
349
		label: string,
350
		values: HelpItemValue[],
351 352
		quickInputService: IQuickInputService,
		environmentService: IWorkbenchEnvironmentService,
353 354
		private openerService: IOpenerService,
		remoteExplorerService: IRemoteExplorerService
355
	) {
M
Martin Aeschlimann 已提交
356
		super(icon, label, values, quickInputService, environmentService, remoteExplorerService);
357 358
	}

359 360 361 362
	protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise<void> {
		await this.openerService.open(URI.parse(url));
	}
}
363

364 365
class IssueReporterItem extends HelpItemBase {
	constructor(
366
		icon: ThemeIcon,
367
		label: string,
368
		values: HelpItemValue[],
369 370 371
		quickInputService: IQuickInputService,
		environmentService: IWorkbenchEnvironmentService,
		private commandService: ICommandService,
372
		remoteExplorerService: IRemoteExplorerService
373
	) {
M
Martin Aeschlimann 已提交
374
		super(icon, label, values, quickInputService, environmentService, remoteExplorerService);
375
	}
376

377 378
	protected async takeAction(extensionDescription: IExtensionDescription): Promise<void> {
		await this.commandService.executeCommand('workbench.action.openIssueReporter', [extensionDescription.identifier.value]);
379 380 381
	}
}

382 383 384
class HelpPanel extends ViewPane {
	static readonly ID = '~remote.helpPanel';
	static readonly TITLE = nls.localize('remote.help', "Help and feedback");
385
	private tree!: WorkbenchAsyncDataTree<HelpModel, IHelpItem, IHelpItem>;
386

387 388 389 390 391 392 393 394
	constructor(
		protected viewModel: IViewModel,
		options: IViewPaneOptions,
		@IKeybindingService protected keybindingService: IKeybindingService,
		@IContextMenuService protected contextMenuService: IContextMenuService,
		@IContextKeyService protected contextKeyService: IContextKeyService,
		@IConfigurationService protected configurationService: IConfigurationService,
		@IInstantiationService protected readonly instantiationService: IInstantiationService,
395
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
396
		@IOpenerService openerService: IOpenerService,
397 398 399
		@IQuickInputService protected quickInputService: IQuickInputService,
		@ICommandService protected commandService: ICommandService,
		@IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService,
400
		@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
401
		@IThemeService themeService: IThemeService,
402
		@ITelemetryService telemetryService: ITelemetryService,
403
	) {
404
		super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
405
	}
406

407
	protected renderBody(container: HTMLElement): void {
J
Joao Moreno 已提交
408 409
		super.renderBody(container);

410
		container.classList.add('remote-help');
411
		const treeContainer = document.createElement('div');
412
		treeContainer.classList.add('remote-help-content');
413 414
		container.appendChild(treeContainer);

415
		this.tree = <WorkbenchAsyncDataTree<HelpModel, IHelpItem, IHelpItem>>this.instantiationService.createInstance(WorkbenchAsyncDataTree,
416 417 418 419 420 421
			'RemoteHelp',
			treeContainer,
			new HelpTreeVirtualDelegate(),
			[new HelpTreeRenderer()],
			new HelpDataSource(),
			{
422 423 424
				accessibilityProvider: {
					getAriaLabel: (item: HelpItemBase) => {
						return item.label;
I
isidor 已提交
425 426
					},
					getWidgetAriaLabel: () => nls.localize('remotehelp', "Remote Help")
J
João Moreno 已提交
427
				}
428
			}
429 430
		);

431
		const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.environmentService);
432 433 434

		this.tree.setInput(model);

435
		this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, 75, true)(e => {
436
			e.element?.handleClick();
437 438 439 440
		}));
	}

	protected layoutBody(height: number, width: number): void {
J
João Moreno 已提交
441
		super.layoutBody(height, width);
442 443 444 445 446 447 448
		this.tree.layout(height, width);
	}
}

class HelpPanelDescriptor implements IViewDescriptor {
	readonly id = HelpPanel.ID;
	readonly name = HelpPanel.TITLE;
S
Sandeep Somavarapu 已提交
449
	readonly ctorDescriptor: SyncDescriptor<HelpPanel>;
450 451
	readonly canToggleVisibility = true;
	readonly hideByDefault = false;
A
Alex Ross 已提交
452
	readonly group = 'help@50';
A
Alex Ross 已提交
453
	readonly order = -10;
454 455

	constructor(viewModel: IViewModel) {
S
Sandeep Somavarapu 已提交
456
		this.ctorDescriptor = new SyncDescriptor(HelpPanel, [viewModel]);
457 458 459
	}
}

460 461 462
export class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewModel {
	private helpPanelDescriptor = new HelpPanelDescriptor(this);
	helpInformation: HelpInformation[] = [];
463 464 465 466 467 468 469 470 471 472

	constructor(
		@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
		@ITelemetryService telemetryService: ITelemetryService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@IStorageService storageService: IStorageService,
		@IConfigurationService configurationService: IConfigurationService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IThemeService themeService: IThemeService,
		@IContextMenuService contextMenuService: IContextMenuService,
473
		@IExtensionService extensionService: IExtensionService,
A
Alex Ross 已提交
474 475
		@IRemoteExplorerService readonly remoteExplorerService: IRemoteExplorerService,
		@IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService,
S
SteVen Batten 已提交
476
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
477
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService
478
	) {
S
Sandeep Somavarapu 已提交
479
		super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService);
480 481 482 483 484 485 486 487 488 489 490
		this.addConstantViewDescriptors([this.helpPanelDescriptor]);
		remoteHelpExtPoint.setHandler((extensions) => {
			let helpInformation: HelpInformation[] = [];
			for (let extension of extensions) {
				this._handleRemoteInfoExtensionPoint(extension, helpInformation);
			}

			this.helpInformation = helpInformation;

			const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
			if (this.helpInformation.length) {
S
Sandeep Somavarapu 已提交
491
				viewsRegistry.registerViews([this.helpPanelDescriptor], this.viewContainer);
492
			} else {
S
Sandeep Somavarapu 已提交
493
				viewsRegistry.deregisterViews([this.helpPanelDescriptor], this.viewContainer);
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
			}
		});
	}

	private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser<HelpInformation>, helpInformation: HelpInformation[]) {
		if (!extension.description.enableProposedApi) {
			return;
		}

		if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) {
			return;
		}

		helpInformation.push({
			extensionDescription: extension.description,
			getStarted: extension.value.getStarted,
			documentation: extension.value.documentation,
			feedback: extension.value.feedback,
			issues: extension.value.issues,
			remoteName: extension.value.remoteName
		});
515 516 517 518 519 520 521 522
	}

	protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined {
		return isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority[0] : viewDescriptor.remoteAuthority;
	}

	public getActionViewItem(action: Action): IActionViewItem | undefined {
		if (action.id === SwitchRemoteAction.ID) {
523
			return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getViews(this.viewContainer), this.contextKeyService));
524 525 526 527 528
		}

		return super.getActionViewItem(action);
	}

529 530 531 532 533 534
	getTitle(): string {
		const title = nls.localize('remote.explorer', "Remote Explorer");
		return title;
	}
}

A
Alex Ross 已提交
535 536
registerAction2(SwitchRemoteAction);

537 538 539 540
Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
	{
		id: VIEWLET_ID,
		name: nls.localize('remote.explorer', "Remote Explorer"),
S
Sandeep Somavarapu 已提交
541
		ctorDescriptor: new SyncDescriptor(RemoteViewPaneContainer),
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
		hideIfEmpty: true,
		viewOrderDelegate: {
			getOrder: (group?: string) => {
				if (!group) {
					return;
				}

				let matches = /^targets@(\d+)$/.exec(group);
				if (matches) {
					return -1000;
				}

				matches = /^details(@(\d+))?$/.exec(group);

				if (matches) {
557
					return -500 + Number(matches[2]);
558 559
				}

A
Alex Ross 已提交
560 561 562 563 564
				matches = /^help(@(\d+))?$/.exec(group);
				if (matches) {
					return -10;
				}

565 566 567
				return;
			}
		},
M
Martin Aeschlimann 已提交
568
		icon: icons.remoteExplorerViewIcon,
569 570
		order: 4
	}, ViewContainerLocation.Sidebar);
571 572 573 574

class OpenRemoteViewletAction extends ShowViewletAction {

	static readonly ID = VIEWLET_ID;
575
	static readonly LABEL = nls.localize('toggleRemoteViewlet', "Show Remote Explorer");
576 577 578 579 580 581 582 583

	constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService) {
		super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService);
	}
}

// Register Action to Open Viewlet
Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction(
584
	SyncActionDescriptor.from(OpenRemoteViewletAction, {
585 586 587
		primary: 0
	}),
	'View: Show Remote Explorer',
588
	CATEGORIES.View.value
589
);
A
Alex Dima 已提交
590

591
class VisibleProgress {
A
Alex Dima 已提交
592

593 594 595 596 597
	private _isDisposed: boolean;
	private _lastReport: string | null;
	private _currentProgressPromiseResolve: (() => void) | null;
	private _currentProgress: IProgress<IProgressStep> | null;
	private _currentTimer: ReconnectionTimer2 | null;
A
Alex Dima 已提交
598

599 600
	public get lastReport(): string | null {
		return this._lastReport;
A
Alex Dima 已提交
601 602
	}

603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
	constructor(progressService: IProgressService, location: ProgressLocation, initialReport: string | null, buttons: string[], onDidCancel: (choice: number | undefined, lastReport: string | null) => void) {
		this._isDisposed = false;
		this._lastReport = initialReport;
		this._currentProgressPromiseResolve = null;
		this._currentProgress = null;
		this._currentTimer = null;

		const promise = new Promise<void>((resolve) => this._currentProgressPromiseResolve = resolve);

		progressService.withProgress(
			{ location: location, buttons: buttons },
			(progress) => { if (!this._isDisposed) { this._currentProgress = progress; } return promise; },
			(choice) => onDidCancel(choice, this._lastReport)
		);

		if (this._lastReport) {
			this.report();
		}
	}

	public dispose(): void {
		this._isDisposed = true;
		if (this._currentProgressPromiseResolve) {
			this._currentProgressPromiseResolve();
			this._currentProgressPromiseResolve = null;
		}
		this._currentProgress = null;
		if (this._currentTimer) {
			this._currentTimer.dispose();
			this._currentTimer = null;
		}
A
Alex Dima 已提交
634 635
	}

636
	public report(message?: string) {
A
Alex Dima 已提交
637
		if (message) {
638
			this._lastReport = message;
A
Alex Dima 已提交
639 640
		}

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
		if (this._lastReport && this._currentProgress) {
			this._currentProgress.report({ message: this._lastReport });
		}
	}

	public startTimer(completionTime: number): void {
		this.stopTimer();
		this._currentTimer = new ReconnectionTimer2(this, completionTime);
	}

	public stopTimer(): void {
		if (this._currentTimer) {
			this._currentTimer.dispose();
			this._currentTimer = null;
		}
	}
}

class ReconnectionTimer2 implements IDisposable {
	private readonly _parent: VisibleProgress;
	private readonly _completionTime: number;
	private readonly _token: any;

	constructor(parent: VisibleProgress, completionTime: number) {
		this._parent = parent;
		this._completionTime = completionTime;
		this._token = setInterval(() => this._render(), 1000);
		this._render();
	}

	public dispose(): void {
		clearInterval(this._token);
	}

	private _render() {
		const remainingTimeMs = this._completionTime - Date.now();
		if (remainingTimeMs < 0) {
			return;
		}
		const remainingTime = Math.ceil(remainingTimeMs / 1000);
		if (remainingTime === 1) {
			this._parent.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime));
		} else {
			this._parent.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime));
A
Alex Dima 已提交
685 686 687 688 689 690 691 692 693 694 695 696 697 698
		}
	}
}

class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
	constructor(
		@IRemoteAgentService remoteAgentService: IRemoteAgentService,
		@IProgressService progressService: IProgressService,
		@IDialogService dialogService: IDialogService,
		@ICommandService commandService: ICommandService,
		@IContextKeyService contextKeyService: IContextKeyService
	) {
		const connection = remoteAgentService.getConnection();
		if (connection) {
699 700
			let visibleProgress: VisibleProgress | null = null;
			let lastLocation: ProgressLocation.Dialog | ProgressLocation.Notification | null = null;
A
Alex Dima 已提交
701 702 703
			let reconnectWaitEvent: ReconnectionWaitEvent | null = null;
			let disposableListener: IDisposable | null = null;

704 705 706 707
			function showProgress(location: ProgressLocation.Dialog | ProgressLocation.Notification, buttons: { label: string, callback: () => void }[], initialReport: string | null = null): VisibleProgress {
				if (visibleProgress) {
					visibleProgress.dispose();
					visibleProgress = null;
A
Alex Dima 已提交
708 709 710 711
				}

				lastLocation = location;

712 713 714 715 716 717 718 719 720
				return new VisibleProgress(
					progressService, location, initialReport, buttons.map(button => button.label),
					(choice, lastReport) => {
						// Handle choice from dialog
						if (typeof choice !== 'undefined' && buttons[choice]) {
							buttons[choice].callback();
						} else {
							if (location === ProgressLocation.Dialog) {
								visibleProgress = showProgress(ProgressLocation.Notification, buttons, lastReport);
A
Alex Dima 已提交
721 722 723
							} else {
								hideProgress();
							}
724 725 726
						}
					}
				);
A
Alex Dima 已提交
727 728 729
			}

			function hideProgress() {
730 731 732
				if (visibleProgress) {
					visibleProgress.dispose();
					visibleProgress = null;
A
Alex Dima 已提交
733 734 735
				}
			}

736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
			const reconnectButton = {
				label: nls.localize('reconnectNow', "Reconnect Now"),
				callback: () => {
					if (reconnectWaitEvent) {
						reconnectWaitEvent.skipWait();
					}
				}
			};

			const reloadButton = {
				label: nls.localize('reloadWindow', "Reload Window"),
				callback: () => {
					commandService.executeCommand(ReloadWindowAction.ID);
				}
			};

A
Alex Dima 已提交
752
			connection.onDidStateChange((e) => {
753 754
				if (visibleProgress) {
					visibleProgress.stopTimer();
A
Alex Dima 已提交
755 756 757 758 759 760 761 762
				}

				if (disposableListener) {
					disposableListener.dispose();
					disposableListener = null;
				}
				switch (e.type) {
					case PersistentConnectionEventType.ConnectionLost:
763 764
						if (!visibleProgress) {
							visibleProgress = showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]);
A
Alex Dima 已提交
765
						}
766
						visibleProgress.report(nls.localize('connectionLost', "Connection Lost"));
A
Alex Dima 已提交
767 768 769
						break;
					case PersistentConnectionEventType.ReconnectionWait:
						reconnectWaitEvent = e;
770 771
						visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]);
						visibleProgress.startTimer(Date.now() + 1000 * e.durationSeconds);
A
Alex Dima 已提交
772 773
						break;
					case PersistentConnectionEventType.ReconnectionRunning:
774 775
						visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]);
						visibleProgress.report(nls.localize('reconnectionRunning', "Attempting to reconnect..."));
A
Alex Dima 已提交
776 777 778

						// Register to listen for quick input is opened
						disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => {
779
							const reconnectInteraction = new Set<string>([inQuickPickContextKeyValue]);
A
Alex Dima 已提交
780 781
							if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) {
								// Need to move from dialog if being shown and user needs to type in a prompt
782 783
								if (lastLocation === ProgressLocation.Dialog && visibleProgress !== null) {
									visibleProgress = showProgress(ProgressLocation.Notification, [reloadButton], visibleProgress.lastReport);
A
Alex Dima 已提交
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
								}
							}
						});

						break;
					case PersistentConnectionEventType.ReconnectionPermanentFailure:
						hideProgress();

						dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(result => {
							// Reload the window
							if (result.choice === 0) {
								commandService.executeCommand(ReloadWindowAction.ID);
							}
						});
						break;
					case PersistentConnectionEventType.ConnectionGain:
						hideProgress();
						break;
				}
			});
		}
	}
}

const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
810
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteStatusIndicator, LifecyclePhase.Starting);
A
Alex Ross 已提交
811
workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Eventually);
812
workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually);