Merge branch 'ads-main-vscode-2020-07-15T23-51-12' into main

This commit is contained in:
cssuh
2020-07-17 15:00:28 -04:00
558 changed files with 15180 additions and 8232 deletions

View File

@@ -11,6 +11,21 @@ html, body {
word-wrap: break-word;
}
body {
padding-top: 1em;
}
/* Reset margin top for elements */
h1, h2, h3, h4, h5, h6,
p, ol, ul, pre {
margin-top: 0;
}
h2, h3, h4, h5, h6 {
font-weight: normal;
margin-bottom: 0.2em;
}
#code-csp-warning {
position: fixed;
top: 0;
@@ -112,6 +127,20 @@ textarea:focus {
outline-offset: -1px;
}
p {
margin-bottom: 1.5em;
}
/* don't space 2 paragraphs too far apart */
p + p {
margin-top: -0.8em;
}
ul,
ol {
margin-bottom: 1.5em;
}
hr {
border: 0;
height: 2px;
@@ -123,9 +152,6 @@ h1 {
line-height: 1.2;
border-bottom-width: 1px;
border-bottom-style: solid;
}
h1, h2, h3 {
font-weight: normal;
}

View File

@@ -79,7 +79,7 @@
"editor/title": [
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"when": "editorLangId == markdown && !notebookEditorFocused",
"alt": "markdown.showPreview",
"group": "navigation"
},
@@ -115,23 +115,23 @@
{
"command": "markdown.showPreview",
"when": "resourceLangId == markdown",
"group": "navigation"
"group": "1_open"
}
],
"commandPalette": [
{
"command": "markdown.showPreview",
"when": "editorLangId == markdown",
"when": "editorLangId == markdown && !notebookEditorFocused",
"group": "navigation"
},
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"when": "editorLangId == markdown && !notebookEditorFocused",
"group": "navigation"
},
{
"command": "markdown.showLockedPreviewToSide",
"when": "editorLangId == markdown",
"when": "editorLangId == markdown && !notebookEditorFocused",
"group": "navigation"
},
{
@@ -141,7 +141,7 @@
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "editorLangId == markdown"
"when": "editorLangId == markdown && !notebookEditorFocused"
},
{
"command": "markdown.showPreviewSecuritySelector",
@@ -153,7 +153,7 @@
},
{
"command": "markdown.preview.refresh",
"when": "editorLangId == markdown"
"when": "editorLangId == markdown && !notebookEditorFocused"
},
{
"command": "markdown.preview.refresh",
@@ -166,13 +166,13 @@
"command": "markdown.showPreview",
"key": "shift+ctrl+v",
"mac": "shift+cmd+v",
"when": "editorLangId == markdown"
"when": "editorLangId == markdown && !notebookEditorFocused"
},
{
"command": "markdown.showPreviewToSide",
"key": "ctrl+k v",
"mac": "cmd+k v",
"when": "editorLangId == markdown"
"when": "editorLangId == markdown && !notebookEditorFocused"
}
],
"configuration": {

View File

@@ -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,22 @@ export class OpenDocumentLinkCommand implements Command {
public static createCommandUri(
fromResource: vscode.Uri,
path: string,
path: vscode.Uri,
fragment: string,
): vscode.Uri {
const toJson = (uri: vscode.Uri) => {
return {
scheme: uri.scheme,
authority: uri.authority,
path: uri.path,
fragment: uri.fragment,
query: uri.query,
};
};
return vscode.Uri.parse(`command:${OpenDocumentLinkCommand.id}?${encodeURIComponent(JSON.stringify(<OpenDocumentLinkArgs>{
path: encodeURIComponent(path),
path: toJson(path),
fragment,
fromResource: encodeURIComponent(fromResource.toString(true)),
fromResource: toJson(fromResource),
}))}`);
}
@@ -43,26 +52,29 @@ export class OpenDocumentLinkCommand implements Command {
private readonly engine: MarkdownEngine
) { }
public execute(args: OpenDocumentLinkArgs) {
const fromResource = vscode.Uri.parse(decodeURIComponent(args.fromResource));
const targetPath = decodeURIComponent(args.path);
const column = this.getViewColumn(fromResource);
return this.tryOpen(targetPath, args, column).catch(() => {
if (targetPath && extname(targetPath) === '') {
return this.tryOpen(targetPath + '.md', args, column);
}
const targetResource = vscode.Uri.file(targetPath);
return Promise.resolve(undefined)
.then(() => vscode.commands.executeCommand('vscode.open', targetResource, column))
.then(() => undefined);
});
public async execute(args: OpenDocumentLinkArgs) {
return OpenDocumentLinkCommand.execute(this.engine, args);
}
private async tryOpen(path: string, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) {
const resource = vscode.Uri.file(path);
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(engine, targetResource, args, column);
} catch {
if (extname(targetResource.path) === '') {
return this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column);
}
await vscode.commands.executeCommand('vscode.open', targetResource, column);
return undefined;
}
}
private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn) {
if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
if (!path || vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) {
return this.tryRevealLine(vscode.window.activeTextEditor, args.fragment);
if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) {
return this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment);
}
}
@@ -73,10 +85,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<OpenMarkdownLinks>('links.openLocation', OpenMarkdownLinks.currentGroup);
switch (openLinks) {
@@ -88,18 +100,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);
}
}
}

View File

@@ -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));

View File

@@ -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
}

View File

