Skip to content

Commit

Permalink
[shell/plugin] open view command and menu
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Jul 15, 2019
1 parent 30019c7 commit 280d9f0
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 52 deletions.
22 changes: 22 additions & 0 deletions packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { ResourceContextKey } from './resource-context-key';
import { UriSelection } from '../common/selection';
import { StorageService } from './storage-service';
import { Navigatable } from './navigatable';
import { QuickViewService } from './quick-view-service';
import { PrefixQuickOpenService } from './quick-open';

export namespace CommonMenus {

Expand Down Expand Up @@ -156,6 +158,11 @@ export namespace CommonCommands {
category: VIEW_CATEGORY,
label: 'Toggle Maximized'
};
export const OPEN_VIEW: Command = {
id: 'core.openView',
category: VIEW_CATEGORY,
label: 'Open View'
};

export const SAVE: Command = {
id: 'core.save',
Expand Down Expand Up @@ -224,6 +231,12 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
@inject(StorageService)
protected readonly storageService: StorageService;

@inject(QuickViewService)
protected readonly quickView: QuickViewService;

@inject(PrefixQuickOpenService)
protected readonly quickOpen: PrefixQuickOpenService;

@postConstruct()
protected init(): void {
this.contextKeyService.createKey<boolean>('isLinux', OS.type() === OS.Type.Linux);
Expand Down Expand Up @@ -348,6 +361,11 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
label: 'About',
order: '9'
});

registry.registerMenuAction(CommonMenus.VIEW_PRIMARY, {
commandId: CommonCommands.OPEN_VIEW.id,
label: 'Open View...'
});
}

registerCommands(commandRegistry: CommandRegistry): void {
Expand Down Expand Up @@ -486,6 +504,10 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
commandRegistry.registerCommand(CommonCommands.ABOUT_COMMAND, {
execute: () => this.openAbout()
});

commandRegistry.registerCommand(CommonCommands.OPEN_VIEW, {
execute: () => this.quickOpen.open(this.quickView.prefix)
});
}

private findTabBar(event?: Event): TabBar<Widget> | undefined {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import { KeyboardLayoutService } from './keyboard/keyboard-layout-service';
import { MimeService } from './mime-service';
import { ApplicationShellMouseTracker } from './shell/application-shell-mouse-tracker';
import { ViewContainer, ViewContainerIdentifier } from './view-container';
import { QuickViewService } from './quick-view-service';

export const frontendApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => {
const themeService = ThemeService.get();
Expand Down Expand Up @@ -247,6 +248,9 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
container.bind(ViewContainer).toSelf().inSingletonScope();
return container.get(ViewContainer);
});

bind(QuickViewService).toSelf().inSingletonScope();
bind(QuickOpenContribution).toService(QuickViewService);
});

