remote.ts 23.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./remoteViewlet';
import * as nls from 'vs/nls';
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';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
S
SteVen Batten 已提交
18
import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet';
19 20 21 22 23 24
import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
25
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
26
import { ICommandService } from 'vs/platform/commands/common/commands';
S
SteVen Batten 已提交
27
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet';
28 29 30 31
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
A
Alex Dima 已提交
32 33 34 35 36 37 38 39 40
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';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
41 42 43
import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems';
import { Action, IActionViewItem, IAction } from 'vs/base/common/actions';
import { isStringArray } from 'vs/base/common/types';
44
import { IRemoteExplorerService, HelpInformation } from 'vs/workbench/services/remote/common/remoteExplorerService';
45 46
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { startsWith } from 'vs/base/common/strings';
A
Alex Ross 已提交
47 48
import { TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView';
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
S
SteVen Batten 已提交
49
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
50 51

class HelpModel {
B
Benjamin Pasero 已提交
52
	items: IHelpItem[] | undefined;
53 54 55 56

	constructor(
		openerService: IOpenerService,
		quickInputService: IQuickInputService,
57 58
		commandService: ICommandService,
		remoteExplorerService: IRemoteExplorerService,
59
		environmentService: IWorkbenchEnvironmentService
60 61
	) {
		let helpItems: IHelpItem[] = [];
62
		const getStarted = remoteExplorerService.helpInformation.filter(info => info.getStarted);
63 64 65

		if (getStarted.length) {
			helpItems.push(new HelpItem(
66
				['getStarted'],
67
				nls.localize('remote.help.getStarted', "$(star) Get Started"),
68 69
				getStarted.map((info: HelpInformation) => ({
					extensionDescription: info.extensionDescription,
70 71
					url: info.getStarted!,
					remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName
72
				})),
73 74
				quickInputService,
				environmentService,
75 76
				openerService,
				remoteExplorerService
77 78 79
			));
		}

80
		const documentation = remoteExplorerService.helpInformation.filter(info => info.documentation);
81 82 83

		if (documentation.length) {
			helpItems.push(new HelpItem(
84
				['documentation'],
85
				nls.localize('remote.help.documentation', "$(book) Read Documentation"),
86 87
				documentation.map((info: HelpInformation) => ({
					extensionDescription: info.extensionDescription,
88 89
					url: info.documentation!,
					remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName
90
				})),
91 92
				quickInputService,
				environmentService,
93 94
				openerService,
				remoteExplorerService
95 96 97
			));
		}

98
		const feedback = remoteExplorerService.helpInformation.filter(info => info.feedback);
99 100 101

		if (feedback.length) {
			helpItems.push(new HelpItem(
102
				['feedback'],
103
				nls.localize('remote.help.feedback', "$(twitter) Provide Feedback"),
104 105
				feedback.map((info: HelpInformation) => ({
					extensionDescription: info.extensionDescription,
106 107
					url: info.feedback!,
					remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName
108
				})),
109 110
				quickInputService,
				environmentService,
111 112
				openerService,
				remoteExplorerService
113 114 115
			));
		}

116
		const issues = remoteExplorerService.helpInformation.filter(info => info.issues);
117 118 119

		if (issues.length) {
			helpItems.push(new HelpItem(
120
				['issues'],
121
				nls.localize('remote.help.issues', "$(issues) Review Issues"),
122 123
				issues.map((info: HelpInformation) => ({
					extensionDescription: info.extensionDescription,
124 125
					url: info.issues!,
					remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName
126
				})),
127 128
				quickInputService,
				environmentService,
129 130
				openerService,
				remoteExplorerService
131 132 133 134 135
			));
		}

		if (helpItems.length) {
			helpItems.push(new IssueReporterItem(
136
				['issueReporter'],
137
				nls.localize('remote.help.report', "$(comment) Report Issue"),
138 139 140 141
				remoteExplorerService.helpInformation.map(info => ({
					extensionDescription: info.extensionDescription,
					remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName
				})),
142
				quickInputService,
143
				environmentService,
144 145
				commandService,
				remoteExplorerService
146 147 148 149 150 151 152 153 154
			));
		}

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

155 156 157 158
interface IHelpItem extends IQuickPickItem {
	label: string;
	handleClick(): Promise<void>;
}
159

160
abstract class HelpItemBase implements IHelpItem {
161
	constructor(
162 163
		public iconClasses: string[],
		public label: string,
164 165
		public values: { extensionDescription: IExtensionDescription, url?: string, remoteAuthority: string[] | undefined }[],
		private quickInputService: IQuickInputService,
166 167
		private environmentService: IWorkbenchEnvironmentService,
		private remoteExplorerService: IRemoteExplorerService
168
	) {
169
		iconClasses.push('remote-help-tree-node-item-icon');
170 171
	}

172
	async handleClick() {
173
		const remoteAuthority = this.environmentService.configuration.remoteAuthority;
174
		if (remoteAuthority && startsWith(remoteAuthority, this.remoteExplorerService.targetType)) {
175 176 177 178 179 180 181 182 183 184 185 186
			for (let value of this.values) {
				if (value.remoteAuthority) {
					for (let authority of value.remoteAuthority) {
						if (startsWith(remoteAuthority, authority)) {
							await this.takeAction(value.extensionDescription, value.url);
							return;
						}
					}
				}
			}
		}

187 188 189 190
		if (this.values.length > 1) {
			let actions = this.values.map(value => {
				return {
					label: value.extensionDescription.displayName || value.extensionDescription.identifier.value,
191 192
					description: value.url,
					extensionDescription: value.extensionDescription
193 194
				};
			});
195

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

198
			if (action) {
199
				await this.takeAction(action.extensionDescription, action.description);
200 201
			}
		} else {
202
			await this.takeAction(this.values[0].extensionDescription, this.values[0].url);
203 204
		}
	}
205 206

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

209
class HelpItem extends HelpItemBase {
210
	constructor(
211 212 213 214 215
		iconClasses: string[],
		label: string,
		values: { extensionDescription: IExtensionDescription; url: string, remoteAuthority: string[] | undefined }[],
		quickInputService: IQuickInputService,
		environmentService: IWorkbenchEnvironmentService,
216 217
		private openerService: IOpenerService,
		remoteExplorerService: IRemoteExplorerService
218
	) {
219
		super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService);
220 221
	}

222 223 224 225
	protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise<void> {
		await this.openerService.open(URI.parse(url));
	}
}
226

227 228 229 230 231 232 233 234
class IssueReporterItem extends HelpItemBase {
	constructor(
		iconClasses: string[],
		label: string,
		values: { extensionDescription: IExtensionDescription; remoteAuthority: string[] | undefined }[],
		quickInputService: IQuickInputService,
		environmentService: IWorkbenchEnvironmentService,
		private commandService: ICommandService,
235
		remoteExplorerService: IRemoteExplorerService
236
	) {
237
		super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService);
238
	}
239

240 241
	protected async takeAction(extensionDescription: IExtensionDescription): Promise<void> {
		await this.commandService.executeCommand('workbench.action.openIssueReporter', [extensionDescription.identifier.value]);
242 243 244
	}
}

245 246
class HelpAction extends Action {
	static readonly ID = 'remote.explorer.help';
247
	static readonly LABEL = nls.localize('remote.explorer.help', "Help, Documentation, and Feedback");
248 249 250 251 252 253 254
	private helpModel: HelpModel;

	constructor(id: string,
		label: string,
		@IOpenerService private readonly openerService: IOpenerService,
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@ICommandService private readonly commandService: ICommandService,
255 256
		@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
		@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
257 258
	) {
		super(id, label, 'codicon codicon-question');
259
		this.helpModel = new HelpModel(openerService, quickInputService, commandService, remoteExplorerService, environmentService);
260
	}
261

262 263
	async run(event?: any): Promise<any> {
		if (!this.helpModel.items) {
264
			this.helpModel = new HelpModel(this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.environmentService);
265 266 267 268 269 270 271
		}
		if (this.helpModel.items) {
			const selection = await this.quickInputService.pick(this.helpModel.items, { placeHolder: nls.localize('remote.explorer.helpPlaceholder', "Help and Feedback") });
			if (selection) {
				return selection.handleClick();
			}
		}
272 273 274
	}
}

S
SteVen Batten 已提交
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
export class RemoteViewlet extends Viewlet {
	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IStorageService protected storageService: IStorageService,
		@IInstantiationService protected instantiationService: IInstantiationService,
		@IThemeService themeService: IThemeService,
		@IContextMenuService protected contextMenuService: IContextMenuService,
		@IExtensionService protected extensionService: IExtensionService,
		@IWorkspaceContextService protected contextService: IWorkspaceContextService,
		@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
		@IConfigurationService protected configurationService: IConfigurationService
	) {
		super(VIEWLET_ID, instantiationService.createInstance(RemoteViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService);
	}
}

export class RemoteViewPaneContainer extends FilterViewPaneContainer {
292
	private actions: IAction[] | undefined;
A
Alex Ross 已提交
293
	private tunnelPanelDescriptor: TunnelPanelDescriptor | undefined;
294
	private static contextKeyName: string = 'forwardedPortsViewEnabled';
295 296 297 298 299 300 301 302 303 304

	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,
305
		@IExtensionService extensionService: IExtensionService,
A
Alex Ross 已提交
306 307
		@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
		@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
308
		@IContextKeyService private readonly contextKeyService: IContextKeyService
309
	) {
310
		super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
311
		this.contextKeyService.createKey(RemoteViewPaneContainer.contextKeyName, false);
312 313 314 315 316 317 318 319
	}

	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) {
320
			return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER), this.contextKeyService));
