Path: blob/main/extensions/markdown-language-features/src/preview/previewManager.ts
3292 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { ILogger } from '../logging';7import { MarkdownContributionProvider } from '../markdownExtensions';8import { Disposable, disposeAll } from '../util/dispose';9import { isMarkdownFile } from '../util/file';10import { MdLinkOpener } from '../util/openDocumentLink';11import { MdDocumentRenderer } from './documentRenderer';12import { DynamicMarkdownPreview, IManagedMarkdownPreview, StaticMarkdownPreview } from './preview';13import { MarkdownPreviewConfigurationManager } from './previewConfig';14import { scrollEditorToLine, StartingScrollFragment } from './scrolling';15import { TopmostLineMonitor } from './topmostLineMonitor';161718export interface DynamicPreviewSettings {19readonly resourceColumn: vscode.ViewColumn;20readonly previewColumn: vscode.ViewColumn;21readonly locked: boolean;22}2324class PreviewStore<T extends IManagedMarkdownPreview> extends Disposable {2526private readonly _previews = new Set<T>();2728public override dispose(): void {29super.dispose();30for (const preview of this._previews) {31preview.dispose();32}33this._previews.clear();34}3536[Symbol.iterator](): Iterator<T> {37return this._previews[Symbol.iterator]();38}3940public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined {41const previewColumn = this._resolvePreviewColumn(previewSettings);42for (const preview of this._previews) {43if (preview.matchesResource(resource, previewColumn, previewSettings.locked)) {44return preview;45}46}47return undefined;48}4950public add(preview: T) {51this._previews.add(preview);52}5354public delete(preview: T) {55this._previews.delete(preview);56}5758private _resolvePreviewColumn(previewSettings: DynamicPreviewSettings): vscode.ViewColumn | undefined {59if (previewSettings.previewColumn === vscode.ViewColumn.Active) {60return vscode.window.tabGroups.activeTabGroup.viewColumn;61}6263if (previewSettings.previewColumn === vscode.ViewColumn.Beside) {64return vscode.window.tabGroups.activeTabGroup.viewColumn + 1;65}6667return previewSettings.previewColumn;68}69}7071export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider {7273private readonly _topmostLineMonitor = new TopmostLineMonitor();74private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();7576private readonly _dynamicPreviews = this._register(new PreviewStore<DynamicMarkdownPreview>());77private readonly _staticPreviews = this._register(new PreviewStore<StaticMarkdownPreview>());7879private _activePreview: IManagedMarkdownPreview | undefined = undefined;8081public constructor(82private readonly _contentProvider: MdDocumentRenderer,83private readonly _logger: ILogger,84private readonly _contributions: MarkdownContributionProvider,85private readonly _opener: MdLinkOpener,86) {87super();8889this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this));9091this._register(vscode.window.registerCustomEditorProvider(StaticMarkdownPreview.customEditorViewType, this, {92webviewOptions: { enableFindWidget: true }93}));9495this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => {96// When at a markdown file, apply existing scroll settings97if (textEditor?.document && isMarkdownFile(textEditor.document)) {98const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri);99if (typeof line === 'number') {100scrollEditorToLine(line, textEditor);101}102}103}));104}105106public refresh() {107for (const preview of this._dynamicPreviews) {108preview.refresh();109}110for (const preview of this._staticPreviews) {111preview.refresh();112}113}114115public updateConfiguration() {116for (const preview of this._dynamicPreviews) {117preview.updateConfiguration();118}119for (const preview of this._staticPreviews) {120preview.updateConfiguration();121}122}123124public openDynamicPreview(125resource: vscode.Uri,126settings: DynamicPreviewSettings127): void {128let preview = this._dynamicPreviews.get(resource, settings);129if (preview) {130preview.reveal(settings.previewColumn);131} else {132preview = this._createNewDynamicPreview(resource, settings);133}134135preview.update(136resource,137resource.fragment ? new StartingScrollFragment(resource.fragment) : undefined138);139}140141public get activePreviewResource() {142return this._activePreview?.resource;143}144145public get activePreviewResourceColumn() {146return this._activePreview?.resourceColumn;147}148149public findPreview(resource: vscode.Uri): IManagedMarkdownPreview | undefined {150for (const preview of [...this._dynamicPreviews, ...this._staticPreviews]) {151if (preview.resource.fsPath === resource.fsPath) {152return preview;153}154}155return undefined;156}157158public toggleLock() {159const preview = this._activePreview;160if (preview instanceof DynamicMarkdownPreview) {161preview.toggleLock();162163// Close any previews that are now redundant, such as having two dynamic previews in the same editor group164for (const otherPreview of this._dynamicPreviews) {165if (otherPreview !== preview && preview.matches(otherPreview)) {166otherPreview.dispose();167}168}169}170}171172public openDocumentLink(linkText: string, fromResource: vscode.Uri) {173const viewColumn = this.findPreview(fromResource)?.resourceColumn;174return this._opener.openDocumentLink(linkText, fromResource, viewColumn);175}176177public async deserializeWebviewPanel(178webview: vscode.WebviewPanel,179state: any180): Promise<void> {181try {182const resource = vscode.Uri.parse(state.resource);183const locked = state.locked;184const line = state.line;185const resourceColumn = state.resourceColumn;186187const preview = DynamicMarkdownPreview.revive(188{ resource, locked, line, resourceColumn },189webview,190this._contentProvider,191this._previewConfigurations,192this._logger,193this._topmostLineMonitor,194this._contributions,195this._opener);196197this._registerDynamicPreview(preview);198} catch (e) {199console.error(e);200201webview.webview.html = /* html */`<!DOCTYPE html>202<html lang="en">203<head>204<meta charset="UTF-8">205206<!-- Disable pinch zooming -->207<meta name="viewport"208content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">209210<title>Markdown Preview</title>211212<style>213html, body {214min-height: 100%;215height: 100%;216}217218.error-container {219display: flex;220justify-content: center;221align-items: center;222text-align: center;223}224</style>225226<meta http-equiv="Content-Security-Policy" content="default-src 'none';">227</head>228<body class="error-container">229<p>${vscode.l10n.t("An unexpected error occurred while restoring the Markdown preview.")}</p>230</body>231</html>`;232}233}234235public async resolveCustomTextEditor(236document: vscode.TextDocument,237webview: vscode.WebviewPanel238): Promise<void> {239const lineNumber = this._topmostLineMonitor.getPreviousStaticTextEditorLineByUri(document.uri);240const preview = StaticMarkdownPreview.revive(241document.uri,242webview,243this._contentProvider,244this._previewConfigurations,245this._topmostLineMonitor,246this._logger,247this._contributions,248this._opener,249lineNumber250);251this._registerStaticPreview(preview);252this._activePreview = preview;253}254255private _createNewDynamicPreview(256resource: vscode.Uri,257previewSettings: DynamicPreviewSettings258): DynamicMarkdownPreview {259const activeTextEditorURI = vscode.window.activeTextEditor?.document.uri;260const scrollLine = (activeTextEditorURI?.toString() === resource.toString()) ? vscode.window.activeTextEditor?.visibleRanges[0].start.line : undefined;261const preview = DynamicMarkdownPreview.create(262{263resource,264resourceColumn: previewSettings.resourceColumn,265locked: previewSettings.locked,266line: scrollLine,267},268previewSettings.previewColumn,269this._contentProvider,270this._previewConfigurations,271this._logger,272this._topmostLineMonitor,273this._contributions,274this._opener);275276this._activePreview = preview;277return this._registerDynamicPreview(preview);278}279280private _registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {281this._dynamicPreviews.add(preview);282283preview.onDispose(() => {284this._dynamicPreviews.delete(preview);285});286287this._trackActive(preview);288289preview.onDidChangeViewState(() => {290// Remove other dynamic previews in our column291disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview)));292});293return preview;294}295296private _registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview {297this._staticPreviews.add(preview);298299preview.onDispose(() => {300this._staticPreviews.delete(preview);301});302303this._trackActive(preview);304return preview;305}306307private _trackActive(preview: IManagedMarkdownPreview): void {308preview.onDidChangeViewState(({ webviewPanel }) => {309this._activePreview = webviewPanel.active ? preview : undefined;310});311312preview.onDispose(() => {313if (this._activePreview === preview) {314this._activePreview = undefined;315}316});317}318319}320321322