mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
@@ -102,10 +102,11 @@ export function stripAngleBrackets(link: string) {
|
||||
return link.replace(angleBracketLinkRe, '$1');
|
||||
}
|
||||
|
||||
const linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*(".*?")?\)/g;
|
||||
const referenceLinkPattern = /(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]/g;
|
||||
const definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm;
|
||||
|
||||
export default class LinkProvider implements vscode.DocumentLinkProvider {
|
||||
private readonly linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*(".*?")?\)/g;
|
||||
private readonly referenceLinkPattern = /(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]/g;
|
||||
private readonly definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm;
|
||||
|
||||
public provideDocumentLinks(
|
||||
document: vscode.TextDocument,
|
||||
@@ -124,7 +125,7 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
|
||||
document: vscode.TextDocument,
|
||||
): vscode.DocumentLink[] {
|
||||
const results: vscode.DocumentLink[] = [];
|
||||
for (const match of text.matchAll(this.linkPattern)) {
|
||||
for (const match of text.matchAll(linkPattern)) {
|
||||
const matchImage = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index);
|
||||
if (matchImage) {
|
||||
results.push(matchImage);
|
||||
@@ -143,8 +144,8 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
|
||||
): vscode.DocumentLink[] {
|
||||
const results: vscode.DocumentLink[] = [];
|
||||
|
||||
const definitions = this.getDefinitions(text, document);
|
||||
for (const match of text.matchAll(this.referenceLinkPattern)) {
|
||||
const definitions = LinkProvider.getDefinitions(text, document);
|
||||
for (const match of text.matchAll(referenceLinkPattern)) {
|
||||
let linkStart: vscode.Position;
|
||||
let linkEnd: vscode.Position;
|
||||
let reference = match[3];
|
||||
@@ -188,9 +189,9 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
|
||||
return results;
|
||||
}
|
||||
|
||||
private getDefinitions(text: string, document: vscode.TextDocument) {
|
||||
public static getDefinitions(text: string, document: vscode.TextDocument) {
|
||||
const out = new Map<string, { link: string, linkRange: vscode.Range }>();
|
||||
for (const match of text.matchAll(this.definitionPattern)) {
|
||||
for (const match of text.matchAll(definitionPattern)) {
|
||||
const pre = match[1];
|
||||
const reference = match[2];
|
||||
const link = match[3].trim();
|
||||
|
||||
@@ -8,19 +8,23 @@ import * as vscode from 'vscode';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContentsProvider } from '../tableOfContentsProvider';
|
||||
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
|
||||
import LinkProvider from './documentLinkProvider';
|
||||
|
||||
enum LinkKind {
|
||||
Link, // [...](...)
|
||||
ReferenceLink, // [...][...]
|
||||
enum CompletionContextKind {
|
||||
Link, // [...](|)
|
||||
|
||||
ReferenceLink, // [...][|]
|
||||
|
||||
LinkDefinition, // []: | // TODO: not implemented
|
||||
}
|
||||
|
||||
interface CompletionContext {
|
||||
readonly linkKind: LinkKind;
|
||||
readonly kind: CompletionContextKind;
|
||||
|
||||
/**
|
||||
* Text of the link before the current position
|
||||
*
|
||||
* For `[abc](xy|z)` this would be `xy`
|
||||
* For `[abc](xy#z|abc)` this would be `xy#z`
|
||||
*/
|
||||
readonly linkPrefix: string;
|
||||
|
||||
@@ -28,7 +32,7 @@ interface CompletionContext {
|
||||
readonly linkTextStartPosition: vscode.Position;
|
||||
|
||||
/**
|
||||
* Info if the link looks like its for an anchor: `[](#header)`
|
||||
* Info if the link looks like it is for an anchor: `[](#header)`
|
||||
*/
|
||||
readonly anchorInfo?: {
|
||||
/** Text before the `#` */
|
||||
@@ -59,14 +63,23 @@ export class PathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (context.kind === CompletionContextKind.ReferenceLink) {
|
||||
const completionRange = new vscode.Range(context.linkTextStartPosition, position);
|
||||
return this.provideReferenceSuggestions(document, completionRange);
|
||||
}
|
||||
|
||||
if (context.kind === CompletionContextKind.LinkDefinition) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
const isAnchorInCurrentDoc = context.anchorInfo && context.anchorInfo.beforeAnchor.length === 0;
|
||||
|
||||
// Add anchor #links in current doc
|
||||
if (context.linkPrefix.length === 0 || isAnchorInCurrentDoc) {
|
||||
const range = new vscode.Range(context.linkTextStartPosition, position);
|
||||
items.push(...(await this.provideHeaderSuggestions(document, range)));
|
||||
const completionRange = new vscode.Range(context.linkTextStartPosition, position);
|
||||
items.push(...(await this.provideHeaderSuggestions(document, completionRange)));
|
||||
}
|
||||
|
||||
if (!isAnchorInCurrentDoc) {
|
||||
@@ -98,6 +111,9 @@ export class PathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
/// [...](...|
|
||||
private readonly linkStartPattern = /\[([^\]]*?)\]\(\s*([^\s\(\)]*)$/;
|
||||
|
||||
/// [...][...|
|
||||
private readonly referenceLinkStartPattern = /\[([^\]]*?)\]\[\s*([^\s\(\)]*)$/;
|
||||
|
||||
private getPathCompletionContext(document: vscode.TextDocument, position: vscode.Position): CompletionContext | undefined {
|
||||
const prefixRange = new vscode.Range(position.with({ character: 0 }), position);
|
||||
const linePrefix = document.getText(prefixRange);
|
||||
@@ -112,7 +128,7 @@ export class PathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
const anchorMatch = prefix.match(/^(.*)#([\w\d\-]*)$/);
|
||||
|
||||
return {
|
||||
linkKind: LinkKind.Link,
|
||||
kind: CompletionContextKind.Link,
|
||||
linkPrefix: prefix,
|
||||
linkTextStartPosition: position.translate({ characterDelta: -prefix.length }),
|
||||
anchorInfo: anchorMatch ? {
|
||||
@@ -122,10 +138,35 @@ export class PathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
};
|
||||
}
|
||||
|
||||
const referenceLinkPrefixMatch = linePrefix.match(this.referenceLinkStartPattern);
|
||||
if (referenceLinkPrefixMatch) {
|
||||
const prefix = referenceLinkPrefixMatch[2];
|
||||
return {
|
||||
kind: CompletionContextKind.ReferenceLink,
|
||||
linkPrefix: prefix,
|
||||
linkTextStartPosition: position.translate({ characterDelta: -prefix.length }),
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async provideHeaderSuggestions(document: vscode.TextDocument, range: vscode.Range,): Promise<vscode.CompletionItem[]> {
|
||||
private provideReferenceSuggestions(document: vscode.TextDocument, completionRange: vscode.Range): vscode.CompletionItem[] {
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
const definitions = LinkProvider.getDefinitions(document.getText(), document);
|
||||
for (const def of definitions) {
|
||||
items.push({
|
||||
kind: vscode.CompletionItemKind.Reference,
|
||||
label: def[0],
|
||||
range: completionRange,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private async provideHeaderSuggestions(document: vscode.TextDocument, completionRange: vscode.Range): Promise<vscode.CompletionItem[]> {
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
const tocProvider = new TableOfContentsProvider(this.engine, document);
|
||||
@@ -134,7 +175,7 @@ export class PathCompletionProvider implements vscode.CompletionItemProvider {
|
||||
items.push({
|
||||
kind: vscode.CompletionItemKind.Reference,
|
||||
label: '#' + entry.slug.value,
|
||||
range: range,
|
||||
range: completionRange,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string) {
|
||||
}
|
||||
|
||||
|
||||
suite('markdown.PathCompletionProvider', () => {
|
||||
suite('Markdown path completion provider', () => {
|
||||
|
||||
setup(async () => {
|
||||
// These tests assume that the markdown completion provider is already registered
|
||||
@@ -109,4 +109,17 @@ suite('markdown.PathCompletionProvider', () => {
|
||||
assert.ok(completions.some(x => x.label === '#b'), 'Has #b header completion');
|
||||
assert.ok(completions.some(x => x.label === '#header1'), 'Has #header1 header completion');
|
||||
});
|
||||
|
||||
test('Should reference links for current file', async () => {
|
||||
const completions = await getCompletionsAtCursor(workspaceFile('sub', 'new.md'), joinLines(
|
||||
`[][${CURSOR}`,
|
||||
``,
|
||||
`[ref-1]: bla`,
|
||||
`[ref-2]: bla`,
|
||||
));
|
||||
|
||||
assert.strictEqual(completions.length, 2);
|
||||
assert.ok(completions.some(x => x.label === 'ref-1'), 'Has ref-1 reference completion');
|
||||
assert.ok(completions.some(x => x.label === 'ref-2'), 'Has ref-2 reference completion');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user