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

Support In-Document links inside of the markdown editor (#19411)

* Support In Document links inside of the markdown editor

Fixes #17288

* Cleaning up code to reduce duplication
上级 3b2ac054
......@@ -8,15 +8,22 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { MarkdownEngine } from './markdownEngine';
import { TableOfContentProvider } from './tableOfContentsProvider';
export default class MarkdownDocumentLinkProvider implements vscode.DocumentLinkProvider {
private _linkPattern = /(\[[^\]]*\]\(\s*?)(\S+?)(\s+[^\)]*)?\)/g;
constructor(private engine: MarkdownEngine) { }
public provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentLink[] {
const results: vscode.DocumentLink[] = [];
const base = path.dirname(document.uri.fsPath);
const text = document.getText();
const toc = new TableOfContentProvider(this.engine, document);
this._linkPattern.lastIndex = 0;
let match: RegExpMatchArray | null;
while ((match = this._linkPattern.exec(text))) {
......@@ -26,20 +33,9 @@ export default class MarkdownDocumentLinkProvider implements vscode.DocumentLink
const linkStart = document.positionAt(offset);
const linkEnd = document.positionAt(offset + link.length);
try {
let uri = vscode.Uri.parse(link);
if (!uri.scheme) {
// assume it must be a file
let file;
if (uri.path[0] === '/') {
file = path.join(vscode.workspace.rootPath, uri.path);
} else {
file = path.join(base, uri.path);
}
uri = vscode.Uri.file(file);
}
results.push(new vscode.DocumentLink(
new vscode.Range(linkStart, linkEnd),
uri));
this.normalizeLink(link, base, toc)));
} catch (e) {
// noop
}
......@@ -47,4 +43,27 @@ export default class MarkdownDocumentLinkProvider implements vscode.DocumentLink
return results;
}
};
private normalizeLink(link: string, base: string, toc: TableOfContentProvider): vscode.Uri {
let uri = vscode.Uri.parse(link);
if (!uri.scheme) {
if (uri.fragment && !uri.path) {
// local link
const line = toc.lookup(uri.fragment);
if (!isNaN(line)) {
return vscode.Uri.parse(`command:revealLine?${encodeURIComponent(JSON.stringify({ lineNumber: line, at: 'top' }))}`);
}
}
// assume it must be a file
let file;
if (uri.path[0] === '/') {
file = path.join(vscode.workspace.rootPath, uri.path);
} else {
file = path.join(base, uri.path);
}
uri = vscode.Uri.file(file);
}
return uri;
}
}
......@@ -8,28 +8,16 @@
import * as vscode from 'vscode';
import { MarkdownEngine } from './markdownEngine';
import { TableOfContentProvider } from './tableOfContentsProvider';
export default class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
constructor(private engine: MarkdownEngine) { }
provideDocumentSymbols(document: vscode.TextDocument): vscode.ProviderResult<vscode.SymbolInformation[]> {
const tokens = this.engine.parse(document.getText());
const headings = tokens.filter(token => token.type === 'heading_open');
return headings.map(heading => {
const lineNumber = heading.map[0];
const line = document.lineAt(lineNumber);
const location = new vscode.Location(document.uri, line.range);
// # Header => 'Header'
// ## Header ## => 'Header'
// ## Header #### => 'Header'
// Header ## => 'Header ##'
// =========
const text = line.text.replace(/^\s*(#)+\s*(.*?)\s*\1*$/, '$2');
return new vscode.SymbolInformation(text, vscode.SymbolKind.Module, '', location);
const toc = new TableOfContentProvider(this.engine, document);
return toc.getToc().map(entry => {
return new vscode.SymbolInformation(entry.text, vscode.SymbolKind.Module, '', entry.location);
});
}
}
\ No newline at end of file
......@@ -33,7 +33,7 @@ export function activate(context: vscode.ExtensionContext) {
const symbolsProviderRegistration = vscode.languages.registerDocumentSymbolProvider({ language: 'markdown' }, symbolsProvider);
context.subscriptions.push(contentProviderRegistration, symbolsProviderRegistration);
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider('markdown', new DocumentLinkProvider()));
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider('markdown', new DocumentLinkProvider(engine)));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreview', showPreview));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreviewToSide', uri => showPreview(uri, true)));
......
/*---------------------------------------------------------------------------------------------
* 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 { MarkdownEngine, IToken } from './markdownEngine';
export interface TocEntry {
slug: string;
text: string;
line: number;
location: vscode.Location;
}
export class TableOfContentProvider {
private toc: TocEntry[];
public constructor(
private engine: MarkdownEngine,
private document: vscode.TextDocument) { }
public getToc(): TocEntry[] {
if (!this.toc) {
try {
this.toc = this.buildToc(this.document);
} catch (e) {
this.toc = [];
}
}
return this.toc;
}
public lookup(fragment: string): number {
const slug = TableOfContentProvider.slugify(fragment);
for (const entry of this.getToc()) {
if (entry.slug === slug) {
return entry.line;
}
}
return NaN;
}
private buildToc(document: vscode.TextDocument): any {
const toc: TocEntry[] = [];
const tokens: IToken[] = this.engine.parse(this.document.getText());
for (const heading of tokens.filter(token => token.type === 'heading_open')) {
const lineNumber = heading.map[0];
const line = document.lineAt(lineNumber);
const href = TableOfContentProvider.slugify(line.text);
if (href) {
toc.push({
slug: href,
text: TableOfContentProvider.getHeaderText(line.text),
line: lineNumber,
location: new vscode.Location(document.uri, line.range)
});
}
}
return toc;
}
private static getHeaderText(header: string): string {
return header.replace(/^\s*(#)+\s*(.*?)\s*\1*$/, '$2').trim();
}
public static slugify(header: string): string {
return encodeURI(TableOfContentProvider.getHeaderText(header)
.toLowerCase()
.replace(/\s/g, '-'));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册