Skip to content

Commit 33e5678

Browse files
authored
Change remote explorer to use drop-down UI. (microsoft#84482)
Part of microsoft/vscode-remote-release#1778
1 parent cec8079 commit 33e5678

8 files changed

Lines changed: 316 additions & 45 deletions

File tree

src/vs/workbench/browser/parts/views/views.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ export class ContributableViewsModel extends Disposable {
240240
private _onDidChangeViewState = this._register(new Emitter<IViewDescriptorRef>());
241241
protected readonly onDidChangeViewState: Event<IViewDescriptorRef> = this._onDidChangeViewState.event;
242242

243+
private _onDidChangeActiveViews = this._register(new Emitter<IViewDescriptor[]>());
244+
readonly onDidChangeActiveViews: Event<IViewDescriptor[]> = this._onDidChangeActiveViews.event;
245+
243246
constructor(
244247
container: ViewContainer,
245248
viewsService: IViewsService,
@@ -469,6 +472,8 @@ export class ContributableViewsModel extends Disposable {
469472
if (toAdd.length) {
470473
this._onDidAdd.fire(toAdd);
471474
}
475+
476+
this._onDidChangeActiveViews.fire(this.viewDescriptors);
472477
}
473478
}
474479

src/vs/workbench/browser/parts/views/viewsViewlet.ts

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { DefaultPanelDndController } from 'vs/base/browser/ui/splitview/panelvie
2323
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
2424
import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
2525
import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
26-
import { Event } from 'vs/base/common/event';
26+
import { Event, Emitter } from 'vs/base/common/event';
2727
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2828
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
2929
import { localize } from 'vs/nls';
@@ -112,7 +112,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
112112
}));
113113

114114
result.push(...viewToggleActions);
115-
const parentActions = super.getContextMenuActions();
115+
const parentActions = this.getViewletContextMenuActions();
116116
if (viewToggleActions.length && parentActions.length) {
117117
result.push(new Separator());
118118
}
@@ -121,6 +121,10 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
121121
return result;
122122
}
123123

124+
protected getViewletContextMenuActions() {
125+
return super.getContextMenuActions();
126+
}
127+
124128
setVisible(visible: boolean): void {
125129
super.setVisible(visible);
126130
this.panels.filter(view => view.isVisible() !== visible)
@@ -264,7 +268,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
264268
});
265269
}
266270

