Skip to content

Commit

Permalink
Switch Remote button not working after hiding (#194604)
Browse files Browse the repository at this point in the history
* Switch Remote button not working after hiding
Fixes #194569
Part of #194603

* Respond to PR feedback

* Fix for uninstalled extensions and for extensions that don't activate

* Fix active remote view not being selected
  • Loading branch information
alexr00 authored Oct 4, 2023
1 parent d309643 commit 4f7a76b
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 100 deletions.
20 changes: 12 additions & 8 deletions src/vs/workbench/browser/parts/views/viewsViewlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef, IView } from 'vs/workbench/common/views';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
Expand Down Expand Up @@ -48,13 +48,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
this.onFilterChanged(newFilterValue);
}));

this._register(this.onDidChangeViewVisibility(view => {
const descriptorMap = Array.from(this.allViews.entries()).find(entry => entry[1].has(view.id));
if (descriptorMap && !this.filterValue?.includes(descriptorMap[0])) {
this.setFilter(descriptorMap[1].get(view.id)!);
}
}));

this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => {
this.updateAllViews(this.viewContainerModel.activeViewDescriptors);
}));
Expand Down Expand Up @@ -137,6 +130,17 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
return panes;
}

override openView(id: string, focus?: boolean): IView | undefined {
const result = super.openView(id, focus);
if (result) {
const descriptorMap = Array.from(this.allViews.entries()).find(entry => entry[1].has(id));
if (descriptorMap && !this.filterValue?.includes(descriptorMap[0])) {
this.setFilter(descriptorMap[1].get(id)!);
}
}
return result;
}

abstract override getTitle(): string;

}
149 changes: 81 additions & 68 deletions src/vs/workbench/contrib/remote/browser/explorerViewItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,60 @@
*--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import { IAction } from 'vs/base/common/actions';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IRemoteExplorerService, REMOTE_EXPLORER_TYPE_KEY } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import { IViewDescriptor } from 'vs/workbench/common/views';
import { isStringArray } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
import { VIEWLET_ID } from 'vs/workbench/contrib/remote/browser/remoteExplorer';
import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Disposable, DisposableMap } from 'vs/base/common/lifecycle';

interface IRemoteSelectItem extends ISelectOptionItem {
authority: string[];
virtualWorkspace?: string;
dispose(): void;
}

export class SwitchRemoteViewItem extends SelectActionViewItem<IRemoteSelectItem> {
export const SELECTED_REMOTE_IN_EXPLORER = new RawContextKey<string>('selectedRemoteInExplorer', '');

export class SwitchRemoteViewItem extends Disposable {
private switchRemoteMenu: MenuId;
private completedRemotes: DisposableMap<string, IRemoteSelectItem> = this._register(new DisposableMap());
private readonly selectedRemoteContext: IContextKey<string>;

constructor(
action: IAction,
private readonly optionsItems: IRemoteSelectItem[],
@IContextViewService contextViewService: IContextViewService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IRemoteExplorerService private remoteExplorerService: IRemoteExplorerService,
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@IStorageService private readonly storageService: IStorageService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService
) {
super(null, action, optionsItems, 0, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('remotes', 'Switch Remote') });
super();
this.selectedRemoteContext = SELECTED_REMOTE_IN_EXPLORER.bindTo(contextKeyService);

this.switchRemoteMenu = new MenuId('workbench.remote.menu.switchRemoteMenu');
this._register(MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, {
submenu: this.switchRemoteMenu,
title: nls.localize('switchRemote.label', "Switch Remote"),
group: 'navigation',
when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID),
order: 1,
isSelection: true
}));
this._register(remoteExplorerService.onDidChangeTargetType(e => {
this.select(e);
}));
}

