Skip to content

Commit fdf23dc

Browse files
committed
Working on supporting markdown links in serverless+web
For microsoft#101203
1 parent 362d345 commit fdf23dc

12 files changed

Lines changed: 158 additions & 68 deletions

File tree

extensions/markdown-language-features/src/commands/openDocumentLink.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { isMarkdownFile } from '../util/file';
1313

1414

1515
export interface OpenDocumentLinkArgs {
16-
readonly path: string;
16+
readonly path: {};
1717
readonly fragment: string;
18-
readonly fromResource: any;
18+
readonly fromResource: {};
1919
}
2020

2121
enum OpenMarkdownLinks {
@@ -29,13 +29,13 @@ export class OpenDocumentLinkCommand implements Command {
2929

3030
public static createCommandUri(
3131
fromResource: vscode.Uri,
32-
path: string,
32+
path: vscode.Uri,
3333
fragment: string,
3434
): vscode.Uri {
3535
return vscode.Uri.parse(`command:${OpenDocumentLinkCommand.id}?${encodeURIComponent(JSON.stringify(<OpenDocumentLinkArgs>{
36-
path: encodeURIComponent(path),
36+
path: path.toJSON(),
3737
fragment,
38-
fromResource: encodeURIComponent(fromResource.toString(true)),
38+
fromResource: fromResource.toJSON(),
3939
}))}`);
4040
}
4141

@@ -44,25 +44,28 @@ export class OpenDocumentLinkCommand implements Command {
4444
) { }
4545

4646
public async execute(args: OpenDocumentLinkArgs) {
47-
const fromResource = vscode.Uri.parse(decodeURIComponent(args.fromResource));
48-
const targetPath = decodeURIComponent(args.path);
49-
const targetResource = vscode.Uri.file(targetPath);
47+
return OpenDocumentLinkCommand.execute(this.engine, args);
48+
}
49+
50+
public static async execute(engine: MarkdownEngine, args: OpenDocumentLinkArgs) {
51+
const fromResource = vscode.Uri.parse('').with(args.fromResource);
52+
const targetResource = vscode.Uri.parse('').with(args.path);
5053
const column = this.getViewColumn(fromResource);
5154
try {
52-
return await this.tryOpen(targetResource, args, column);
55+
return await this.tryOpen(engine, targetResource, args, column);
5356
} catch {
5457
if (extname(targetResource.path) === '') {
55-
return this.tryOpen(targetResource.with({ path: targetResource.path + '.md' }), args, column);
58+
return this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column);
5659
}
5760
await vscode.commands.executeCommand('vscode.open', targetResource, column);
5861
return undefined;
5962
}
6063
}
6164

62-
private async tryOpen(resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) {
65+
private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) {
6366
if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
6467
if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) {
65-
return this.tryRevealLine(vscode.window.activeTextEditor, args.fragment);
68+
return this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment);
6669
}
6770
}
6871

@@ -73,10 +76,10 @@ export class OpenDocumentLinkCommand implements Command {
7376

7477
return vscode.workspace.openTextDocument(resource)
7578
.then(document => vscode.window.showTextDocument(document, column))
76-
.then(editor => this.tryRevealLine(editor, args.fragment));
79+
.then(editor => this.tryRevealLine(engine, editor, args.fragment));
7780
}
7881

79-
private getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
82+
private static getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
8083
const config = vscode.workspace.getConfiguration('markdown', resource);
8184
const openLinks = config.get<OpenMarkdownLinks>('links.openLocation', OpenMarkdownLinks.currentGroup);
8285
switch (openLinks) {
@@ -88,18 +91,22 @@ export class OpenDocumentLinkCommand implements Command {
8891
}
8992
}
9093

