diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts index 23fce2c7b4f..af44be59720 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts @@ -207,9 +207,9 @@ async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): P return { multiline, inline }; } -function isLinkInsideCode(code: CodeInDocument, link: MdLink) { - return code.multiline.some(interval => link.source.hrefRange.start.line >= interval[0] && link.source.hrefRange.start.line < interval[1]) || - code.inline.some(position => position.intersection(link.source.hrefRange)); +function isLinkInsideCode(code: CodeInDocument, linkHrefRange: vscode.Range) { + return code.multiline.some(interval => linkHrefRange.start.line >= interval[0] && linkHrefRange.start.line < interval[1]) || + code.inline.some(position => position.intersection(linkHrefRange)); } export class MdLinkProvider implements vscode.DocumentLinkProvider { @@ -257,33 +257,31 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } public async getAllLinks(document: SkinnyTextDocument): Promise { + const codeInDocument = await findCode(document, this.engine); return Array.from([ - ...(await this.getInlineLinks(document)), - ...this.getReferenceLinks(document), - ...this.getLinkDefinitions(document), - ...this.getAutoLinks(document), + ...this.getInlineLinks(document, codeInDocument), + ...this.getReferenceLinks(document, codeInDocument), + ...this.getLinkDefinitions2(document, codeInDocument), + ...this.getAutoLinks(document, codeInDocument), ]); } - private async getInlineLinks(document: SkinnyTextDocument): Promise { + private *getInlineLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { const text = document.getText(); - const results: MdLink[] = []; - const codeInDocument = await findCode(document, this.engine); for (const match of text.matchAll(linkPattern)) { const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index); - if (matchImageData && !isLinkInsideCode(codeInDocument, matchImageData)) { - results.push(matchImageData); + if (matchImageData && !isLinkInsideCode(codeInDocument, matchImageData.source.hrefRange)) { + yield matchImageData; } const matchLinkData = extractDocumentLink(document, match[1].length, match[5], match.index); - if (matchLinkData && !isLinkInsideCode(codeInDocument, matchLinkData)) { - results.push(matchLinkData); + if (matchLinkData && !isLinkInsideCode(codeInDocument, matchLinkData.source.hrefRange)) { + yield matchLinkData; } } - return results; } - private *getAutoLinks(document: SkinnyTextDocument): Iterable { + private *getAutoLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { const text = document.getText(); for (const match of text.matchAll(autoLinkPattern)) { @@ -293,6 +291,10 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { const offset = (match.index ?? 0) + 1; const linkStart = document.positionAt(offset); const linkEnd = document.positionAt(offset + link.length); + const hrefRange = new vscode.Range(linkStart, linkEnd); + if (isLinkInsideCode(codeInDocument, hrefRange)) { + continue; + } yield { kind: 'link', href: linkTarget, @@ -307,7 +309,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } } - private *getReferenceLinks(document: SkinnyTextDocument): Iterable { + private *getReferenceLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { const text = document.getText(); for (const match of text.matchAll(referenceLinkPattern)) { let linkStart: vscode.Position; @@ -327,12 +329,17 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { continue; } + const hrefRange = new vscode.Range(linkStart, linkEnd); + if (isLinkInsideCode(codeInDocument, hrefRange)) { + continue; + } + yield { kind: 'link', source: { text: reference, resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), + hrefRange, fragmentRange: undefined, }, href: { @@ -343,7 +350,12 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } } - public *getLinkDefinitions(document: SkinnyTextDocument): Iterable { + public async getLinkDefinitions(document: SkinnyTextDocument): Promise> { + const codeInDocument = await findCode(document, this.engine); + return this.getLinkDefinitions2(document, codeInDocument); + } + + private *getLinkDefinitions2(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { const text = document.getText(); for (const match of text.matchAll(definitionPattern)) { const pre = match[1]; @@ -354,41 +366,35 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { const refStart = document.positionAt((match.index ?? 0) + 1); const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); + let linkStart: vscode.Position; + let linkEnd: vscode.Position; + let text: string; if (angleBracketLinkRe.test(link)) { - const linkStart = document.positionAt(offset + 1); - const linkEnd = document.positionAt(offset + link.length - 1); - const text = link.substring(1, link.length - 1); - const target = parseLink(document, text); - if (target) { - yield { - kind: 'definition', - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - fragmentRange: getFragmentRange(link, linkStart, linkEnd), - }, - ref: { text: reference, range: refRange }, - href: target, - }; - } + linkStart = document.positionAt(offset + 1); + linkEnd = document.positionAt(offset + link.length - 1); + text = link.substring(1, link.length - 1); } else { - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); - const target = parseLink(document, link); - if (target) { - yield { - kind: 'definition', - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - fragmentRange: getFragmentRange(link, linkStart, linkEnd) - }, - ref: { text: reference, range: refRange }, - href: target, - }; - } + linkStart = document.positionAt(offset); + linkEnd = document.positionAt(offset + link.length); + text = link; + } + const hrefRange = new vscode.Range(linkStart, linkEnd); + if (isLinkInsideCode(codeInDocument, hrefRange)) { + continue; + } + const target = parseLink(document, text); + if (target) { + yield { + kind: 'definition', + source: { + text: link, + resource: document.uri, + hrefRange, + fragmentRange: getFragmentRange(link, linkStart, linkEnd), + }, + ref: { text: reference, range: refRange }, + href: target, + }; } } } diff --git a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts index 612698acbc6..c4e3b5afe5a 100644 --- a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts +++ b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts @@ -103,7 +103,11 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider { switch (context.kind) { case CompletionContextKind.ReferenceLink: { - return Array.from(this.provideReferenceSuggestions(document, position, context)); + const items: vscode.CompletionItem[] = []; + for await (const item of this.provideReferenceSuggestions(document, position, context)) { + items.push(item); + } + return items; } case CompletionContextKind.LinkDefinition: @@ -232,11 +236,11 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider { }; } - private *provideReferenceSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): Iterable { + private async *provideReferenceSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable { const insertionRange = new vscode.Range(context.linkTextStartPosition, position); const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length })); - const definitions = this.linkProvider.getLinkDefinitions(document); + const definitions = await this.linkProvider.getLinkDefinitions(document); for (const def of definitions) { yield { kind: vscode.CompletionItemKind.Reference, diff --git a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts index f1b0a7eb1ee..b7e43bf3a6e 100644 --- a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts +++ b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts @@ -213,6 +213,26 @@ suite('markdown.DocumentLinkProvider', () => { assert.strictEqual(links.length, 0); }); + test('Should not consider link references in code fenced with backticks (#146714)', async () => { + const text = joinLines( + '```', + '[a] [bb]', + '```'); + const links = await getLinksForFile(text); + assert.strictEqual(links.length, 0); + }); + + test('Should not consider reference sources in code fenced with backticks (#146714)', async () => { + const text = joinLines( + '```', + '[a]: http://example.com;', + '[b]: ;', + '[c]: (http://example.com);', + '```'); + const links = await getLinksForFile(text); + assert.strictEqual(links.length, 0); + }); + test('Should not consider links in multiline inline code span between between text', async () => { const text = joinLines( '[b](https://1.com) `[b](https://2.com)',