Markdown path completions tests use mocked out fs (#153045)

* Markdown path completions tests use mocked out fs

This updates the path completion tests to stop depending on the actual fs and instead use `IMdWorkspace`

* Update remaining tests
This commit is contained in:
Matt Bierner 2022-06-24 16:01:24 -07:00 committed by GitHub
parent 1b570248a5
commit 3b549009fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 200 additions and 85 deletions

View file

@ -51,7 +51,7 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(workspace, parser, tocProvider);
const contentProvider = new MdDocumentRenderer(engine, context, cspArbiter, contributions, logger);
const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, tocProvider);
const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider);
context.subscriptions.push(previewManager);
context.subscriptions.push(registerMarkdownLanguageFeatures(parser, workspace, commandManager, tocProvider, logger));
@ -88,7 +88,7 @@ function registerMarkdownLanguageFeatures(
registerFindFileReferenceSupport(commandManager, referencesProvider),
registerFoldingSupport(selector, parser, tocProvider),
registerPasteSupport(selector),
registerPathCompletionSupport(selector, parser, linkProvider),
registerPathCompletionSupport(selector, workspace, parser, linkProvider),
registerReferencesSupport(selector, referencesProvider),
registerRenameSupport(selector, workspace, referencesProvider, parser.slugifier),
registerSmartSelectSupport(selector, parser, tocProvider),

View file

@ -9,6 +9,8 @@ import { IMdParser } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { ITextDocument } from '../types/textDocument';
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
import { Schemes } from '../util/schemes';
import { IMdWorkspace } from '../workspace';
import { MdLinkProvider } from './documentLinks';
enum CompletionContextKind {
@ -87,6 +89,7 @@ function tryDecodeUriComponent(str: string): string {
export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProvider {
constructor(
private readonly workspace: IMdWorkspace,
private readonly parser: IMdParser,
private readonly linkProvider: MdLinkProvider,
) { }
@ -128,7 +131,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv
if (context.anchorInfo) { // Anchor to a different document
const rawUri = this.resolveReference(document, context.anchorInfo.beforeAnchor);
if (rawUri) {
const otherDoc = await resolveUriToMarkdownFile(rawUri);
const otherDoc = await resolveUriToMarkdownFile(this.workspace, rawUri);
if (otherDoc) {
const anchorStartPosition = position.translate({ characterDelta: -(context.anchorInfo.anchorPrefix.length + 1) });
const range = new vscode.Range(anchorStartPosition, position);
@ -284,13 +287,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv
const pathSegmentEnd = position.translate({ characterDelta: context.linkSuffix.length });
const replacementRange = new vscode.Range(pathSegmentStart, pathSegmentEnd);
let dirInfo: Array<[string, vscode.FileType]>;
try {
dirInfo = await vscode.workspace.fs.readDirectory(parentDir);
} catch {
return;
}
const dirInfo = await this.workspace.readDirectory(parentDir);
for (const [name, type] of dirInfo) {
// Exclude paths that start with `.`
if (name.startsWith('.')) {
@ -328,7 +325,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv
private resolvePath(root: vscode.Uri, ref: string): vscode.Uri | undefined {
try {
if (root.scheme === 'file') {
if (root.scheme === Schemes.file) {
return vscode.Uri.file(resolve(dirname(root.fsPath), ref));
} else {
return root.with({
@ -356,8 +353,9 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv
export function registerPathCompletionSupport(
selector: vscode.DocumentSelector,
workspace: IMdWorkspace,
parser: IMdParser,
linkProvider: MdLinkProvider,
): vscode.Disposable {
return vscode.languages.registerCompletionItemProvider(selector, new MdVsCodePathCompletionProvider(parser, linkProvider), '.', '/', '#');
return vscode.languages.registerCompletionItemProvider(selector, new MdVsCodePathCompletionProvider(workspace, parser, linkProvider), '.', '/', '#');
}

View file

@ -14,6 +14,7 @@ import { isMarkdownFile } from '../util/file';
import { openDocumentLink, resolveDocumentLink, resolveUriToMarkdownFile } from '../util/openDocumentLink';
import { WebviewResourceProvider } from '../util/resources';
import { urlToUri } from '../util/url';
import { IMdWorkspace } from '../workspace';
import { MdDocumentRenderer } from './documentRenderer';
import { MarkdownPreviewConfigurationManager } from './previewConfig';
import { scrollEditorToLine, StartingScrollFragment, StartingScrollLine, StartingScrollLocation } from './scrolling';
@ -118,6 +119,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
private readonly delegate: MarkdownPreviewDelegate,
private readonly _contentProvider: MdDocumentRenderer,
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
private readonly _workspace: IMdWorkspace,
private readonly _logger: ILogger,
private readonly _contributionProvider: MarkdownContributionProvider,
private readonly _tocProvider: MdTableOfContentsProvider,
@ -449,7 +451,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
const config = vscode.workspace.getConfiguration('markdown', this.resource);
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
if (openLinks === 'inPreview') {
const linkedDoc = await resolveUriToMarkdownFile(targetResource);
const linkedDoc = await resolveUriToMarkdownFile(this._workspace, targetResource);
if (linkedDoc) {
this.delegate.openPreviewLinkToMarkdownFile(linkedDoc.uri, targetResource.fragment);
return;
@ -502,12 +504,13 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow
contentProvider: MdDocumentRenderer,
previewConfigurations: MarkdownPreviewConfigurationManager,
topmostLineMonitor: TopmostLineMonitor,
workspace: IMdWorkspace,
logger: ILogger,
contributionProvider: MarkdownContributionProvider,
tocProvider: MdTableOfContentsProvider,
scrollLine?: number,
): StaticMarkdownPreview {
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, tocProvider, scrollLine);
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, workspace, logger, contributionProvider, tocProvider, scrollLine);
}
private readonly preview: MarkdownPreview;
@ -518,6 +521,7 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow
contentProvider: MdDocumentRenderer,
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
topmostLineMonitor: TopmostLineMonitor,
workspace: IMdWorkspace,
logger: ILogger,
contributionProvider: MarkdownContributionProvider,
tocProvider: MdTableOfContentsProvider,
@ -532,7 +536,7 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow
fragment
}), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn);
}
}, contentProvider, _previewConfigurations, logger, contributionProvider, tocProvider));
}, contentProvider, _previewConfigurations, workspace, logger, contributionProvider, tocProvider));
this._register(this._webviewPanel.onDidDispose(() => {
this.dispose();
@ -613,6 +617,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
webview: vscode.WebviewPanel,
contentProvider: MdDocumentRenderer,
previewConfigurations: MarkdownPreviewConfigurationManager,
workspace: IMdWorkspace,
logger: ILogger,
topmostLineMonitor: TopmostLineMonitor,
contributionProvider: MarkdownContributionProvider,
@ -621,7 +626,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
webview.iconPath = contentProvider.iconPath;
return new DynamicMarkdownPreview(webview, input,
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, tocProvider);
contentProvider, previewConfigurations, workspace, logger, topmostLineMonitor, contributionProvider, tocProvider);
}
public static create(
@ -629,6 +634,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
previewColumn: vscode.ViewColumn,
contentProvider: MdDocumentRenderer,
previewConfigurations: MarkdownPreviewConfigurationManager,
workspace: IMdWorkspace,
logger: ILogger,
topmostLineMonitor: TopmostLineMonitor,
contributionProvider: MarkdownContributionProvider,
@ -642,7 +648,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
webview.iconPath = contentProvider.iconPath;
return new DynamicMarkdownPreview(webview, input,
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, tocProvider);
contentProvider, previewConfigurations, workspace, logger, topmostLineMonitor, contributionProvider, tocProvider);
}
private constructor(
@ -650,6 +656,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
input: DynamicPreviewInput,
private readonly _contentProvider: MdDocumentRenderer,
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
private readonly _workspace: IMdWorkspace,
private readonly _logger: ILogger,
private readonly _topmostLineMonitor: TopmostLineMonitor,
private readonly _contributionProvider: MarkdownContributionProvider,
@ -807,6 +814,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
},
this._contentProvider,
this._previewConfigurations,
this._workspace,
this._logger,
this._contributionProvider,
this._tocProvider);

View file

@ -9,6 +9,7 @@ import { MarkdownContributionProvider } from '../markdownExtensions';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { Disposable, disposeAll } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { IMdWorkspace } from '../workspace';
import { MdDocumentRenderer } from './documentRenderer';
import { DynamicMarkdownPreview, IManagedMarkdownPreview, StaticMarkdownPreview } from './preview';
import { MarkdownPreviewConfigurationManager } from './previewConfig';
@ -69,6 +70,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
public constructor(
private readonly _contentProvider: MdDocumentRenderer,
private readonly _workspace: IMdWorkspace,
private readonly _logger: ILogger,
private readonly _contributions: MarkdownContributionProvider,
private readonly _tocProvider: MdTableOfContentsProvider,
@ -163,6 +165,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
webview,
this._contentProvider,
this._previewConfigurations,
this._workspace,
this._logger,
this._topmostLineMonitor,
this._contributions,
@ -182,6 +185,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
this._contentProvider,
this._previewConfigurations,
this._topmostLineMonitor,
this._workspace,
this._logger,
this._contributions,
this._tocProvider,
@ -206,6 +210,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
previewSettings.previewColumn,
this._contentProvider,
this._previewConfigurations,
this._workspace,
this._logger,
this._topmostLineMonitor,
this._contributions,

View file

@ -35,6 +35,19 @@ export class InMemoryMdWorkspace implements IMdWorkspace {
return this._documents.has(resource);
}
public async readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]> {
const files = new Map<string, vscode.FileType>();
const pathPrefix = resource.fsPath + (resource.fsPath.endsWith('/') ? '' : '/');
for (const doc of this._documents.values()) {
const path = doc.uri.fsPath;
if (path.startsWith(pathPrefix)) {
const parts = path.slice(pathPrefix.length).split('/');
files.set(parts[0], parts.length > 1 ? vscode.FileType.Directory : vscode.FileType.File);
}
}
return Array.from(files.entries());
}
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<ITextDocument>();
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;

View file

@ -10,36 +10,45 @@ import { MdLinkProvider } from '../languageFeatures/documentLinks';
import { MdVsCodePathCompletionProvider } from '../languageFeatures/pathCompletions';
import { noopToken } from '../util/cancellation';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { CURSOR, getCursorPositions, joinLines, workspacePath } from './util';
function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string) {
async function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string, workspace?: IMdWorkspace) {
const doc = new InMemoryDocument(resource, fileContents);
const workspace = new InMemoryMdWorkspace([doc]);
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine, workspace, nulLogger);
const provider = new MdVsCodePathCompletionProvider(engine, linkProvider);
const ws = workspace ?? new InMemoryMdWorkspace([doc]);
const linkProvider = new MdLinkProvider(engine, ws, nulLogger);
const provider = new MdVsCodePathCompletionProvider(ws, engine, linkProvider);
const cursorPositions = getCursorPositions(fileContents, doc);
return provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
const completions = await provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
triggerCharacter: undefined,
triggerKind: vscode.CompletionTriggerKind.Invoke,
});
return completions.sort((a, b) => (a.label as string).localeCompare(b.label as string));
}
suite('Markdown path completion provider', () => {
function assertCompletionsEqual(actual: readonly vscode.CompletionItem[], expected: readonly { label: string; insertText?: string }[]) {
assert.strictEqual(actual.length, expected.length, 'Completion counts should be equal');
setup(async () => {
// These tests assume that the markdown completion provider is already registered
await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate();
});
for (let i = 0; i < actual.length; ++i) {
assert.strictEqual(actual[i].label, expected[i].label, `Completion labels ${i} should be equal`);
if (typeof expected[i].insertText !== 'undefined') {
assert.strictEqual(actual[i].insertText, expected[i].insertText, `Completion insert texts ${i} should be equal`);
}
}
}
suite('Markdown: Path completions', () => {
test('Should not return anything when triggered in empty doc', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), `${CURSOR}`);
assert.strictEqual(completions.length, 0);
assertCompletionsEqual(completions, []);
});
test('Should return anchor completions', async () => {
@ -50,9 +59,10 @@ suite('Markdown path completion provider', () => {
`# x y Z`,
));
assert.strictEqual(completions.length, 2);
assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has a-b-c anchor completion');
assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has x-y-z anchor completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: '#x-y-z' },
]);
});
test('Should not return suggestions for http links', async () => {
@ -64,53 +74,87 @@ suite('Markdown path completion provider', () => {
`# https:`,
));
assert.strictEqual(completions.length, 0);
assertCompletionsEqual(completions, []);
});
test('Should return relative path suggestions', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/foo.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](${CURSOR}`,
``,
`# A b C`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should return relative path suggestions using ./', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/foo.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./${CURSOR}`,
``,
`# A b C`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assertCompletionsEqual(completions, [
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should return absolute path suggestions using /', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/c.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[](/${CURSOR}`,
``,
`# A b C`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assert.ok(!completions.some(x => x.label === 'c.md'), 'Should not have c.md from sub folder');
assertCompletionsEqual(completions, [
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should return anchor suggestions in other file', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('b.md'), joinLines(
`# b`,
``,
`[./a](./a)`,
``,
`# header1`,
)),
]);
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[](/b.md#${CURSOR}`,
));
), workspace);
assert.ok(completions.some(x => x.label === '#b'), 'Has #b header completion');
assert.ok(completions.some(x => x.label === '#header1'), 'Has #header1 header completion');
assertCompletionsEqual(completions, [
{ label: '#b' },
{ label: '#header1' },
]);
});
test('Should reference links for current file', async () => {
@ -121,9 +165,10 @@ suite('Markdown path completion provider', () => {
`[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');
assertCompletionsEqual(completions, [
{ label: 'ref-1' },
{ label: 'ref-2' },
]);
});
test('Should complete headers in link definitions', async () => {
@ -133,67 +178,118 @@ suite('Markdown path completion provider', () => {
`[ref-1]: ${CURSOR}`,
));
assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has #a-b-c header completion');
assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has #x-y-z header completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: '#x-y-z' },
{ label: 'new.md' },
]);
});
test('Should complete relative paths in link definitions', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/c.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`# a B c`,
`[ref-1]: ${CURSOR}`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should escape spaces in path names', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/file with space.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./sub/${CURSOR})`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file%20with%20space.md'), 'Has encoded path completion');
assertCompletionsEqual(completions, [
{ label: 'file with space.md', insertText: 'file%20with%20space.md' },
]);
});
test('Should support completions on angle bracket path with spaces', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('sub with space/a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](</sub with space/${CURSOR}`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has path completion');
assertCompletionsEqual(completions, [
{ label: 'a.md', insertText: 'a.md' },
]);
});
test('Should not escape spaces in path names that use angle brackets', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('sub/file with space.md'), ''),
]);
{
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](<./sub/${CURSOR}`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file with space.md'), 'Has encoded path completion');
assertCompletionsEqual(completions, [
{ label: 'file with space.md', insertText: 'file with space.md' },
]);
}
{
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](<./sub/${CURSOR}>`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file with space.md'), 'Has encoded path completion');
assertCompletionsEqual(completions, [
{ label: 'file with space.md', insertText: 'file with space.md' },
]);
}
});
test('Should complete paths for path with encoded spaces', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./sub%20with%20space/${CURSOR})`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space');
assertCompletionsEqual(completions, [
{ label: 'file.md', insertText: 'file.md' },
]);
});
test('Should complete definition path for path with encoded spaces', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[def]: ./sub%20with%20space/${CURSOR}`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space');
assertCompletionsEqual(completions, [
{ label: 'file.md', insertText: 'file.md' },
]);
});
});