public setSelectionForConnection(): boolean {
let isSetForConnection = false;
if (this.optionsItems.length > 0) {
let index = 0;
if (this.completedRemotes.size > 0) {
let authority: string[] | undefined;
const remoteAuthority = this.environmentService.remoteAuthority;
let virtualWorkspace: string | undefined;
if (!remoteAuthority) {
Expand All @@ -54,78 +68,77 @@ export class SwitchRemoteViewItem extends SelectActionViewItem<IRemoteSelectItem
: (virtualWorkspace ? [virtualWorkspace]
: (this.storageService.get(REMOTE_EXPLORER_TYPE_KEY, StorageScope.WORKSPACE)?.split(',') ?? this.storageService.get(REMOTE_EXPLORER_TYPE_KEY, StorageScope.PROFILE)?.split(',')));
if (explorerType !== undefined) {
index = this.getOptionIndexForExplorerType(explorerType);
authority = this.getAuthorityForExplorerType(explorerType);
}
if (authority) {
this.select(authority);
}
this.select(index);
this.remoteExplorerService.targetType = this.optionsItems[index].authority;
}
return isSetForConnection;
}

public setSelection() {
const index = this.getOptionIndexForExplorerType(this.remoteExplorerService.targetType);
this.select(index);
private select(authority: string[]) {
this.selectedRemoteContext.set(authority[0]);
this.remoteExplorerService.targetType = authority;
}

private getOptionIndexForExplorerType(explorerType: string[]): number {
let index = 0;
for (let optionIterator = 0; (optionIterator < this.optionsItems.length) && (index === 0); optionIterator++) {
for (let authorityIterator = 0; authorityIterator < this.optionsItems[optionIterator].authority.length; authorityIterator++) {
for (let i = 0; i < explorerType.length; i++) {
if (this.optionsItems[optionIterator].authority[authorityIterator] === explorerType[i]) {
index = optionIterator;
private getAuthorityForExplorerType(explorerType: string[]): string[] | undefined {
let authority: string[] | undefined;
for (const option of this.completedRemotes) {
for (const authorityOption of option[1].authority) {
for (const explorerOption of explorerType) {
if (authorityOption === explorerOption) {
authority = option[1].authority;
break;
} else if (this.optionsItems[optionIterator].virtualWorkspace === explorerType[i]) {
index = optionIterator;
} else if (option[1].virtualWorkspace === explorerOption) {
authority = option[1].authority;
break;
}
}
}
}
return index;
return authority;
}

override render(container: HTMLElement) {
if (this.optionsItems.length > 1) {
super.render(container);
container.classList.add('switch-remote');
public removeOptionItems(views: IViewDescriptor[]) {
for (const view of views) {
if (view.group && view.group.startsWith('targets') && view.remoteAuthority && (!view.when || this.contextKeyService.contextMatchesRules(view.when))) {
const authority = isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority];
this.completedRemotes.deleteAndDispose(authority[0]);
}
}
}

protected override getActionContext(_: string, index: number): IRemoteSelectItem {
return this.optionsItems[index];
}

static createOptionItems(views: IViewDescriptor[], contextKeyService: IContextKeyService): IRemoteSelectItem[] {
const options: IRemoteSelectItem[] = [];
views.forEach(view => {
if (view.group && view.group.startsWith('targets') && view.remoteAuthority && (!view.when || contextKeyService.contextMatchesRules(view.when))) {
options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority], virtualWorkspace: view.virtualWorkspace });
public createOptionItems(views: IViewDescriptor[]) {
const startingCount = this.completedRemotes.size;
for (const view of views) {
if (view.group && view.group.startsWith('targets') && view.remoteAuthority && (!view.when || this.contextKeyService.contextMatchesRules(view.when))) {
const text = view.name;
const authority = isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority];
if (this.completedRemotes.has(authority[0])) {
continue;
}
const thisCapture = this;
const action = registerAction2(class extends Action2 {
constructor() {
super({
id: `workbench.action.remoteExplorer.show.${authority[0]}`,
title: text,
toggled: SELECTED_REMOTE_IN_EXPLORER.isEqualTo(authority[0]),
menu: {
id: thisCapture.switchRemoteMenu
}
});
}
async run(): Promise<void> {
thisCapture.select(authority);
}
});
this.completedRemotes.set(authority[0], { text, authority, virtualWorkspace: view.virtualWorkspace, dispose: () => action.dispose() });
}
});
return options;
}
}

export class SwitchRemoteAction extends Action2 {

public static readonly ID = 'remote.explorer.switch';
public static readonly LABEL = nls.localize('remote.explorer.switch', "Switch Remote");

constructor() {
super({
id: SwitchRemoteAction.ID,
title: SwitchRemoteAction.LABEL,
menu: [{
id: MenuId.ViewContainerTitle,
when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID),
group: 'navigation',
order: 1
}],
});
}

public async run(accessor: ServicesAccessor, args: IRemoteSelectItem): Promise<any> {
accessor.get(IRemoteExplorerService).targetType = args.authority;
}
if (this.completedRemotes.size > startingCount) {
this.setSelectionForConnection();
}
}
}
45 changes: 21 additions & 24 deletions src/vs/workbench/contrib/remote/browser/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { IProgress, IProgressStep, IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
Expand All @@ -35,8 +34,7 @@ import { ReconnectionWaitEvent, PersistentConnectionEventType } from 'vs/platfor
import Severity from 'vs/base/common/severity';
import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems';
import { Action } from 'vs/base/common/actions';
import { SwitchRemoteViewItem } from 'vs/workbench/contrib/remote/browser/explorerViewItems';
import { isStringArray } from 'vs/base/common/types';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
Expand All @@ -52,7 +50,6 @@ import * as icons from 'vs/workbench/contrib/remote/browser/remoteIcons';
import { ILogService } from 'vs/platform/log/common/log';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService';
Expand Down Expand Up @@ -582,8 +579,8 @@ class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewMo
helpInformation: HelpInformation[] = [];
private _onDidChangeHelpInformation = new Emitter<void>();
public onDidChangeHelpInformation: Event<void> = this._onDidChangeHelpInformation.event;
private hasSetSwitchForConnection: boolean = false;
private hasRegisteredHelpView: boolean = false;
private remoteSwitcher: SwitchRemoteViewItem | undefined;

constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
Expand All @@ -596,11 +593,12 @@ class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewMo
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
) {
super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService);
this.addConstantViewDescriptors([this.helpPanelDescriptor]);
this._register(this.remoteSwitcher = this.instantiationService.createInstance(SwitchRemoteViewItem));
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
remoteHelpExtPoint.setHandler((extensions) => {
const helpInformation: HelpInformation[] = [];
for (const extension of extensions) {
Expand All @@ -610,7 +608,6 @@ class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewMo
this.helpInformation = helpInformation;
this._onDidChangeHelpInformation.fire();

const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
if (this.helpInformation.length && !this.hasRegisteredHelpView) {
viewsRegistry.registerViews([this.helpPanelDescriptor], this.viewContainer);
this.hasRegisteredHelpView = true;
Expand All @@ -619,6 +616,23 @@ class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewMo
this.hasRegisteredHelpView = false;
}
});
this.remoteSwitcher.createOptionItems(viewsRegistry.getViews(this.viewContainer));
this._register(viewsRegistry.onViewsRegistered(e => {
const remoteViews: IViewDescriptor[] = [];
for (const view of e) {
if (view.viewContainer.id === VIEWLET_ID) {
remoteViews.push(...view.views);
}
}
if (remoteViews.length > 0) {
this.remoteSwitcher!.createOptionItems(remoteViews);
}
}));
this._register(viewsRegistry.onViewsDeregistered(e => {
if (e.viewContainer.id === VIEWLET_ID) {
this.remoteSwitcher!.removeOptionItems(e.views);
}
}));
}

private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser<HelpInformation>, helpInformation: HelpInformation[]) {
Expand Down Expand Up @@ -649,29 +663,12 @@ class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewMo
this.remoteExplorerService.targetType = isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority : [viewDescriptor.remoteAuthority!];
}

public override getActionViewItem(action: Action): IActionViewItem | undefined {
if (action.id === SwitchRemoteAction.ID) {
const optionItems = SwitchRemoteViewItem.createOptionItems(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getViews(this.viewContainer), this.contextKeyService);
const item = this.instantiationService.createInstance(SwitchRemoteViewItem, action, optionItems);
if (!this.hasSetSwitchForConnection) {
this.hasSetSwitchForConnection = item.setSelectionForConnection();
} else {
item.setSelection();
}
return item;
}

return super.getActionViewItem(action);
}

getTitle(): string {
const title = nls.localize('remote.explorer', "Remote Explorer");
return title;
}
}

registerAction2(SwitchRemoteAction);

Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
{
id: VIEWLET_ID,
Expand Down

0 comments on commit 4f7a76b

Please sign in to comment.