Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/markdown-language-features/src/preview/previewManager.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as vscode from 'vscode';
7
import { ILogger } from '../logging';
8
import { MarkdownContributionProvider } from '../markdownExtensions';
9
import { Disposable, disposeAll } from '../util/dispose';
10
import { isMarkdownFile } from '../util/file';
11
import { MdLinkOpener } from '../util/openDocumentLink';
12
import { MdDocumentRenderer } from './documentRenderer';
13
import { DynamicMarkdownPreview, IManagedMarkdownPreview, StaticMarkdownPreview } from './preview';
14
import { MarkdownPreviewConfigurationManager } from './previewConfig';
15
import { scrollEditorToLine, StartingScrollFragment } from './scrolling';
16
import { TopmostLineMonitor } from './topmostLineMonitor';
17
18
19
export interface DynamicPreviewSettings {
20
readonly resourceColumn: vscode.ViewColumn;
21
readonly previewColumn: vscode.ViewColumn;
22
readonly locked: boolean;
23
}
24
25
class PreviewStore<T extends IManagedMarkdownPreview> extends Disposable {
26
27
private readonly _previews = new Set<T>();
28
29
public override dispose(): void {
30
super.dispose();
31
for (const preview of this._previews) {
32
preview.dispose();
33
}
34
this._previews.clear();
35
}
36
37
[Symbol.iterator](): Iterator<T> {
38
return this._previews[Symbol.iterator]();
39
}
40
41
public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined {
42
const previewColumn = this._resolvePreviewColumn(previewSettings);
43
for (const preview of this._previews) {
44
if (preview.matchesResource(resource, previewColumn, previewSettings.locked)) {
45
return preview;
46
}
47
}
48
return undefined;
49
}
50
51
public add(preview: T) {
52
this._previews.add(preview);
53
}
54
55
public delete(preview: T) {
56
this._previews.delete(preview);
57
}
58
59
private _resolvePreviewColumn(previewSettings: DynamicPreviewSettings): vscode.ViewColumn | undefined {
60
if (previewSettings.previewColumn === vscode.ViewColumn.Active) {
61
return vscode.window.tabGroups.activeTabGroup.viewColumn;
62
}
63
64
if (previewSettings.previewColumn === vscode.ViewColumn.Beside) {
65
return vscode.window.tabGroups.activeTabGroup.viewColumn + 1;
66
}
67
68
return previewSettings.previewColumn;
69
}
70
}
71
72
export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider {
73
74
private readonly _topmostLineMonitor = new TopmostLineMonitor();
75
private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();
76
77
private readonly _dynamicPreviews = this._register(new PreviewStore<DynamicMarkdownPreview>());
78
private readonly _staticPreviews = this._register(new PreviewStore<StaticMarkdownPreview>());
79
80
private _activePreview: IManagedMarkdownPreview | undefined = undefined;
81
82
public constructor(
83
private readonly _contentProvider: MdDocumentRenderer,
84
private readonly _logger: ILogger,
85
private readonly _contributions: MarkdownContributionProvider,
86
private readonly _opener: MdLinkOpener,
87
) {
88
super();
89
90
this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this));
91
92
this._register(vscode.window.registerCustomEditorProvider(StaticMarkdownPreview.customEditorViewType, this, {
93
webviewOptions: { enableFindWidget: true }
94
}));
95
96
this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => {
97
// When at a markdown file, apply existing scroll settings
98
if (textEditor?.document && isMarkdownFile(textEditor.document)) {
99
const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri);
100
if (typeof line === 'number') {
101
scrollEditorToLine(line, textEditor);
102
}
103
}
104
}));
105
}
106
107
public refresh() {
108
for (const preview of this._dynamicPreviews) {
109
preview.refresh();
110
}
111
for (const preview of this._staticPreviews) {
112
preview.refresh();
113
}
114
}
115
116
public updateConfiguration() {
117
for (const preview of this._dynamicPreviews) {
118
preview.updateConfiguration();
119
}
120
for (const preview of this._staticPreviews) {
121
preview.updateConfiguration();
122
}
123
}
124
125
public openDynamicPreview(
126
resource: vscode.Uri,
127
settings: DynamicPreviewSettings
128
): void {
129
let preview = this._dynamicPreviews.get(resource, settings);
130
if (preview) {
131
preview.reveal(settings.previewColumn);
132
} else {
133
preview = this._createNewDynamicPreview(resource, settings);
134
}
135
136
preview.update(
137
resource,
138
resource.fragment ? new StartingScrollFragment(resource.fragment) : undefined
139
);
140
}
141
142
public get activePreviewResource() {
143
return this._activePreview?.resource;
144
}
145
146
public get activePreviewResourceColumn() {
147
return this._activePreview?.resourceColumn;
148
}
149
150
public findPreview(resource: vscode.Uri): IManagedMarkdownPreview | undefined {
151
for (const preview of [...this._dynamicPreviews, ...this._staticPreviews]) {
152
if (preview.resource.fsPath === resource.fsPath) {
153
return preview;
154
}
155
}
156
return undefined;
157
}
158
159
public toggleLock() {
160
const preview = this._activePreview;
161
if (preview instanceof DynamicMarkdownPreview) {
162
preview.toggleLock();
163
164
// Close any previews that are now redundant, such as having two dynamic previews in the same editor group
165
for (const otherPreview of this._dynamicPreviews) {
166
if (otherPreview !== preview && preview.matches(otherPreview)) {
167
otherPreview.dispose();
168
}
169
}
170
}
171
}
172
173
public openDocumentLink(linkText: string, fromResource: vscode.Uri) {
174
const viewColumn = this.findPreview(fromResource)?.resourceColumn;
175
return this._opener.openDocumentLink(linkText, fromResource, viewColumn);
176
}
177
178
public async deserializeWebviewPanel(
179
webview: vscode.WebviewPanel,
180
state: any
181
): Promise<void> {
182
try {
183
const resource = vscode.Uri.parse(state.resource);
184
const locked = state.locked;
185
const line = state.line;
186
const resourceColumn = state.resourceColumn;
187
188
const preview = DynamicMarkdownPreview.revive(
189
{ resource, locked, line, resourceColumn },
190
webview,
191
this._contentProvider,
192
this._previewConfigurations,
193
this._logger,
194
this._topmostLineMonitor,
195
this._contributions,
196
this._opener);
197
198
this._registerDynamicPreview(preview);
199
} catch (e) {
200
console.error(e);
201
202
webview.webview.html = /* html */`<!DOCTYPE html>
203
<html lang="en">
204
<head>
205
<meta charset="UTF-8">
206
207
<!-- Disable pinch zooming -->
208
<meta name="viewport"
209
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
210
211
<title>Markdown Preview</title>
212
213
<style>
214
html, body {
215
min-height: 100%;
216
height: 100%;
217
}
218
219
.error-container {
220
display: flex;
221
justify-content: center;
222
align-items: center;
223
text-align: center;
224
}
225
</style>
226
227
<meta http-equiv="Content-Security-Policy" content="default-src 'none';">
228
</head>
229
<body class="error-container">
230
<p>${vscode.l10n.t("An unexpected error occurred while restoring the Markdown preview.")}</p>
231
</body>
232
</html>`;
233
}
234
}
235
236
public async resolveCustomTextEditor(
237
document: vscode.TextDocument,
238
webview: vscode.WebviewPanel
239
): Promise<void> {
240
const lineNumber = this._topmostLineMonitor.getPreviousStaticTextEditorLineByUri(document.uri);
241
const preview = StaticMarkdownPreview.revive(
242
document.uri,
243
webview,
244
this._contentProvider,
245
this._previewConfigurations,
246
this._topmostLineMonitor,
247
this._logger,
248
this._contributions,
249
this._opener,
250
lineNumber
251
);
252
this._registerStaticPreview(preview);
253
this._activePreview = preview;
254
}
255
256
private _createNewDynamicPreview(
257
resource: vscode.Uri,
258
previewSettings: DynamicPreviewSettings
259
): DynamicMarkdownPreview {
260
const activeTextEditorURI = vscode.window.activeTextEditor?.document.uri;
261
const scrollLine = (activeTextEditorURI?.toString() === resource.toString()) ? vscode.window.activeTextEditor?.visibleRanges[0].start.line : undefined;
262
const preview = DynamicMarkdownPreview.create(
263
{
264
resource,
265
resourceColumn: previewSettings.resourceColumn,
266
locked: previewSettings.locked,
267
line: scrollLine,
268
},
269
previewSettings.previewColumn,
270
this._contentProvider,
271
this._previewConfigurations,
272
this._logger,
273
this._topmostLineMonitor,
274
this._contributions,
275
this._opener);
276
277
this._activePreview = preview;
278
return this._registerDynamicPreview(preview);
279
}
280
281
private _registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
282
this._dynamicPreviews.add(preview);
283
284
preview.onDispose(() => {
285
this._dynamicPreviews.delete(preview);
286
});
287
288
this._trackActive(preview);
289
290
preview.onDidChangeViewState(() => {
291
// Remove other dynamic previews in our column
292
disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview)));
293
});
294
return preview;
295
}
296
297
private _registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview {
298
this._staticPreviews.add(preview);
299
300
preview.onDispose(() => {
301
this._staticPreviews.delete(preview);
302
});
303
304
this._trackActive(preview);
305
return preview;
306
}
307
308
private _trackActive(preview: IManagedMarkdownPreview): void {
309
preview.onDidChangeViewState(({ webviewPanel }) => {
310
this._activePreview = webviewPanel.active ? preview : undefined;
311
});
312
313
preview.onDispose(() => {
314
if (this._activePreview === preview) {
315
this._activePreview = undefined;
316
}
317
});
318
}
319
320
}
321
322