@@ -3,20 +3,20 @@
* Licensed under the Source EULA. 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();
@@ -612,7 +618,12 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow
}));
this._register(vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor && isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) {
// Only allow previewing normal text editors which have a viewColumn: See #101514
if (typeof editor?.viewColumn === 'undefined') {
return;
}
if (isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) {
const line = getVisibleLine(editor);
this.update(editor.document.uri, line ? new StartingScrollLine(line) : undefined);
}
@@ -724,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,

View File

@@ -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;

View File

@@ -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 {

View File

@@ -0,0 +1,145 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { joinLines } from './util';
const testFileA = workspaceFile('a.md');
function workspaceFile(...segments: string[]) {
return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
}
async function getLinksForFile(file: vscode.Uri): Promise<vscode.DocumentLink[]> {
return (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file))!;
}
suite('Markdown Document links', () => {
teardown(async () => {
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
test('Should navigate to markdown file', async () => {
await withFileContents(testFileA, '[b](b.md)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('b.md'));
});
test('Should navigate to markdown file with leading ./', async () => {
await withFileContents(testFileA, '[b](./b.md)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('b.md'));
});
test('Should navigate to markdown file with leading /', async () => {
await withFileContents(testFileA, '[b](./b.md)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('b.md'));
});
test('Should navigate to markdown file without file extension', async () => {
await withFileContents(testFileA, '[b](b)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('b.md'));
});
test('Should navigate to markdown file in directory', async () => {
await withFileContents(testFileA, '[b](sub/c)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('sub', 'c.md'));
});
test('Should navigate to fragment by title in file', async () => {
await withFileContents(testFileA, '[b](sub/c#second)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('sub', 'c.md'));
assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1);
});
test('Should navigate to fragment by line', async () => {
await withFileContents(testFileA, '[b](sub/c#L2)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('sub', 'c.md'));
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);
});
});
function assertActiveDocumentUri(expectedUri: vscode.Uri) {
assert.strictEqual(
vscode.window.activeTextEditor!.document.uri.fsPath,
expectedUri.fsPath
);
}
async function withFileContents(file: vscode.Uri, contents: string): Promise<void> {
const document = await vscode.workspace.openTextDocument(file);
const editor = await vscode.window.showTextDocument(document);
await editor.edit(edit => {
edit.replace(new vscode.Range(0, 0, 1000, 0), contents);
});
}
async function executeLink(link: vscode.DocumentLink) {
const args = JSON.parse(decodeURIComponent(link.target!.query));
await vscode.commands.executeCommand(link.target!.path, args);
}

View File

@@ -10,7 +10,7 @@ import LinkProvider from '../features/documentLinkProvider';
import { InMemoryDocument } from './inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');
const testFile = vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, 'x.md');
const noopToken = new class implements vscode.CancellationToken {
private _onCancellationRequestedEmitter = new vscode.EventEmitter<void>();
@@ -20,7 +20,7 @@ const noopToken = new class implements vscode.CancellationToken {
};
function getLinksForFile(fileContents: string) {
const doc = new InMemoryDocument(testFileName, fileContents);
const doc = new InMemoryDocument(testFile, fileContents);
const provider = new LinkProvider();
return provider.provideDocumentLinks(doc, noopToken);
}
@@ -118,24 +118,24 @@ suite('markdown.DocumentLinkProvider', () => {
const links = getLinksForFile('[![alt text](image.jpg)](https://example.com)');
assert.strictEqual(links.length, 2);
const [link1, link2] = links;
assertRangeEqual(link1.range, new vscode.Range(0,13,0,22));
assertRangeEqual(link2.range, new vscode.Range(0,25,0,44));
assertRangeEqual(link1.range, new vscode.Range(0, 13, 0, 22));
assertRangeEqual(link2.range, new vscode.Range(0, 25, 0, 44));
}
{
const links = getLinksForFile('[![a]( whitespace.jpg )]( https://whitespace.com )');
assert.strictEqual(links.length, 2);
const [link1, link2] = links;
assertRangeEqual(link1.range, new vscode.Range(0,7,0,21));
assertRangeEqual(link2.range, new vscode.Range(0,26,0,48));
assertRangeEqual(link1.range, new vscode.Range(0, 7, 0, 21));
assertRangeEqual(link2.range, new vscode.Range(0, 26, 0, 48));
}
{
const links = getLinksForFile('[![a](img1.jpg)](file1.txt) text [![a](img2.jpg)](file2.txt)');
assert.strictEqual(links.length, 4);
const [link1, link2, link3, link4] = links;
assertRangeEqual(link1.range, new vscode.Range(0,6,0,14));
assertRangeEqual(link2.range, new vscode.Range(0,17,0,26));
assertRangeEqual(link3.range, new vscode.Range(0,39,0,47));
assertRangeEqual(link4.range, new vscode.Range(0,50,0,59));
assertRangeEqual(link1.range, new vscode.Range(0, 6, 0, 14));
assertRangeEqual(link2.range, new vscode.Range(0, 17, 0, 26));
assertRangeEqual(link3.range, new vscode.Range(0, 39, 0, 47));
assertRangeEqual(link4.range, new vscode.Range(0, 50, 0, 59));
}
});
});

View File

@@ -1 +0,0 @@
DO NOT DELETE, USED BY INTEGRATION TESTS

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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');

View File

@@ -9,6 +9,7 @@ export const Schemes = {
http: 'http:',
https: 'https:',
file: 'file:',
untitled: 'untitled',
mailto: 'mailto:',
data: 'data:',
vscode: 'vscode:',

View File

@@ -0,0 +1,4 @@
[b](b)
[b](b.md)
[b](./b.md)
[b](/b.md)

View File

@@ -0,0 +1,3 @@
# b
[](./a)

View File

@@ -0,0 +1,6 @@
# First
# Second
[b](/b.md)
[b](../b.md)
[b](./../b.md)