321 322 323 324 325 326 327 328 329
		}

		return super.getActionViewItem(action);
	}

	public getActions(): IAction[] {
		if (!this.actions) {
			this.actions = [
				this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL),
330
				this.instantiationService.createInstance(HelpAction, HelpAction.ID, HelpAction.LABEL)
331 332 333 334 335 336
			];
			this.actions.forEach(a => {
				this._register(a);
			});
		}
		return this.actions;
337 338 339 340 341 342
	}

	getTitle(): string {
		const title = nls.localize('remote.explorer', "Remote Explorer");
		return title;
	}
A
Alex Ross 已提交
343

S
SteVen Batten 已提交
344
	onDidAddViews(added: IAddedViewDescriptorRef[]): ViewPane[] {
A
Alex Ross 已提交
345
		// Call to super MUST be first, since registering the additional view will cause this to be called again.
S
SteVen Batten 已提交
346
		const panels: ViewPane[] = super.onDidAddViews(added);
347 348 349
		// This context key is set to false in the constructor, but is expected to be changed by resolver extensions to enable the forwarded ports view.
		const viewEnabled: boolean = !!this.contextKeyService.getContextKeyValue<boolean>(RemoteViewPaneContainer.contextKeyName);
		if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && viewEnabled) {
A
Alex Ross 已提交
350 351 352 353 354 355
			this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService);
			const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
			viewsRegistry.registerViews([this.tunnelPanelDescriptor!], VIEW_CONTAINER);
		}
		return panels;
	}
