diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts
index 23ce0b495b0..9c678e4a6ec 100644
--- a/extensions/markdown-language-features/src/features/preview.ts
+++ b/extensions/markdown-language-features/src/features/preview.ts
@@ -11,7 +11,7 @@ import { Logger } from '../logger';
import { MarkdownContributionProvider } from '../markdownExtensions';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
-import { normalizeResource, WebviewResourceProvider } from '../util/resources';
+import { WebviewResourceProvider } from '../util/resources';
import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor';
import { MarkdownPreviewConfigurationManager } from './previewConfig';
import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider';
@@ -423,7 +423,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
baseRoots.push(vscode.Uri.file(path.dirname(this._resource.fsPath)));
}
- return baseRoots.map(root => normalizeResource(this._resource, root));
+ return baseRoots;
}
@@ -456,7 +456,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
//#region WebviewResourceProvider
asWebviewUri(resource: vscode.Uri) {
- return this._webviewPanel.webview.asWebviewUri(normalizeResource(this._resource, resource));
+ return this._webviewPanel.webview.asWebviewUri(resource);
}
get cspSource() {
diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts
index 474b1864ed7..e2b28c092cb 100644
--- a/extensions/markdown-language-features/src/features/previewContentProvider.ts
+++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts
@@ -81,7 +81,7 @@ export class MarkdownContentProvider {
const nonce = new Date().getTime() + '' + new Date().getMilliseconds();
const csp = this.getCsp(resourceProvider, sourceUri, nonce);
- const body = await this.engine.render(markdownDocument);
+ const body = await this.engine.render(markdownDocument, resourceProvider);
const html = `
diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts
index 7e62db9824b..346547f3da9 100644
--- a/extensions/markdown-language-features/src/markdownEngine.ts
+++ b/extensions/markdown-language-features/src/markdownEngine.ts
@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { MarkdownIt, Token } from 'markdown-it';
-import * as path from 'path';
import * as vscode from 'vscode';
import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions';
import { Slugifier } from './slugify';
import { SkinnyTextDocument } from './tableOfContentsProvider';
import { hash } from './util/hash';
-import { isOfScheme, MarkdownFileExtensions, Schemes } from './util/links';
+import { isOfScheme, Schemes } from './util/links';
+import { WebviewResourceProvider } from './util/resources';
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
@@ -62,12 +62,13 @@ export interface RenderOutput {
interface RenderEnv {
containingImages: { src: string }[];
+ currentDocument: vscode.Uri | undefined;
+ resourceProvider: WebviewResourceProvider | undefined;
}
export class MarkdownEngine {
private md?: Promise;
- private currentDocument?: vscode.Uri;
private _slugCount = new Map();
private _tokenCache = new TokenCache();
@@ -113,7 +114,7 @@ export class MarkdownEngine {
this.addLineNumberRenderer(md, renderName);
}
- this.addImageStabilizer(md);
+ this.addImageRenderer(md);
this.addFencedRenderer(md);
this.addLinkNormalizer(md);
this.addLinkValidator(md);
@@ -138,8 +139,6 @@ export class MarkdownEngine {
return cached;
}
- this.currentDocument = document.uri;
-
const tokens = this.tokenizeString(document.getText(), engine);
this._tokenCache.update(document, config, tokens);
return tokens;
@@ -151,7 +150,7 @@ export class MarkdownEngine {
return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {});
}
- public async render(input: SkinnyTextDocument | string): Promise {
+ public async render(input: SkinnyTextDocument | string, resourceProvider?: WebviewResourceProvider): Promise {
const config = this.getConfig(typeof input === 'string' ? undefined : input.uri);
const engine = await this.getEngine(config);
@@ -160,7 +159,9 @@ export class MarkdownEngine {
: this.tokenizeDocument(input, config, engine);
const env: RenderEnv = {
- containingImages: []
+ containingImages: [],
+ currentDocument: typeof input === 'string' ? undefined : input.uri,
+ resourceProvider,
};
const html = engine.renderer.render(tokens, {
@@ -210,7 +211,7 @@ export class MarkdownEngine {
};
}
- private addImageStabilizer(md: MarkdownIt): void {
+ private addImageRenderer(md: MarkdownIt): void {
const original = md.renderer.rules.image;
md.renderer.rules.image = (tokens: Token[], idx: number, options: any, env: RenderEnv, self: any) => {
const token = tokens[idx];
@@ -221,6 +222,11 @@ export class MarkdownEngine {
env.containingImages?.push({ src });
const imgHash = hash(src);
token.attrSet('id', `image-hash-${imgHash}`);
+
+ if (!token.attrGet('data-src')) {
+ token.attrSet('src', this.toResourceUri(src, env.currentDocument, env.resourceProvider));
+ token.attrSet('data-src', src);
+ }
}
if (original) {
@@ -252,40 +258,6 @@ export class MarkdownEngine {
return normalizeLink(vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }).toString());
}
- // Support file:// links
- if (isOfScheme(Schemes.file, link)) {
- // Ensure link is relative by prepending `/` so that it uses the element URI
- // when resolving the absolute URL
- return normalizeLink('/' + link.replace(/^file:/, 'file'));
- }
-
- // If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
- if (!/^[a-z\-]+:/i.test(link)) {
- // Use a fake scheme for parsing
- let uri = vscode.Uri.parse('markdown-link:' + link);
-
- // Relative paths should be resolved correctly inside the preview but we need to
- // handle absolute paths specially (for images) to resolve them relative to the workspace root
- if (uri.path[0] === '/') {
- const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!);
- if (root) {
- uri = vscode.Uri.joinPath(root.uri, uri.fsPath).with({
- scheme: 'markdown-link',
- fragment: uri.fragment,
- query: uri.query,
- });
- }
- }
-
- const extname = path.extname(uri.fsPath);
-
- if (uri.fragment && (extname === '' || MarkdownFileExtensions.includes(extname))) {
- uri = uri.with({
- fragment: this.slugifier.fromHeading(uri.fragment).value
- });
- }
- return normalizeLink(uri.toString(true).replace(/^markdown-link:/, ''));
- }
} catch (e) {
// noop
}
@@ -343,6 +315,50 @@ export class MarkdownEngine {
return old_render(tokens, idx, options, env, self);
};
}
+
+ private toResourceUri(href: string, currentDocument: vscode.Uri | undefined, resourceProvider: WebviewResourceProvider | undefined): string {
+ try {
+ // Support file:// links
+ if (isOfScheme(Schemes.file, href)) {
+ const uri = vscode.Uri.parse(href);
+ if (resourceProvider) {
+ return resourceProvider.asWebviewUri(uri).toString(true);
+ }
+ // Not sure how to resolve this
+ return href;
+ }
+
+ // If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
+ if (!/^[a-z\-]+:/i.test(href)) {
+ // Use a fake scheme for parsing
+ let uri = vscode.Uri.parse('markdown-link:' + href);
+
+ // Relative paths should be resolved correctly inside the preview but we need to
+ // handle absolute paths specially to resolve them relative to the workspace root
+ if (uri.path[0] === '/' && currentDocument) {
+ const root = vscode.workspace.getWorkspaceFolder(currentDocument);
+ if (root) {
+ uri = vscode.Uri.joinPath(root.uri, uri.fsPath).with({
+ fragment: uri.fragment,
+ query: uri.query,
+ });
+
+ if (resourceProvider) {
+ return resourceProvider.asWebviewUri(uri).toString(true);
+ } else {
+ uri = uri.with({ scheme: 'markdown-link' });
+ }
+ }
+ }
+
+ return uri.toString(true).replace(/^markdown-link:/, '');
+ }
+
+ return href;
+ } catch {
+ return href;
+ }
+ }
}
async function getMarkdownOptions(md: () => MarkdownIt) {
diff --git a/extensions/markdown-language-features/src/util/resources.ts b/extensions/markdown-language-features/src/util/resources.ts
index 063c410b39e..f1f2d0886ab 100644
--- a/extensions/markdown-language-features/src/util/resources.ts
+++ b/extensions/markdown-language-features/src/util/resources.ts
@@ -11,23 +11,3 @@ export interface WebviewResourceProvider {
readonly cspSource: string;
}
-export function normalizeResource(
- base: vscode.Uri,
- resource: vscode.Uri
-): vscode.Uri {
- // If we have a windows path and are loading a workspace with an authority,
- // make sure we use a unc path with an explicit localhost authority.
- //
- // Otherwise, the `` rule will insert the authority into the resolved resource
- // URI incorrectly.
- if (base.authority && !resource.authority) {
- const driveMatch = resource.path.match(/^\/(\w):\//);
- if (driveMatch) {
- return vscode.Uri.file(`\\\\localhost\\${driveMatch[1]}$\\${resource.fsPath.replace(/^\w:\\/, '')}`).with({
- fragment: resource.fragment,
- query: resource.query
- });
- }
- }
- return resource;
-}