mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-23 19:59:37 +00:00
Add webview restoration api proposal (#46380)
Adds a proposed webiew serialization api that allows webviews to be restored automatically when vscode restarts
This commit is contained in:
@@ -22,7 +22,8 @@
|
|||||||
"onCommand:markdown.showPreviewToSide",
|
"onCommand:markdown.showPreviewToSide",
|
||||||
"onCommand:markdown.showLockedPreviewToSide",
|
"onCommand:markdown.showLockedPreviewToSide",
|
||||||
"onCommand:markdown.showSource",
|
"onCommand:markdown.showSource",
|
||||||
"onCommand:markdown.showPreviewSecuritySelector"
|
"onCommand:markdown.showPreviewSecuritySelector",
|
||||||
|
"onView:markdown.preview"
|
||||||
],
|
],
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"commands": [
|
"commands": [
|
||||||
|
|||||||
@@ -18,37 +18,85 @@ const localize = nls.loadMessageBundle();
|
|||||||
|
|
||||||
export class MarkdownPreview {
|
export class MarkdownPreview {
|
||||||
|
|
||||||
public static previewViewType = 'markdown.preview';
|
public static viewType = 'markdown.preview';
|
||||||
|
|
||||||
private readonly webview: vscode.Webview;
|
private readonly webview: vscode.Webview;
|
||||||
private throttleTimer: any;
|
private throttleTimer: any;
|
||||||
private initialLine: number | undefined = undefined;
|
private line: number | undefined = undefined;
|
||||||
private readonly disposables: vscode.Disposable[] = [];
|
private readonly disposables: vscode.Disposable[] = [];
|
||||||
private firstUpdate = true;
|
private firstUpdate = true;
|
||||||
private currentVersion?: { resource: vscode.Uri, version: number };
|
private currentVersion?: { resource: vscode.Uri, version: number };
|
||||||
private forceUpdate = false;
|
private forceUpdate = false;
|
||||||
private isScrolling = false;
|
private isScrolling = false;
|
||||||
|
|
||||||
constructor(
|
public static revive(
|
||||||
private _resource: vscode.Uri,
|
webview: vscode.Webview,
|
||||||
|
state: any,
|
||||||
|
contentProvider: MarkdownContentProvider,
|
||||||
|
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
logger: Logger,
|
||||||
|
topmostLineMonitor: MarkdownFileTopmostLineMonitor
|
||||||
|
): MarkdownPreview {
|
||||||
|
const resource = vscode.Uri.parse(state.resource);
|
||||||
|
const locked = state.locked;
|
||||||
|
const line = state.line;
|
||||||
|
|
||||||
|
const preview = new MarkdownPreview(
|
||||||
|
webview,
|
||||||
|
resource,
|
||||||
|
locked,
|
||||||
|
contentProvider,
|
||||||
|
previewConfigurations,
|
||||||
|
logger,
|
||||||
|
topmostLineMonitor);
|
||||||
|
|
||||||
|
if (!isNaN(line)) {
|
||||||
|
preview.line = line;
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(
|
||||||
|
resource: vscode.Uri,
|
||||||
previewColumn: vscode.ViewColumn,
|
previewColumn: vscode.ViewColumn,
|
||||||
public locked: boolean,
|
locked: boolean,
|
||||||
private readonly contentProvider: MarkdownContentProvider,
|
contentProvider: MarkdownContentProvider,
|
||||||
private readonly previewConfigurations: MarkdownPreviewConfigurationManager,
|
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
private readonly logger: Logger,
|
logger: Logger,
|
||||||
topmostLineMonitor: MarkdownFileTopmostLineMonitor,
|
topmostLineMonitor: MarkdownFileTopmostLineMonitor,
|
||||||
private readonly contributions: MarkdownContributions
|
contributions: MarkdownContributions
|
||||||
) {
|
): MarkdownPreview {
|
||||||
this.webview = vscode.window.createWebview(
|
const webview = vscode.window.createWebview(
|
||||||
MarkdownPreview.previewViewType,
|
MarkdownPreview.viewType,
|
||||||
this.getPreviewTitle(this._resource),
|
MarkdownPreview.getPreviewTitle(resource, locked),
|
||||||
previewColumn, {
|
previewColumn, {
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
enableCommandUris: true,
|
enableCommandUris: true,
|
||||||
enableFindWidget: true,
|
enableFindWidget: true,
|
||||||
localResourceRoots: this.getLocalResourceRoots(_resource)
|
localResourceRoots: MarkdownPreview.getLocalResourceRoots(resource, contributions)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return new MarkdownPreview(
|
||||||
|
webview,
|
||||||
|
resource,
|
||||||
|
locked,
|
||||||
|
contentProvider,
|
||||||
|
previewConfigurations,
|
||||||
|
logger,
|
||||||
|
topmostLineMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
webview: vscode.Webview,
|
||||||
|
private _resource: vscode.Uri,
|
||||||
|
public locked: boolean,
|
||||||
|
private readonly contentProvider: MarkdownContentProvider,
|
||||||
|
private readonly previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
topmostLineMonitor: MarkdownFileTopmostLineMonitor
|
||||||
|
) {
|
||||||
|
this.webview = webview;
|
||||||
|
|
||||||
this.webview.onDidDispose(() => {
|
this.webview.onDidDispose(() => {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
}, null, this.disposables);
|
}, null, this.disposables);
|
||||||
@@ -111,6 +159,14 @@ export class MarkdownPreview {
|
|||||||
return this._resource;
|
return this._resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get state() {
|
||||||
|
return {
|
||||||
|
resource: this.resource.toString(),
|
||||||
|
locked: this.locked,
|
||||||
|
line: this.line
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this._onDisposeEmitter.fire();
|
this._onDisposeEmitter.fire();
|
||||||
|
|
||||||
@@ -124,9 +180,7 @@ export class MarkdownPreview {
|
|||||||
public update(resource: vscode.Uri) {
|
public update(resource: vscode.Uri) {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (editor && editor.document.uri.fsPath === resource.fsPath) {
|
if (editor && editor.document.uri.fsPath === resource.fsPath) {
|
||||||
this.initialLine = getVisibleLine(editor);
|
this.line = getVisibleLine(editor);
|
||||||
} else {
|
|
||||||
this.initialLine = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have changed resources, cancel any pending updates
|
// If we have changed resources, cancel any pending updates
|
||||||
@@ -169,6 +223,10 @@ export class MarkdownPreview {
|
|||||||
return this._resource.fsPath === resource.fsPath;
|
return this._resource.fsPath === resource.fsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isWebviewOf(webview: vscode.Webview): boolean {
|
||||||
|
return this.webview === webview;
|
||||||
|
}
|
||||||
|
|
||||||
public matchesResource(
|
public matchesResource(
|
||||||
otherResource: vscode.Uri,
|
otherResource: vscode.Uri,
|
||||||
otherViewColumn: vscode.ViewColumn | undefined,
|
otherViewColumn: vscode.ViewColumn | undefined,
|
||||||
@@ -195,11 +253,11 @@ export class MarkdownPreview {
|
|||||||
|
|
||||||
public toggleLock() {
|
public toggleLock() {
|
||||||
this.locked = !this.locked;
|
this.locked = !this.locked;
|
||||||
this.webview.title = this.getPreviewTitle(this._resource);
|
this.webview.title = MarkdownPreview.getPreviewTitle(this._resource, this.locked);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPreviewTitle(resource: vscode.Uri): string {
|
private static getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
|
||||||
return this.locked
|
return locked
|
||||||
? localize('lockedPreviewTitle', '[Preview] {0}', path.basename(resource.fsPath))
|
? localize('lockedPreviewTitle', '[Preview] {0}', path.basename(resource.fsPath))
|
||||||
: localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
|
: localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
|
||||||
}
|
}
|
||||||
@@ -216,7 +274,7 @@ export class MarkdownPreview {
|
|||||||
|
|
||||||
if (typeof topLine === 'number') {
|
if (typeof topLine === 'number') {
|
||||||
this.logger.log('updateForView', { markdownFile: resource });
|
this.logger.log('updateForView', { markdownFile: resource });
|
||||||
this.initialLine = topLine;
|
this.line = topLine;
|
||||||
this.webview.postMessage({
|
this.webview.postMessage({
|
||||||
type: 'updateView',
|
type: 'updateView',
|
||||||
line: topLine,
|
line: topLine,
|
||||||
@@ -233,25 +291,28 @@ export class MarkdownPreview {
|
|||||||
|
|
||||||
const document = await vscode.workspace.openTextDocument(resource);
|
const document = await vscode.workspace.openTextDocument(resource);
|
||||||
if (!this.forceUpdate && this.currentVersion && this.currentVersion.resource.fsPath === resource.fsPath && this.currentVersion.version === document.version) {
|
if (!this.forceUpdate && this.currentVersion && this.currentVersion.resource.fsPath === resource.fsPath && this.currentVersion.version === document.version) {
|
||||||
if (this.initialLine) {
|
if (this.line) {
|
||||||
this.updateForView(resource, this.initialLine);
|
this.updateForView(resource, this.line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.forceUpdate = false;
|
this.forceUpdate = false;
|
||||||
|
|
||||||
this.currentVersion = { resource, version: document.version };
|
this.currentVersion = { resource, version: document.version };
|
||||||
this.contentProvider.provideTextDocumentContent(document, this.previewConfigurations, this.initialLine)
|
this.contentProvider.provideTextDocumentContent(document, this.previewConfigurations, this.line)
|
||||||
.then(content => {
|
.then(content => {
|
||||||
if (this._resource === resource) {
|
if (this._resource === resource) {
|
||||||
this.webview.title = this.getPreviewTitle(this._resource);
|
this.webview.title = MarkdownPreview.getPreviewTitle(this._resource, this.locked);
|
||||||
this.webview.html = content;
|
this.webview.html = content;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLocalResourceRoots(resource: vscode.Uri): vscode.Uri[] {
|
private static getLocalResourceRoots(
|
||||||
const baseRoots = this.contributions.previewResourceRoots;
|
resource: vscode.Uri,
|
||||||
|
contributions: MarkdownContributions
|
||||||
|
): vscode.Uri[] {
|
||||||
|
const baseRoots = contributions.previewResourceRoots;
|
||||||
|
|
||||||
const folder = vscode.workspace.getWorkspaceFolder(resource);
|
const folder = vscode.workspace.getWorkspaceFolder(resource);
|
||||||
if (folder) {
|
if (folder) {
|
||||||
@@ -266,6 +327,7 @@ export class MarkdownPreview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onDidScrollPreview(line: number) {
|
private onDidScrollPreview(line: number) {
|
||||||
|
this.line = line;
|
||||||
for (const editor of vscode.window.visibleTextEditors) {
|
for (const editor of vscode.window.visibleTextEditors) {
|
||||||
if (!this.isPreviewOf(editor.document.uri)) {
|
if (!this.isPreviewOf(editor.document.uri)) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { isMarkdownFile } from '../util/file';
|
|||||||
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||||
import { MarkdownContributions } from '../markdownExtensions';
|
import { MarkdownContributions } from '../markdownExtensions';
|
||||||
|
|
||||||
export class MarkdownPreviewManager {
|
export class MarkdownPreviewManager implements vscode.WebviewSerializer {
|
||||||
private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus';
|
private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus';
|
||||||
|
|
||||||
private readonly topmostLineMonitor = new MarkdownFileTopmostLineMonitor();
|
private readonly topmostLineMonitor = new MarkdownFileTopmostLineMonitor();
|
||||||
@@ -29,15 +29,14 @@ export class MarkdownPreviewManager {
|
|||||||
private readonly contributions: MarkdownContributions
|
private readonly contributions: MarkdownContributions
|
||||||
) {
|
) {
|
||||||
vscode.window.onDidChangeActiveTextEditor(editor => {
|
vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||||
if (editor) {
|
if (editor && isMarkdownFile(editor.document)) {
|
||||||
if (isMarkdownFile(editor.document)) {
|
|
||||||
for (const preview of this.previews.filter(preview => !preview.locked)) {
|
for (const preview of this.previews.filter(preview => !preview.locked)) {
|
||||||
preview.update(editor.document.uri);
|
preview.update(editor.document.uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, null, this.disposables);
|
}, null, this.disposables);
|
||||||
|
|
||||||
|
this.disposables.push(vscode.window.registerWebviewSerializer(MarkdownPreview.viewType, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
@@ -66,7 +65,6 @@ export class MarkdownPreviewManager {
|
|||||||
preview.reveal(previewSettings.previewColumn);
|
preview.reveal(previewSettings.previewColumn);
|
||||||
} else {
|
} else {
|
||||||
preview = this.createNewPreview(resource, previewSettings);
|
preview = this.createNewPreview(resource, previewSettings);
|
||||||
this.previews.push(preview);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preview.update(resource);
|
preview.update(resource);
|
||||||
@@ -90,6 +88,30 @@ export class MarkdownPreviewManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deserializeWebview(
|
||||||
|
webview: vscode.Webview,
|
||||||
|
state: any
|
||||||
|
): Promise<boolean> {
|
||||||
|
const preview = MarkdownPreview.revive(
|
||||||
|
webview,
|
||||||
|
state,
|
||||||
|
this.contentProvider,
|
||||||
|
this.previewConfigurations,
|
||||||
|
this.logger,
|
||||||
|
this.topmostLineMonitor);
|
||||||
|
|
||||||
|
this.registerPreview(preview);
|
||||||
|
preview.refresh();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async serializeWebview(
|
||||||
|
webview: vscode.Webview,
|
||||||
|
): Promise<any> {
|
||||||
|
const preview = this.previews.find(preview => preview.isWebviewOf(webview));
|
||||||
|
return preview ? preview.state : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private getExistingPreview(
|
private getExistingPreview(
|
||||||
resource: vscode.Uri,
|
resource: vscode.Uri,
|
||||||
previewSettings: PreviewSettings
|
previewSettings: PreviewSettings
|
||||||
@@ -101,8 +123,8 @@ export class MarkdownPreviewManager {
|
|||||||
private createNewPreview(
|
private createNewPreview(
|
||||||
resource: vscode.Uri,
|
resource: vscode.Uri,
|
||||||
previewSettings: PreviewSettings
|
previewSettings: PreviewSettings
|
||||||
) {
|
): MarkdownPreview {
|
||||||
const preview = new MarkdownPreview(
|
const preview = MarkdownPreview.create(
|
||||||
resource,
|
resource,
|
||||||
previewSettings.previewColumn,
|
previewSettings.previewColumn,
|
||||||
previewSettings.locked,
|
previewSettings.locked,
|
||||||
@@ -112,6 +134,14 @@ export class MarkdownPreviewManager {
|
|||||||
this.topmostLineMonitor,
|
this.topmostLineMonitor,
|
||||||
this.contributions);
|
this.contributions);
|
||||||
|
|
||||||
|
return this.registerPreview(preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerPreview(
|
||||||
|
preview: MarkdownPreview
|
||||||
|
): MarkdownPreview {
|
||||||
|
this.previews.push(preview);
|
||||||
|
|
||||||
preview.onDispose(() => {
|
preview.onDispose(() => {
|
||||||
const existing = this.previews.indexOf(preview!);
|
const existing = this.previews.indexOf(preview!);
|
||||||
if (existing >= 0) {
|
if (existing >= 0) {
|
||||||
|
|||||||
45
src/vs/vscode.proposed.d.ts
vendored
45
src/vs/vscode.proposed.d.ts
vendored
@@ -568,7 +568,7 @@ declare module 'vscode' {
|
|||||||
*/
|
*/
|
||||||
export interface Webview {
|
export interface Webview {
|
||||||
/**
|
/**
|
||||||
* The type of the webview, such as `'markdownw.preview'`
|
* The type of the webview, such as `'markdown.preview'`
|
||||||
*/
|
*/
|
||||||
readonly viewType: string;
|
readonly viewType: string;
|
||||||
|
|
||||||
@@ -636,16 +636,57 @@ declare module 'vscode' {
|
|||||||
dispose(): any;
|
dispose(): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save and restore webviews that have been persisted when vscode shuts down.
|
||||||
|
*/
|
||||||
|
interface WebviewSerializer {
|
||||||
|
/**
|
||||||
|
* Save a webview's `state`.
|
||||||
|
*
|
||||||
|
* Called before shutdown. Webview may or may not be visible.
|
||||||
|
*
|
||||||
|
* @param webview Webview to serialize.
|
||||||
|
*
|
||||||
|
* @returns JSON serializable state blob.
|
||||||
|
*/
|
||||||
|
serializeWebview(webview: Webview): Thenable<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a webview from its `state`.
|
||||||
|
*
|
||||||
|
* Called when a serialized webview first becomes active.
|
||||||
|
*
|
||||||
|
* @param webview Webview to restore. The serializer should take ownership of this webview.
|
||||||
|
* @param state Persisted state.
|
||||||
|
*
|
||||||
|
* @return Was deserialization successful?
|
||||||
|
*/
|
||||||
|
deserializeWebview(webview: Webview, state: any): Thenable<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
namespace window {
|
namespace window {
|
||||||
/**
|
/**
|
||||||
* Create and show a new webview.
|
* Create and show a new webview.
|
||||||
*
|
*
|
||||||
* @param viewType Identifier the type of the webview.
|
* @param viewType Identifies the type of the webview.
|
||||||
* @param title Title of the webview.
|
* @param title Title of the webview.
|
||||||
* @param column Editor column to show the new webview in.
|
* @param column Editor column to show the new webview in.
|
||||||
* @param options Content settings for the webview.
|
* @param options Content settings for the webview.
|
||||||
*/
|
*/
|
||||||
export function createWebview(viewType: string, title: string, column: ViewColumn, options: WebviewOptions): Webview;
|
export function createWebview(viewType: string, title: string, column: ViewColumn, options: WebviewOptions): Webview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a webview serializer.
|
||||||
|
*
|
||||||
|
* Extensions that support reviving should have an `"onView:viewType"` activation method and
|
||||||
|
* make sure that `registerWebviewSerializer` is called during activation.
|
||||||
|
*
|
||||||
|
* Only a single serializer may be registered at a time for a given `viewType`.
|
||||||
|
*
|
||||||
|
* @param viewType Type of the webview that can be serialized.
|
||||||
|
* @param reviver Webview serializer.
|
||||||
|
*/
|
||||||
|
export function registerWebviewSerializer(viewType: string, reviver: WebviewSerializer): Disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@@ -2,44 +2,58 @@
|
|||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
import * as map from 'vs/base/common/map';
|
import * as map from 'vs/base/common/map';
|
||||||
import { MainThreadWebviewsShape, MainContext, IExtHostContext, ExtHostContext, ExtHostWebviewsShape, WebviewHandle } from 'vs/workbench/api/node/extHost.protocol';
|
|
||||||
import { dispose, Disposable } from 'vs/base/common/lifecycle';
|
|
||||||
import { extHostNamedCustomer } from './extHostCustomers';
|
|
||||||
import { Position } from 'vs/platform/editor/common/editor';
|
|
||||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
|
||||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
||||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
|
||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
|
||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
import { WebviewInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { Position } from 'vs/platform/editor/common/editor';
|
||||||
|
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
|
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewHandle } from 'vs/workbench/api/node/extHost.protocol';
|
||||||
import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor';
|
import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor';
|
||||||
|
import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput';
|
||||||
|
import { IWebviewService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewService';
|
||||||
|
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
|
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||||
|
import { extHostNamedCustomer } from './extHostCustomers';
|
||||||
|
|
||||||
@extHostNamedCustomer(MainContext.MainThreadWebviews)
|
@extHostNamedCustomer(MainContext.MainThreadWebviews)
|
||||||
export class MainThreadWebviews implements MainThreadWebviewsShape {
|
export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver {
|
||||||
|
|
||||||
|
private static readonly viewType = 'mainThreadWebview';
|
||||||
|
|
||||||
private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto'];
|
private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto'];
|
||||||
|
|
||||||
private _toDispose: Disposable[] = [];
|
private static revivalPool = 0;
|
||||||
|
|
||||||
|
private _toDispose: IDisposable[] = [];
|
||||||
|
|
||||||
private readonly _proxy: ExtHostWebviewsShape;
|
private readonly _proxy: ExtHostWebviewsShape;
|
||||||
private readonly _webviews = new Map<WebviewHandle, WebviewInput>();
|
private readonly _webviews = new Map<WebviewHandle, WebviewEditorInput>();
|
||||||
|
private readonly _revivers = new Set<string>();
|
||||||
|
|
||||||
private _activeWebview: WebviewInput | undefined = undefined;
|
private _activeWebview: WebviewEditorInput | undefined = undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
context: IExtHostContext,
|
context: IExtHostContext,
|
||||||
@IContextKeyService _contextKeyService: IContextKeyService,
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
@IPartService private readonly _partService: IPartService,
|
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||||
|
@ILifecycleService lifecycleService: ILifecycleService,
|
||||||
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
||||||
@IEditorGroupService private readonly _editorGroupService: IEditorGroupService,
|
@IWebviewService private readonly _webviewService: IWebviewService,
|
||||||
@IOpenerService private readonly _openerService: IOpenerService
|
@IOpenerService private readonly _openerService: IOpenerService,
|
||||||
|
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews);
|
this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews);
|
||||||
_editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose);
|
editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose);
|
||||||
|
|
||||||
|
_webviewService.registerReviver(MainThreadWebviews.viewType, this);
|
||||||
|
this._toDispose.push(lifecycleService.onWillShutdown(e => {
|
||||||
|
e.veto(this._onWillShutdown());
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
@@ -51,30 +65,31 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
|
|||||||
viewType: string,
|
viewType: string,
|
||||||
title: string,
|
title: string,
|
||||||
column: Position,
|
column: Position,
|
||||||
options: vscode.WebviewOptions,
|
options: WebviewInputOptions,
|
||||||
extensionFolderPath: string
|
extensionFolderPath: string
|
||||||
): void {
|
): void {
|
||||||
const webviewInput = new WebviewInput(title, options, '', {
|
const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, column, options, extensionFolderPath, {
|
||||||
|
onDidClickLink: uri => this.onDidClickLink(uri, webview.options),
|
||||||
onMessage: message => this._proxy.$onMessage(handle, message),
|
onMessage: message => this._proxy.$onMessage(handle, message),
|
||||||
onDidChangePosition: position => this._proxy.$onDidChangePosition(handle, position),
|
onDidChangePosition: position => this._proxy.$onDidChangePosition(handle, position),
|
||||||
onDispose: () => {
|
onDispose: () => {
|
||||||
this._proxy.$onDidDisposeWeview(handle).then(() => {
|
this._proxy.$onDidDisposeWeview(handle).then(() => {
|
||||||
this._webviews.delete(handle);
|
this._webviews.delete(handle);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
onDidClickLink: (link, options) => this.onDidClickLink(link, options)
|
});
|
||||||
}, this._partService);
|
|
||||||
|
|
||||||
this._webviews.set(handle, webviewInput);
|
webview.state = {
|
||||||
|
viewType: viewType,
|
||||||
|
state: undefined
|
||||||
|
};
|
||||||
|
|
||||||
this._editorService.openEditor(webviewInput, { pinned: true }, column);
|
this._webviews.set(handle, webview);
|
||||||
}
|
}
|
||||||
|
|
||||||
$disposeWebview(handle: WebviewHandle): void {
|
$disposeWebview(handle: WebviewHandle): void {
|
||||||
const webview = this.getWebview(handle);
|
const webview = this.getWebview(handle);
|
||||||
if (webview) {
|
webview.dispose();
|
||||||
this._editorService.closeEditor(webview.position, webview);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$setTitle(handle: WebviewHandle, value: string): void {
|
$setTitle(handle: WebviewHandle, value: string): void {
|
||||||
@@ -84,24 +99,20 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
|
|||||||
|
|
||||||
$setHtml(handle: WebviewHandle, value: string): void {
|
$setHtml(handle: WebviewHandle, value: string): void {
|
||||||
const webview = this.getWebview(handle);
|
const webview = this.getWebview(handle);
|
||||||
webview.setHtml(value);
|
webview.html = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$reveal(handle: WebviewHandle, column: Position): void {
|
$reveal(handle: WebviewHandle, column: Position): void {
|
||||||
const webviewInput = this.getWebview(handle);
|
const webview = this.getWebview(handle);
|
||||||
if (webviewInput.position === column) {
|
this._webviewService.revealWebview(webview, column);
|
||||||
this._editorService.openEditor(webviewInput, { preserveFocus: true }, column);
|
|
||||||
} else {
|
|
||||||
this._editorGroupService.moveEditor(webviewInput, webviewInput.position, column, { preserveFocus: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async $sendMessage(handle: WebviewHandle, message: any): Promise<boolean> {
|
async $sendMessage(handle: WebviewHandle, message: any): Promise<boolean> {
|
||||||
const webviewInput = this.getWebview(handle);
|
const webview = this.getWebview(handle);
|
||||||
const editors = this._editorService.getVisibleEditors()
|
const editors = this._editorService.getVisibleEditors()
|
||||||
.filter(e => e instanceof WebviewEditor)
|
.filter(e => e instanceof WebviewEditor)
|
||||||
.map(e => e as WebviewEditor)
|
.map(e => e as WebviewEditor)
|
||||||
.filter(e => e.input.matches(webviewInput));
|
.filter(e => e.input.matches(webview));
|
||||||
|
|
||||||
for (const editor of editors) {
|
for (const editor of editors) {
|
||||||
editor.sendMessage(message);
|
editor.sendMessage(message);
|
||||||
@@ -110,18 +121,74 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
|
|||||||
return (editors.length > 0);
|
return (editors.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWebview(handle: number): WebviewInput {
|
$registerSerializer(viewType: string): void {
|
||||||
const webviewInput = this._webviews.get(handle);
|
this._revivers.add(viewType);
|
||||||
if (!webviewInput) {
|
}
|
||||||
|
|
||||||
|
$unregisterSerializer(viewType: string): void {
|
||||||
|
this._revivers.delete(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
reviveWebview(webview: WebviewEditorInput) {
|
||||||
|
this._extensionService.activateByEvent(`onView:${webview.state.viewType}`).then(() => {
|
||||||
|
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
|
||||||
|
this._webviews.set(handle, webview);
|
||||||
|
|
||||||
|
webview._events = {
|
||||||
|
onDidClickLink: uri => this.onDidClickLink(uri, webview.options),
|
||||||
|
onMessage: message => this._proxy.$onMessage(handle, message),
|
||||||
|
onDidChangePosition: position => this._proxy.$onDidChangePosition(handle, position),
|
||||||
|
onDispose: () => {
|
||||||
|
this._proxy.$onDidDisposeWeview(handle).then(() => {
|
||||||
|
this._webviews.delete(handle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._proxy.$deserializeWebview(handle, webview.state.viewType, webview.state.state, webview.position, webview.options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canRevive(webview: WebviewEditorInput): boolean {
|
||||||
|
return this._revivers.has(webview.viewType) || webview.reviver !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onWillShutdown(): TPromise<boolean> {
|
||||||
|
const toRevive: WebviewHandle[] = [];
|
||||||
|
this._webviews.forEach((view, key) => {
|
||||||
|
if (this.canRevive(view)) {
|
||||||
|
toRevive.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const reviveResponses = toRevive.map(handle =>
|
||||||
|
this._proxy.$serializeWebview(handle).then(state => ({ handle, state })));
|
||||||
|
|
||||||
|
return TPromise.join(reviveResponses).then(results => {
|
||||||
|
for (const result of results) {
|
||||||
|
if (result.state) {
|
||||||
|
const view = this._webviews.get(result.handle);
|
||||||
|
if (view) {
|
||||||
|
view.state.state = result.state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // Don't veto shutdown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWebview(handle: WebviewHandle): WebviewEditorInput {
|
||||||
|
const webview = this._webviews.get(handle);
|
||||||
|
if (!webview) {
|
||||||
throw new Error('Unknown webview handle:' + handle);
|
throw new Error('Unknown webview handle:' + handle);
|
||||||
}
|
}
|
||||||
return webviewInput;
|
return webview;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onEditorsChanged() {
|
private onEditorsChanged() {
|
||||||
const activeEditor = this._editorService.getActiveEditor();
|
const activeEditor = this._editorService.getActiveEditor();
|
||||||
let newActiveWebview: { input: WebviewInput, handle: WebviewHandle } | undefined = undefined;
|
let newActiveWebview: { input: WebviewEditorInput, handle: WebviewHandle } | undefined = undefined;
|
||||||
if (activeEditor && activeEditor.input instanceof WebviewInput) {
|
if (activeEditor && activeEditor.input instanceof WebviewEditorInput) {
|
||||||
for (const handle of map.keys(this._webviews)) {
|
for (const handle of map.keys(this._webviews)) {
|
||||||
const input = this._webviews.get(handle);
|
const input = this._webviews.get(handle);
|
||||||
if (input.matches(activeEditor.input)) {
|
if (input.matches(activeEditor.input)) {
|
||||||
@@ -132,7 +199,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newActiveWebview) {
|
if (newActiveWebview) {
|
||||||
if (!this._activeWebview || !newActiveWebview.input.matches(this._activeWebview)) {
|
if (!this._activeWebview || newActiveWebview.input !== this._activeWebview) {
|
||||||
this._proxy.$onDidChangeActiveWeview(newActiveWebview.handle);
|
this._proxy.$onDidChangeActiveWeview(newActiveWebview.handle);
|
||||||
this._activeWebview = newActiveWebview.input;
|
this._activeWebview = newActiveWebview.input;
|
||||||
}
|
}
|
||||||
@@ -144,7 +211,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDidClickLink(link: URI, options: vscode.WebviewOptions): void {
|
private onDidClickLink(link: URI, options: WebviewInputOptions): void {
|
||||||
if (!link) {
|
if (!link) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,9 @@ export function createApiFactory(
|
|||||||
}),
|
}),
|
||||||
createWebview: proposedApiFunction(extension, (viewType: string, title: string, column: vscode.ViewColumn, options: vscode.WebviewOptions) => {
|
createWebview: proposedApiFunction(extension, (viewType: string, title: string, column: vscode.ViewColumn, options: vscode.WebviewOptions) => {
|
||||||
return extHostWebviews.createWebview(viewType, title, column, options, extension.extensionFolderPath);
|
return extHostWebviews.createWebview(viewType, title, column, options, extension.extensionFolderPath);
|
||||||
|
}),
|
||||||
|
registerWebviewSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewSerializer) => {
|
||||||
|
return extHostWebviews.registerWebviewSerializer(viewType, serializer);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export interface MainThreadTelemetryShape extends IDisposable {
|
|||||||
$publicLog(eventName: string, data?: any): void;
|
$publicLog(eventName: string, data?: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebviewHandle = number;
|
export type WebviewHandle = string;
|
||||||
|
|
||||||
export interface MainThreadWebviewsShape extends IDisposable {
|
export interface MainThreadWebviewsShape extends IDisposable {
|
||||||
$createWebview(handle: WebviewHandle, viewType: string, title: string, column: EditorPosition, options: vscode.WebviewOptions, extensionFolderPath: string): void;
|
$createWebview(handle: WebviewHandle, viewType: string, title: string, column: EditorPosition, options: vscode.WebviewOptions, extensionFolderPath: string): void;
|
||||||
@@ -356,12 +356,18 @@ export interface MainThreadWebviewsShape extends IDisposable {
|
|||||||
$setTitle(handle: WebviewHandle, value: string): void;
|
$setTitle(handle: WebviewHandle, value: string): void;
|
||||||
$setHtml(handle: WebviewHandle, value: string): void;
|
$setHtml(handle: WebviewHandle, value: string): void;
|
||||||
$sendMessage(handle: WebviewHandle, value: any): Thenable<boolean>;
|
$sendMessage(handle: WebviewHandle, value: any): Thenable<boolean>;
|
||||||
|
|
||||||
|
$registerSerializer(viewType: string): void;
|
||||||
|
$unregisterSerializer(viewType: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostWebviewsShape {
|
export interface ExtHostWebviewsShape {
|
||||||
$onMessage(handle: WebviewHandle, message: any): void;
|
$onMessage(handle: WebviewHandle, message: any): void;
|
||||||
$onDidChangeActiveWeview(handle: WebviewHandle | undefined): void;
|
$onDidChangeActiveWeview(handle: WebviewHandle | undefined): void;
|
||||||
$onDidDisposeWeview(handle: WebviewHandle): Thenable<void>;
|
$onDidDisposeWeview(handle: WebviewHandle): Thenable<void>;
|
||||||
$onDidChangePosition(handle: WebviewHandle, newPosition: EditorPosition): void;
|
$onDidChangePosition(handle: WebviewHandle, newPosition: EditorPosition): void;
|
||||||
|
$deserializeWebview(newWebviewHandle: WebviewHandle, viewType: string, state: any, position: EditorPosition, options: vscode.WebviewOptions): void;
|
||||||
|
$serializeWebview(webviewHandle: WebviewHandle): Thenable<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainThreadWorkspaceShape extends IDisposable {
|
export interface MainThreadWorkspaceShape extends IDisposable {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||||
import { Position } from 'vs/platform/editor/common/editor';
|
import { Position } from 'vs/platform/editor/common/editor';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { Disposable } from './extHostTypes';
|
||||||
|
|
||||||
export class ExtHostWebview implements vscode.Webview {
|
export class ExtHostWebview implements vscode.Webview {
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export class ExtHostWebview implements vscode.Webview {
|
|||||||
private _isDisposed: boolean = false;
|
private _isDisposed: boolean = false;
|
||||||
private _viewColumn: vscode.ViewColumn;
|
private _viewColumn: vscode.ViewColumn;
|
||||||
private _active: boolean;
|
private _active: boolean;
|
||||||
|
private _state: any;
|
||||||
|
|
||||||
public readonly onMessageEmitter = new Emitter<any>();
|
public readonly onMessageEmitter = new Emitter<any>();
|
||||||
public readonly onDidReceiveMessage: Event<any> = this.onMessageEmitter.event;
|
public readonly onDidReceiveMessage: Event<any> = this.onMessageEmitter.event;
|
||||||
@@ -85,6 +87,11 @@ export class ExtHostWebview implements vscode.Webview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get state(): any {
|
||||||
|
this.assertNotDisposed();
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
get options(): vscode.WebviewOptions {
|
get options(): vscode.WebviewOptions {
|
||||||
this.assertNotDisposed();
|
this.assertNotDisposed();
|
||||||
return this._options;
|
return this._options;
|
||||||
@@ -128,11 +135,12 @@ export class ExtHostWebview implements vscode.Webview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExtHostWebviews implements ExtHostWebviewsShape {
|
export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||||
private static handlePool = 1;
|
private static webviewHandlePool = 1;
|
||||||
|
|
||||||
private readonly _proxy: MainThreadWebviewsShape;
|
private readonly _proxy: MainThreadWebviewsShape;
|
||||||
|
|
||||||
private readonly _webviews = new Map<WebviewHandle, ExtHostWebview>();
|
private readonly _webviews = new Map<WebviewHandle, ExtHostWebview>();
|
||||||
|
private readonly _serializers = new Map<string, vscode.WebviewSerializer>();
|
||||||
|
|
||||||
private _activeWebview: ExtHostWebview | undefined;
|
private _activeWebview: ExtHostWebview | undefined;
|
||||||
|
|
||||||
@@ -149,7 +157,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
|||||||
options: vscode.WebviewOptions,
|
options: vscode.WebviewOptions,
|
||||||
extensionFolderPath: string
|
extensionFolderPath: string
|
||||||
): vscode.Webview {
|
): vscode.Webview {
|
||||||
const handle = ExtHostWebviews.handlePool++;
|
const handle = ExtHostWebviews.webviewHandlePool++ + '';
|
||||||
this._proxy.$createWebview(handle, viewType, title, typeConverters.fromViewColumn(viewColumn), options, extensionFolderPath);
|
this._proxy.$createWebview(handle, viewType, title, typeConverters.fromViewColumn(viewColumn), options, extensionFolderPath);
|
||||||
|
|
||||||
const webview = new ExtHostWebview(handle, this._proxy, viewType, viewColumn, options);
|
const webview = new ExtHostWebview(handle, this._proxy, viewType, viewColumn, options);
|
||||||
@@ -157,6 +165,23 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
|||||||
return webview;
|
return webview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerWebviewSerializer(
|
||||||
|
viewType: string,
|
||||||
|
serializer: vscode.WebviewSerializer
|
||||||
|
): vscode.Disposable {
|
||||||
|
if (this._serializers.has(viewType)) {
|
||||||
|
throw new Error(`Serializer for '${viewType}' already registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._serializers.set(viewType, serializer);
|
||||||
|
this._proxy.$registerSerializer(viewType);
|
||||||
|
|
||||||
|
return new Disposable(() => {
|
||||||
|
this._serializers.delete(viewType);
|
||||||
|
this._proxy.$unregisterSerializer(viewType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$onMessage(handle: WebviewHandle, message: any): void {
|
$onMessage(handle: WebviewHandle, message: any): void {
|
||||||
const webview = this.getWebview(handle);
|
const webview = this.getWebview(handle);
|
||||||
if (webview) {
|
if (webview) {
|
||||||
@@ -206,8 +231,35 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _onDidChangeActiveWebview = new Emitter<ExtHostWebview | undefined>();
|
$deserializeWebview(
|
||||||
public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event;
|
webviewHandle: WebviewHandle,
|
||||||
|
viewType: string,
|
||||||
|
state: any,
|
||||||
|
position: Position,
|
||||||
|
options: vscode.WebviewOptions
|
||||||
|
): void {
|
||||||
|
const serializer = this._serializers.get(viewType);
|
||||||
|
if (!serializer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const revivedWebview = new ExtHostWebview(webviewHandle, this._proxy, viewType, typeConverters.toViewColumn(position), options);
|
||||||
|
this._webviews.set(webviewHandle, revivedWebview);
|
||||||
|
serializer.deserializeWebview(revivedWebview, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
$serializeWebview(
|
||||||
|
webviewHandle: WebviewHandle
|
||||||
|
): Thenable<any> {
|
||||||
|
const webview = this.getWebview(webviewHandle);
|
||||||
|
|
||||||
|
const serialzer = this._serializers.get(webview.viewType);
|
||||||
|
if (!serialzer) {
|
||||||
|
return TPromise.as(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialzer.serializeWebview(webview);
|
||||||
|
}
|
||||||
|
|
||||||
private getWebview(handle: WebviewHandle) {
|
private getWebview(handle: WebviewHandle) {
|
||||||
return this._webviews.get(handle);
|
return this._webviews.get(handle);
|
||||||
|
|||||||
@@ -5,29 +5,29 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||||
import { marked } from 'vs/base/common/marked/marked';
|
import { marked } from 'vs/base/common/marked/marked';
|
||||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
import { OS } from 'vs/base/common/platform';
|
||||||
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { asText } from 'vs/base/node/request';
|
||||||
import { IMode, TokenizationRegistry } from 'vs/editor/common/modes';
|
import { IMode, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||||
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
|
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
|
||||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
||||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||||
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
|
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|
||||||
import { IRequestService } from 'vs/platform/request/node/request';
|
|
||||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
||||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
|
||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
||||||
import { WebviewInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput';
|
|
||||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
||||||
import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils';
|
|
||||||
import URI from 'vs/base/common/uri';
|
|
||||||
import { asText } from 'vs/base/node/request';
|
|
||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import { OS } from 'vs/base/common/platform';
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
|
import { IRequestService } from 'vs/platform/request/node/request';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils';
|
||||||
|
import { IWebviewService } from 'vs/workbench/parts/webview/electron-browser/webviewService';
|
||||||
|
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
|
||||||
|
import { Position } from 'vs/platform/editor/common/editor';
|
||||||
|
import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput';
|
||||||
|
|
||||||
function renderBody(
|
function renderBody(
|
||||||
body: string,
|
body: string,
|
||||||
@@ -51,18 +51,17 @@ export class ReleaseNotesManager {
|
|||||||
|
|
||||||
private _releaseNotesCache: { [version: string]: TPromise<string>; } = Object.create(null);
|
private _releaseNotesCache: { [version: string]: TPromise<string>; } = Object.create(null);
|
||||||
|
|
||||||
private _currentReleaseNotes: WebviewInput | undefined = undefined;
|
private _currentReleaseNotes: WebviewEditorInput | undefined = undefined;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
@IEditorGroupService private readonly _editorGroupService: IEditorGroupService,
|
|
||||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||||
@IModeService private readonly _modeService: IModeService,
|
@IModeService private readonly _modeService: IModeService,
|
||||||
@IOpenerService private readonly _openerService: IOpenerService,
|
@IOpenerService private readonly _openerService: IOpenerService,
|
||||||
@IPartService private readonly _partService: IPartService,
|
|
||||||
@IRequestService private readonly _requestService: IRequestService,
|
@IRequestService private readonly _requestService: IRequestService,
|
||||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||||
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
||||||
|
@IWebviewService private readonly _webviewService: IWebviewService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public async show(
|
public async show(
|
||||||
@@ -73,21 +72,23 @@ export class ReleaseNotesManager {
|
|||||||
const html = await this.renderBody(releaseNoteText);
|
const html = await this.renderBody(releaseNoteText);
|
||||||
const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version);
|
const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version);
|
||||||
|
|
||||||
|
const activeEditor = this._editorService.getActiveEditor();
|
||||||
if (this._currentReleaseNotes) {
|
if (this._currentReleaseNotes) {
|
||||||
this._currentReleaseNotes.setName(title);
|
this._currentReleaseNotes.setName(title);
|
||||||
this._currentReleaseNotes.setHtml(html);
|
this._currentReleaseNotes.html = html;
|
||||||
const activeEditor = this._editorService.getActiveEditor();
|
this._webviewService.revealWebview(this._currentReleaseNotes, activeEditor ? activeEditor.position : undefined);
|
||||||
if (activeEditor && activeEditor.position !== this._currentReleaseNotes.position) {
|
|
||||||
this._editorGroupService.moveEditor(this._currentReleaseNotes, this._currentReleaseNotes.position, activeEditor.position, { preserveFocus: true });
|
|
||||||
} else {
|
} else {
|
||||||
this._editorService.openEditor(this._currentReleaseNotes, { preserveFocus: true });
|
this._currentReleaseNotes = this._webviewService.createWebview(
|
||||||
}
|
'releaseNotes',
|
||||||
} else {
|
title,
|
||||||
this._currentReleaseNotes = new WebviewInput(title, { tryRestoreScrollPosition: true, enableFindWidget: true }, html, {
|
activeEditor ? activeEditor.position : Position.ONE,
|
||||||
|
{ tryRestoreScrollPosition: true, enableFindWidget: true },
|
||||||
|
undefined, {
|
||||||
onDidClickLink: uri => this.onDidClickLink(uri),
|
onDidClickLink: uri => this.onDidClickLink(uri),
|
||||||
onDispose: () => { this._currentReleaseNotes = undefined; }
|
onDispose: () => { this._currentReleaseNotes = undefined; }
|
||||||
}, this._partService);
|
});
|
||||||
await this._editorService.openEditor(this._currentReleaseNotes, { pinned: true });
|
|
||||||
|
this._currentReleaseNotes.html = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7,11 +7,21 @@ import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } fro
|
|||||||
import { WebviewEditor } from './webviewEditor';
|
import { WebviewEditor } from './webviewEditor';
|
||||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { WebviewInput } from './webviewInput';
|
import { WebviewEditorInput } from './webviewInput';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
|
import { IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
|
||||||
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
|
import { IWebviewService, WebviewService } from './webviewService';
|
||||||
|
import { WebviewInputFactory } from 'vs/workbench/parts/webview/electron-browser/webviewInputFactory';
|
||||||
|
|
||||||
(Registry.as<IEditorRegistry>(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(
|
(Registry.as<IEditorRegistry>(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(
|
||||||
WebviewEditor,
|
WebviewEditor,
|
||||||
WebviewEditor.ID,
|
WebviewEditor.ID,
|
||||||
localize('webview.editor.label', "webview editor")),
|
localize('webview.editor.label', "webview editor")),
|
||||||
[new SyncDescriptor(WebviewInput)]);
|
[new SyncDescriptor(WebviewEditorInput)]);
|
||||||
|
|
||||||
|
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(
|
||||||
|
WebviewInputFactory.ID,
|
||||||
|
WebviewInputFactory);
|
||||||
|
|
||||||
|
registerSingleton(IWebviewService, WebviewService);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
|||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { WebviewInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput';
|
import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput';
|
||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
|
|
||||||
export class WebviewEditor extends BaseWebviewEditor {
|
export class WebviewEditor extends BaseWebviewEditor {
|
||||||
@@ -29,10 +29,12 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
private editorFrame: HTMLElement;
|
private editorFrame: HTMLElement;
|
||||||
private content: HTMLElement;
|
private content: HTMLElement;
|
||||||
private webviewContent: HTMLElement | undefined;
|
private webviewContent: HTMLElement | undefined;
|
||||||
private readonly _onDidFocusWebview: Emitter<void>;
|
|
||||||
private _webviewFocusTracker?: DOM.IFocusTracker;
|
private _webviewFocusTracker?: DOM.IFocusTracker;
|
||||||
private _webviewFocusListenerDisposable?: IDisposable;
|
private _webviewFocusListenerDisposable?: IDisposable;
|
||||||
|
|
||||||
|
private readonly _onDidFocusWebview = new Emitter<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ITelemetryService telemetryService: ITelemetryService,
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
@@ -43,8 +45,6 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService
|
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService
|
||||||
) {
|
) {
|
||||||
super(WebviewEditor.ID, telemetryService, themeService, _contextKeyService);
|
super(WebviewEditor.ID, telemetryService, themeService, _contextKeyService);
|
||||||
|
|
||||||
this._onDidFocusWebview = new Emitter<void>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createEditor(parent: Builder): void {
|
protected createEditor(parent: Builder): void {
|
||||||
@@ -54,7 +54,7 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private doUpdateContainer() {
|
private doUpdateContainer() {
|
||||||
const webviewContainer = this.input && (this.input as WebviewInput).container;
|
const webviewContainer = this.input && (this.input as WebviewEditorInput).container;
|
||||||
if (webviewContainer && webviewContainer.parentElement) {
|
if (webviewContainer && webviewContainer.parentElement) {
|
||||||
const frameRect = this.editorFrame.getBoundingClientRect();
|
const frameRect = this.editorFrame.getBoundingClientRect();
|
||||||
const containerRect = webviewContainer.parentElement.getBoundingClientRect();
|
const containerRect = webviewContainer.parentElement.getBoundingClientRect();
|
||||||
@@ -103,14 +103,14 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected setEditorVisible(visible: boolean, position?: Position): void {
|
protected setEditorVisible(visible: boolean, position?: Position): void {
|
||||||
if (this.input && this.input instanceof WebviewInput) {
|
if (this.input && this.input instanceof WebviewEditorInput) {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this.input.claimWebview(this);
|
this.input.claimWebview(this);
|
||||||
} else {
|
} else {
|
||||||
this.input.releaseWebview(this);
|
this.input.releaseWebview(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateWebview(this.input as WebviewInput);
|
this.updateWebview(this.input as WebviewEditorInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.webviewContent) {
|
if (this.webviewContent) {
|
||||||
@@ -126,7 +126,7 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public clearInput() {
|
public clearInput() {
|
||||||
if (this.input && this.input instanceof WebviewInput) {
|
if (this.input && this.input instanceof WebviewEditorInput) {
|
||||||
this.input.releaseWebview(this);
|
this.input.releaseWebview(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,24 +136,24 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
super.clearInput();
|
super.clearInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInput(input: WebviewInput, options: EditorOptions): TPromise<void> {
|
async setInput(input: WebviewEditorInput, options: EditorOptions): TPromise<void> {
|
||||||
if (this.input && this.input.matches(input)) {
|
if (this.input && this.input.matches(input)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.input) {
|
if (this.input) {
|
||||||
(this.input as WebviewInput).releaseWebview(this);
|
(this.input as WebviewEditorInput).releaseWebview(this);
|
||||||
this._webview = undefined;
|
this._webview = undefined;
|
||||||
this.webviewContent = undefined;
|
this.webviewContent = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await super.setInput(input, options);
|
await super.setInput(input, options);
|
||||||
|
|
||||||
input.onDidChangePosition(this.position);
|
input.onBecameActive(this.position);
|
||||||
this.updateWebview(input);
|
this.updateWebview(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWebview(input: WebviewInput) {
|
private updateWebview(input: WebviewEditorInput) {
|
||||||
const webview = this.getWebview(input);
|
const webview = this.getWebview(input);
|
||||||
input.claimWebview(this);
|
input.claimWebview(this);
|
||||||
webview.options = {
|
webview.options = {
|
||||||
@@ -163,7 +163,7 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
useSameOriginForRoot: false,
|
useSameOriginForRoot: false,
|
||||||
localResourceRoots: input.options.localResourceRoots || this.getDefaultLocalResourceRoots()
|
localResourceRoots: input.options.localResourceRoots || this.getDefaultLocalResourceRoots()
|
||||||
};
|
};
|
||||||
input.setHtml(input.html);
|
input.html = input.html;
|
||||||
|
|
||||||
if (this.webviewContent) {
|
if (this.webviewContent) {
|
||||||
this.webviewContent.style.visibility = 'visible';
|
this.webviewContent.style.visibility = 'visible';
|
||||||
@@ -174,13 +174,13 @@ export class WebviewEditor extends BaseWebviewEditor {
|
|||||||
|
|
||||||
private getDefaultLocalResourceRoots(): URI[] {
|
private getDefaultLocalResourceRoots(): URI[] {
|
||||||
const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri);
|
const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri);
|
||||||
if ((this.input as WebviewInput).extensionFolderPath) {
|
if ((this.input as WebviewEditorInput).extensionFolderPath) {
|
||||||
rootPaths.push((this.input as WebviewInput).extensionFolderPath);
|
rootPaths.push((this.input as WebviewEditorInput).extensionFolderPath);
|
||||||
}
|
}
|
||||||
return rootPaths;
|
return rootPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWebview(input: WebviewInput): Webview {
|
private getWebview(input: WebviewEditorInput): Webview {
|
||||||
if (this._webview) {
|
if (this._webview) {
|
||||||
return this._webview;
|
return this._webview;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,69 +5,61 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
|
||||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { IEditorInput, IEditorModel, Position } from 'vs/platform/editor/common/editor';
|
||||||
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
|
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
|
||||||
import { IEditorModel, Position, IEditorInput } from 'vs/platform/editor/common/editor';
|
|
||||||
import { Webview } from 'vs/workbench/parts/html/electron-browser/webview';
|
import { Webview } from 'vs/workbench/parts/html/electron-browser/webview';
|
||||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||||
import * as vscode from 'vscode';
|
import { WebviewEvents, WebviewInputOptions, WebviewReviver } from './webviewService';
|
||||||
import URI from 'vs/base/common/uri';
|
|
||||||
|
|
||||||
export interface WebviewEvents {
|
|
||||||
onMessage?(message: any): void;
|
|
||||||
onDidChangePosition?(newPosition: Position): void;
|
|
||||||
onDispose?(): void;
|
|
||||||
onDidClickLink?(link: URI, options: vscode.WebviewOptions): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebviewInputOptions extends vscode.WebviewOptions {
|
export class WebviewEditorInput extends EditorInput {
|
||||||
tryRestoreScrollPosition?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WebviewInput extends EditorInput {
|
|
||||||
private static handlePool = 0;
|
private static handlePool = 0;
|
||||||
|
|
||||||
|
public static readonly typeId = 'workbench.editors.webviewInput';
|
||||||
|
|
||||||
private _name: string;
|
private _name: string;
|
||||||
private _options: WebviewInputOptions;
|
private _options: WebviewInputOptions;
|
||||||
private _html: string;
|
private _html: string = '';
|
||||||
private _currentWebviewHtml: string = '';
|
private _currentWebviewHtml: string = '';
|
||||||
private _events: WebviewEvents | undefined;
|
public _events: WebviewEvents | undefined;
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private _webview: Webview | undefined;
|
private _webview: Webview | undefined;
|
||||||
private _webviewOwner: any;
|
private _webviewOwner: any;
|
||||||
private _webviewDisposables: IDisposable[] = [];
|
private _webviewDisposables: IDisposable[] = [];
|
||||||
private _position?: Position;
|
private _position?: Position;
|
||||||
private _scrollYPercentage: number = 0;
|
private _scrollYPercentage: number = 0;
|
||||||
|
private _state: any;
|
||||||
|
|
||||||
|
private _revived: boolean = false;
|
||||||
|
|
||||||
public readonly extensionFolderPath: URI | undefined;
|
public readonly extensionFolderPath: URI | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
public readonly viewType: string,
|
||||||
name: string,
|
name: string,
|
||||||
options: WebviewInputOptions,
|
options: WebviewInputOptions,
|
||||||
html: string,
|
state: any,
|
||||||
events: WebviewEvents,
|
events: WebviewEvents,
|
||||||
partService: IPartService,
|
extensionFolderPath: string | undefined,
|
||||||
extensionFolderPath?: string
|
public readonly reviver: WebviewReviver | undefined,
|
||||||
|
@IPartService private readonly _partService: IPartService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._name = name;
|
this._name = name;
|
||||||
this._options = options;
|
this._options = options;
|
||||||
this._html = html;
|
|
||||||
this._events = events;
|
this._events = events;
|
||||||
|
this._state = state;
|
||||||
|
|
||||||
if (extensionFolderPath) {
|
if (extensionFolderPath) {
|
||||||
this.extensionFolderPath = URI.file(extensionFolderPath);
|
this.extensionFolderPath = URI.file(extensionFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = WebviewInput.handlePool++;
|
|
||||||
this._container = document.createElement('div');
|
|
||||||
this._container.id = `webview-${id}`;
|
|
||||||
|
|
||||||
partService.getContainer(Parts.EDITOR_PART).appendChild(this._container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTypeId(): string {
|
public getTypeId(): string {
|
||||||
return 'webview';
|
return WebviewEditorInput.typeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
@@ -119,7 +111,7 @@ export class WebviewInput extends EditorInput {
|
|||||||
return this._html;
|
return this._html;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setHtml(value: string): void {
|
public set html(value: string) {
|
||||||
if (value === this._currentWebviewHtml) {
|
if (value === this._currentWebviewHtml) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -132,6 +124,14 @@ export class WebviewInput extends EditorInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get state(): any {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set state(value: any) {
|
||||||
|
this._state = value;
|
||||||
|
}
|
||||||
|
|
||||||
public get options(): WebviewInputOptions {
|
public get options(): WebviewInputOptions {
|
||||||
return this._options;
|
return this._options;
|
||||||
}
|
}
|
||||||
@@ -149,6 +149,12 @@ export class WebviewInput extends EditorInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get container(): HTMLElement {
|
public get container(): HTMLElement {
|
||||||
|
if (!this._container) {
|
||||||
|
const id = WebviewEditorInput.handlePool++;
|
||||||
|
this._container = document.createElement('div');
|
||||||
|
this._container.id = `webview-${id}`;
|
||||||
|
this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container);
|
||||||
|
}
|
||||||
return this._container;
|
return this._container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,10 +221,16 @@ export class WebviewInput extends EditorInput {
|
|||||||
this._currentWebviewHtml = '';
|
this._currentWebviewHtml = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDidChangePosition(position: Position) {
|
public onBecameActive(position: Position) {
|
||||||
|
this._position = position;
|
||||||
|
|
||||||
if (this._events && this._events.onDidChangePosition) {
|
if (this._events && this._events.onDidChangePosition) {
|
||||||
this._events.onDidChangePosition(position);
|
this._events.onDidChangePosition(position);
|
||||||
}
|
}
|
||||||
this._position = position;
|
|
||||||
|
if (this.reviver && !this._revived) {
|
||||||
|
this._revived = true;
|
||||||
|
this.reviver.reviveWebview(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IEditorInputFactory } from 'vs/workbench/common/editor';
|
||||||
|
import { IWebviewService, WebviewInputOptions } from './webviewService';
|
||||||
|
import { WebviewEditorInput } from './webviewInput';
|
||||||
|
|
||||||
|
interface SerializedWebview {
|
||||||
|
readonly viewType: string;
|
||||||
|
readonly title: string;
|
||||||
|
readonly options: WebviewInputOptions;
|
||||||
|
readonly extensionFolderPath: string;
|
||||||
|
readonly state: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WebviewInputFactory implements IEditorInputFactory {
|
||||||
|
|
||||||
|
public static readonly ID = WebviewEditorInput.typeId;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
@IWebviewService private readonly _webviewService: IWebviewService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public serialize(
|
||||||
|
input: WebviewEditorInput
|
||||||
|
): string {
|
||||||
|
// Only attempt revival if we may have a reviver
|
||||||
|
if (!this._webviewService.canRevive(input) && !input.reviver) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: SerializedWebview = {
|
||||||
|
viewType: input.viewType,
|
||||||
|
title: input.getName(),
|
||||||
|
options: input.options,
|
||||||
|
extensionFolderPath: input.extensionFolderPath.fsPath,
|
||||||
|
state: input.state
|
||||||
|
};
|
||||||
|
return JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deserialize(
|
||||||
|
instantiationService: IInstantiationService,
|
||||||
|
serializedEditorInput: string
|
||||||
|
): WebviewEditorInput {
|
||||||
|
const data: SerializedWebview = JSON.parse(serializedEditorInput);
|
||||||
|
return this._webviewService.createRevivableWebview(data.viewType, data.title, data.state, data.options, data.extensionFolderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { Position } from 'vs/platform/editor/common/editor';
|
||||||
|
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { WebviewEditorInput } from './webviewInput';
|
||||||
|
|
||||||
|
export const IWebviewService = createDecorator<IWebviewService>('webviewService');
|
||||||
|
|
||||||
|
export interface IWebviewService {
|
||||||
|
_serviceBrand: any;
|
||||||
|
|
||||||
|
createWebview(
|
||||||
|
viewType: string,
|
||||||
|
title: string,
|
||||||
|
column: Position,
|
||||||
|
options: WebviewInputOptions,
|
||||||
|
extensionFolderPath: string,
|
||||||
|
events: WebviewEvents
|
||||||
|
): WebviewEditorInput;
|
||||||
|
|
||||||
|
createRevivableWebview(
|
||||||
|
viewType: string,
|
||||||
|
title: string,
|
||||||
|
state: any,
|
||||||
|
options: WebviewInputOptions,
|
||||||
|
extensionFolderPath: string
|
||||||
|
): WebviewEditorInput;
|
||||||
|
|
||||||
|
revealWebview(
|
||||||
|
webview: WebviewEditorInput,
|
||||||
|
column: Position | undefined
|
||||||
|
): void;
|
||||||
|
|
||||||
|
registerReviver(
|
||||||
|
viewType: string,
|
||||||
|
reviver: WebviewReviver
|
||||||
|
): IDisposable;
|
||||||
|
|
||||||
|
canRevive(
|
||||||
|
input: WebviewEditorInput
|
||||||
|
): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebviewReviver {
|
||||||
|
canRevive(
|
||||||
|
webview: WebviewEditorInput
|
||||||
|
): boolean;
|
||||||
|
|
||||||
|
reviveWebview(
|
||||||
|
webview: WebviewEditorInput
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebviewEvents {
|
||||||
|
onMessage?(message: any): void;
|
||||||
|
onDidChangePosition?(newPosition: Position): void;
|
||||||
|
onDispose?(): void;
|
||||||
|
onDidClickLink?(link: URI, options: vscode.WebviewOptions): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebviewInputOptions extends vscode.WebviewOptions {
|
||||||
|
tryRestoreScrollPosition?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WebviewService implements IWebviewService {
|
||||||
|
_serviceBrand: any;
|
||||||
|
|
||||||
|
private readonly _revivers = new Map<string, WebviewReviver>();
|
||||||
|
private readonly _needingRevival = new Map<string, WebviewEditorInput[]>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
||||||
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
|
@IEditorGroupService private readonly _editorGroupService: IEditorGroupService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
createWebview(
|
||||||
|
viewType: string,
|
||||||
|
title: string,
|
||||||
|
column: Position,
|
||||||
|
options: vscode.WebviewOptions,
|
||||||
|
extensionFolderPath: string,
|
||||||
|
events: WebviewEvents
|
||||||
|
): WebviewEditorInput {
|
||||||
|
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, title, options, {}, events, extensionFolderPath, undefined);
|
||||||
|
this._editorService.openEditor(webviewInput, { pinned: true }, column);
|
||||||
|
return webviewInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
revealWebview(
|
||||||
|
webview: WebviewEditorInput,
|
||||||
|
column: Position | undefined
|
||||||
|
): void {
|
||||||
|
if (typeof column === 'undefined') {
|
||||||
|
column = webview.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webview.position === column) {
|
||||||
|
this._editorService.openEditor(webview, { preserveFocus: true }, column);
|
||||||
|
} else {
|
||||||
|
this._editorGroupService.moveEditor(webview, webview.position, column, { preserveFocus: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createRevivableWebview(
|
||||||
|
viewType: string,
|
||||||
|
title: string,
|
||||||
|
state: any,
|
||||||
|
options: WebviewInputOptions,
|
||||||
|
extensionFolderPath: string
|
||||||
|
): WebviewEditorInput {
|
||||||
|
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, title, options, state, {}, extensionFolderPath, {
|
||||||
|
canRevive: (webview) => {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
reviveWebview: (webview) => {
|
||||||
|
if (!this._needingRevival.has(viewType)) {
|
||||||
|
this._needingRevival.set(viewType, []);
|
||||||
|
}
|
||||||
|
this._needingRevival.get(viewType).push(webviewInput);
|
||||||
|
this.tryRevive(viewType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return webviewInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerReviver(
|
||||||
|
viewType: string,
|
||||||
|
reviver: WebviewReviver
|
||||||
|
): IDisposable {
|
||||||
|
if (this._revivers.has(viewType)) {
|
||||||
|
throw new Error(`Reveriver for 'viewType' already registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._revivers.set(viewType, reviver);
|
||||||
|
this.tryRevive(viewType);
|
||||||
|
|
||||||
|
return toDisposable(() => {
|
||||||
|
this._revivers.delete(viewType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canRevive(
|
||||||
|
webview: WebviewEditorInput
|
||||||
|
): boolean {
|
||||||
|
const viewType = webview.viewType;
|
||||||
|
return this._revivers.has(viewType) && this._revivers.get(viewType).canRevive(webview);
|
||||||
|
}
|
||||||
|
|
||||||
|
tryRevive(
|
||||||
|
viewType: string
|
||||||
|
) {
|
||||||
|
const reviver = this._revivers.get(viewType);
|
||||||
|
if (!reviver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toRevive = this._needingRevival.get(viewType);
|
||||||
|
if (!toRevive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const webview of toRevive) {
|
||||||
|
reviver.reviveWebview(webview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user