export function bindMessageService(bind: interfaces.Bind): interfaces.BindingWhenOnSyntax<MessageService> {
Expand Down
76 changes: 76 additions & 0 deletions packages/core/src/browser/quick-view-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/********************************************************************************
* Copyright (C) 2019 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable } from 'inversify';
import { QuickOpenModel, QuickOpenHandler, QuickOpenOptions, QuickOpenItem, QuickOpenMode, QuickOpenContribution, QuickOpenHandlerRegistry } from './quick-open';
import { Disposable } from '../common/disposable';

export interface QuickViewItem {
readonly label: string;
// tslint:disable-next-line:no-any
readonly open: () => any;
}

@injectable()
export class QuickViewService implements QuickOpenModel, QuickOpenHandler, QuickOpenContribution {

readonly prefix: string = 'view ';

readonly description: string = 'Open View';

protected readonly items: QuickOpenItem[] = [];

registerItem(item: QuickViewItem): Disposable {
const quickOpenItem = new QuickOpenItem({
label: item.label,
run: mode => {
if (mode !== QuickOpenMode.OPEN) {
return false;
}
item.open();
return true;
}
});
this.items.push(quickOpenItem);
this.items.sort((a, b) => a.getLabel()!.localeCompare(b.getLabel()!));
return Disposable.create(() => {
const index = this.items.indexOf(quickOpenItem);
if (index !== -1) {
this.items.splice(index, 1);
}
});
}

getModel(): QuickOpenModel {
return this;
}

getOptions(): QuickOpenOptions {
return {
skipPrefix: this.prefix.length,
fuzzyMatchLabel: true
};
}

onType(_: string, acceptor: (items: QuickOpenItem[]) => void): void {
acceptor(this.items);
}

registerQuickOpenHandlers(handlers: QuickOpenHandlerRegistry): void {
handlers.registerHandler(this);
}

}
88 changes: 67 additions & 21 deletions packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ export class ApplicationShell extends Widget {
*
* Widgets added to the top area are not tracked regarding the _current_ and _active_ states.
*/
addWidget(widget: Widget, options: Readonly<ApplicationShell.WidgetOptions> = {}) {
async addWidget(widget: Widget, options: Readonly<ApplicationShell.WidgetOptions> = {}): Promise<void> {
if (!widget.id) {
console.error('Widgets added to the application shell must have a unique id property.');
return;
Expand Down Expand Up @@ -720,7 +720,7 @@ export class ApplicationShell extends Widget {
throw new Error('Unexpected area: ' + options.area);
}
if (area !== 'top') {
this.track(widget);
await this.track(widget);
}
}

Expand Down Expand Up @@ -869,15 +869,27 @@ export class ApplicationShell extends Widget {
Saveable.apply(widget);
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
for (const toTrack of await widget.getTrackableWidgets()) {
this.tracker.add(toTrack);
Saveable.apply(toTrack);
await this.track(toTrack);
}
if (widget.onDidChangeTrackableWidgets) {
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
}
}
}

protected toTrackedStack(id: string): Widget[] {
const tracked = new Map<string, Widget>(this.tracker.widgets.map(w => [w.id, w] as [string, Widget]));
let current = tracked.get(id);
const stack: Widget[] = [];
while (current) {
if (tracked.has(current.id)) {
stack.push(current);
}
current = current.parent || undefined;
}
return stack;
}

/**
* Activate a widget in the application shell. This makes the widget visible and usually
* also assigns focus to it.
Expand All @@ -889,38 +901,45 @@ export class ApplicationShell extends Widget {
* @returns the activated widget if it was found
*/
activateWidget(id: string): Widget | undefined {
let widget = this.tracker.widgets.find(w => w.id === id);
while (widget) {
if (widget.id) {
const result = this.doActivateWidget(widget.id);
if (result) {
return result;
}
const stack = this.toTrackedStack(id);
let current = stack.pop();
if (current && !this.doActivateWidget(current.id)) {
return undefined;
}
while (current && stack.length) {
const child = stack.pop()!;
if (ApplicationShell.TrackableWidgetProvider.is(current) && current.activateWidget) {
current = current.activateWidget(child.id);
} else {
child.activate();
current = child;
}
widget = widget.parent || undefined;
}
return undefined;
return current && this.checkActivation(current);
}

/**
* Activate top-level area widget.
*/
protected doActivateWidget(id: string): Widget | undefined {
let widget = find(this.mainPanel.widgets(), w => w.id === id);
if (widget) {
this.mainPanel.activateWidget(widget);
return this.checkActivation(widget);
return widget;
}
widget = find(this.bottomPanel.widgets(), w => w.id === id);
if (widget) {
this.expandBottomPanel();
this.bottomPanel.activateWidget(widget);
return this.checkActivation(widget);
return widget;
}
widget = this.leftPanelHandler.activate(id);
if (widget) {
return this.checkActivation(widget);
return widget;
}
widget = this.rightPanelHandler.activate(id);
if (widget) {
return this.checkActivation(widget);
return widget;
}
}

Expand All @@ -947,6 +966,26 @@ export class ApplicationShell extends Widget {
* @returns the revealed widget if it was found
*/
revealWidget(id: string): Widget | undefined {
const stack = this.toTrackedStack(id);
let current = stack.pop();
if (current && !this.doRevealWidget(current.id)) {
return undefined;
}
while (current && stack.length) {
const child = stack.pop()!;
if (ApplicationShell.TrackableWidgetProvider.is(current) && current.revealWidget) {
current = current.revealWidget(child.id);
} else {
current = child;
}
}
return current;
}

/**
* Reveal top-level area widget.
*/
protected doRevealWidget(id: string): Widget | undefined {
let widget = find(this.mainPanel.widgets(), w => w.id === id);
if (!widget) {
widget = find(this.bottomPanel.widgets(), w => w.id === id);
Expand All @@ -965,10 +1004,7 @@ export class ApplicationShell extends Widget {
if (widget) {
return widget;
}
widget = this.rightPanelHandler.expand(id);
if (widget) {
return widget;
}
return this.rightPanelHandler.expand(id);
}

/**
Expand Down Expand Up @@ -1545,6 +1581,16 @@ export namespace ApplicationShell {
export interface TrackableWidgetProvider {
getTrackableWidgets(): MaybePromise<Widget[]>
readonly onDidChangeTrackableWidgets?: CommonEvent<Widget[]>
/**
* Make visible and focus a trackable widget for the given id.
* If not implemented then `activate` request will be sent to a child widget directly.
*/
activateWidget?(id: string): Widget | undefined;
/**
* Make visible a trackable widget for the given id.
* If not implemented then a widget should be always visible when an owner is visible.
*/
revealWidget?(id: string): Widget | undefined;
}

export namespace TrackableWidgetProvider {
Expand Down
14 changes: 11 additions & 3 deletions packages/core/src/browser/shell/view-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { KeybindingContribution, KeybindingRegistry } from '../keybinding';
import { WidgetManager } from '../widget-manager';
import { CommonMenus } from '../common-frontend-contribution';
import { ApplicationShell } from './application-shell';
import { QuickViewService } from '../quick-view-service';

export interface OpenViewArguments extends ApplicationShell.WidgetOptions {
toggle?: boolean
Expand Down Expand Up @@ -58,6 +59,9 @@ export abstract class AbstractViewContribution<T extends Widget> implements Comm
@inject(WidgetManager) protected readonly widgetManager: WidgetManager;
@inject(ApplicationShell) protected readonly shell: ApplicationShell;

@inject(QuickViewService)
protected readonly quickView: QuickViewService;

readonly toggleCommand?: Command;

constructor(
Expand Down Expand Up @@ -90,15 +94,15 @@ export abstract class AbstractViewContribution<T extends Widget> implements Comm
...this.options.defaultWidgetOptions,
...args
};
shell.addWidget(widget, widgetArgs);
await shell.addWidget(widget, widgetArgs);
} else if (args.toggle && area && shell.isExpanded(area) && tabBar.currentTitle === widget.title) {
// The widget is attached and visible, so close it (toggle)
widget.close();
}
if (widget.isAttached && args.activate) {
shell.activateWidget(widget.id);
shell.activateWidget(this.options.widgetId);
} else if (widget.isAttached && args.reveal) {
shell.revealWidget(widget.id);
shell.revealWidget(this.options.widgetId);
}
return this.widget;
}
Expand All @@ -112,6 +116,10 @@ export abstract class AbstractViewContribution<T extends Widget> implements Comm
})
});
}
this.quickView.registerItem({
label: this.options.widgetName,
open: () => this.openView({ activate: true })
});
}

registerMenus(menus: MenuModelRegistry): void {
Expand Down
Loading

0 comments on commit 280d9f0

Please sign in to comment.