From 69821795e8b7904d00a7d98ab6e4dbd5fda0e5e9 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 12 Oct 2020 14:44:06 +0200 Subject: [PATCH] Add badge for remote explorer Fixes microsoft/vscode-remote-release#3824 --- .../api/browser/viewsExtensionPoint.ts | 2 +- .../contrib/remote/browser/remote.ts | 24 ++---- .../contrib/remote/browser/remoteExplorer.ts | 81 +++++++++++++++++++ .../contrib/remote/browser/tunnelView.ts | 4 +- .../remote/common/remote.contribution.ts | 2 - .../remote/common/remoteExplorerService.ts | 1 + 6 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 src/vs/workbench/contrib/remote/browser/remoteExplorer.ts diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 08658904e86..f88bdb42230 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; -import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote.contribution'; +import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 4f8fb0ba2cf..a0c3d99803d 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -17,9 +17,9 @@ 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'; import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; +import { ForwardedPortsView, VIEWLET_ID } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService, IAddedViewDescriptorRef, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService, IViewsService } 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'; @@ -44,7 +44,7 @@ import { Action, IActionViewItem, IAction } from 'vs/base/common/actions'; import { isStringArray } from 'vs/base/common/types'; import { IRemoteExplorerService, MakeAddress, mapHasTunnelLocalhostOrAllInterfaces } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { TunnelPanelDescriptor, TunnelViewModel, forwardedPortsViewEnabled, OpenPortInBrowserAction } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { forwardedPortsViewEnabled, OpenPortInBrowserAction } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -471,7 +471,6 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements private helpPanelDescriptor = new HelpPanelDescriptor(this); helpInformation: HelpInformation[] = []; private actions: IAction[] | undefined; - private tunnelPanelDescriptor: TunnelPanelDescriptor | undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -483,8 +482,8 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteExplorerService readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { @@ -555,18 +554,6 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements return title; } - onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] { - // Call to super MUST be first, since registering the additional view will cause this to be called again. - const panels: ViewPane[] = super.onDidAddViewDescriptors(added); - // 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 = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); - if (this.environmentService.remoteAuthority && !this.tunnelPanelDescriptor && viewEnabled) { - this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViews([this.tunnelPanelDescriptor!], this.viewContainer); - } - return panels; - } } Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( @@ -928,4 +915,5 @@ class AutomaticPortForwarding extends Disposable implements IWorkbenchContributi const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteStatusIndicator, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(ForwardedPortsView, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(AutomaticPortForwarding, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts new file mode 100644 index 00000000000..6c93199cdb1 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Extensions, IViewDescriptorService, IViewsRegistry } from 'vs/workbench/common/views'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { IRemoteExplorerService, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { forwardedPortsViewEnabled, TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export const VIEWLET_ID = 'workbench.view.remote'; + +export class ForwardedPortsView extends Disposable implements IWorkbenchContribution { + private contextKeyListener?: IDisposable; + private _activityBadge?: IDisposable; + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IActivityService private readonly activityService: IActivityService + ) { + super(); + this.enableForwardedPortsView(); + this.enableBadge(); + } + + private enableForwardedPortsView() { + if (this.contextKeyListener) { + this.contextKeyListener.dispose(); + this.contextKeyListener = undefined; + } + + const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); + if (this.environmentService.remoteAuthority && viewEnabled) { + const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + const viewContainer = this.viewDescriptorService.getViewContainerById(VIEWLET_ID); + if (viewContainer) { + viewsRegistry.registerViews([tunnelPanelDescriptor!], viewContainer); + } + } else if (this.environmentService.remoteAuthority) { + this.contextKeyListener = this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { + this.enableForwardedPortsView(); + } + }); + } + } + + private enableBadge() { + this._register(this.remoteExplorerService.tunnelModel.onForwardPort(() => this.updateActivityBadge())); + this._register(this.remoteExplorerService.tunnelModel.onClosePort(() => this.updateActivityBadge())); + const disposable = Registry.as(Extensions.ViewsRegistry).onViewsRegistered(e => { + if (e.find(view => view.views.find(viewDescriptor => viewDescriptor.id === TUNNEL_VIEW_ID))) { + this.updateActivityBadge(); + disposable.dispose(); + } + }); + } + + private updateActivityBadge() { + if (this._activityBadge) { + this._activityBadge.dispose(); + } + if (this.remoteExplorerService.tunnelModel.forwarded.size > 0) { + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(TUNNEL_VIEW_ID); + if (viewContainer) { + this._activityBadge = this.activityService.showViewContainerActivity(viewContainer.id, { + badge: new NumberBadge(this.remoteExplorerService.tunnelModel.forwarded.size, n => n === 1 ? nls.localize('1forwardedPort', "1 forwarded port") : nls.localize('nForwardedPorts', "{0} forwarded ports", n)) + }); + } + } + } +} diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 809ea341f1b..eab9b8607e3 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction, ILocalizedString, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel, mapHasTunnelLocalhostOrAllInterfaces } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel, mapHasTunnelLocalhostOrAllInterfaces, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -438,7 +438,7 @@ const PortChangableContextKey = new RawContextKey('portChangable', fals class TunnelDataTree extends WorkbenchAsyncDataTree { } export class TunnelPanel extends ViewPane { - static readonly ID = '~remote.forwardedPorts'; + static readonly ID = TUNNEL_VIEW_ID; static readonly TITLE = nls.localize('remote.tunnel', "Forwarded Ports"); private tree!: TunnelDataTree; private tunnelTypeContext: IContextKey; diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index fd8ee7ee9a2..ca725098357 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -21,8 +21,6 @@ import { ShowCandidateContribution } from 'vs/workbench/contrib/remote/common/sh import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -export const VIEWLET_ID = 'workbench.view.remote'; - export class LabelContribution implements IWorkbenchContribution { constructor( @ILabelService private readonly labelService: ILabelService, diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index f90a296d55d..7d2f38f30d0 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -18,6 +18,7 @@ import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnectio export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; +export const TUNNEL_VIEW_ID = '~remote.forwardedPorts'; export enum TunnelType { Candidate = 'Candidate',