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