/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import { Logger } from '../logger'; import { MarkdownEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; import { Disposable, disposeAll } from '../util/dispose'; import { TopmostLineMonitor } from '../util/topmostLineMonitor'; import { DynamicMarkdownPreview, ManagedMarkdownPreview, StaticMarkdownPreview } from './preview'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { MarkdownContentProvider } from './previewContentProvider'; export interface DynamicPreviewSettings { readonly resourceColumn: vscode.ViewColumn; readonly previewColumn: vscode.ViewColumn; readonly locked: boolean; } class PreviewStore extends Disposable { private readonly _previews = new Set(); public dispose(): void { super.dispose(); for (const preview of this._previews) { preview.dispose(); } this._previews.clear(); } [Symbol.iterator](): Iterator { return this._previews[Symbol.iterator](); } public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined { for (const preview of this._previews) { if (preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked)) { return preview; } } return undefined; } public add(preview: T) { this._previews.add(preview); } public delete(preview: T) { this._previews.delete(preview); } } export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider { private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus'; private readonly _topmostLineMonitor = new TopmostLineMonitor(); private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager(); private readonly _dynamicPreviews = this._register(new PreviewStore()); private readonly _staticPreviews = this._register(new PreviewStore()); private _activePreview: ManagedMarkdownPreview | undefined = undefined; private readonly customEditorViewType = 'vscode.markdown.preview.editor'; public constructor( private readonly _contentProvider: MarkdownContentProvider, private readonly _logger: Logger, private readonly _contributions: MarkdownContributionProvider, private readonly _engine: MarkdownEngine, ) { super(); this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); this._register(vscode.window.registerCustomEditorProvider(this.customEditorViewType, this)); } public refresh() { for (const preview of this._dynamicPreviews) { preview.refresh(); } for (const preview of this._staticPreviews) { preview.refresh(); } } public updateConfiguration() { for (const preview of this._dynamicPreviews) { preview.updateConfiguration(); } for (const preview of this._staticPreviews) { preview.updateConfiguration(); } } public openDynamicPreview( resource: vscode.Uri, settings: DynamicPreviewSettings ): void { let preview = this._dynamicPreviews.get(resource, settings); if (preview) { preview.reveal(settings.previewColumn); } else { preview = this.createNewDynamicPreview(resource, settings); } preview.update(resource); } public get activePreviewResource() { return this._activePreview?.resource; } public get activePreviewResourceColumn() { return this._activePreview?.resourceColumn; } public toggleLock() { const preview = this._activePreview; if (preview instanceof DynamicMarkdownPreview) { preview.toggleLock(); // Close any previews that are now redundant, such as having two dynamic previews in the same editor group for (const otherPreview of this._dynamicPreviews) { if (otherPreview !== preview && preview.matches(otherPreview)) { otherPreview.dispose(); } } } } public async deserializeWebviewPanel( webview: vscode.WebviewPanel, state: any ): Promise { const resource = vscode.Uri.parse(state.resource); const locked = state.locked; const line = state.line; const resourceColumn = state.resourceColumn; const preview = await DynamicMarkdownPreview.revive( { resource, locked, line, resourceColumn }, webview, this._contentProvider, this._previewConfigurations, this._logger, this._topmostLineMonitor, this._contributions, this._engine); this.registerDynamicPreview(preview); } public async resolveCustomTextEditor( document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise { const preview = StaticMarkdownPreview.revive( document.uri, webview, this._contentProvider, this._previewConfigurations, this._logger, this._contributions, this._engine); this.registerStaticPreview(preview); } private createNewDynamicPreview( resource: vscode.Uri, previewSettings: DynamicPreviewSettings ): DynamicMarkdownPreview { const preview = DynamicMarkdownPreview.create( { resource, resourceColumn: previewSettings.resourceColumn, locked: previewSettings.locked, }, previewSettings.previewColumn, this._contentProvider, this._previewConfigurations, this._logger, this._topmostLineMonitor, this._contributions, this._engine); this.setPreviewActiveContext(true); this._activePreview = preview; return this.registerDynamicPreview(preview); } private registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview { this._dynamicPreviews.add(preview); preview.onDispose(() => { this._dynamicPreviews.delete(preview); }); this.trackActive(preview); preview.onDidChangeViewState(() => { // Remove other dynamic previews in our column disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview))); }); return preview; } private registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview { this._staticPreviews.add(preview); preview.onDispose(() => { this._staticPreviews.delete(preview); }); this.trackActive(preview); return preview; } private trackActive(preview: ManagedMarkdownPreview): void { preview.onDidChangeViewState(({ webviewPanel }) => { this.setPreviewActiveContext(webviewPanel.active); this._activePreview = webviewPanel.active ? preview : undefined; }); preview.onDispose(() => { if (this._activePreview === preview) { this.setPreviewActiveContext(false); this._activePreview = undefined; } }); } private setPreviewActiveContext(value: boolean) { vscode.commands.executeCommand('setContext', MarkdownPreviewManager.markdownPreviewActiveContextKey, value); } }