diff --git a/extensions/markdown-language-features/src/commands/openDocumentLink.ts b/extensions/markdown-language-features/src/commands/openDocumentLink.ts index d394368095f..9f8919bd9ec 100644 --- a/extensions/markdown-language-features/src/commands/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/commands/openDocumentLink.ts @@ -13,9 +13,9 @@ import { isMarkdownFile } from '../util/file'; export interface OpenDocumentLinkArgs { - readonly path: string; + readonly path: {}; readonly fragment: string; - readonly fromResource: any; + readonly fromResource: {}; } enum OpenMarkdownLinks { @@ -29,13 +29,13 @@ export class OpenDocumentLinkCommand implements Command { public static createCommandUri( fromResource: vscode.Uri, - path: string, + path: vscode.Uri, fragment: string, ): vscode.Uri { return vscode.Uri.parse(`command:${OpenDocumentLinkCommand.id}?${encodeURIComponent(JSON.stringify({ - path: encodeURIComponent(path), + path: path.toJSON(), fragment, - fromResource: encodeURIComponent(fromResource.toString(true)), + fromResource: fromResource.toJSON(), }))}`); } @@ -44,25 +44,28 @@ export class OpenDocumentLinkCommand implements Command { ) { } public async execute(args: OpenDocumentLinkArgs) { - const fromResource = vscode.Uri.parse(decodeURIComponent(args.fromResource)); - const targetPath = decodeURIComponent(args.path); - const targetResource = vscode.Uri.file(targetPath); + return OpenDocumentLinkCommand.execute(this.engine, args); + } + + public static async execute(engine: MarkdownEngine, args: OpenDocumentLinkArgs) { + const fromResource = vscode.Uri.parse('').with(args.fromResource); + const targetResource = vscode.Uri.parse('').with(args.path); const column = this.getViewColumn(fromResource); try { - return await this.tryOpen(targetResource, args, column); + return await this.tryOpen(engine, targetResource, args, column); } catch { if (extname(targetResource.path) === '') { - return this.tryOpen(targetResource.with({ path: targetResource.path + '.md' }), args, column); + return this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column); } await vscode.commands.executeCommand('vscode.open', targetResource, column); return undefined; } } - private async tryOpen(resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) { + private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) { if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) { if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) { - return this.tryRevealLine(vscode.window.activeTextEditor, args.fragment); + return this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment); } } @@ -73,10 +76,10 @@ export class OpenDocumentLinkCommand implements Command { return vscode.workspace.openTextDocument(resource) .then(document => vscode.window.showTextDocument(document, column)) - .then(editor => this.tryRevealLine(editor, args.fragment)); + .then(editor => this.tryRevealLine(engine, editor, args.fragment)); } - private getViewColumn(resource: vscode.Uri): vscode.ViewColumn { + private static getViewColumn(resource: vscode.Uri): vscode.ViewColumn { const config = vscode.workspace.getConfiguration('markdown', resource); const openLinks = config.get('links.openLocation', OpenMarkdownLinks.currentGroup); switch (openLinks) { @@ -88,18 +91,22 @@ export class OpenDocumentLinkCommand implements Command { } } - private async tryRevealLine(editor: vscode.TextEditor, fragment?: string) { + private static async tryRevealLine(engine: MarkdownEngine, editor: vscode.TextEditor, fragment?: string) { if (editor && fragment) { - const toc = new TableOfContentsProvider(this.engine, editor.document); + const toc = new TableOfContentsProvider(engine, editor.document); const entry = await toc.lookup(fragment); if (entry) { - return editor.revealRange(new vscode.Range(entry.line, 0, entry.line, 0), vscode.TextEditorRevealType.AtTop); + const lineStart = new vscode.Range(entry.line, 0, entry.line, 0); + editor.selection = new vscode.Selection(lineStart.start, lineStart.end); + return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop); } const lineNumberFragment = fragment.match(/^L(\d+)$/i); if (lineNumberFragment) { const line = +lineNumberFragment[1] - 1; if (!isNaN(line)) { - return editor.revealRange(new vscode.Range(line, 0, line, 0), vscode.TextEditorRevealType.AtTop); + const lineStart = new vscode.Range(line, 0, line, 0); + editor.selection = new vscode.Selection(lineStart.start, lineStart.end); + return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop); } } } diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 0bcc8473745..f5c0b4114a4 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -15,9 +15,9 @@ import MarkdownWorkspaceSymbolProvider from './features/workspaceSymbolProvider' import { Logger } from './logger'; import { MarkdownEngine } from './markdownEngine'; import { getMarkdownExtensionContributions } from './markdownExtensions'; -import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector, ContentSecurityPolicyArbiter } from './security'; -import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; +import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security'; import { githubSlugifier } from './slugify'; +import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; export function activate(context: vscode.ExtensionContext) { @@ -33,7 +33,7 @@ export function activate(context: vscode.ExtensionContext) { const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger); const symbolProvider = new MDDocumentSymbolProvider(engine); - const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions); + const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, engine); context.subscriptions.push(previewManager); context.subscriptions.push(registerMarkdownLanguageFeatures(symbolProvider, engine)); diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts index 441055e5e71..89237df66d6 100644 --- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts @@ -14,8 +14,7 @@ const localize = nls.loadMessageBundle(); function parseLink( document: vscode.TextDocument, link: string, - base: string -): { uri: vscode.Uri, tooltip?: string } { +): { uri: vscode.Uri, tooltip?: string } | undefined { const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link); if (externalSchemeUri) { // Normalize VS Code links to target currently running version @@ -29,24 +28,43 @@ function parseLink( // Use a fake scheme to avoid parse warnings const tempUri = vscode.Uri.parse(`vscode-resource:${link}`); - let resourcePath = tempUri.path; - if (!tempUri.path && document.uri.scheme === 'file') { - resourcePath = document.uri.path; + let resourceUri: vscode.Uri | undefined; + if (!tempUri.path) { + resourceUri = document.uri; } else if (tempUri.path[0] === '/') { - const root = vscode.workspace.getWorkspaceFolder(document.uri); + const root = getWorkspaceFolder(document); if (root) { - resourcePath = path.join(root.uri.fsPath, tempUri.path); + resourceUri = vscode.Uri.joinPath(root, tempUri.path); } } else { - resourcePath = base ? path.join(base, tempUri.path) : tempUri.path; + if (document.uri.scheme === Schemes.untitled) { + const root = getWorkspaceFolder(document); + if (root) { + resourceUri = vscode.Uri.joinPath(root, tempUri.path); + } + } else { + const base = document.uri.with({ path: path.dirname(document.uri.fsPath) }); + resourceUri = vscode.Uri.joinPath(base, tempUri.path); + } } + if (!resourceUri) { + return undefined; + } + + resourceUri = resourceUri.with({ fragment: tempUri.fragment }); + return { - uri: OpenDocumentLinkCommand.createCommandUri(document.uri, resourcePath, tempUri.fragment), + uri: OpenDocumentLinkCommand.createCommandUri(document.uri, resourceUri, tempUri.fragment), tooltip: localize('documentLink.tooltip', 'Follow link') }; } +function getWorkspaceFolder(document: vscode.TextDocument) { + return vscode.workspace.getWorkspaceFolder(document.uri)?.uri + || vscode.workspace.workspaceFolders?.[0]?.uri; +} + function matchAll( pattern: RegExp, text: string @@ -62,7 +80,6 @@ function matchAll( function extractDocumentLink( document: vscode.TextDocument, - base: string, pre: number, link: string, matchIndex: number | undefined @@ -71,11 +88,14 @@ function extractDocumentLink( const linkStart = document.positionAt(offset); const linkEnd = document.positionAt(offset + link.length); try { - const { uri, tooltip } = parseLink(document, link, base); + const linkData = parseLink(document, link); + if (!linkData) { + return undefined; + } const documentLink = new vscode.DocumentLink( new vscode.Range(linkStart, linkEnd), - uri); - documentLink.tooltip = tooltip; + linkData.uri); + documentLink.tooltip = linkData.tooltip; return documentLink; } catch (e) { return undefined; @@ -91,27 +111,25 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { document: vscode.TextDocument, _token: vscode.CancellationToken ): vscode.DocumentLink[] { - const base = document.uri.scheme === 'file' ? path.dirname(document.uri.fsPath) : ''; const text = document.getText(); return [ - ...this.providerInlineLinks(text, document, base), - ...this.provideReferenceLinks(text, document, base) + ...this.providerInlineLinks(text, document), + ...this.provideReferenceLinks(text, document) ]; } private providerInlineLinks( text: string, document: vscode.TextDocument, - base: string ): vscode.DocumentLink[] { const results: vscode.DocumentLink[] = []; for (const match of matchAll(this.linkPattern, text)) { - const matchImage = match[4] && extractDocumentLink(document, base, match[3].length + 1, match[4], match.index); + const matchImage = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index); if (matchImage) { results.push(matchImage); } - const matchLink = extractDocumentLink(document, base, match[1].length, match[5], match.index); + const matchLink = extractDocumentLink(document, match[1].length, match[5], match.index); if (matchLink) { results.push(matchLink); } @@ -122,7 +140,6 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { private provideReferenceLinks( text: string, document: vscode.TextDocument, - base: string ): vscode.DocumentLink[] { const results: vscode.DocumentLink[] = []; @@ -159,8 +176,10 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { for (const definition of definitions.values()) { try { - const { uri } = parseLink(document, definition.link, base); - results.push(new vscode.DocumentLink(definition.linkRange, uri)); + const linkData = parseLink(document, definition.link); + if (linkData) { + results.push(new vscode.DocumentLink(definition.linkRange, linkData.uri)); + } } catch (e) { // noop } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index b52ba6f636a..f2fb800347c 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -3,20 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import * as path from 'path'; - -import { Logger } from '../logger'; -import { MarkdownContentProvider } from './previewContentProvider'; -import { Disposable } from '../util/dispose'; - +import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; +import { OpenDocumentLinkCommand, resolveLinkToMarkdownFile } from '../commands/openDocumentLink'; +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 { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; -import { MarkdownContributionProvider } from '../markdownExtensions'; -import { isMarkdownFile } from '../util/file'; -import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink'; -import { WebviewResourceProvider, normalizeResource } from '../util/resources'; +import { MarkdownContentProvider } from './previewContentProvider'; +import { MarkdownEngine } from '../markdownEngine'; + const localize = nls.loadMessageBundle(); interface WebviewMessage { @@ -123,6 +123,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { resource: vscode.Uri, startingScroll: StartingScrollLocation | undefined, private readonly delegate: MarkdownPreviewDelegate, + private readonly engine: MarkdownEngine, private readonly _contentProvider: MarkdownContentProvider, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, private readonly _logger: Logger, @@ -407,7 +408,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } } - vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource }); + OpenDocumentLinkCommand.execute(this.engine, { path: hrefPath, fragment, fromResource: this.resource.toJSON() }); } //#region WebviewResourceProvider @@ -452,8 +453,9 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown previewConfigurations: MarkdownPreviewConfigurationManager, logger: Logger, contributionProvider: MarkdownContributionProvider, + engine: MarkdownEngine, ): StaticMarkdownPreview { - return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider); + return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider, engine); } private readonly preview: MarkdownPreview; @@ -465,13 +467,14 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, logger: Logger, contributionProvider: MarkdownContributionProvider, + engine: MarkdownEngine, ) { super(); this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, undefined, { getAdditionalState: () => { return {}; }, openPreviewLinkToMarkdownFile: () => { /* todo */ } - }, contentProvider, _previewConfigurations, logger, contributionProvider)); + }, engine, contentProvider, _previewConfigurations, logger, contributionProvider)); this._register(this._webviewPanel.onDidDispose(() => { this.dispose(); @@ -548,9 +551,10 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow logger: Logger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, + engine: MarkdownEngine, ): DynamicMarkdownPreview { return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider); + contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine); } public static create( @@ -560,7 +564,8 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow previewConfigurations: MarkdownPreviewConfigurationManager, logger: Logger, topmostLineMonitor: TopmostLineMonitor, - contributionProvider: MarkdownContributionProvider + contributionProvider: MarkdownContributionProvider, + engine: MarkdownEngine, ): DynamicMarkdownPreview { const webview = vscode.window.createWebviewPanel( DynamicMarkdownPreview.viewType, @@ -568,7 +573,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow previewColumn, { enableFindWidget: true, }); return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider); + contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine); } private constructor( @@ -579,6 +584,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow private readonly _logger: Logger, private readonly _topmostLineMonitor: TopmostLineMonitor, private readonly _contributionProvider: MarkdownContributionProvider, + private readonly _engine: MarkdownEngine, ) { super(); @@ -729,6 +735,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined); } }, + this._engine, this._contentProvider, this._previewConfigurations, this._logger, diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 93a7f466a90..3c8ddd77446 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -5,10 +5,11 @@ import * as vscode from 'vscode'; import { Logger } from '../logger'; +import { MarkdownEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; -import { disposeAll, Disposable } from '../util/dispose'; +import { Disposable, disposeAll } from '../util/dispose'; import { TopmostLineMonitor } from '../util/topmostLineMonitor'; -import { DynamicMarkdownPreview, StaticMarkdownPreview, ManagedMarkdownPreview } from './preview'; +import { DynamicMarkdownPreview, ManagedMarkdownPreview, StaticMarkdownPreview } from './preview'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { MarkdownContentProvider } from './previewContentProvider'; @@ -68,7 +69,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview public constructor( private readonly _contentProvider: MarkdownContentProvider, private readonly _logger: Logger, - private readonly _contributions: MarkdownContributionProvider + private readonly _contributions: MarkdownContributionProvider, + private readonly _engine: MarkdownEngine, ) { super(); this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this)); @@ -145,7 +147,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._previewConfigurations, this._logger, this._topmostLineMonitor, - this._contributions); + this._contributions, + this._engine); this.registerDynamicPreview(preview); } @@ -160,7 +163,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._contentProvider, this._previewConfigurations, this._logger, - this._contributions); + this._contributions, + this._engine); this.registerStaticPreview(preview); } @@ -179,7 +183,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._previewConfigurations, this._logger, this._topmostLineMonitor, - this._contributions); + this._contributions, + this._engine); this.setPreviewActiveContext(true); this._activePreview = preview; diff --git a/extensions/markdown-language-features/src/markdownExtensions.ts b/extensions/markdown-language-features/src/markdownExtensions.ts index 3ca25a091f2..387a41388c0 100644 --- a/extensions/markdown-language-features/src/markdownExtensions.ts +++ b/extensions/markdown-language-features/src/markdownExtensions.ts @@ -70,7 +70,7 @@ export namespace MarkdownContributions { const previewStyles = getContributedStyles(contributions, extension); const previewScripts = getContributedScripts(contributions, extension); - const previewResourceRoots = previewStyles.length || previewScripts.length ? [vscode.Uri.file(extension.extensionPath)] : []; + const previewResourceRoots = previewStyles.length || previewScripts.length ? [extension.extensionUri] : []; const markdownItPlugins = getContributedMarkdownItPlugins(contributions, extension); return { diff --git a/extensions/markdown-language-features/src/test/documentLink.test.ts b/extensions/markdown-language-features/src/test/documentLink.test.ts index 89b6259dea1..9b26d995f27 100644 --- a/extensions/markdown-language-features/src/test/documentLink.test.ts +++ b/extensions/markdown-language-features/src/test/documentLink.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; +import { joinLines } from './util'; const testFileA = workspaceFile('a.md'); @@ -75,7 +76,7 @@ suite('Markdown Document links', () => { await executeLink(link); assertActiveDocumentUri(workspaceFile('sub', 'c.md')); - assert.strictEqual( vscode.window.activeTextEditor!.selection.start.line, 1); + assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1); }); test('Should navigate to fragment by line', async () => { @@ -85,7 +86,39 @@ suite('Markdown Document links', () => { await executeLink(link); assertActiveDocumentUri(workspaceFile('sub', 'c.md')); - assert.strictEqual( vscode.window.activeTextEditor!.selection.start.line, 1); + assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1); + }); + + test('Should navigate to fragment within current file', async () => { + await withFileContents(testFileA, joinLines( + '[](a#header)', + '[](#header)', + '# Header')); + + const links = await getLinksForFile(testFileA); + { + await executeLink(links[0]); + assertActiveDocumentUri(workspaceFile('a.md')); + assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 2); + } + { + await executeLink(links[1]); + assertActiveDocumentUri(workspaceFile('a.md')); + assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 2); + } + }); + + test('Should navigate to fragment within current untitled file', async () => { + const testFile = workspaceFile('x.md').with({ scheme: 'untitled' }); + await withFileContents(testFile, joinLines( + '[](#second)', + '# Second')); + + const [link] = await getLinksForFile(testFile); + await executeLink(link); + + assertActiveDocumentUri(testFile); + assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1); }); }); diff --git a/extensions/markdown-language-features/src/test/util.ts b/extensions/markdown-language-features/src/test/util.ts new file mode 100644 index 00000000000..d50e9ca5db2 --- /dev/null +++ b/extensions/markdown-language-features/src/test/util.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; + +export const joinLines = (...args: string[]) => + args.join(os.platform() === 'win32' ? '\r\n' : '\n'); diff --git a/extensions/markdown-language-features/src/util/links.ts b/extensions/markdown-language-features/src/util/links.ts index 58725d6f778..3545d03a54d 100644 --- a/extensions/markdown-language-features/src/util/links.ts +++ b/extensions/markdown-language-features/src/util/links.ts @@ -9,6 +9,7 @@ export const Schemes = { http: 'http:', https: 'https:', file: 'file:', + untitled: 'untitled', mailto: 'mailto:', data: 'data:', vscode: 'vscode:', diff --git a/extensions/markdown-language-features/test-workspace/a.md b/extensions/markdown-language-features/test-workspace/a.md index e69de29bb2d..9d70918067b 100644 --- a/extensions/markdown-language-features/test-workspace/a.md +++ b/extensions/markdown-language-features/test-workspace/a.md @@ -0,0 +1,4 @@ +[b](b) +[b](b.md) +[b](./b.md) +[b](/b.md) diff --git a/extensions/markdown-language-features/test-workspace/b.md b/extensions/markdown-language-features/test-workspace/b.md index 42d51fb4b22..730f53ee6ce 100644 --- a/extensions/markdown-language-features/test-workspace/b.md +++ b/extensions/markdown-language-features/test-workspace/b.md @@ -1 +1,3 @@ -![](./a) \ No newline at end of file +# b + +[](./a) \ No newline at end of file diff --git a/extensions/markdown-language-features/test-workspace/sub/c.md b/extensions/markdown-language-features/test-workspace/sub/c.md index 4c48ee63f14..f5c9bec43ac 100644 --- a/extensions/markdown-language-features/test-workspace/sub/c.md +++ b/extensions/markdown-language-features/test-workspace/sub/c.md @@ -1,2 +1,6 @@ # First # Second + +[b](/b.md) +[b](../b.md) +[b](./../b.md)