91-
private async tryRevealLine(editor: vscode.TextEditor, fragment?: string) {
94+
private static async tryRevealLine(engine: MarkdownEngine, editor: vscode.TextEditor, fragment?: string) {
9295
if (editor && fragment) {
93-
const toc = new TableOfContentsProvider(this.engine, editor.document);
96+
const toc = new TableOfContentsProvider(engine, editor.document);
9497
const entry = await toc.lookup(fragment);
9598
if (entry) {
96-
return editor.revealRange(new vscode.Range(entry.line, 0, entry.line, 0), vscode.TextEditorRevealType.AtTop);
99+
const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);
100+
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
101+
return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
97102
}
98103
const lineNumberFragment = fragment.match(/^L(\d+)$/i);
99104
if (lineNumberFragment) {
100105
const line = +lineNumberFragment[1] - 1;
101106
if (!isNaN(line)) {
102-
return editor.revealRange(new vscode.Range(line, 0, line, 0), vscode.TextEditorRevealType.AtTop);
107+
const lineStart = new vscode.Range(line, 0, line, 0);
108+
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
109+
return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
103110
}
104111
}
105112
}

extensions/markdown-language-features/src/extension.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import MarkdownWorkspaceSymbolProvider from './features/workspaceSymbolProvider'
1515
import { Logger } from './logger';
1616
import { MarkdownEngine } from './markdownEngine';
1717
import { getMarkdownExtensionContributions } from './markdownExtensions';
18-
import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector, ContentSecurityPolicyArbiter } from './security';
19-
import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter';
18+
import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security';
2019
import { githubSlugifier } from './slugify';
20+
import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter';
2121

2222

2323
export function activate(context: vscode.ExtensionContext) {
@@ -33,7 +33,7 @@ export function activate(context: vscode.ExtensionContext) {
3333

3434
const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger);
3535
const symbolProvider = new MDDocumentSymbolProvider(engine);
36-
const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions);
36+
const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, engine);
3737
context.subscriptions.push(previewManager);
3838

3939
context.subscriptions.push(registerMarkdownLanguageFeatures(symbolProvider, engine));