267-
private toggleViewVisibility(viewId: string): void {
271+
protected toggleViewVisibility(viewId: string): void {
268272
const visible = !this.viewsModel.isVisible(viewId);
269273
type ViewsToggleVisibilityClassification = {
270274
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
@@ -321,6 +325,109 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
321325
}
322326
}
323327

328+
export abstract class FilterViewContainerViewlet extends ViewContainerViewlet {
329+
private constantViewDescriptors: Map<string, IViewDescriptor> = new Map();
330+
private allViews: Map<string, Map<string, IViewDescriptor>> = new Map();
331+
private filterValue: string;
332+
333+
protected onDidChangeFilterValue: Emitter<string> = new Emitter();
334+
335+
constructor(
336+
viewletId: string,
337+
@IConfigurationService configurationService: IConfigurationService,
338+
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
339+
@ITelemetryService telemetryService: ITelemetryService,
340+
@IStorageService storageService: IStorageService,
341+
@IInstantiationService instantiationService: IInstantiationService,
342+
@IThemeService themeService: IThemeService,
343+
@IContextMenuService contextMenuService: IContextMenuService,
344+
@IExtensionService extensionService: IExtensionService,
345+
@IWorkspaceContextService contextService: IWorkspaceContextService
346+
) {
347+
super(viewletId, `${viewletId}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
348+
this._register(this.onDidChangeFilterValue.event(newFilterValue => {
349+
this.filterValue = newFilterValue;
350+
this.onFilterChanged(newFilterValue);
351+
}));
352+
353+
this._register(this.viewsModel.onDidChangeActiveViews((viewDescriptors) => {
354+
viewDescriptors.forEach(descriptor => {
355+
let filterOnValue = this.getFilterOn(descriptor);
356+
if (!filterOnValue) {
357+
return;
358+
}
359+
if (!this.allViews.has(filterOnValue)) {
360+
this.allViews.set(filterOnValue, new Map());
361+
}
362+
this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor);
363+
if (filterOnValue !== this.filterValue) {
364+
this.viewsModel.setVisible(descriptor.id, false);
365+
}
366+
});
367+
}));
368+
}
369+
370+
protected addConstantViewDescriptors(constantViewDescriptors: IViewDescriptor[]) {
371+
constantViewDescriptors.forEach(viewDescriptor => this.constantViewDescriptors.set(viewDescriptor.id, viewDescriptor));
372+
}
373+
374+
protected abstract getFilterOn(viewDescriptor: IViewDescriptor): string | undefined;
375+
376+
private onFilterChanged(newFilterValue: string) {
377+
this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false));
378+
this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true));
379+
}
380+
381+
getContextMenuActions(): IAction[] {
382+
const result: IAction[] = [];
383+
let viewToggleActions: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => (<IAction>{
384+
id: `${viewDescriptor.id}.toggleVisibility`,
385+
label: viewDescriptor.name,
386+
checked: this.viewsModel.isVisible(viewDescriptor.id),
387+
enabled: viewDescriptor.canToggleVisibility,
388+
run: () => this.toggleViewVisibility(viewDescriptor.id)
389+
}));
390+
391+
result.push(...viewToggleActions);
392+
const parentActions = this.getViewletContextMenuActions();
393+
if (viewToggleActions.length && parentActions.length) {
394+
result.push(new Separator());
395+
}
396+
397+
result.push(...parentActions);
398+
return result;
399+
}
400+
401+
private getViewsForTarget(target: string): IViewDescriptor[] {
402+
return this.allViews.has(target) ? Array.from(this.allViews.get(target)!.values()) : [];
403+
}
404+
405+
private getViewsNotForTarget(target: string): IViewDescriptor[] {
406+
const iterable = this.allViews.keys();
407+
let key = iterable.next();
408+
let views: IViewDescriptor[] = [];
409+
while (!key.done) {
410+
if (key.value !== target) {
411+
views = views.concat(this.getViewsForTarget(key.value));
412+
}
413+
key = iterable.next();
414+
}
415+
return views;
416+
}
417+
418+
onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] {
419+
const panels: ViewletPanel[] = super.onDidAddViews(added);
420+
for (let i = 0; i < added.length; i++) {
421+
if (this.constantViewDescriptors.has(added[i].viewDescriptor.id)) {
422+
panels[i].setExpanded(false);
423+
}
424+
}
425+
return panels;
426+
}
427+
428+
abstract getTitle(): string;
429+
}
430+
324431
export class FileIconThemableWorkbenchTree extends WorkbenchTree {
325432

326433
constructor(
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as nls from 'vs/nls';
7+
import * as dom from 'vs/base/browser/dom';
8+
9+
import { IActionRunner, IAction, Action } from 'vs/base/common/actions';
10+
import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
11+
import { IThemeService } from 'vs/platform/theme/common/themeService';
12+
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
13+
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
14+
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
15+
import { selectBorder } from 'vs/platform/theme/common/colorRegistry';
16+
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
17+
import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
18+
import { IViewDescriptor } from 'vs/workbench/common/views';
19+
import { startsWith } from 'vs/base/common/strings';
20+
import { isStringArray } from 'vs/base/common/types';
21+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
22+
23+
export interface IRemoteSelectItem extends ISelectOptionItem {
24+
authority: string[];
25+
}
26+
27+
export class SwitchRemoteViewItem extends SelectActionViewItem {
28+
29+
actionRunner!: IActionRunner;
30+
31+
constructor(
32+
action: IAction,
33+
private readonly optionsItems: IRemoteSelectItem[],
34+
@IThemeService private readonly themeService: IThemeService,
35+
@IContextViewService contextViewService: IContextViewService,
36+
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService,
37+
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
38+
) {
39+
super(null, action, optionsItems, 0, contextViewService, { ariaLabel: nls.localize('remotes', 'Switch Remote') });
40+
this._register(attachSelectBoxStyler(this.selectBox, themeService, {
41+
selectBackground: SIDE_BAR_BACKGROUND
42+
}));
43+
44+
this.setSelectionForConnection(optionsItems, environmentService, remoteExplorerService);
45+
}
46+
47+
private setSelectionForConnection(optionsItems: IRemoteSelectItem[], environmentService: IWorkbenchEnvironmentService, remoteExplorerService: IRemoteExplorerService) {
48+
// TODO: set from saved state
49+
if (this.optionsItems.length > 0) {
50+
const remoteAuthority = environmentService.configuration.remoteAuthority;
51+
let index = 0;
52+
if (remoteAuthority) {
53+
const actualRemoteAuthority = remoteAuthority.split('+')[0];
54+
for (let optionIterator = 0; (optionIterator < this.optionsItems.length) && (index === 0); optionIterator++) {
55+
for (let authorityIterator = 0; authorityIterator < optionsItems[optionIterator].authority.length; authorityIterator++) {
56+
if (optionsItems[optionIterator].authority[authorityIterator] === actualRemoteAuthority) {
57+
index = optionIterator;
58+
break;
59+
}
60+
}
61+
}
62+
}
63+
this.select(index);
64+
remoteExplorerService.targetType = optionsItems[index].authority[0];
65+
}
66+
}
67+
68+
render(container: HTMLElement) {
69+
super.render(container);
70+
dom.addClass(container, 'switch-remote');
71+
this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => {
72+
container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : '';
73+
}));
74+
}
75+
76+
protected getActionContext(_: string, index: number): any {
77+
return this.optionsItems[index];
78+
}
79+
80+
static createOptionItems(views: IViewDescriptor[]): IRemoteSelectItem[] {
81+
let options: IRemoteSelectItem[] = [];
82+
views.forEach(view => {
83+
if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority) {
84+
options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] });
85+
}
86+
});
87+
return options;
88+
}
89+
}
90+
91+
export class SwitchRemoteAction extends Action {
92+
93+
public static readonly ID = 'remote.explorer.switch';
94+
public static readonly LABEL = nls.localize('remote.explorer.switch', "Switch Remote");
95+
96+
constructor(
97+
id: string, label: string,
98+
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService
99+
) {
100+
super(id, label);
101+
}
102+
103+
public async run(item: IRemoteSelectItem): Promise<any> {
104+
this.remoteExplorerService.targetType = item.authority[0];
105+
}
106+
}

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

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
1616
import { IThemeService } from 'vs/platform/theme/common/themeService';
1717
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
1818
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
19-
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
19+
import { FilterViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
2020
import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution';
2121
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
22-
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
2322
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2423
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2524
import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views';
@@ -47,7 +46,10 @@ import Severity from 'vs/base/common/severity';
4746
import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions';
4847
import { IDisposable } from 'vs/base/common/lifecycle';
4948
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
50-
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
49+
import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems';
50+
import { Action, IActionViewItem, IAction } from 'vs/base/common/actions';
51+
import { isStringArray } from 'vs/base/common/types';
52+
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
5153

5254
interface HelpInformation {
5355
extensionDescription: IExtensionDescription;
@@ -364,10 +366,9 @@ class HelpPanelDescriptor implements IViewDescriptor {
364366
}
365367
}
366368

367-
368-
export class RemoteViewlet extends ViewContainerViewlet implements IViewModel {
369+
export class RemoteViewlet extends FilterViewContainerViewlet implements IViewModel {
369370
private helpPanelDescriptor = new HelpPanelDescriptor(this);
370-
371+
private actions: IAction[] | undefined;
371372
helpInformations: HelpInformation[] = [];
372373

373374
constructor(
@@ -380,10 +381,10 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel {
380381
@IThemeService themeService: IThemeService,
381382
@IContextMenuService contextMenuService: IContextMenuService,
382383
@IExtensionService extensionService: IExtensionService,
383-
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
384+
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService
384385
) {
385-
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
386-
386+
super(VIEWLET_ID, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
387+
this.addConstantViewDescriptors([this.helpPanelDescriptor]);
387388
remoteHelpExtPoint.setHandler((extensions) => {
388389
let helpInformation: HelpInformation[] = [];
389390
for (let extension of extensions) {
@@ -399,6 +400,34 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel {
399400
viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER);
400401
}
401402
});
403+
404+
this._register(this.remoteExplorerService.onDidChangeTargetType(() => {
405+
this.onDidChangeFilterValue.fire(this.remoteExplorerService.targetType);
406+
}));
407+
}
408+
409+
protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined {
410+
return isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority[0] : viewDescriptor.remoteAuthority;
411+
}
412+
413+
public getActionViewItem(action: Action): IActionViewItem | undefined {
414+
if (action.id === SwitchRemoteAction.ID) {
415+
return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER)));
416+
}
417+
418+
return super.getActionViewItem(action);
419+
}
420+
421+
public getActions(): IAction[] {
422+
if (!this.actions) {
423+
this.actions = [
424+
this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL),
425+
];
426+
this.actions.forEach(a => {
427+
this._register(a);
428+
});
429+
}
430+
return this.actions;
402431
}
403432

404433
private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser<HelpInformation>, helpInformation: HelpInformation[]) {
@@ -419,38 +448,6 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel {
419448
});
420449
}
421450

422-
onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] {
423-
// too late, already added to the view model
424-
const result = super.onDidAddViews(added);
425-
426-
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
427-
if (remoteAuthority) {
428-
const actualRemoteAuthority = remoteAuthority.split('+')[0];
429-
added.forEach((descriptor) => {
430-
const panel = this.getView(descriptor.viewDescriptor.id);
431-
if (!panel) {
432-
return;
433-
}
434-
435-
const descriptorAuthority = descriptor.viewDescriptor.remoteAuthority;
436-
if (typeof descriptorAuthority === 'undefined') {
437-
panel.setExpanded(true);
438-
} else if (descriptor.viewDescriptor.id === HelpPanel.ID) {
439-
// Do nothing, keep the default behavior for Help
440-
} else {
441-
const descriptorAuthorityArr = Array.isArray(descriptorAuthority) ? descriptorAuthority : [descriptorAuthority];
442-
if (descriptorAuthorityArr.indexOf(actualRemoteAuthority) >= 0) {
443-
panel.setExpanded(true);
444-
} else {
445-
panel.setExpanded(false);
446-
}
447-
}
448-
});
449-
}
450-
451-
return result;
452-
}
453-
454451
getTitle(): string {
455452
const title = nls.localize('remote.explorer', "Remote Explorer");
456453
return title;

0 commit comments

Comments
 (0)