Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CHE-3896 add the needed methods for Webview API #4892

Merged
merged 1 commit into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,13 @@ export interface StatusBarExt {
export enum EditorPosition {
ONE = 0,
TWO = 1,
THREE = 2
THREE = 2,
FOUR = 3,
FIVE = 4,
SIX = 5,
SEVEN = 6,
EIGHT = 7,
NINE = 8
}

export interface Position {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { DebugService } from '@theia/debug/lib/common/debug-service';
import { PluginSharedStyle } from './plugin-shared-style';
import { FSResourceResolver } from './file-system-main';
import { SelectionProviderCommandContribution } from './selection-provider-command';
import { ViewColumnService } from './view-column-service';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bindHostedPluginPreferences(bind);
Expand Down Expand Up @@ -139,4 +140,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(DebugService).toService(PluginDebugService);
bind(PluginDebugSessionContributionRegistry).toSelf().inSingletonScope();
rebind(DebugSessionContributionRegistry).toService(PluginDebugSessionContributionRegistry);

bind(ViewColumnService).toSelf().inSingletonScope();
});
95 changes: 95 additions & 0 deletions packages/plugin-ext/src/main/browser/view-column-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/********************************************************************************
* Copyright (C) 2019 Red Hat, Inc. 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, inject } from 'inversify';
import { Emitter, Event } from '@theia/core';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { TheiaDockPanel } from '@theia/core/lib/browser/shell/theia-dock-panel';
import { toArray } from '@phosphor/algorithm';

@injectable()
export class ViewColumnService {
private mainPanel: TheiaDockPanel;
private columnValues = new Map<string, number>();
private viewColumnIds = new Map<number, string[]>();

protected readonly onViewColumnChangedEmitter = new Emitter<{ id: string, viewColumn: number }>();

constructor(
@inject(ApplicationShell) private readonly shell: ApplicationShell,
) {
this.mainPanel = this.shell.mainPanel;

let oldColumnValues = new Map<string, number>();
const update = async () => {
await new Promise((resolve => setTimeout(() => resolve())));
this.updateViewColumns();
this.viewColumnIds.forEach((ids: string[], viewColumn: number) => {
ids.forEach((id: string) => {
if (!oldColumnValues.has(id) || oldColumnValues.get(id) !== viewColumn) {
this.onViewColumnChangedEmitter.fire({ id, viewColumn });
}
});
});
oldColumnValues = new Map(this.columnValues.entries());
};
this.mainPanel.widgetAdded.connect(() => update());
this.mainPanel.widgetRemoved.connect(() => update());
}

get onViewColumnChanged(): Event<{ id: string, viewColumn: number }> {
return this.onViewColumnChangedEmitter.event;
}

updateViewColumns(): void {
const positionIds = new Map<number, string[]>();
toArray(this.mainPanel.tabBars()).forEach(tabBar => {
if (!tabBar.node.style.left) {
return;
}
const position = parseInt(tabBar.node.style.left);
const viewColumnIds = tabBar.titles.map(title => title.owner.id);
positionIds.set(position, viewColumnIds);
});
this.columnValues.clear();
this.viewColumnIds.clear();
[...positionIds.keys()].sort((a, b) => a - b).forEach((key: number, viewColumn: number) => {
positionIds.get(key)!.forEach((id: string) => {
this.columnValues.set(id, viewColumn);
if (!this.viewColumnIds.has(viewColumn)) {
this.viewColumnIds.set(viewColumn, []);
}
this.viewColumnIds.get(viewColumn)!.push(id);
});
});
}

getViewColumnIds(viewColumn: number): string[] {
return this.viewColumnIds.get(viewColumn) || [];
}

getViewColumn(id: string): number | undefined {
return this.columnValues.get(id);
}

hasViewColumn(id: string): boolean {
return this.columnValues.has(id);
}

viewColumnsSize(): number {
return this.viewColumnIds.size;
}
}
3 changes: 1 addition & 2 deletions packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class WebviewWidget extends BaseWidget {
private static readonly ID = new IdGenerator('webview-widget-');
protected readonly toDispose = new DisposableCollection();
private iframe: HTMLIFrameElement;
private state: string | undefined = undefined;
private state: { [key: string]: any } | undefined = undefined;
private loadTimeout: number | undefined;

constructor(title: string, private options: WebviewWidgetOptions, private eventDelegate: WebviewEvents) {
Expand Down Expand Up @@ -73,7 +73,6 @@ export class WebviewWidget extends BaseWidget {
}

setHTML(html: string) {
html = html.replace(/theia-resource:/g, '/webview/');
const newDocument = new DOMParser().parseFromString(html, 'text/html');
if (!newDocument || !newDocument.body) {
return;
Expand Down
121 changes: 107 additions & 14 deletions packages/plugin-ext/src/main/browser/webviews-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,40 @@ import { WebviewWidget } from './webview/webview';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { ThemeRulesService } from './webview/theme-rules-service';
import { DisposableCollection } from '@theia/core';
import { ViewColumnService } from './view-column-service';

import debounce = require('lodash.debounce');

export class WebviewsMainImpl implements WebviewsMain {
private readonly revivers = new Set<string>();
private readonly proxy: WebviewsExt;
protected readonly shell: ApplicationShell;
protected readonly viewColumnService: ViewColumnService;
protected readonly keybindingRegistry: KeybindingRegistry;
protected readonly themeService = ThemeService.get();
protected readonly themeRulesService = ThemeRulesService.get();
protected readonly updateViewOptions: () => void;

private readonly views = new Map<string, WebviewWidget>();
private readonly viewsOptions = new Map<string, { panelOptions: WebviewPanelShowOptions; panelId: string; active: boolean; visible: boolean; }>();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WEBVIEWS_EXT);
this.shell = container.get(ApplicationShell);
this.keybindingRegistry = container.get(KeybindingRegistry);
this.viewColumnService = container.get(ViewColumnService);
this.updateViewOptions = debounce<() => void>(() => {
for (const key of this.viewsOptions.keys()) {
this.checkViewOptions(key);
}
}, 100);
this.shell.activeChanged.connect(() => this.updateViewOptions());
this.shell.currentChanged.connect(() => this.updateViewOptions());
this.viewColumnService.onViewColumnChanged(() => this.updateViewOptions());
}

$createWebviewPanel(
viewId: string,
panelId: string,
viewType: string,
title: string,
showOptions: WebviewPanelShowOptions,
Expand All @@ -54,7 +70,7 @@ export class WebviewsMainImpl implements WebviewsMain {
allowScripts: options ? options.enableScripts : false
}, {
onMessage: m => {
this.proxy.$onMessage(viewId, m);
this.proxy.$onMessage(panelId, m);
},
onKeyboardEvent: e => {
this.keybindingRegistry.run(e);
Expand All @@ -71,7 +87,7 @@ export class WebviewsMainImpl implements WebviewsMain {
const parent = contentDocument.head ? contentDocument.head : contentDocument.body;
styleElement = this.themeRulesService.createStyleSheet(parent);
styleElement.id = styleId;
parent.appendChild((styleElement));
parent.appendChild(styleElement);
}

this.themeRulesService.setRules(styleElement, this.themeRulesService.getCurrentThemeRules());
Expand All @@ -82,23 +98,61 @@ export class WebviewsMainImpl implements WebviewsMain {
});
view.disposed.connect(() => {
toDispose.dispose();
this.onCloseView(viewId);
this.onCloseView(panelId);
});
this.views.set(viewId, view);

this.views.set(panelId, view);
this.viewsOptions.set(view.id, { panelOptions: showOptions, panelId, visible: false, active: false });
this.addOrReattachWidget(panelId, showOptions);
}
private addOrReattachWidget(handler: string, showOptions: WebviewPanelShowOptions) {
const view = this.views.get(handler);
if (!view) {
return;
}
const widgetOptions: ApplicationShell.WidgetOptions = { area: showOptions.area ? showOptions.area : 'main' };
// FIXME translate all view columns properly

let mode = 'open-to-right';
if (showOptions.viewColumn === -2) {
const ref = this.shell.currentWidget;
if (ref && this.shell.getAreaFor(ref) === widgetOptions.area) {
Object.assign(widgetOptions, { ref, mode: 'open-to-right' });
Object.assign(widgetOptions, { ref, mode });
}
} else if (widgetOptions.area === 'main' && showOptions.viewColumn !== undefined) {
this.viewColumnService.updateViewColumns();
let widgetIds = this.viewColumnService.getViewColumnIds(showOptions.viewColumn);
if (widgetIds.length > 0) {
mode = 'tab-after';
} else if (showOptions.viewColumn >= 0) {
const columnsSize = this.viewColumnService.viewColumnsSize();
if (columnsSize) {
showOptions.viewColumn = columnsSize - 1;
widgetIds = this.viewColumnService.getViewColumnIds(showOptions.viewColumn);
}
}
const ref = this.shell.getWidgets(widgetOptions.area).find(widget => widget.isVisible && widgetIds.indexOf(widget.id) !== -1);
if (ref) {
Object.assign(widgetOptions, { ref, mode });
}
}

this.shell.addWidget(view, widgetOptions);
const visible = true;
let active: boolean;
if (showOptions.preserveFocus) {
this.shell.revealWidget(view.id);
active = false;
} else {
this.shell.activateWidget(view.id);
active = true;
}
const options = this.viewsOptions.get(view.id);
if (!options) {
return;
}
options.panelOptions = showOptions;
options.visible = visible;
options.active = active;
}
$disposeWebview(handle: string): void {
const view = this.views.get(handle);
Expand All @@ -107,15 +161,29 @@ export class WebviewsMainImpl implements WebviewsMain {
}
}
$reveal(handle: string, showOptions: WebviewPanelShowOptions): void {
const webview = this.getWebview(handle);
if (webview.isDisposed) {
const view = this.getWebview(handle);
if (view.isDisposed) {
return;
}
// FIXME handle view column here too!
if (showOptions.viewColumn !== undefined || showOptions.area !== undefined) {
this.viewColumnService.updateViewColumns();
const options = this.viewsOptions.get(view.id);
if (!options) {
return;
}
const columnIds = showOptions.viewColumn ? this.viewColumnService.getViewColumnIds(showOptions.viewColumn) : [];
if (columnIds.indexOf(view.id) === -1 || options.panelOptions.area !== showOptions.area) {
this.addOrReattachWidget(options.panelId, showOptions);
options.panelOptions = showOptions;
this.checkViewOptions(view.id, options.panelOptions.viewColumn);
this.updateViewOptions();
return;
}
}
if (showOptions.preserveFocus) {
this.shell.revealWidget(webview.id);
this.shell.revealWidget(view.id);
} else {
this.shell.activateWidget(webview.id);
this.shell.activateWidget(view.id);
}
}
$setTitle(handle: string, value: string): void {
Expand Down Expand Up @@ -144,10 +212,34 @@ export class WebviewsMainImpl implements WebviewsMain {
return Promise.resolve(webview !== undefined);
}
$registerSerializer(viewType: string): void {
throw new Error('Method not implemented.');
this.revivers.add(viewType);
}
$unregisterSerializer(viewType: string): void {
throw new Error('Method not implemented.');
this.revivers.delete(viewType);
}

private async checkViewOptions(handler: string, viewColumn?: number | undefined) {
const options = this.viewsOptions.get(handler);
if (!options || !options.panelOptions) {
return;
}
const view = this.views.get(options.panelId);
if (!view) {
return;
}
const active = !!this.shell.activeWidget ? this.shell.activeWidget.id === view!.id : false;
const visible = view!.isVisible;
if (viewColumn === undefined) {
this.viewColumnService.updateViewColumns();
viewColumn = this.viewColumnService.hasViewColumn(view.id) ? this.viewColumnService.getViewColumn(view.id)! : 0;
if (options.panelOptions.viewColumn === viewColumn && options.visible === visible && options.active === active) {
return;
}
}
options.active = active;
options.visible = visible;
options.panelOptions.viewColumn = viewColumn;
this.proxy.$onDidChangeWebviewPanelViewState(options.panelId, { active, visible, position: options.panelOptions.viewColumn! });
}

private getWebview(viewId: string): WebviewWidget {
Expand All @@ -165,6 +257,7 @@ export class WebviewsMainImpl implements WebviewsMain {
}
const cleanUp = () => {
this.views.delete(viewId);
this.viewsOptions.delete(viewId);
};
this.proxy.$onDidDisposeWebviewPanel(viewId).then(cleanUp, cleanUp);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ export function toViewColumn(ep?: EditorPosition): theia.ViewColumn | undefined
return <number>types.ViewColumn.Two;
} else if (ep === EditorPosition.THREE) {
return <number>types.ViewColumn.Three;
} else if (ep === EditorPosition.FOUR) {
return <number>types.ViewColumn.Four;
} else if (ep === EditorPosition.FIVE) {
return <number>types.ViewColumn.Five;
} else if (ep === EditorPosition.SIX) {
return <number>types.ViewColumn.Six;
} else if (ep === EditorPosition.SEVEN) {
return <number>types.ViewColumn.Seven;
} else if (ep === EditorPosition.EIGHT) {
return <number>types.ViewColumn.Eight;
} else if (ep === EditorPosition.NINE) {
return <number>types.ViewColumn.Nine;
}

return undefined;
Expand Down
8 changes: 6 additions & 2 deletions packages/plugin-ext/src/plugin/webviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ export class WebviewsExtImpl implements WebviewsExt {
}

private getWebviewPanel(viewId: string): WebviewPanelImpl | undefined {
return this.webviewPanels.get(viewId);
if (this.webviewPanels.has(viewId)) {
return this.webviewPanels.get(viewId);
}
return undefined;
}
}

Expand Down Expand Up @@ -164,7 +167,8 @@ export class WebviewImpl implements theia.Webview {
return this._html;
}

set html(newHtml: string) {
set html(html: string) {
const newHtml = html.replace(new RegExp('theia-resource:/', 'g'), '/webview/');
this.checkIsDisposed();
if (this._html !== newHtml) {
this._html = newHtml;
Expand Down