extensions/markdown-language-features/src/features/documentLinkProvider.ts

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ const localize = nls.loadMessageBundle();
1414
function parseLink(
1515
document: vscode.TextDocument,
1616
link: string,
17-
base: string
18-
): { uri: vscode.Uri, tooltip?: string } {
17+
): { uri: vscode.Uri, tooltip?: string } | undefined {
1918
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
2019
if (externalSchemeUri) {
2120
// Normalize VS Code links to target currently running version
@@ -29,24 +28,43 @@ function parseLink(
2928
// Use a fake scheme to avoid parse warnings
3029
const tempUri = vscode.Uri.parse(`vscode-resource:${link}`);
3130

32-
let resourcePath = tempUri.path;
33-
if (!tempUri.path && document.uri.scheme === 'file') {
34-
resourcePath = document.uri.path;
31+
let resourceUri: vscode.Uri | undefined;
32+
if (!tempUri.path) {
33+
resourceUri = document.uri;
3534
} else if (tempUri.path[0] === '/') {
36-
const root = vscode.workspace.getWorkspaceFolder(document.uri);
35+
const root = getWorkspaceFolder(document);
3736
if (root) {
38-
resourcePath = path.join(root.uri.fsPath, tempUri.path);
37+
resourceUri = vscode.Uri.joinPath(root, tempUri.path);
3938
}
4039
} else {
41-
resourcePath = base ? path.join(base, tempUri.path) : tempUri.path;
40+
if (document.uri.scheme === Schemes.untitled) {
41+
const root = getWorkspaceFolder(document);
42+
if (root) {
43+
resourceUri = vscode.Uri.joinPath(root, tempUri.path);
44+
}
45+
} else {
46+
const base = document.uri.with({ path: path.dirname(document.uri.fsPath) });
47+
resourceUri = vscode.Uri.joinPath(base, tempUri.path);
48+
}
49+
}
50+
51+
if (!resourceUri) {
52+
return undefined;
4253
}
4354

55+
resourceUri = resourceUri.with({ fragment: tempUri.fragment });
56+
4457
return {
45-
uri: OpenDocumentLinkCommand.createCommandUri(document.uri, resourcePath, tempUri.fragment),
58+
uri: OpenDocumentLinkCommand.createCommandUri(document.uri, resourceUri, tempUri.fragment),
4659
tooltip: localize('documentLink.tooltip', 'Follow link')
4760
};
4861
}
4962

63+
function getWorkspaceFolder(document: vscode.TextDocument) {
64+
return vscode.workspace.getWorkspaceFolder(document.uri)?.uri
65+
|| vscode.workspace.workspaceFolders?.[0]?.uri;
66+
}
67+
5068
function matchAll(
5169
pattern: RegExp,
5270
text: string
@@ -62,7 +80,6 @@ function matchAll(
6280

6381
function extractDocumentLink(
6482
document: vscode.TextDocument,
65-
base: string,
6683
pre: number,
6784
link: string,
6885
matchIndex: number | undefined
@@ -71,11 +88,14 @@ function extractDocumentLink(
7188
const linkStart = document.positionAt(offset);
7289
const linkEnd = document.positionAt(offset + link.length);
7390
try {
74-
const { uri, tooltip } = parseLink(document, link, base);
91+
const linkData = parseLink(document, link);
92+
if (!linkData) {
93+
return undefined;
94+
}
7595
const documentLink = new vscode.DocumentLink(
7696
new vscode.Range(linkStart, linkEnd),
77-
uri);
78-
documentLink.tooltip = tooltip;
97+
linkData.uri);
98+
documentLink.tooltip = linkData.tooltip;
7999
return documentLink;
80100
} catch (e) {
81101
return undefined;
@@ -91,27 +111,25 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
91111
document: vscode.TextDocument,
92112
_token: vscode.CancellationToken
93113
): vscode.DocumentLink[] {
94-
const base = document.uri.scheme === 'file' ? path.dirname(document.uri.fsPath) : '';
95114
const text = document.getText();
96115

97116
return [
98-
...this.providerInlineLinks(text, document, base),
99-
...this.provideReferenceLinks(text, document, base)
117+
...this.providerInlineLinks(text, document),
118+
...this.provideReferenceLinks(text, document)
100119
];
101120
}
102121

103122
private providerInlineLinks(
104123
text: string,
105124
document: vscode.TextDocument,
106-
base: string
107125
): vscode.DocumentLink[] {
108126
const results: vscode.DocumentLink[] = [];
109127
for (const match of matchAll(this.linkPattern, text)) {
110-
const matchImage = match[4] && extractDocumentLink(document, base, match[3].length + 1, match[4], match.index);
128+
const matchImage = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index);
111129
if (matchImage) {
112130
results.push(matchImage);
113131
}
114-
const matchLink = extractDocumentLink(document, base, match[1].length, match[5], match.index);
132+
const matchLink = extractDocumentLink(document, match[1].length, match[5], match.index);
115133
if (matchLink) {
116134
results.push(matchLink);
117135
}
@@ -122,7 +140,6 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
122140
private provideReferenceLinks(
123141
text: string,
124142
document: vscode.TextDocument,
125-
base: string
126143
): vscode.DocumentLink[] {
127144
const results: vscode.DocumentLink[] = [];
128145

@@ -159,8 +176,10 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
159176

160177
for (const definition of definitions.values()) {
161178
try {
162-
const { uri } = parseLink(document, definition.link, base);
163-
results.push(new vscode.DocumentLink(definition.linkRange, uri));
179+
const linkData = parseLink(document, definition.link);
180+
if (linkData) {
181+
results.push(new vscode.DocumentLink(definition.linkRange, linkData.uri));
182+
}
164183
} catch (e) {
165184
// noop
166185
}

extensions/markdown-language-features/src/features/preview.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as vscode from 'vscode';
76
import * as path from 'path';
8-
7+
import * as vscode from 'vscode';
8+
import * as nls from 'vscode-nls';
9+
import { OpenDocumentLinkCommand, resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
910
import { Logger } from '../logger';
10-
import { MarkdownContentProvider } from './previewContentProvider';
11+
import { MarkdownContributionProvider } from '../markdownExtensions';
1112
import { Disposable } from '../util/dispose';
12-
13-
import * as nls from 'vscode-nls';
13+
import { isMarkdownFile } from '../util/file';
14+
import { normalizeResource, WebviewResourceProvider } from '../util/resources';
1415
import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
1516
import { MarkdownPreviewConfigurationManager } from './previewConfig';
16-
import { MarkdownContributionProvider } from '../markdownExtensions';
17-
import { isMarkdownFile } from '../util/file';
18-
import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
19-
import { WebviewResourceProvider, normalizeResource } from '../util/resources';
17+
import { MarkdownContentProvider } from './previewContentProvider';
18+
import { MarkdownEngine } from '../markdownEngine';
19+
2020
const localize = nls.loadMessageBundle();
2121

2222
interface WebviewMessage {
@@ -123,6 +123,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
123123
resource: vscode.Uri,
124124
startingScroll: StartingScrollLocation | undefined,
125125
private readonly delegate: MarkdownPreviewDelegate,
126+
private readonly engine: MarkdownEngine,
126127
private readonly _contentProvider: MarkdownContentProvider,
127128
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
128129
private readonly _logger: Logger,
@@ -407,7 +408,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
407408
}
408409
}
409410

410-
vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource });
411+
OpenDocumentLinkCommand.execute(this.engine, { path: hrefPath, fragment, fromResource: this.resource.toJSON() });
411412
}
412413

413414
//#region WebviewResourceProvider
@@ -452,8 +453,9 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
452453
previewConfigurations: MarkdownPreviewConfigurationManager,
453454
logger: Logger,
454455
contributionProvider: MarkdownContributionProvider,
456+
engine: MarkdownEngine,
455457
): StaticMarkdownPreview {
456-
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider);
458+
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider, engine);
457459
}
458460

459461
private readonly preview: MarkdownPreview;
@@ -465,13 +467,14 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
465467
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
466468
logger: Logger,
467469
contributionProvider: MarkdownContributionProvider,
470+
engine: MarkdownEngine,
468471
) {
469472
super();
470473

471474
this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, undefined, {
472475
getAdditionalState: () => { return {}; },
473476
openPreviewLinkToMarkdownFile: () => { /* todo */ }
474-
}, contentProvider, _previewConfigurations, logger, contributionProvider));
477+
}, engine, contentProvider, _previewConfigurations, logger, contributionProvider));
475478

