提交 d85dc993 编写于 作者: M Matt Bierner 提交者: GitHub

Support Local File Links from the Markdown Preview (#18824)

* Support Local File Links from the Markdown Preview

Fixes #989

**Bug**

* `file://` links do not work in the markdown preview.
* `./file` and `/file` and `file` links do not work in the markdown preview.

**Fix**
For the first issue, allow `file://` links explicitly.

Add a custom link validator. If the incoming link has no scheme, try to resolve it relative to the current file or workspace. Likes like `/file` are relative to the workspace, while links like `file` or `./file` are relative to the current file.

* Fix reveal of source line on load in markdown preview
上级 bf89d878
......@@ -21,7 +21,7 @@
}
const entry = { line: lineNumber, element: element };
if (lineNumber === targetLine) {
return { before: entry, next: null };
return { previous: entry, next: null };
} else if (lineNumber > targetLine) {
return { previous, next: entry };
}
......
......@@ -19,23 +19,21 @@ body.scrollBeyondLastLine {
position: relative;
}
.code-active-line:before {
.code-active-line:before,
.code-line:hover:before {
content: "";
display: block;
position: absolute;
top: 0;
left: -12px;
height: 100%;
}
.code-active-line:before {
border-left: 3px solid rgba(0, 122, 204, 0.5);
}
.code-line:hover:before {
content: "";
display: block;
position: absolute;
top: 0;
left: -12px;
height: 100%;
border-left: 3px solid #4080D0;
}
......
......@@ -8,6 +8,7 @@
import * as vscode from 'vscode';
import * as path from 'path';
import TelemetryReporter from 'vscode-extension-telemetry';
import { MarkdownEngine } from './markdownEngine';
import DocumentLinkProvider from './documentLinkProvider';
interface IPackageInfo {
......@@ -18,77 +19,6 @@ interface IPackageInfo {
var telemetryReporter: TelemetryReporter | null;
interface IToken {
type: string;
map: [number, number];
}
interface MarkdownIt {
render(text: string): string;
parse(text: string): IToken[];
renderer: { rules: any };
}
class MarkdownEngine {
private _markdownIt: MarkdownIt | undefined;
private get markdownIt(): MarkdownIt {
if (!this._markdownIt) {
const hljs = require('highlight.js');
const mdnh = require('markdown-it-named-headers');
const md = require('markdown-it')({
html: true,
highlight: (str: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) {
try {
return `<pre class="hljs"><code><div>${hljs.highlight(lang, str, true).value}</div></code></pre>`;
} catch (error) { }
}
return `<pre class="hljs"><code><div>${md.utils.escapeHtml(str)}</div></code></pre>`;
}
}).use(mdnh, {});
const createLineNumberRenderer = (ruleName: string) => {
const original = md.renderer.rules[ruleName];
return (tokens: any, idx: number, options: any, env: any, self: any) => {
const token = tokens[idx];
if (token.level === 0 && token.map && token.map.length) {
token.attrSet('data-line', token.map[0]);
token.attrJoin('class', 'code-line');
}
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
}
};
};
md.renderer.rules.paragraph_open = createLineNumberRenderer('paragraph_open');
md.renderer.rules.heading_open = createLineNumberRenderer('heading_open');
md.renderer.rules.image = createLineNumberRenderer('image');
md.renderer.rules.code_block = createLineNumberRenderer('code_block');
this._markdownIt = md as MarkdownIt;
}
return this._markdownIt;
}
get rules(): any {
return this.markdownIt.renderer.rules;
}
render(text: string): string {
return this.markdownIt.render(text);
}
parse(text: string): IToken[] {
return this.markdownIt.parse(text);
}
}
const FrontMatterRegex = /^---\s*(.|\s)*?---\s*/;
export function activate(context: vscode.ExtensionContext) {
......@@ -248,7 +178,10 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
private _waiting: boolean;
constructor(private engine: MarkdownEngine, private context: vscode.ExtensionContext) {
constructor(
private engine: MarkdownEngine,
private context: vscode.ExtensionContext
) {
this._waiting = false;
}
......@@ -325,7 +258,7 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
const previewFrontMatter = vscode.workspace.getConfiguration('markdown')['previewFrontMatter'];
const text = document.getText();
const contents = previewFrontMatter === 'hide' ? text.replace(FrontMatterRegex, '') : text;
const body = this.engine.render(contents);
const body = this.engine.render(sourceUri, contents);
return `<!DOCTYPE html>
<html>
......@@ -386,7 +319,7 @@ class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
const tokens = this.engine.parse(text);
const headings = tokens.filter(token => token.type === 'heading_open');
const symbols = headings.map(heading => {
return headings.map(heading => {
const lineNumber = heading.map[0];
const line = document.lineAt(lineNumber + offset);
const location = new vscode.Location(document.uri, line.range);
......@@ -400,7 +333,5 @@ class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
return new vscode.SymbolInformation(text, vscode.SymbolKind.Module, '', location);
});
return symbols;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as path from 'path';
export interface IToken {
type: string;
map: [number, number];
}
interface MarkdownIt {
render(text: string): string;
parse(text: string): IToken[];
utils: any;
}
export class MarkdownEngine {
private md: MarkdownIt;
private currentDocument: vscode.Uri;
private get engine(): MarkdownIt {
if (!this.md) {
const hljs = require('highlight.js');
const mdnh = require('markdown-it-named-headers');
this.md = require('markdown-it')({
html: true,
highlight: (str: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) {
try {
return `<pre class="hljs"><code><div>${hljs.highlight(lang, str, true).value}</div></code></pre>`;
} catch (error) { }
}
return `<pre class="hljs"><code><div>${this.engine.utils.escapeHtml(str)}</div></code></pre>`;
}
}).use(mdnh, {});
this.addLineNumberRenderer(this.md, 'paragraph_open');
this.addLineNumberRenderer(this.md, 'heading_open');
this.addLineNumberRenderer(this.md, 'image');
this.addLineNumberRenderer(this.md, 'code_block');
this.addLinkNormalizer(this.md);
this.addLinkValidator(this.md);
}
return this.md;
}
public render(document: vscode.Uri, text: string): string {
this.currentDocument = document;
return this.engine.render(text);
}
public parse(text: string): IToken[] {
return this.engine.parse(text);
}
private addLineNumberRenderer(md: any, ruleName: string): void {
const original = md.renderer.rules[ruleName];
md.renderer.rules[ruleName] = (tokens: any, idx: number, options: any, env: any, self: any) => {
const token = tokens[idx];
if (token.level === 0 && token.map && token.map.length) {
token.attrSet('data-line', token.map[0]);
token.attrJoin('class', 'code-line');
}
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
}
};
}
private addLinkNormalizer(md: any): void {
const normalizeLink = md.normalizeLink;
md.normalizeLink = (link: string) => {
try {
let uri = vscode.Uri.parse(link);
if (!uri.scheme) {
// Assume it must be a file
if (uri.path[0] === '/') {
uri = vscode.Uri.file(path.join(vscode.workspace.rootPath, uri.path));
} else {
uri = vscode.Uri.file(path.join(path.dirname(this.currentDocument.path), uri.path));
}
return normalizeLink(uri.toString(true));
}
} catch (e) {
// noop
}
return normalizeLink(link);
};
}
private addLinkValidator(md: any): void {
const validateLink = md.validateLink;
md.validateLink = (link: string) => {
if (validateLink(link)) {
return true;
}
// support file:// links
return link.indexOf('file:') === 0;
};
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册