Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/markdown-language-features/src/preview/preview.ts
5240 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 * as uri from 'vscode-uri';
8
import { ILogger } from '../logging';
9
import { MarkdownContributionProvider } from '../markdownExtensions';
10
import { Disposable } from '../util/dispose';
11
import { isMarkdownFile } from '../util/file';
12
import { MdLinkOpener } from '../util/openDocumentLink';
13
import { WebviewResourceProvider } from '../util/resources';
14
import { urlToUri } from '../util/url';
15
import { ImageInfo, MdDocumentRenderer } from './documentRenderer';
16
import { MarkdownPreviewConfigurationManager } from './previewConfig';
17
import { scrollEditorToLine, StartingScrollFragment, StartingScrollLine, StartingScrollLocation } from './scrolling';
18
import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from './topmostLineMonitor';
19
import type { FromWebviewMessage, ToWebviewMessage } from '../../types/previewMessaging';
20
21
export class PreviewDocumentVersion {
22
23
public readonly resource: vscode.Uri;
24
private readonly _version: number;
25
26
public constructor(document: vscode.TextDocument) {
27
this.resource = document.uri;
28
this._version = document.version;
29
}
30
31
public equals(other: PreviewDocumentVersion): boolean {
32
return this.resource.fsPath === other.resource.fsPath
33
&& this._version === other._version;
34
}
35
}
36
37
interface MarkdownPreviewDelegate {
38
getTitle?(resource: vscode.Uri): string;
39
getAdditionalState(): {};
40
openPreviewLinkToMarkdownFile(markdownLink: vscode.Uri, fragment: string | undefined): void;
41
}
42
43
class MarkdownPreview extends Disposable implements WebviewResourceProvider {
44
45
private static readonly _unwatchedImageSchemes = new Set(['https', 'http', 'data']);
46
47
private _disposed: boolean = false;
48
49
private readonly _delay = 300;
50
private _throttleTimer: any;
51
52
private readonly _resource: vscode.Uri;
53
private readonly _webviewPanel: vscode.WebviewPanel;
54
55
private _line: number | undefined;
56
private readonly _scrollToFragment: string | undefined;
57
private _firstUpdate = true;
58
private _currentVersion?: PreviewDocumentVersion;
59
private _isScrolling = false;
60
61
private _imageInfo: readonly ImageInfo[] = [];
62
private readonly _fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>();
63
64
private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
65
public readonly onScroll = this._onScrollEmitter.event;
66
67
private readonly _disposeCts = this._register(new vscode.CancellationTokenSource());
68
69
constructor(
70
webview: vscode.WebviewPanel,
71
resource: vscode.Uri,
72
startingScroll: StartingScrollLocation | undefined,
73
private readonly _delegate: MarkdownPreviewDelegate,
74
private readonly _contentProvider: MdDocumentRenderer,
75
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
76
private readonly _logger: ILogger,
77
private readonly _contributionProvider: MarkdownContributionProvider,
78
private readonly _opener: MdLinkOpener,
79
) {
80
super();
81
82
this._webviewPanel = webview;
83
this._resource = resource;
84
85
switch (startingScroll?.type) {
86
case 'line':
87
if (!isNaN(startingScroll.line!)) {
88
this._line = startingScroll.line;
89
}
90
break;
91
92
case 'fragment':
93
this._scrollToFragment = startingScroll.fragment;
94
break;
95
}
96
97
this._register(_contributionProvider.onContributionsChanged(() => {
98
setTimeout(() => this.refresh(true), 0);
99
}));
100
101
this._register(vscode.workspace.onDidChangeTextDocument(event => {
102
if (this.isPreviewOf(event.document.uri)) {
103
this.refresh();
104
}
105
}));
106
107
this._register(vscode.workspace.onDidOpenTextDocument(document => {
108
if (this.isPreviewOf(document.uri)) {
109
this.refresh();
110
}
111
}));
112
113
if (vscode.workspace.fs.isWritableFileSystem(resource.scheme)) {
114
const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*')));
115
this._register(watcher.onDidChange(uri => {
116
if (this.isPreviewOf(uri)) {
117
// Only use the file system event when VS Code does not already know about the file
118
if (!vscode.workspace.textDocuments.some(doc => doc.uri.toString() === uri.toString())) {
119
this.refresh();
120
}
121
}
122
}));
123
}
124
125
this._register(this._webviewPanel.webview.onDidReceiveMessage((e: FromWebviewMessage.Type) => {
126
if (e.source !== this._resource.toString()) {
127
return;
128
}
129
130
switch (e.type) {
131
case 'cacheImageSizes':
132
this._imageInfo = e.imageData;
133
break;
134
135
case 'revealLine':
136
this._onDidScrollPreview(e.line);
137
break;
138
139
case 'didClick':
140
this._onDidClickPreview(e.line);
141
break;
142
143
case 'openLink':
144
this._onDidClickPreviewLink(e.href);
145
break;
146
147
case 'showPreviewSecuritySelector':
148
vscode.commands.executeCommand('markdown.showPreviewSecuritySelector', e.source);
149
break;
150
151
case 'previewStyleLoadError':
152
vscode.window.showWarningMessage(
153
vscode.l10n.t("Could not load 'markdown.styles': {0}", e.unloadedStyles.join(', ')));
154
break;
155
}
156
}));
157
158
this.refresh();
159
}
160
161
override dispose() {
162
this._disposeCts.cancel();
163
164
super.dispose();
165
166
this._disposed = true;
167
168
clearTimeout(this._throttleTimer);
169
for (const entry of this._fileWatchersBySrc.values()) {
170
entry.dispose();
171
}
172
this._fileWatchersBySrc.clear();
173
}
174
175
public get resource(): vscode.Uri {
176
return this._resource;
177
}
178
179
public get state() {
180
return {
181
resource: this._resource.toString(),
182
line: this._line,
183
fragment: this._scrollToFragment,
184
...this._delegate.getAdditionalState(),
185
};
186
}
187
188
/**
189
* The first call immediately refreshes the preview,
190
* calls happening shortly thereafter are debounced.
191
*/
192
public refresh(forceUpdate: boolean = false) {
193
// Schedule update if none is pending
194
if (!this._throttleTimer) {
195
if (this._firstUpdate) {
196
this._updatePreview(true);
197
} else {
198
this._throttleTimer = setTimeout(() => this._updatePreview(forceUpdate), this._delay);
199
}
200
}
201
202
this._firstUpdate = false;
203
}
204
205
206
public isPreviewOf(resource: vscode.Uri): boolean {
207
return this._resource.fsPath === resource.fsPath;
208
}
209
210
public postMessage(msg: ToWebviewMessage.Type) {
211
if (!this._disposed) {
212
this._webviewPanel.webview.postMessage(msg);
213
}
214
}
215
216
public scrollTo(topLine: number) {
217
if (this._disposed) {
218
return;
219
}
220
221
if (this._isScrolling) {
222
this._isScrolling = false;
223
return;
224
}
225
226
this._logger.trace('MarkdownPreview', 'updateForView', { markdownFile: this._resource });
227
this._line = topLine;
228
this.postMessage({
229
type: 'updateView',
230
line: topLine,
231
source: this._resource.toString()
232
});
233
}
234
235
private async _updatePreview(forceUpdate?: boolean): Promise<void> {
236
clearTimeout(this._throttleTimer);
237
this._throttleTimer = undefined;
238
239
if (this._disposed) {
240
return;
241
}
242
243
let document: vscode.TextDocument;
244
try {
245
document = await vscode.workspace.openTextDocument(this._resource);
246
} catch {
247
if (!this._disposed) {
248
await this._showFileNotFoundError();
249
}
250
return;
251
}
252
253
if (this._disposed) {
254
return;
255
}
256
257
const pendingVersion = new PreviewDocumentVersion(document);
258
if (!forceUpdate && this._currentVersion?.equals(pendingVersion)) {
259
if (this._line) {
260
this.scrollTo(this._line);
261
}
262
return;
263
}
264
265
const shouldReloadPage = forceUpdate || !this._currentVersion || this._currentVersion.resource.toString() !== pendingVersion.resource.toString() || !this._webviewPanel.visible;
266
this._currentVersion = pendingVersion;
267
268
let selectedLine: number | undefined = undefined;
269
for (const editor of vscode.window.visibleTextEditors) {
270
if (this.isPreviewOf(editor.document.uri)) {
271
selectedLine = editor.selection.active.line;
272
break;
273
}
274
}
275
276
const content = await (shouldReloadPage
277
? this._contentProvider.renderDocument(document, this, this._previewConfigurations, this._line, selectedLine, this.state, this._imageInfo, this._disposeCts.token)
278
: this._contentProvider.renderBody(document, this));
279
280
// Another call to `doUpdate` may have happened.
281
// Make sure we are still updating for the correct document
282
if (this._currentVersion?.equals(pendingVersion)) {
283
this._updateWebviewContent(content.html, shouldReloadPage);
284
this._updateImageWatchers(content.containingImages);
285
}
286
}
287
288
private _onDidScrollPreview(line: number) {
289
this._line = line;
290
this._onScrollEmitter.fire({ line: this._line, uri: this._resource });
291
const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource);
292
if (!config.scrollEditorWithPreview) {
293
return;
294
}
295
296
for (const editor of vscode.window.visibleTextEditors) {
297
if (!this.isPreviewOf(editor.document.uri)) {
298
continue;
299
}
300
301
this._isScrolling = true;
302
scrollEditorToLine(line, editor);
303
}
304
}
305
306
private async _onDidClickPreview(line: number): Promise<void> {
307
// fix #82457, find currently opened but unfocused source tab
308
await vscode.commands.executeCommand('markdown.showSource');
309
310
const revealLineInEditor = (editor: vscode.TextEditor) => {
311
const position = new vscode.Position(line, 0);
312
const newSelection = new vscode.Selection(position, position);
313
editor.selection = newSelection;
314
editor.revealRange(newSelection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
315
};
316
317
for (const visibleEditor of vscode.window.visibleTextEditors) {
318
if (this.isPreviewOf(visibleEditor.document.uri)) {
319
const editor = await vscode.window.showTextDocument(visibleEditor.document, visibleEditor.viewColumn);
320
revealLineInEditor(editor);
321
return;
322
}
323
}
324
325
await vscode.workspace.openTextDocument(this._resource)
326
.then(vscode.window.showTextDocument)
327
.then((editor) => {
328
revealLineInEditor(editor);
329
}, () => {
330
vscode.window.showErrorMessage(vscode.l10n.t('Could not open {0}', this._resource.toString()));
331
});
332
}
333
334
private async _showFileNotFoundError() {
335
this._webviewPanel.webview.html = this._contentProvider.renderFileNotFoundDocument(this._resource);
336
}
337
338
private _updateWebviewContent(html: string, reloadPage: boolean): void {
339
if (this._disposed) {
340
return;
341
}
342
343
if (this._delegate.getTitle) {
344
this._webviewPanel.title = this._delegate.getTitle(this._resource);
345
}
346
this._webviewPanel.webview.options = this._getWebviewOptions();
347
348
if (reloadPage) {
349
this._webviewPanel.webview.html = html;
350
} else {
351
this.postMessage({
352
type: 'updateContent',
353
content: html,
354
source: this._resource.toString(),
355
});
356
}
357
}
358
359
private _updateImageWatchers(srcs: Set<string>) {
360
// Delete stale file watchers.
361
for (const [src, watcher] of this._fileWatchersBySrc) {
362
if (!srcs.has(src)) {
363
watcher.dispose();
364
this._fileWatchersBySrc.delete(src);
365
}
366
}
367
368
// Create new file watchers.
369
const root = vscode.Uri.joinPath(this._resource, '../');
370
for (const src of srcs) {
371
const uri = urlToUri(src, root);
372
if (uri && !MarkdownPreview._unwatchedImageSchemes.has(uri.scheme) && !this._fileWatchersBySrc.has(src)) {
373
const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'));
374
watcher.onDidChange(() => {
375
this.refresh(true);
376
});
377
this._fileWatchersBySrc.set(src, watcher);
378
}
379
}
380
}
381
382
private _getWebviewOptions(): vscode.WebviewOptions {
383
return {
384
enableScripts: true,
385
enableForms: false,
386
localResourceRoots: this._getLocalResourceRoots()
387
};
388
}
389
390
private _getLocalResourceRoots(): ReadonlyArray<vscode.Uri> {
391
const baseRoots = Array.from(this._contributionProvider.contributions.previewResourceRoots);
392
393
const folder = vscode.workspace.getWorkspaceFolder(this._resource);
394
if (folder) {
395
const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri);
396
if (workspaceRoots) {
397
baseRoots.push(...workspaceRoots);
398
}
399
} else {
400
baseRoots.push(uri.Utils.dirname(this._resource));
401
}
402
403
return baseRoots;
404
}
405
406
private async _onDidClickPreviewLink(href: string) {
407
const config = vscode.workspace.getConfiguration('markdown', this.resource);
408
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
409
if (openLinks === 'inPreview') {
410
const resolved = await this._opener.resolveDocumentLink(href, this.resource);
411
if (resolved.kind === 'file') {
412
try {
413
const doc = await vscode.workspace.openTextDocument(vscode.Uri.from(resolved.uri));
414
if (isMarkdownFile(doc)) {
415
return this._delegate.openPreviewLinkToMarkdownFile(doc.uri, resolved.fragment ? decodeURIComponent(resolved.fragment) : undefined);
416
}
417
} catch {
418
// Noop
419
}
420
}
421
}
422
423
return this._opener.openDocumentLink(href, this.resource);
424
}
425
426
//#region WebviewResourceProvider
427
428
asWebviewUri(resource: vscode.Uri) {
429
return this._webviewPanel.webview.asWebviewUri(resource);
430
}
431
432
get cspSource() {
433
return [
434
this._webviewPanel.webview.cspSource,
435
436
// On web, we also need to allow loading of resources from contributed extensions
437
...this._contributionProvider.contributions.previewResourceRoots
438
.filter(root => root.scheme === 'http' || root.scheme === 'https')
439
.map(root => {
440
const dirRoot = root.path.endsWith('/') ? root : root.with({ path: root.path + '/' });
441
return dirRoot.toString();
442
}),
443
].join(' ');
444
}
445
446
//#endregion
447
}
448
449
export interface IManagedMarkdownPreview {
450
451
readonly resource: vscode.Uri;
452
readonly resourceColumn: vscode.ViewColumn;
453
454
readonly onDispose: vscode.Event<void>;
455
readonly onDidChangeViewState: vscode.Event<vscode.WebviewPanelOnDidChangeViewStateEvent>;
456
457
copyImage(id: string): void;
458
dispose(): void;
459
refresh(): void;
460
updateConfiguration(): void;
461
462
matchesResource(
463
otherResource: vscode.Uri,
464
otherPosition: vscode.ViewColumn | undefined,
465
otherLocked: boolean
466
): boolean;
467
}
468
469
export class StaticMarkdownPreview extends Disposable implements IManagedMarkdownPreview {
470
471
public static readonly customEditorViewType = 'vscode.markdown.preview.editor';
472
473
public static revive(
474
resource: vscode.Uri,
475
webview: vscode.WebviewPanel,
476
contentProvider: MdDocumentRenderer,
477
previewConfigurations: MarkdownPreviewConfigurationManager,
478
topmostLineMonitor: TopmostLineMonitor,
479
logger: ILogger,
480
contributionProvider: MarkdownContributionProvider,
481
opener: MdLinkOpener,
482
scrollLine?: number,
483
): StaticMarkdownPreview {
484
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, opener, scrollLine);
485
}
486
487
private readonly _preview: MarkdownPreview;
488
489
private constructor(
490
private readonly _webviewPanel: vscode.WebviewPanel,
491
resource: vscode.Uri,
492
contentProvider: MdDocumentRenderer,
493
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
494
topmostLineMonitor: TopmostLineMonitor,
495
logger: ILogger,
496
contributionProvider: MarkdownContributionProvider,
497
opener: MdLinkOpener,
498
scrollLine?: number,
499
) {
500
super();
501
const topScrollLocation = scrollLine ? new StartingScrollLine(scrollLine) : undefined;
502
this._preview = this._register(new MarkdownPreview(this._webviewPanel, resource, topScrollLocation, {
503
getAdditionalState: () => { return {}; },
504
openPreviewLinkToMarkdownFile: (markdownLink, fragment) => {
505
return vscode.commands.executeCommand('vscode.openWith', markdownLink.with({
506
fragment
507
}), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn);
508
}
509
}, contentProvider, _previewConfigurations, logger, contributionProvider, opener));
510
511
this._register(this._webviewPanel.onDidDispose(() => {
512
this.dispose();
513
}));
514
515
this._register(this._webviewPanel.onDidChangeViewState(e => {
516
this._onDidChangeViewState.fire(e);
517
}));
518
519
this._register(this._preview.onScroll((scrollInfo) => {
520
topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo);
521
}));
522
523
this._register(topmostLineMonitor.onDidChanged(event => {
524
if (this._preview.isPreviewOf(event.resource)) {
525
this._preview.scrollTo(event.line);
526
}
527
}));
528
}
529
530
copyImage(id: string) {
531
this._webviewPanel.reveal();
532
this._preview.postMessage({
533
type: 'copyImage',
534
source: this.resource.toString(),
535
id: id
536
});
537
}
538
539
private readonly _onDispose = this._register(new vscode.EventEmitter<void>());
540
public readonly onDispose = this._onDispose.event;
541
542
private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
543
public readonly onDidChangeViewState = this._onDidChangeViewState.event;
544
545
override dispose() {
546
this._onDispose.fire();
547
super.dispose();
548
}
549
550
public matchesResource(
551
_otherResource: vscode.Uri,
552
_otherPosition: vscode.ViewColumn | undefined,
553
_otherLocked: boolean
554
): boolean {
555
return false;
556
}
557
558
public refresh() {
559
this._preview.refresh(true);
560
}
561
562
public updateConfiguration() {
563
if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) {
564
this.refresh();
565
}
566
}
567
568
public get resource() {
569
return this._preview.resource;
570
}
571
572
public get resourceColumn() {
573
return this._webviewPanel.viewColumn || vscode.ViewColumn.One;
574
}
575
}
576
577
interface DynamicPreviewInput {
578
readonly resource: vscode.Uri;
579
readonly resourceColumn: vscode.ViewColumn;
580
readonly locked: boolean;
581
readonly line?: number;
582
}
583
584
export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdownPreview {
585
586
public static readonly viewType = 'markdown.preview';
587
588
private readonly _resourceColumn: vscode.ViewColumn;
589
private _locked: boolean;
590
591
private readonly _webviewPanel: vscode.WebviewPanel;
592
private _preview: MarkdownPreview;
593
594
public static revive(
595
input: DynamicPreviewInput,
596
webview: vscode.WebviewPanel,
597
contentProvider: MdDocumentRenderer,
598
previewConfigurations: MarkdownPreviewConfigurationManager,
599
logger: ILogger,
600
topmostLineMonitor: TopmostLineMonitor,
601
contributionProvider: MarkdownContributionProvider,
602
opener: MdLinkOpener,
603
): DynamicMarkdownPreview {
604
webview.iconPath = contentProvider.iconPath;
605
606
return new DynamicMarkdownPreview(webview, input,
607
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener);
608
}
609
610
public static create(
611
input: DynamicPreviewInput,
612
previewColumn: vscode.ViewColumn,
613
contentProvider: MdDocumentRenderer,
614
previewConfigurations: MarkdownPreviewConfigurationManager,
615
logger: ILogger,
616
topmostLineMonitor: TopmostLineMonitor,
617
contributionProvider: MarkdownContributionProvider,
618
opener: MdLinkOpener,
619
): DynamicMarkdownPreview {
620
const webview = vscode.window.createWebviewPanel(
621
DynamicMarkdownPreview.viewType,
622
DynamicMarkdownPreview._getPreviewTitle(input.resource, input.locked),
623
previewColumn, { enableFindWidget: true, });
624
625
webview.iconPath = contentProvider.iconPath;
626
627
return new DynamicMarkdownPreview(webview, input,
628
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener);
629
}
630
631
private constructor(
632
webview: vscode.WebviewPanel,
633
input: DynamicPreviewInput,
634
private readonly _contentProvider: MdDocumentRenderer,
635
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
636
private readonly _logger: ILogger,
637
private readonly _topmostLineMonitor: TopmostLineMonitor,
638
private readonly _contributionProvider: MarkdownContributionProvider,
639
private readonly _opener: MdLinkOpener,
640
) {
641
super();
642
643
this._webviewPanel = webview;
644
645
this._resourceColumn = input.resourceColumn;
646
this._locked = input.locked;
647
648
this._preview = this._createPreview(input.resource, typeof input.line === 'number' ? new StartingScrollLine(input.line) : undefined);
649
650
this._register(webview.onDidDispose(() => { this.dispose(); }));
651
652
this._register(this._webviewPanel.onDidChangeViewState(e => {
653
this._onDidChangeViewStateEmitter.fire(e);
654
}));
655
656
this._register(this._topmostLineMonitor.onDidChanged(event => {
657
if (this._preview.isPreviewOf(event.resource)) {
658
this._preview.scrollTo(event.line);
659
}
660
}));
661
662
this._register(vscode.window.onDidChangeTextEditorSelection(event => {
663
if (this._preview.isPreviewOf(event.textEditor.document.uri)) {
664
this._preview.postMessage({
665
type: 'onDidChangeTextEditorSelection',
666
line: event.selections[0].active.line,
667
source: this._preview.resource.toString()
668
});
669
}
670
}));
671
672
this._register(vscode.window.onDidChangeActiveTextEditor(editor => {
673
// Only allow previewing normal text editors which have a viewColumn: See #101514
674
if (typeof editor?.viewColumn === 'undefined') {
675
return;
676
}
677
678
if (isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) {
679
const line = getVisibleLine(editor);
680
this.update(editor.document.uri, line ? new StartingScrollLine(line) : undefined);
681
}
682
}));
683
}
684
685
copyImage(id: string) {
686
this._webviewPanel.reveal();
687
this._preview.postMessage({
688
type: 'copyImage',
689
source: this.resource.toString(),
690
id: id
691
});
692
}
693
694
private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter<void>());
695
public readonly onDispose = this._onDisposeEmitter.event;
696
697
private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
698
public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event;
699
700
override dispose() {
701
this._preview.dispose();
702
this._webviewPanel.dispose();
703
704
this._onDisposeEmitter.fire();
705
this._onDisposeEmitter.dispose();
706
super.dispose();
707
}
708
709
public get resource() {
710
return this._preview.resource;
711
}
712
713
public get resourceColumn() {
714
return this._resourceColumn;
715
}
716
717
public reveal(viewColumn: vscode.ViewColumn) {
718
this._webviewPanel.reveal(viewColumn);
719
}
720
721
public refresh() {
722
this._preview.refresh(true);
723
}
724
725
public updateConfiguration() {
726
if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) {
727
this.refresh();
728
}
729
}
730
731
public update(newResource: vscode.Uri, scrollLocation?: StartingScrollLocation) {
732
if (this._preview.isPreviewOf(newResource)) {
733
switch (scrollLocation?.type) {
734
case 'line':
735
this._preview.scrollTo(scrollLocation.line);
736
return;
737
738
case 'fragment':
739
// Workaround. For fragments, just reload the entire preview
740
break;
741
742
default:
743
return;
744
}
745
}
746
747
this._preview.dispose();
748
this._preview = this._createPreview(newResource, scrollLocation);
749
}
750
751
public toggleLock() {
752
this._locked = !this._locked;
753
this._webviewPanel.title = DynamicMarkdownPreview._getPreviewTitle(this._preview.resource, this._locked);
754
}
755
756
private static _getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
757
const resourceLabel = uri.Utils.basename(resource);
758
return locked
759
? vscode.l10n.t('[Preview] {0}', resourceLabel)
760
: vscode.l10n.t('Preview {0}', resourceLabel);
761
}
762
763
public get position(): vscode.ViewColumn | undefined {
764
return this._webviewPanel.viewColumn;
765
}
766
767
public matchesResource(
768
otherResource: vscode.Uri,
769
otherPosition: vscode.ViewColumn | undefined,
770
otherLocked: boolean
771
): boolean {
772
if (this.position !== otherPosition) {
773
return false;
774
}
775
776
if (this._locked) {
777
return otherLocked && this._preview.isPreviewOf(otherResource);
778
} else {
779
return !otherLocked;
780
}
781
}
782
783
public matches(otherPreview: DynamicMarkdownPreview): boolean {
784
return this.matchesResource(otherPreview._preview.resource, otherPreview.position, otherPreview._locked);
785
}
786
787
private _createPreview(resource: vscode.Uri, startingScroll?: StartingScrollLocation): MarkdownPreview {
788
return new MarkdownPreview(this._webviewPanel, resource, startingScroll, {
789
getTitle: (resource) => DynamicMarkdownPreview._getPreviewTitle(resource, this._locked),
790
getAdditionalState: () => {
791
return {
792
resourceColumn: this.resourceColumn,
793
locked: this._locked,
794
};
795
},
796
openPreviewLinkToMarkdownFile: (link: vscode.Uri, fragment?: string) => {
797
this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined);
798
}
799
},
800
this._contentProvider,
801
this._previewConfigurations,
802
this._logger,
803
this._contributionProvider,
804
this._opener);
805
}
806
}
807
808