Rework markdown preview code to better support markdown preview editors
Splits the preview part of the markdown preview from the dynamic preview management part of things. Static preview swap to preview the active markdown file and don't scroll sync with any other markdown files
This commit is contained in:
parent
49bcd96469
commit
9cfd597153
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -18,8 +18,8 @@ const settings = getSettings();
|
||||||
|
|
||||||
const vscode = acquireVsCodeApi();
|
const vscode = acquireVsCodeApi();
|
||||||
|
|
||||||
// Set VS Code state
|
const state = { ...vscode.getState(), ...getData<any>('data-state') };
|
||||||
let state = getData<{ line: number; fragment: string; }>('data-state');
|
// Make sure to sync VS Code state here
|
||||||
vscode.setState(state);
|
vscode.setState(state);
|
||||||
|
|
||||||
const messaging = createPosterForVsCode(vscode);
|
const messaging = createPosterForVsCode(vscode);
|
||||||
|
@ -32,23 +32,35 @@ window.onload = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onceDocumentLoaded(() => {
|
onceDocumentLoaded(() => {
|
||||||
|
const scrollProgress = state.scrollProgress;
|
||||||
|
|
||||||
|
if (typeof scrollProgress === 'number' && !settings.fragment) {
|
||||||
|
setImmediate(() => {
|
||||||
|
scrollDisabled = true;
|
||||||
|
window.scrollTo(0, scrollProgress * document.body.clientHeight);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.scrollPreviewWithEditor) {
|
if (settings.scrollPreviewWithEditor) {
|
||||||
setTimeout(() => {
|
setImmediate(() => {
|
||||||
// Try to scroll to fragment if available
|
// Try to scroll to fragment if available
|
||||||
if (state.fragment) {
|
if (settings.fragment) {
|
||||||
const element = getLineElementForFragment(state.fragment);
|
state.fragment = undefined;
|
||||||
|
vscode.setState(state);
|
||||||
|
|
||||||
|
const element = getLineElementForFragment(settings.fragment);
|
||||||
if (element) {
|
if (element) {
|
||||||
scrollDisabled = true;
|
scrollDisabled = true;
|
||||||
scrollToRevealSourceLine(element.line);
|
scrollToRevealSourceLine(element.line);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const initialLine = +settings.line;
|
if (!isNaN(settings.line!)) {
|
||||||
if (!isNaN(initialLine)) {
|
|
||||||
scrollDisabled = true;
|
scrollDisabled = true;
|
||||||
scrollToRevealSourceLine(initialLine);
|
scrollToRevealSourceLine(settings.line!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0);
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,9 +70,10 @@ const onUpdateView = (() => {
|
||||||
scrollToRevealSourceLine(line);
|
scrollToRevealSourceLine(line);
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
return (line: number, settings: any) => {
|
return (line: number) => {
|
||||||
if (!isNaN(line)) {
|
if (!isNaN(line)) {
|
||||||
settings.line = line;
|
state.line = line;
|
||||||
|
|
||||||
doScroll(line);
|
doScroll(line);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -91,6 +104,7 @@ let updateImageSizes = throttle(() => {
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
scrollDisabled = true;
|
scrollDisabled = true;
|
||||||
|
updateScrollProgress();
|
||||||
updateImageSizes();
|
updateImageSizes();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
@ -105,7 +119,7 @@ window.addEventListener('message', event => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'updateView':
|
case 'updateView':
|
||||||
onUpdateView(event.data.line, settings);
|
onUpdateView(event.data.line);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
@ -165,15 +179,20 @@ document.addEventListener('click', event => {
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
window.addEventListener('scroll', throttle(() => {
|
window.addEventListener('scroll', throttle(() => {
|
||||||
|
updateScrollProgress();
|
||||||
|
|
||||||
if (scrollDisabled) {
|
if (scrollDisabled) {
|
||||||
scrollDisabled = false;
|
scrollDisabled = false;
|
||||||
} else {
|
} else {
|
||||||
const line = getEditorLineNumberForPageOffset(window.scrollY);
|
const line = getEditorLineNumberForPageOffset(window.scrollY);
|
||||||
if (typeof line === 'number' && !isNaN(line)) {
|
if (typeof line === 'number' && !isNaN(line)) {
|
||||||
messaging.postMessage('revealLine', { line });
|
messaging.postMessage('revealLine', { line });
|
||||||
state.line = line;
|
|
||||||
vscode.setState(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 50));
|
}, 50));
|
||||||
|
|
||||||
|
function updateScrollProgress() {
|
||||||
|
state.scrollProgress = window.scrollY / document.body.clientHeight;
|
||||||
|
vscode.setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
export interface PreviewSettings {
|
export interface PreviewSettings {
|
||||||
readonly source: string;
|
readonly source: string;
|
||||||
readonly line: number;
|
readonly line?: number;
|
||||||
|
readonly fragment?: string
|
||||||
readonly lineCount: number;
|
readonly lineCount: number;
|
||||||
readonly scrollPreviewWithEditor?: boolean;
|
readonly scrollPreviewWithEditor?: boolean;
|
||||||
readonly scrollEditorWithPreview: boolean;
|
readonly scrollEditorWithPreview: boolean;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { Disposable } from '../util/dispose';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
|
import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||||
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||||
import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions';
|
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||||
import { isMarkdownFile } from '../util/file';
|
import { isMarkdownFile } from '../util/file';
|
||||||
import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
|
import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
|
||||||
import { WebviewResourceProvider, normalizeResource } from '../util/resources';
|
import { WebviewResourceProvider, normalizeResource } from '../util/resources';
|
||||||
|
@ -61,10 +61,14 @@ interface PreviewStyleLoadErrorMessage extends WebviewMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PreviewDocumentVersion {
|
export class PreviewDocumentVersion {
|
||||||
public constructor(
|
|
||||||
public readonly resource: vscode.Uri,
|
private readonly resource: vscode.Uri;
|
||||||
public readonly version: number,
|
private readonly version: number;
|
||||||
) { }
|
|
||||||
|
public constructor(document: vscode.TextDocument) {
|
||||||
|
this.resource = document.uri;
|
||||||
|
this.version = document.version;
|
||||||
|
}
|
||||||
|
|
||||||
public equals(other: PreviewDocumentVersion): boolean {
|
public equals(other: PreviewDocumentVersion): boolean {
|
||||||
return this.resource.fsPath === other.resource.fsPath
|
return this.resource.fsPath === other.resource.fsPath
|
||||||
|
@ -72,102 +76,86 @@ export class PreviewDocumentVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DynamicPreviewInput {
|
interface MarkdownPreviewDelegate {
|
||||||
readonly resource: vscode.Uri;
|
getTitle?(resource: vscode.Uri): string;
|
||||||
readonly resourceColumn: vscode.ViewColumn;
|
getAdditionalState(): {},
|
||||||
readonly locked: boolean;
|
openPreviewLinkToMarkdownFile(markdownLink: vscode.Uri, fragment: string): void;
|
||||||
readonly line?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicMarkdownPreview extends Disposable {
|
class StartingScrollLine {
|
||||||
|
public readonly type = 'line';
|
||||||
|
|
||||||
public static readonly viewType = 'markdown.preview';
|
constructor(
|
||||||
|
public readonly line: number,
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class StartingScrollFragment {
|
||||||
|
public readonly type = 'fragment';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly fragment: string,
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartingScrollLocation = StartingScrollLine | StartingScrollFragment;
|
||||||
|
|
||||||
|
class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||||
|
|
||||||
private readonly delay = 300;
|
private readonly delay = 300;
|
||||||
|
|
||||||
private _resource: vscode.Uri;
|
private readonly _resource: vscode.Uri;
|
||||||
private readonly _resourceColumn: vscode.ViewColumn;
|
private readonly _webviewPanel: vscode.WebviewPanel;
|
||||||
|
|
||||||
private _locked: boolean;
|
|
||||||
|
|
||||||
private readonly editor: vscode.WebviewPanel;
|
|
||||||
private throttleTimer: any;
|
private throttleTimer: any;
|
||||||
private line: number | undefined = undefined;
|
|
||||||
|
private line: number | undefined;
|
||||||
|
private scrollToFragment: string | undefined;
|
||||||
|
|
||||||
private firstUpdate = true;
|
private firstUpdate = true;
|
||||||
private currentVersion?: PreviewDocumentVersion;
|
private currentVersion?: PreviewDocumentVersion;
|
||||||
private isScrolling = false;
|
private isScrolling = false;
|
||||||
private _disposed: boolean = false;
|
private _disposed: boolean = false;
|
||||||
private imageInfo: { id: string, width: number, height: number; }[] = [];
|
private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = [];
|
||||||
private scrollToFragment: string | undefined;
|
|
||||||
|
|
||||||
public static revive(
|
constructor(
|
||||||
input: DynamicPreviewInput,
|
|
||||||
webview: vscode.WebviewPanel,
|
webview: vscode.WebviewPanel,
|
||||||
contentProvider: MarkdownContentProvider,
|
resource: vscode.Uri,
|
||||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
startingScroll: StartingScrollLocation | undefined,
|
||||||
logger: Logger,
|
private readonly delegate: MarkdownPreviewDelegate,
|
||||||
topmostLineMonitor: TopmostLineMonitor,
|
|
||||||
contributionProvider: MarkdownContributionProvider,
|
|
||||||
): DynamicMarkdownPreview {
|
|
||||||
webview.webview.options = DynamicMarkdownPreview.getWebviewOptions(input.resource, contributionProvider.contributions);
|
|
||||||
webview.title = DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked);
|
|
||||||
|
|
||||||
return new DynamicMarkdownPreview(webview, input,
|
|
||||||
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static create(
|
|
||||||
input: DynamicPreviewInput,
|
|
||||||
previewColumn: vscode.ViewColumn,
|
|
||||||
contentProvider: MarkdownContentProvider,
|
|
||||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
|
||||||
logger: Logger,
|
|
||||||
topmostLineMonitor: TopmostLineMonitor,
|
|
||||||
contributionProvider: MarkdownContributionProvider
|
|
||||||
): DynamicMarkdownPreview {
|
|
||||||
const webview = vscode.window.createWebviewPanel(
|
|
||||||
DynamicMarkdownPreview.viewType,
|
|
||||||
DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked),
|
|
||||||
previewColumn, {
|
|
||||||
enableFindWidget: true,
|
|
||||||
...DynamicMarkdownPreview.getWebviewOptions(input.resource, contributionProvider.contributions)
|
|
||||||
});
|
|
||||||
|
|
||||||
return new DynamicMarkdownPreview(webview, input,
|
|
||||||
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor(
|
|
||||||
webview: vscode.WebviewPanel,
|
|
||||||
input: DynamicPreviewInput,
|
|
||||||
private readonly _contentProvider: MarkdownContentProvider,
|
private readonly _contentProvider: MarkdownContentProvider,
|
||||||
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
private readonly _logger: Logger,
|
private readonly _logger: Logger,
|
||||||
topmostLineMonitor: TopmostLineMonitor,
|
|
||||||
private readonly _contributionProvider: MarkdownContributionProvider,
|
private readonly _contributionProvider: MarkdownContributionProvider,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._resource = input.resource;
|
|
||||||
this._resourceColumn = input.resourceColumn;
|
this._webviewPanel = webview;
|
||||||
this._locked = input.locked;
|
this._resource = resource;
|
||||||
this.editor = webview;
|
|
||||||
if (!isNaN(input.line!)) {
|
switch (startingScroll?.type) {
|
||||||
this.line = input.line;
|
case 'line':
|
||||||
|
if (!isNaN(startingScroll.line!)) {
|
||||||
|
this.line = startingScroll.line;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fragment':
|
||||||
|
this.scrollToFragment = startingScroll.fragment;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._register(this.editor.onDidDispose(() => {
|
|
||||||
this.dispose();
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(this.editor.onDidChangeViewState(e => {
|
|
||||||
this._onDidChangeViewStateEmitter.fire(e);
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(_contributionProvider.onContributionsChanged(() => {
|
this._register(_contributionProvider.onContributionsChanged(() => {
|
||||||
setImmediate(() => this.refresh());
|
setImmediate(() => this.refresh());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(this.editor.webview.onDidReceiveMessage((e: CacheImageSizesMessage | RevealLineMessage | DidClickMessage | ClickLinkMessage | ShowPreviewSecuritySelectorMessage | PreviewStyleLoadErrorMessage) => {
|
this._register(vscode.workspace.onDidChangeTextDocument(event => {
|
||||||
|
if (this.isPreviewOf(event.document.uri)) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._webviewPanel.webview.onDidReceiveMessage((e: CacheImageSizesMessage | RevealLineMessage | DidClickMessage | ClickLinkMessage | ShowPreviewSecuritySelectorMessage | PreviewStyleLoadErrorMessage) => {
|
||||||
if (e.source !== this._resource.toString()) {
|
if (e.source !== this._resource.toString()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -194,158 +182,50 @@ export class DynamicMarkdownPreview extends Disposable {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'previewStyleLoadError':
|
case 'previewStyleLoadError':
|
||||||
vscode.window.showWarningMessage(localize('onPreviewStyleLoadError', "Could not load 'markdown.styles': {0}", e.body.unloadedStyles.join(', ')));
|
vscode.window.showWarningMessage(
|
||||||
|
localize('onPreviewStyleLoadError',
|
||||||
|
"Could not load 'markdown.styles': {0}",
|
||||||
|
e.body.unloadedStyles.join(', ')));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(vscode.workspace.onDidChangeTextDocument(event => {
|
this.updatePreview();
|
||||||
if (this.isPreviewOf(event.document.uri)) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(topmostLineMonitor.onDidChanged(event => {
|
|
||||||
if (this.isPreviewOf(event.resource)) {
|
|
||||||
this.updateForView(event.resource, event.line);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(vscode.window.onDidChangeTextEditorSelection(event => {
|
|
||||||
if (this.isPreviewOf(event.textEditor.document.uri)) {
|
|
||||||
this.postMessage({
|
|
||||||
type: 'onDidChangeTextEditorSelection',
|
|
||||||
line: event.selections[0].active.line,
|
|
||||||
source: this.resource.toString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(vscode.window.onDidChangeActiveTextEditor(editor => {
|
|
||||||
if (editor && isMarkdownFile(editor.document) && !this._locked) {
|
|
||||||
this.update(editor.document.uri, false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.doUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter<void>());
|
dispose() {
|
||||||
public readonly onDispose = this._onDisposeEmitter.event;
|
super.dispose();
|
||||||
|
this._disposed = true;
|
||||||
private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
clearTimeout(this.throttleTimer);
|
||||||
public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event;
|
}
|
||||||
|
|
||||||
public get resource(): vscode.Uri {
|
public get resource(): vscode.Uri {
|
||||||
return this._resource;
|
return this._resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get resourceColumn(): vscode.ViewColumn {
|
|
||||||
return this._resourceColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get state() {
|
public get state() {
|
||||||
return {
|
return {
|
||||||
resource: this.resource.toString(),
|
resource: this._resource.toString(),
|
||||||
locked: this._locked,
|
|
||||||
line: this.line,
|
line: this.line,
|
||||||
resourceColumn: this.resourceColumn,
|
|
||||||
imageInfo: this.imageInfo,
|
imageInfo: this.imageInfo,
|
||||||
fragment: this.scrollToFragment
|
fragment: this.scrollToFragment,
|
||||||
|
...this.delegate.getAdditionalState(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public refresh() {
|
||||||
if (this._disposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._disposed = true;
|
|
||||||
this._onDisposeEmitter.fire();
|
|
||||||
this._onDisposeEmitter.dispose();
|
|
||||||
|
|
||||||
this.editor.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(resource: vscode.Uri, isRefresh = true) {
|
|
||||||
// Reposition scroll preview, position scroll to the top if active text editor
|
|
||||||
// doesn't corresponds with preview
|
|
||||||
const editor = vscode.window.activeTextEditor;
|
|
||||||
if (editor) {
|
|
||||||
if (!isRefresh || this._previewConfigurations.loadAndCacheConfiguration(this._resource).scrollEditorWithPreview) {
|
|
||||||
if (editor.document.uri.fsPath === resource.fsPath) {
|
|
||||||
this.line = getVisibleLine(editor);
|
|
||||||
} else {
|
|
||||||
this.line = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have changed resources, cancel any pending updates
|
|
||||||
const isResourceChange = resource.fsPath !== this._resource.fsPath;
|
|
||||||
if (isResourceChange) {
|
|
||||||
clearTimeout(this.throttleTimer);
|
|
||||||
this.throttleTimer = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._resource = resource;
|
|
||||||
|
|
||||||
// Schedule update if none is pending
|
// Schedule update if none is pending
|
||||||
if (!this.throttleTimer) {
|
if (!this.throttleTimer) {
|
||||||
if (isResourceChange || this.firstUpdate) {
|
if (this.firstUpdate) {
|
||||||
this.doUpdate(isRefresh);
|
this.updatePreview(true);
|
||||||
} else {
|
} else {
|
||||||
this.throttleTimer = setTimeout(() => this.doUpdate(isRefresh), this.delay);
|
this.throttleTimer = setTimeout(() => this.updatePreview(true), this.delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.firstUpdate = false;
|
this.firstUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public refresh() {
|
|
||||||
this.update(this._resource, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateConfiguration() {
|
|
||||||
if (this._previewConfigurations.hasConfigurationChanged(this._resource)) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get position(): vscode.ViewColumn | undefined {
|
|
||||||
return this.editor.viewColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public matchesResource(
|
|
||||||
otherResource: vscode.Uri,
|
|
||||||
otherPosition: vscode.ViewColumn | undefined,
|
|
||||||
otherLocked: boolean
|
|
||||||
): boolean {
|
|
||||||
if (this.position !== otherPosition) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._locked) {
|
|
||||||
return otherLocked && this.isPreviewOf(otherResource);
|
|
||||||
} else {
|
|
||||||
return !otherLocked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public matches(otherPreview: DynamicMarkdownPreview): boolean {
|
|
||||||
return this.matchesResource(otherPreview._resource, otherPreview.position, otherPreview._locked);
|
|
||||||
}
|
|
||||||
|
|
||||||
public reveal(viewColumn: vscode.ViewColumn) {
|
|
||||||
this.editor.reveal(viewColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleLock() {
|
|
||||||
this._locked = !this._locked;
|
|
||||||
this.editor.title = DynamicMarkdownPreview.getPreviewTitle(this._resource, this._locked);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get iconPath() {
|
private get iconPath() {
|
||||||
const root = path.join(this._contributionProvider.extensionPath, 'media');
|
const root = path.join(this._contributionProvider.extensionPath, 'media');
|
||||||
return {
|
return {
|
||||||
|
@ -354,18 +234,18 @@ export class DynamicMarkdownPreview extends Disposable {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private isPreviewOf(resource: vscode.Uri): boolean {
|
public isPreviewOf(resource: vscode.Uri): boolean {
|
||||||
return this._resource.fsPath === resource.fsPath;
|
return this._resource.fsPath === resource.fsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
|
public postMessage(msg: any) {
|
||||||
return locked
|
if (!this._disposed) {
|
||||||
? localize('lockedPreviewTitle', '[Preview] {0}', path.basename(resource.fsPath))
|
this._webviewPanel.webview.postMessage(msg);
|
||||||
: localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateForView(resource: vscode.Uri, topLine: number | undefined) {
|
public scrollTo(topLine: number) {
|
||||||
if (!this.isPreviewOf(resource)) {
|
if (this._disposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,36 +254,26 @@ export class DynamicMarkdownPreview extends Disposable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof topLine === 'number') {
|
this._logger.log('updateForView', { markdownFile: this._resource });
|
||||||
this._logger.log('updateForView', { markdownFile: resource });
|
this.line = topLine;
|
||||||
this.line = topLine;
|
this.postMessage({
|
||||||
this.postMessage({
|
type: 'updateView',
|
||||||
type: 'updateView',
|
line: topLine,
|
||||||
line: topLine,
|
source: this._resource.toString()
|
||||||
source: resource.toString()
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private postMessage(msg: any) {
|
private async updatePreview(forceUpdate?: boolean): Promise<void> {
|
||||||
if (!this._disposed) {
|
clearTimeout(this.throttleTimer);
|
||||||
this.editor.webview.postMessage(msg);
|
this.throttleTimer = undefined;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doUpdate(forceUpdate?: boolean): Promise<void> {
|
|
||||||
if (this._disposed) {
|
if (this._disposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const markdownResource = this._resource;
|
|
||||||
|
|
||||||
clearTimeout(this.throttleTimer);
|
|
||||||
this.throttleTimer = undefined;
|
|
||||||
|
|
||||||
let document: vscode.TextDocument;
|
let document: vscode.TextDocument;
|
||||||
try {
|
try {
|
||||||
document = await vscode.workspace.openTextDocument(markdownResource);
|
document = await vscode.workspace.openTextDocument(this._resource);
|
||||||
} catch {
|
} catch {
|
||||||
await this.showFileNotFoundError();
|
await this.showFileNotFoundError();
|
||||||
return;
|
return;
|
||||||
|
@ -413,61 +283,24 @@ export class DynamicMarkdownPreview extends Disposable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingVersion = new PreviewDocumentVersion(markdownResource, document.version);
|
const pendingVersion = new PreviewDocumentVersion(document);
|
||||||
if (!forceUpdate && this.currentVersion?.equals(pendingVersion)) {
|
if (!forceUpdate && this.currentVersion?.equals(pendingVersion)) {
|
||||||
if (this.line) {
|
if (this.line) {
|
||||||
this.updateForView(markdownResource, this.line);
|
this.scrollTo(this.line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentVersion = pendingVersion;
|
this.currentVersion = pendingVersion;
|
||||||
if (this._resource === markdownResource) {
|
const content = await this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state);
|
||||||
const self = this;
|
|
||||||
const resourceProvider: WebviewResourceProvider = {
|
// Another call to `doUpdate` may have happened.
|
||||||
asWebviewUri: (resource) => {
|
// Make sure we are still updating for the correct document
|
||||||
return this.editor.webview.asWebviewUri(normalizeResource(markdownResource, resource));
|
if (this.currentVersion?.equals(pendingVersion)) {
|
||||||
},
|
this.setContent(content);
|
||||||
get cspSource() { return self.editor.webview.cspSource; }
|
|
||||||
};
|
|
||||||
const content = await this._contentProvider.provideTextDocumentContent(document, resourceProvider, this._previewConfigurations, this.line, this.state);
|
|
||||||
// Another call to `doUpdate` may have happened.
|
|
||||||
// Make sure we are still updating for the correct document
|
|
||||||
if (this.currentVersion && this.currentVersion.equals(pendingVersion)) {
|
|
||||||
this.setContent(content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getWebviewOptions(
|
|
||||||
resource: vscode.Uri,
|
|
||||||
contributions: MarkdownContributions
|
|
||||||
): vscode.WebviewOptions {
|
|
||||||
return {
|
|
||||||
enableScripts: true,
|
|
||||||
localResourceRoots: DynamicMarkdownPreview.getLocalResourceRoots(resource, contributions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getLocalResourceRoots(
|
|
||||||
base: vscode.Uri,
|
|
||||||
contributions: MarkdownContributions
|
|
||||||
): ReadonlyArray<vscode.Uri> {
|
|
||||||
const baseRoots = Array.from(contributions.previewResourceRoots);
|
|
||||||
|
|
||||||
const folder = vscode.workspace.getWorkspaceFolder(base);
|
|
||||||
if (folder) {
|
|
||||||
const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri);
|
|
||||||
if (workspaceRoots) {
|
|
||||||
baseRoots.push(...workspaceRoots);
|
|
||||||
}
|
|
||||||
} else if (!base.scheme || base.scheme === 'file') {
|
|
||||||
baseRoots.push(vscode.Uri.file(path.dirname(base.fsPath)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseRoots.map(root => normalizeResource(base, root));
|
|
||||||
}
|
|
||||||
|
|
||||||
private onDidScrollPreview(line: number) {
|
private onDidScrollPreview(line: number) {
|
||||||
this.line = line;
|
this.line = line;
|
||||||
|
|
||||||
|
@ -513,16 +346,47 @@ export class DynamicMarkdownPreview extends Disposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showFileNotFoundError() {
|
private async showFileNotFoundError() {
|
||||||
this.setContent(this._contentProvider.provideFileNotFoundContent(this._resource));
|
this._webviewPanel.webview.html = this._contentProvider.provideFileNotFoundContent(this._resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setContent(html: string): void {
|
private setContent(html: string): void {
|
||||||
this.editor.title = DynamicMarkdownPreview.getPreviewTitle(this._resource, this._locked);
|
if (this._disposed) {
|
||||||
this.editor.iconPath = this.iconPath;
|
return;
|
||||||
this.editor.webview.options = DynamicMarkdownPreview.getWebviewOptions(this._resource, this._contributionProvider.contributions);
|
}
|
||||||
this.editor.webview.html = html;
|
|
||||||
|
if (this.delegate.getTitle) {
|
||||||
|
this._webviewPanel.title = this.delegate.getTitle(this._resource);
|
||||||
|
}
|
||||||
|
this._webviewPanel.iconPath = this.iconPath;
|
||||||
|
this._webviewPanel.webview.options = this.getWebviewOptions();
|
||||||
|
|
||||||
|
this._webviewPanel.webview.html = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getWebviewOptions(): vscode.WebviewOptions {
|
||||||
|
return {
|
||||||
|
enableScripts: true,
|
||||||
|
localResourceRoots: this.getLocalResourceRoots()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLocalResourceRoots(): ReadonlyArray<vscode.Uri> {
|
||||||
|
const baseRoots = Array.from(this._contributionProvider.contributions.previewResourceRoots);
|
||||||
|
|
||||||
|
const folder = vscode.workspace.getWorkspaceFolder(this._resource);
|
||||||
|
if (folder) {
|
||||||
|
const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri);
|
||||||
|
if (workspaceRoots) {
|
||||||
|
baseRoots.push(...workspaceRoots);
|
||||||
|
}
|
||||||
|
} else if (!this._resource.scheme || this._resource.scheme === 'file') {
|
||||||
|
baseRoots.push(vscode.Uri.file(path.dirname(this._resource.fsPath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseRoots.map(root => normalizeResource(this._resource, root));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async onDidClickPreviewLink(href: string) {
|
private async onDidClickPreviewLink(href: string) {
|
||||||
let [hrefPath, fragment] = decodeURIComponent(href).split('#');
|
let [hrefPath, fragment] = decodeURIComponent(href).split('#');
|
||||||
|
|
||||||
|
@ -537,14 +401,332 @@ export class DynamicMarkdownPreview extends Disposable {
|
||||||
if (openLinks === 'inPreview') {
|
if (openLinks === 'inPreview') {
|
||||||
const markdownLink = await resolveLinkToMarkdownFile(hrefPath);
|
const markdownLink = await resolveLinkToMarkdownFile(hrefPath);
|
||||||
if (markdownLink) {
|
if (markdownLink) {
|
||||||
if (fragment) {
|
this.delegate.openPreviewLinkToMarkdownFile(markdownLink, fragment);
|
||||||
this.scrollToFragment = fragment;
|
|
||||||
}
|
|
||||||
this.update(markdownLink);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource });
|
vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#region WebviewResourceProvider
|
||||||
|
|
||||||
|
asWebviewUri(resource: vscode.Uri) {
|
||||||
|
return this._webviewPanel.webview.asWebviewUri(normalizeResource(this._resource, resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
get cspSource() {
|
||||||
|
return this._webviewPanel.webview.cspSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ManagedMarkdownPreview {
|
||||||
|
|
||||||
|
readonly resource: vscode.Uri;
|
||||||
|
readonly resourceColumn: vscode.ViewColumn;
|
||||||
|
|
||||||
|
readonly onDispose: vscode.Event<void>;
|
||||||
|
readonly onDidChangeViewState: vscode.Event<vscode.WebviewPanelOnDidChangeViewStateEvent>;
|
||||||
|
|
||||||
|
dispose(): void;
|
||||||
|
|
||||||
|
refresh(): void;
|
||||||
|
updateConfiguration(): void;
|
||||||
|
|
||||||
|
matchesResource(
|
||||||
|
otherResource: vscode.Uri,
|
||||||
|
otherPosition: vscode.ViewColumn | undefined,
|
||||||
|
otherLocked: boolean
|
||||||
|
): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StaticMarkdownPreview extends Disposable implements ManagedMarkdownPreview {
|
||||||
|
|
||||||
|
public static revive(
|
||||||
|
resource: vscode.Uri,
|
||||||
|
webview: vscode.WebviewPanel,
|
||||||
|
contentProvider: MarkdownContentProvider,
|
||||||
|
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
logger: Logger,
|
||||||
|
contributionProvider: MarkdownContributionProvider,
|
||||||
|
): StaticMarkdownPreview {
|
||||||
|
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly preview: MarkdownPreview;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private readonly _webviewPanel: vscode.WebviewPanel,
|
||||||
|
resource: vscode.Uri,
|
||||||
|
contentProvider: MarkdownContentProvider,
|
||||||
|
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
logger: Logger,
|
||||||
|
contributionProvider: MarkdownContributionProvider,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, undefined, {
|
||||||
|
getAdditionalState: () => { return {}; },
|
||||||
|
openPreviewLinkToMarkdownFile: () => { /* todo */ }
|
||||||
|
}, contentProvider, _previewConfigurations, logger, contributionProvider));
|
||||||
|
|
||||||
|
this._register(this._webviewPanel.onDidDispose(() => {
|
||||||
|
this.dispose();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._webviewPanel.onDidChangeViewState(e => {
|
||||||
|
this._onDidChangeViewState.fire(e);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _onDispose = this._register(new vscode.EventEmitter<void>());
|
||||||
|
public readonly onDispose = this._onDispose.event;
|
||||||
|
|
||||||
|
private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||||
|
public readonly onDidChangeViewState = this._onDidChangeViewState.event;
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._onDispose.fire();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public matchesResource(
|
||||||
|
_otherResource: vscode.Uri,
|
||||||
|
_otherPosition: vscode.ViewColumn | undefined,
|
||||||
|
_otherLocked: boolean
|
||||||
|
): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public refresh() {
|
||||||
|
this.preview.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateConfiguration() {
|
||||||
|
if (this._previewConfigurations.hasConfigurationChanged(this.preview.resource)) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get resource() {
|
||||||
|
return this.preview.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get resourceColumn() {
|
||||||
|
return this._webviewPanel.viewColumn || vscode.ViewColumn.One;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DynamicPreviewInput {
|
||||||
|
readonly resource: vscode.Uri;
|
||||||
|
readonly resourceColumn: vscode.ViewColumn;
|
||||||
|
readonly locked: boolean;
|
||||||
|
readonly line?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A
|
||||||
|
*/
|
||||||
|
export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdownPreview {
|
||||||
|
|
||||||
|
public static readonly viewType = 'markdown.preview';
|
||||||
|
|
||||||
|
private readonly _resourceColumn: vscode.ViewColumn;
|
||||||
|
private _locked: boolean;
|
||||||
|
|
||||||
|
private readonly _webviewPanel: vscode.WebviewPanel;
|
||||||
|
private _preview: MarkdownPreview;
|
||||||
|
|
||||||
|
public static revive(
|
||||||
|
input: DynamicPreviewInput,
|
||||||
|
webview: vscode.WebviewPanel,
|
||||||
|
contentProvider: MarkdownContentProvider,
|
||||||
|
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
logger: Logger,
|
||||||
|
topmostLineMonitor: TopmostLineMonitor,
|
||||||
|
contributionProvider: MarkdownContributionProvider,
|
||||||
|
): DynamicMarkdownPreview {
|
||||||
|
return new DynamicMarkdownPreview(webview, input,
|
||||||
|
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(
|
||||||
|
input: DynamicPreviewInput,
|
||||||
|
previewColumn: vscode.ViewColumn,
|
||||||
|
contentProvider: MarkdownContentProvider,
|
||||||
|
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
logger: Logger,
|
||||||
|
topmostLineMonitor: TopmostLineMonitor,
|
||||||
|
contributionProvider: MarkdownContributionProvider
|
||||||
|
): DynamicMarkdownPreview {
|
||||||
|
const webview = vscode.window.createWebviewPanel(
|
||||||
|
DynamicMarkdownPreview.viewType,
|
||||||
|
DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked),
|
||||||
|
previewColumn, { enableFindWidget: true, });
|
||||||
|
|
||||||
|
return new DynamicMarkdownPreview(webview, input,
|
||||||
|
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
webview: vscode.WebviewPanel,
|
||||||
|
input: DynamicPreviewInput,
|
||||||
|
private readonly _contentProvider: MarkdownContentProvider,
|
||||||
|
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||||
|
private readonly _logger: Logger,
|
||||||
|
private readonly _topmostLineMonitor: TopmostLineMonitor,
|
||||||
|
private readonly _contributionProvider: MarkdownContributionProvider,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._webviewPanel = webview;
|
||||||
|
|
||||||
|
this._resourceColumn = input.resourceColumn;
|
||||||
|
this._locked = input.locked;
|
||||||
|
|
||||||
|
this._preview = this.createPreview(input.resource, typeof input.line === 'number' ? new StartingScrollLine(input.line) : undefined);
|
||||||
|
|
||||||
|
this._register(webview.onDidDispose(() => { this.dispose(); }));
|
||||||
|
|
||||||
|
this._register(this._webviewPanel.onDidChangeViewState(e => {
|
||||||
|
this._onDidChangeViewStateEmitter.fire(e);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._topmostLineMonitor.onDidChanged(event => {
|
||||||
|
if (this._preview.isPreviewOf(event.resource)) {
|
||||||
|
this._preview.scrollTo(event.line);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(vscode.window.onDidChangeTextEditorSelection(event => {
|
||||||
|
if (this._preview.isPreviewOf(event.textEditor.document.uri)) {
|
||||||
|
this._preview.postMessage({
|
||||||
|
type: 'onDidChangeTextEditorSelection',
|
||||||
|
line: event.selections[0].active.line,
|
||||||
|
source: this._preview.resource.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||||
|
if (editor && isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) {
|
||||||
|
const line = getVisibleLine(editor);
|
||||||
|
this.update(editor.document.uri, line ? new StartingScrollLine(line) : undefined);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter<void>());
|
||||||
|
public readonly onDispose = this._onDisposeEmitter.event;
|
||||||
|
|
||||||
|
private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||||
|
public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event;
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._preview.dispose();
|
||||||
|
this._webviewPanel.dispose();
|
||||||
|
|
||||||
|
this._onDisposeEmitter.fire();
|
||||||
|
this._onDisposeEmitter.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get resource() {
|
||||||
|
return this._preview.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get resourceColumn() {
|
||||||
|
return this._resourceColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public reveal(viewColumn: vscode.ViewColumn) {
|
||||||
|
this._webviewPanel.reveal(viewColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public refresh() {
|
||||||
|
this._preview.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateConfiguration() {
|
||||||
|
if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(newResource: vscode.Uri, scrollLocation?: StartingScrollLocation) {
|
||||||
|
if (this._preview.isPreviewOf(newResource)) {
|
||||||
|
switch (scrollLocation?.type) {
|
||||||
|
case 'line':
|
||||||
|
this._preview.scrollTo(scrollLocation.line);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 'fragment':
|
||||||
|
// Workaround. For fragments, just reload the entire preview
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._preview.dispose();
|
||||||
|
this._preview = this.createPreview(newResource, scrollLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleLock() {
|
||||||
|
this._locked = !this._locked;
|
||||||
|
this._webviewPanel.title = DynamicMarkdownPreview.getPreviewTitle(this._preview.resource, this._locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
|
||||||
|
return locked
|
||||||
|
? localize('lockedPreviewTitle', '[Preview] {0}', path.basename(resource.fsPath))
|
||||||
|
: localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get position(): vscode.ViewColumn | undefined {
|
||||||
|
return this._webviewPanel.viewColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public matchesResource(
|
||||||
|
otherResource: vscode.Uri,
|
||||||
|
otherPosition: vscode.ViewColumn | undefined,
|
||||||
|
otherLocked: boolean
|
||||||
|
): boolean {
|
||||||
|
if (this.position !== otherPosition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._locked) {
|
||||||
|
return otherLocked && this._preview.isPreviewOf(otherResource);
|
||||||
|
} else {
|
||||||
|
return !otherLocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public matches(otherPreview: DynamicMarkdownPreview): boolean {
|
||||||
|
return this.matchesResource(otherPreview._preview.resource, otherPreview.position, otherPreview._locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPreview(resource: vscode.Uri, startingScroll?: StartingScrollLocation): MarkdownPreview {
|
||||||
|
return new MarkdownPreview(this._webviewPanel, resource, startingScroll, {
|
||||||
|
getTitle: (resource) => DynamicMarkdownPreview.getPreviewTitle(resource, this._locked),
|
||||||
|
getAdditionalState: () => {
|
||||||
|
return {
|
||||||
|
resourceColumn: this.resourceColumn,
|
||||||
|
locked: this._locked,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
openPreviewLinkToMarkdownFile: (link: vscode.Uri, fragment?: string) => {
|
||||||
|
this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this._contentProvider,
|
||||||
|
this._previewConfigurations,
|
||||||
|
this._logger,
|
||||||
|
this._contributionProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Logger } from '../logger';
|
||||||
import { MarkdownContributionProvider } from '../markdownExtensions';
|
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||||
import { disposeAll, Disposable } from '../util/dispose';
|
import { disposeAll, Disposable } from '../util/dispose';
|
||||||
import { TopmostLineMonitor } from '../util/topmostLineMonitor';
|
import { TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||||
import { DynamicMarkdownPreview } from './preview';
|
import { DynamicMarkdownPreview, StaticMarkdownPreview, ManagedMarkdownPreview } from './preview';
|
||||||
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||||
import { MarkdownContentProvider } from './previewContentProvider';
|
import { MarkdownContentProvider } from './previewContentProvider';
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ export interface DynamicPreviewSettings {
|
||||||
readonly locked: boolean;
|
readonly locked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreviewStore extends Disposable {
|
class PreviewStore<T extends ManagedMarkdownPreview> extends Disposable {
|
||||||
|
|
||||||
private readonly _previews = new Set<DynamicMarkdownPreview>();
|
private readonly _previews = new Set<T>();
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -30,11 +30,11 @@ class PreviewStore extends Disposable {
|
||||||
this._previews.clear();
|
this._previews.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator](): Iterator<DynamicMarkdownPreview> {
|
[Symbol.iterator](): Iterator<T> {
|
||||||
return this._previews[Symbol.iterator]();
|
return this._previews[Symbol.iterator]();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): DynamicMarkdownPreview | undefined {
|
public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined {
|
||||||
for (const preview of this._previews) {
|
for (const preview of this._previews) {
|
||||||
if (preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked)) {
|
if (preview.matchesResource(resource, previewSettings.previewColumn, previewSettings.locked)) {
|
||||||
return preview;
|
return preview;
|
||||||
|
@ -43,11 +43,11 @@ class PreviewStore extends Disposable {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(preview: DynamicMarkdownPreview) {
|
public add(preview: T) {
|
||||||
this._previews.add(preview);
|
this._previews.add(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(preview: DynamicMarkdownPreview) {
|
public delete(preview: T) {
|
||||||
this._previews.delete(preview);
|
this._previews.delete(preview);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,10 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||||
private readonly _topmostLineMonitor = new TopmostLineMonitor();
|
private readonly _topmostLineMonitor = new TopmostLineMonitor();
|
||||||
private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();
|
private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();
|
||||||
|
|
||||||
private readonly _dynamicPreviews = this._register(new PreviewStore());
|
private readonly _dynamicPreviews = this._register(new PreviewStore<DynamicMarkdownPreview>());
|
||||||
private readonly _staticPreviews = this._register(new PreviewStore());
|
private readonly _staticPreviews = this._register(new PreviewStore<StaticMarkdownPreview>());
|
||||||
|
|
||||||
private _activePreview: DynamicMarkdownPreview | undefined = undefined;
|
private _activePreview: ManagedMarkdownPreview | undefined = undefined;
|
||||||
|
|
||||||
private readonly customEditorViewType = 'vscode.markdown.preview.editor';
|
private readonly customEditorViewType = 'vscode.markdown.preview.editor';
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||||
|
|
||||||
public toggleLock() {
|
public toggleLock() {
|
||||||
const preview = this._activePreview;
|
const preview = this._activePreview;
|
||||||
if (preview) {
|
if (preview instanceof DynamicMarkdownPreview) {
|
||||||
preview.toggleLock();
|
preview.toggleLock();
|
||||||
|
|
||||||
// Close any previews that are now redundant, such as having two dynamic previews in the same editor group
|
// Close any previews that are now redundant, such as having two dynamic previews in the same editor group
|
||||||
|
@ -133,6 +133,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||||
webview: vscode.WebviewPanel,
|
webview: vscode.WebviewPanel,
|
||||||
state: any
|
state: any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
console.log(state);
|
||||||
const resource = vscode.Uri.parse(state.resource);
|
const resource = vscode.Uri.parse(state.resource);
|
||||||
const locked = state.locked;
|
const locked = state.locked;
|
||||||
const line = state.line;
|
const line = state.line;
|
||||||
|
@ -150,21 +151,16 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||||
this.registerDynamicPreview(preview);
|
this.registerDynamicPreview(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openCustomDocument(uri: vscode.Uri) {
|
|
||||||
return { uri, dispose: () => { } };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resolveCustomTextEditor(
|
public async resolveCustomTextEditor(
|
||||||
document: vscode.TextDocument,
|
document: vscode.TextDocument,
|
||||||
webview: vscode.WebviewPanel
|
webview: vscode.WebviewPanel
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const preview = DynamicMarkdownPreview.revive(
|
const preview = StaticMarkdownPreview.revive(
|
||||||
{ resource: document.uri, locked: false, resourceColumn: vscode.ViewColumn.One },
|
document.uri,
|
||||||
webview,
|
webview,
|
||||||
this._contentProvider,
|
this._contentProvider,
|
||||||
this._previewConfigurations,
|
this._previewConfigurations,
|
||||||
this._logger,
|
this._logger,
|
||||||
this._topmostLineMonitor,
|
|
||||||
this._contributions);
|
this._contributions);
|
||||||
this.registerStaticPreview(preview);
|
this.registerStaticPreview(preview);
|
||||||
}
|
}
|
||||||
|
@ -207,7 +203,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerStaticPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
|
private registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview {
|
||||||
this._staticPreviews.add(preview);
|
this._staticPreviews.add(preview);
|
||||||
|
|
||||||
preview.onDispose(() => {
|
preview.onDispose(() => {
|
||||||
|
@ -218,7 +214,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
private trackActive(preview: DynamicMarkdownPreview): void {
|
private trackActive(preview: ManagedMarkdownPreview): void {
|
||||||
preview.onDidChangeViewState(({ webviewPanel }) => {
|
preview.onDidChangeViewState(({ webviewPanel }) => {
|
||||||
this.setPreviewActiveContext(webviewPanel.active);
|
this.setPreviewActiveContext(webviewPanel.active);
|
||||||
this._activePreview = webviewPanel.active ? preview : undefined;
|
this._activePreview = webviewPanel.active ? preview : undefined;
|
||||||
|
|
Loading…
Reference in a new issue