mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
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:
parent
1b570248a5
commit
3b549009fe
|
@ -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),
|
||||
|
|
|
@ -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), '.', '/', '#');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue