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

Add webview restoration api proposal #46380

Merged
merged 6 commits into from
Apr 4, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion extensions/markdown-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"onCommand:markdown.showPreviewToSide",
"onCommand:markdown.showLockedPreviewToSide",
"onCommand:markdown.showSource",
"onCommand:markdown.showPreviewSecuritySelector"
"onCommand:markdown.showPreviewSecuritySelector",
"onView:markdown.preview"
],
"contributes": {
"commands": [
Expand Down
116 changes: 89 additions & 27 deletions extensions/markdown-language-features/src/features/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,85 @@ const localize = nls.loadMessageBundle();

export class MarkdownPreview {

public static previewViewType = 'markdown.preview';
public static viewType = 'markdown.preview';

private readonly webview: vscode.Webview;
private throttleTimer: any;
private initialLine: number | undefined = undefined;
private line: number | undefined = undefined;
private readonly disposables: vscode.Disposable[] = [];
private firstUpdate = true;
private currentVersion?: { resource: vscode.Uri, version: number };
private forceUpdate = false;
private isScrolling = false;

constructor(
private _resource: vscode.Uri,
public static revive(
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,
public locked: boolean,
private readonly contentProvider: MarkdownContentProvider,
private readonly previewConfigurations: MarkdownPreviewConfigurationManager,
private readonly logger: Logger,
locked: boolean,
contentProvider: MarkdownContentProvider,
previewConfigurations: MarkdownPreviewConfigurationManager,
logger: Logger,
topmostLineMonitor: MarkdownFileTopmostLineMonitor,
private readonly contributions: MarkdownContributions
) {
this.webview = vscode.window.createWebview(
MarkdownPreview.previewViewType,
this.getPreviewTitle(this._resource),
contributions: MarkdownContributions
): MarkdownPreview {
const webview = vscode.window.createWebview(
MarkdownPreview.viewType,
MarkdownPreview.getPreviewTitle(resource, locked),
previewColumn, {
enableScripts: true,
enableCommandUris: 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.dispose();
}, null, this.disposables);
Expand Down Expand Up @@ -111,6 +159,14 @@ export class MarkdownPreview {
return this._resource;
}

public get state() {
return {
resource: this.resource.toString(),
locked: this.locked,
line: this.line
};
}

public dispose() {
this._onDisposeEmitter.fire();

Expand All @@ -124,9 +180,7 @@ export class MarkdownPreview {
public update(resource: vscode.Uri) {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.uri.fsPath === resource.fsPath) {
this.initialLine = getVisibleLine(editor);
} else {
this.initialLine = undefined;
this.line = getVisibleLine(editor);
}

// If we have changed resources, cancel any pending updates
Expand Down Expand Up @@ -169,6 +223,10 @@ export class MarkdownPreview {
return this._resource.fsPath === resource.fsPath;
}

public isWebviewOf(webview: vscode.Webview): boolean {
return this.webview === webview;
}

public matchesResource(
otherResource: vscode.Uri,
otherViewColumn: vscode.ViewColumn | undefined,
Expand All @@ -195,11 +253,11 @@ export class MarkdownPreview {

public toggleLock() {
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 {
return this.locked
private static getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
return locked
? localize('lockedPreviewTitle', '[Preview] {0}', path.basename(resource.fsPath))
: localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
}
Expand All @@ -216,7 +274,7 @@ export class MarkdownPreview {

if (typeof topLine === 'number') {
this.logger.log('updateForView', { markdownFile: resource });
this.initialLine = topLine;
this.line = topLine;
this.webview.postMessage({
type: 'updateView',
line: topLine,
Expand All @@ -233,25 +291,28 @@ export class MarkdownPreview {

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.initialLine) {
this.updateForView(resource, this.initialLine);
if (this.line) {
this.updateForView(resource, this.line);
}
return;
}
this.forceUpdate = false;

this.currentVersion = { resource, version: document.version };
this.contentProvider.provideTextDocumentContent(document, this.previewConfigurations, this.initialLine)
this.contentProvider.provideTextDocumentContent(document, this.previewConfigurations, this.line)
.then(content => {
if (this._resource === resource) {
this.webview.title = this.getPreviewTitle(this._resource);
this.webview.title = MarkdownPreview.getPreviewTitle(this._resource, this.locked);
this.webview.html = content;
}
});
}

private getLocalResourceRoots(resource: vscode.Uri): vscode.Uri[] {
const baseRoots = this.contributions.previewResourceRoots;
private static getLocalResourceRoots(
resource: vscode.Uri,
contributions: MarkdownContributions
): vscode.Uri[] {
const baseRoots = contributions.previewResourceRoots;

const folder = vscode.workspace.getWorkspaceFolder(resource);
if (folder) {
Expand All @@ -266,6 +327,7 @@ export class MarkdownPreview {
}

private onDidScrollPreview(line: number) {
this.line = line;
for (const editor of vscode.window.visibleTextEditors) {
if (!this.isPreviewOf(editor.document.uri)) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { isMarkdownFile } from '../util/file';
import { MarkdownPreviewConfigurationManager } from './previewConfig';
import { MarkdownContributions } from '../markdownExtensions';

export class MarkdownPreviewManager {
export class MarkdownPreviewManager implements vscode.WebviewSerializer {
private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus';

private readonly topmostLineMonitor = new MarkdownFileTopmostLineMonitor();
Expand All @@ -29,15 +29,14 @@ export class MarkdownPreviewManager {
private readonly contributions: MarkdownContributions
) {
vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor) {
if (isMarkdownFile(editor.document)) {
for (const preview of this.previews.filter(preview => !preview.locked)) {
preview.update(editor.document.uri);
}
if (editor && isMarkdownFile(editor.document)) {
for (const preview of this.previews.filter(preview => !preview.locked)) {
preview.update(editor.document.uri);
}
}
}, null, this.disposables);

this.disposables.push(vscode.window.registerWebviewSerializer(MarkdownPreview.viewType, this));
}

public dispose(): void {
Expand Down Expand Up @@ -66,7 +65,6 @@ export class MarkdownPreviewManager {
preview.reveal(previewSettings.previewColumn);
} else {
preview = this.createNewPreview(resource, previewSettings);
this.previews.push(preview);
}

preview.update(resource);
Expand All @@ -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(
resource: vscode.Uri,
previewSettings: PreviewSettings
Expand All @@ -101,8 +123,8 @@ export class MarkdownPreviewManager {
private createNewPreview(
resource: vscode.Uri,
previewSettings: PreviewSettings
) {
const preview = new MarkdownPreview(
): MarkdownPreview {
const preview = MarkdownPreview.create(
resource,
previewSettings.previewColumn,
previewSettings.locked,
Expand All @@ -112,6 +134,14 @@ export class MarkdownPreviewManager {
this.topmostLineMonitor,
this.contributions);

return this.registerPreview(preview);
}

private registerPreview(
preview: MarkdownPreview
): MarkdownPreview {
this.previews.push(preview);

preview.onDispose(() => {
const existing = this.previews.indexOf(preview!);
if (existing >= 0) {
Expand Down
45 changes: 43 additions & 2 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ declare module 'vscode' {
*/
export interface Webview {
/**
* The type of the webview, such as `'markdownw.preview'`
* The type of the webview, such as `'markdown.preview'`
*/
readonly viewType: string;

Expand Down Expand Up @@ -636,16 +636,57 @@ declare module 'vscode' {
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 {
/**
* 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 column Editor column to show the new webview in.
* @param options Content settings for the 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
Expand Down