View file

@ -7,6 +7,8 @@ import * as path from 'path';
import * as vscode from 'vscode';
import * as uri from 'vscode-uri';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { ITextDocument } from '../types/textDocument';
import { IMdWorkspace } from '../workspace';
import { isMarkdownFile } from './file';
export interface OpenDocumentLinkArgs {
@ -154,9 +156,9 @@ function tryRevealLineUsingLineFragment(editor: vscode.TextEditor, fragment: str
return false;
}
export async function resolveUriToMarkdownFile(resource: vscode.Uri): Promise<vscode.TextDocument | undefined> {
export async function resolveUriToMarkdownFile(workspace: IMdWorkspace, resource: vscode.Uri): Promise<ITextDocument | undefined> {
try {
const doc = await tryResolveUriToMarkdownFile(resource);
const doc = await workspace.getOrLoadMarkdownDocument(resource);
if (doc) {
return doc;
}
@ -166,21 +168,8 @@ export async function resolveUriToMarkdownFile(resource: vscode.Uri): Promise<vs
// If no extension, try with `.md` extension
if (uri.Utils.extname(resource) === '') {
return tryResolveUriToMarkdownFile(resource.with({ path: resource.path + '.md' }));
return workspace.getOrLoadMarkdownDocument(resource.with({ path: resource.path + '.md' }));
}
return undefined;
}
async function tryResolveUriToMarkdownFile(resource: vscode.Uri): Promise<vscode.TextDocument | undefined> {
let document: vscode.TextDocument;
try {
document = await vscode.workspace.openTextDocument(resource);
} catch {
return undefined;
}
if (isMarkdownFile(document)) {
return document;
}
return undefined;
}

View file

@ -30,6 +30,8 @@ export interface IMdWorkspace {
pathExists(resource: vscode.Uri): Promise<boolean>;
readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]>;
readonly onDidChangeMarkdownDocument: vscode.Event<ITextDocument>;
readonly onDidCreateMarkdownDocument: vscode.Event<ITextDocument>;
readonly onDidDeleteMarkdownDocument: vscode.Event<vscode.Uri>;
@ -189,4 +191,8 @@ export class VsCodeMdWorkspace extends Disposable implements IMdWorkspace {
}
return targetResourceStat.type === vscode.FileType.File || targetResourceStat.type === vscode.FileType.Directory;
}
public async readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]> {
return vscode.workspace.fs.readDirectory(resource);
}
}