476479
this._register(this._webviewPanel.onDidDispose(() => {
477480
this.dispose();
@@ -548,9 +551,10 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow
548551
logger: Logger,
549552
topmostLineMonitor: TopmostLineMonitor,
550553
contributionProvider: MarkdownContributionProvider,
554+
engine: MarkdownEngine,
551555
): DynamicMarkdownPreview {
552556
return new DynamicMarkdownPreview(webview, input,
553-
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
557+
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine);
554558
}
555559

556560
public static create(
@@ -560,15 +564,16 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow
560564
previewConfigurations: MarkdownPreviewConfigurationManager,
561565
logger: Logger,
562566
topmostLineMonitor: TopmostLineMonitor,
563-
contributionProvider: MarkdownContributionProvider
567+
contributionProvider: MarkdownContributionProvider,
568+
engine: MarkdownEngine,
564569
): DynamicMarkdownPreview {
565570
const webview = vscode.window.createWebviewPanel(
566571
DynamicMarkdownPreview.viewType,
567572
DynamicMarkdownPreview.getPreviewTitle(input.resource, input.locked),
568573
previewColumn, { enableFindWidget: true, });
569574

570575
return new DynamicMarkdownPreview(webview, input,
571-
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider);
576+
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine);
572577
}
573578

574579
private constructor(
@@ -579,6 +584,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow
579584
private readonly _logger: Logger,
580585
private readonly _topmostLineMonitor: TopmostLineMonitor,
581586
private readonly _contributionProvider: MarkdownContributionProvider,
587+
private readonly _engine: MarkdownEngine,
582588
) {
583589
super();
584590

@@ -729,6 +735,7 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow
729735
this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined);
730736
}
731737
},
738+
this._engine,
732739
this._contentProvider,
733740
this._previewConfigurations,
734741
this._logger,

0 commit comments

Comments
 (0)