356 357
}

358
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
359 360 361
	RemoteViewlet,
	VIEWLET_ID,
	nls.localize('remote.explorer', "Remote Explorer"),
362
	'codicon-remote-explorer',
363 364
	4
));
365 366 367 368

class OpenRemoteViewletAction extends ShowViewletAction {

	static readonly ID = VIEWLET_ID;
369
	static readonly LABEL = nls.localize('toggleRemoteViewlet', "Show Remote Explorer");
370 371 372 373 374 375 376 377

	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(
378
	SyncActionDescriptor.create(OpenRemoteViewletAction, VIEWLET_ID, nls.localize('toggleRemoteViewlet', "Show Remote Explorer"), {
379 380 381 382 383
		primary: 0
	}),
	'View: Show Remote Explorer',
	nls.localize('view', "View")
);
A
Alex Dima 已提交
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425


class ProgressReporter {
	private _currentProgress: IProgress<IProgressStep> | null = null;
	private lastReport: string | null = null;

	constructor(currentProgress: IProgress<IProgressStep> | null) {
		this._currentProgress = currentProgress;
	}

	set currentProgress(progress: IProgress<IProgressStep>) {
		this._currentProgress = progress;
	}

	report(message?: string) {
		if (message) {
			this.lastReport = message;
		}

		if (this.lastReport && this._currentProgress) {
			this._currentProgress.report({ message: this.lastReport });
		}
	}
}

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) {
			let currentProgressPromiseResolve: (() => void) | null = null;
			let progressReporter: ProgressReporter | null = null;
			let lastLocation: ProgressLocation | null = null;
			let currentTimer: ReconnectionTimer | null = null;
			let reconnectWaitEvent: ReconnectionWaitEvent | null = null;
			let disposableListener: IDisposable | null = null;

426
			function showProgress(location: ProgressLocation, buttons: { label: string, callback: () => void }[]) {
A
Alex Dima 已提交
427 428 429 430 431 432 433 434 435 436
				if (currentProgressPromiseResolve) {
					currentProgressPromiseResolve();
				}

				const promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
				lastLocation = location;

				if (location === ProgressLocation.Dialog) {
					// Show dialog
					progressService!.withProgress(
437
						{ location: ProgressLocation.Dialog, buttons: buttons.map(button => button.label) },
A
Alex Dima 已提交
438 439 440
						(progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; },
						(choice?) => {
							// Handle choice from dialog
441 442
							if (buttons[choice]) {
								buttons[choice].callback();
A
Alex Dima 已提交
443 444 445 446 447 448 449 450 451
							} else {
								showProgress(ProgressLocation.Notification, buttons);
							}

							progressReporter!.report();
						});
				} else {
					// Show notification
					progressService!.withProgress(
452
						{ location: ProgressLocation.Notification, buttons: buttons.map(button => button.label) },
A
Alex Dima 已提交
453 454
						(progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; },
						(choice?) => {
455 456 457
							// Handle choice from dialog
							if (buttons[choice]) {
								buttons[choice].callback();
A
Alex Dima 已提交
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
							} else {
								hideProgress();
							}
						});
				}
			}

			function hideProgress() {
				if (currentProgressPromiseResolve) {
					currentProgressPromiseResolve();
				}

				currentProgressPromiseResolve = null;
			}

473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
			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 已提交
489 490 491 492 493 494 495 496 497 498 499 500 501 502
			connection.onDidStateChange((e) => {
				if (currentTimer) {
					currentTimer.dispose();
					currentTimer = null;
				}

				if (disposableListener) {
					disposableListener.dispose();
					disposableListener = null;
				}
				switch (e.type) {
					case PersistentConnectionEventType.ConnectionLost:
						if (!currentProgressPromiseResolve) {
							progressReporter = new ProgressReporter(null);
503
							showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]);
A
Alex Dima 已提交
504 505 506 507 508 509 510
						}

						progressReporter!.report(nls.localize('connectionLost', "Connection Lost"));
						break;
					case PersistentConnectionEventType.ReconnectionWait:
						hideProgress();
						reconnectWaitEvent = e;
511
						showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]);
A
Alex Dima 已提交
512 513 514 515
						currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds);
						break;
					case PersistentConnectionEventType.ReconnectionRunning:
						hideProgress();
516
						showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]);
A
Alex Dima 已提交
517 518 519 520 521 522 523 524 525
						progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect..."));

						// Register to listen for quick input is opened
						disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => {
							const reconnectInteraction = new Set<string>(['inQuickOpen']);
							if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) {
								// Need to move from dialog if being shown and user needs to type in a prompt
								if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) {
									hideProgress();
526
									showProgress(ProgressLocation.Notification, [reloadButton]);
A
Alex Dima 已提交
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
									progressReporter.report();
								}
							}
						});

						break;
					case PersistentConnectionEventType.ReconnectionPermanentFailure:
						hideProgress();
						progressReporter = null;

						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();
						progressReporter = null;
						break;
				}
			});
		}
	}
}

class ReconnectionTimer implements IDisposable {
	private readonly _progressReporter: ProgressReporter;
	private readonly _completionTime: number;
	private readonly _token: any;

	constructor(progressReporter: ProgressReporter, completionTime: number) {
		this._progressReporter = progressReporter;
		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._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime));
		} else {
			this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime));
		}
	}
}

const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);