mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
@@ -11,6 +11,7 @@ import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvi
|
||||
import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor';
|
||||
import { MdFoldingProvider } from './languageFeatures/foldingProvider';
|
||||
import { MdPathCompletionProvider } from './languageFeatures/pathCompletions';
|
||||
import { MdReferencesProvider } from './languageFeatures/references';
|
||||
import { MdSmartSelect } from './languageFeatures/smartSelect';
|
||||
import { MdWorkspaceSymbolProvider } from './languageFeatures/workspaceSymbolProvider';
|
||||
import { Logger } from './logger';
|
||||
@@ -58,14 +59,15 @@ function registerMarkdownLanguageFeatures(
|
||||
const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' };
|
||||
|
||||
const linkProvider = new MdLinkProvider(engine);
|
||||
const w = new VsCodeMdWorkspaceContents();
|
||||
const workspaceContents = new VsCodeMdWorkspaceContents();
|
||||
|
||||
return vscode.Disposable.from(
|
||||
vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
|
||||
vscode.languages.registerDocumentLinkProvider(selector, linkProvider),
|
||||
vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine)),
|
||||
vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine)),
|
||||
vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, w)),
|
||||
vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, workspaceContents)),
|
||||
vscode.languages.registerReferenceProvider(selector, new MdReferencesProvider(linkProvider, workspaceContents, engine)),
|
||||
MdPathCompletionProvider.register(selector, engine, linkProvider),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,22 +9,38 @@ import * as uri from 'vscode-uri';
|
||||
import { OpenDocumentLinkCommand } from '../commands/openDocumentLink';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { getUriForLinkWithKnownExternalScheme, isOfScheme, Schemes } from '../util/schemes';
|
||||
import { SkinnyTextDocument } from '../workspaceContents';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
function parseLink(
|
||||
document: vscode.TextDocument,
|
||||
link: string,
|
||||
): { uri: vscode.Uri; tooltip?: string } | undefined {
|
||||
export interface ExternalLinkTarget {
|
||||
readonly kind: 'external';
|
||||
readonly uri: vscode.Uri;
|
||||
}
|
||||
|
||||
export interface InternalLinkTarget {
|
||||
readonly kind: 'internal';
|
||||
|
||||
readonly fromResource: vscode.Uri;
|
||||
readonly path: vscode.Uri;
|
||||
readonly fragment: string;
|
||||
}
|
||||
|
||||
export type LinkTarget = ExternalLinkTarget | InternalLinkTarget;
|
||||
|
||||
|
||||
function parseLink(
|
||||
document: SkinnyTextDocument,
|
||||
link: string,
|
||||
): LinkTarget | undefined {
|
||||
const cleanLink = stripAngleBrackets(link);
|
||||
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(cleanLink);
|
||||
if (externalSchemeUri) {
|
||||
// Normalize VS Code links to target currently running version
|
||||
if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
|
||||
return { uri: vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }) };
|
||||
return { kind: 'external', uri: vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }) };
|
||||
}
|
||||
return { uri: externalSchemeUri };
|
||||
return { kind: 'external', uri: externalSchemeUri };
|
||||
}
|
||||
|
||||
// Assume it must be an relative or absolute file path
|
||||
@@ -58,36 +74,42 @@ function parseLink(
|
||||
resourceUri = resourceUri.with({ fragment: tempUri.fragment });
|
||||
|
||||
return {
|
||||
uri: OpenDocumentLinkCommand.createCommandUri(document.uri, resourceUri, tempUri.fragment),
|
||||
tooltip: localize('documentLink.tooltip', 'Follow link')
|
||||
kind: 'internal',
|
||||
fromResource: document.uri,
|
||||
path: resourceUri,
|
||||
fragment: tempUri.fragment,
|
||||
};
|
||||
}
|
||||
|
||||
function getWorkspaceFolder(document: vscode.TextDocument) {
|
||||
function getWorkspaceFolder(document: SkinnyTextDocument) {
|
||||
return vscode.workspace.getWorkspaceFolder(document.uri)?.uri
|
||||
|| vscode.workspace.workspaceFolders?.[0]?.uri;
|
||||
}
|
||||
|
||||
export interface LinkData {
|
||||
readonly target: LinkTarget;
|
||||
readonly sourceRange: vscode.Range;
|
||||
}
|
||||
|
||||
function extractDocumentLink(
|
||||
document: vscode.TextDocument,
|
||||
document: SkinnyTextDocument,
|
||||
pre: number,
|
||||
link: string,
|
||||
matchIndex: number | undefined
|
||||
): vscode.DocumentLink | undefined {
|
||||
): LinkData | undefined {
|
||||
const offset = (matchIndex || 0) + pre;
|
||||
const linkStart = document.positionAt(offset);
|
||||
const linkEnd = document.positionAt(offset + link.length);
|
||||
try {
|
||||
const linkData = parseLink(document, link);
|
||||
if (!linkData) {
|
||||
const linkTarget = parseLink(document, link);
|
||||
if (!linkTarget) {
|
||||
return undefined;
|
||||
}
|
||||
const documentLink = new vscode.DocumentLink(
|
||||
new vscode.Range(linkStart, linkEnd),
|
||||
linkData.uri);
|
||||
documentLink.tooltip = linkData.tooltip;
|
||||
return documentLink;
|
||||
} catch (e) {
|
||||
return {
|
||||
target: linkTarget,
|
||||
sourceRange: new vscode.Range(linkStart, linkEnd)
|
||||
};
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -132,7 +154,7 @@ interface CodeInDocument {
|
||||
readonly inline: readonly vscode.Range[];
|
||||
}
|
||||
|
||||
async function findCode(document: vscode.TextDocument, engine: MarkdownEngine): Promise<CodeInDocument> {
|
||||
async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): Promise<CodeInDocument> {
|
||||
const tokens = await engine.parse(document);
|
||||
const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence') && !!t.map).map(t => t.map) as [number, number][];
|
||||
|
||||
@@ -145,9 +167,21 @@ async function findCode(document: vscode.TextDocument, engine: MarkdownEngine):
|
||||
return { multiline, inline };
|
||||
}
|
||||
|
||||
function isLinkInsideCode(code: CodeInDocument, link: vscode.DocumentLink) {
|
||||
return code.multiline.some(interval => link.range.start.line >= interval[0] && link.range.start.line < interval[1]) ||
|
||||
code.inline.some(position => position.intersection(link.range));
|
||||
function isLinkInsideCode(code: CodeInDocument, link: LinkData) {
|
||||
return code.multiline.some(interval => link.sourceRange.start.line >= interval[0] && link.sourceRange.start.line < interval[1]) ||
|
||||
code.inline.some(position => position.intersection(link.sourceRange));
|
||||
}
|
||||
|
||||
function createDocumentLink(sourceRange: vscode.Range, target: LinkTarget) {
|
||||
if (target.kind === 'external') {
|
||||
return new vscode.DocumentLink(sourceRange, target.uri);
|
||||
} else {
|
||||
|
||||
const uri = OpenDocumentLinkCommand.createCommandUri(target.fromResource, target.path, target.fragment);
|
||||
const documentLink = new vscode.DocumentLink(sourceRange, uri);
|
||||
documentLink.tooltip = localize('documentLink.tooltip', 'Follow link');
|
||||
return documentLink;
|
||||
}
|
||||
}
|
||||
|
||||
export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
||||
@@ -160,23 +194,24 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.DocumentLink[]> {
|
||||
const text = document.getText();
|
||||
const inlineLinks = await this.getInlineLinks(text, document);
|
||||
return [
|
||||
...(await this.getInlineLinks(text, document)),
|
||||
...inlineLinks.map(data => createDocumentLink(data.sourceRange, data.target)),
|
||||
...this.getReferenceLinks(text, document)
|
||||
];
|
||||
}
|
||||
|
||||
private async getInlineLinks(text: string, document: vscode.TextDocument): Promise<vscode.DocumentLink[]> {
|
||||
const results: vscode.DocumentLink[] = [];
|
||||
public async getInlineLinks(text: string, document: SkinnyTextDocument): Promise<LinkData[]> {
|
||||
const results: LinkData[] = [];
|
||||
const codeInDocument = await findCode(document, this.engine);
|
||||
for (const match of text.matchAll(linkPattern)) {
|
||||
const matchImage = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index);
|
||||
if (matchImage && !isLinkInsideCode(codeInDocument, matchImage)) {
|
||||
results.push(matchImage);
|
||||
const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index);
|
||||
if (matchImageData && !isLinkInsideCode(codeInDocument, matchImageData)) {
|
||||
results.push(matchImageData);
|
||||
}
|
||||
const matchLink = extractDocumentLink(document, match[1].length, match[5], match.index);
|
||||
if (matchLink && !isLinkInsideCode(codeInDocument, matchLink)) {
|
||||
results.push(matchLink);
|
||||
const matchLinkData = extractDocumentLink(document, match[1].length, match[5], match.index);
|
||||
if (matchLinkData && !isLinkInsideCode(codeInDocument, matchLinkData)) {
|
||||
results.push(matchLinkData);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
@@ -216,9 +251,9 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
|
||||
|
||||
for (const definition of definitions.values()) {
|
||||
try {
|
||||
const linkData = parseLink(document, definition.link);
|
||||
if (linkData) {
|
||||
yield new vscode.DocumentLink(definition.linkRange, linkData.uri);
|
||||
const target = parseLink(document, definition.link);
|
||||
if (target) {
|
||||
yield createDocumentLink(definition.linkRange, target);
|
||||
}
|
||||
} catch (e) {
|
||||
// noop
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContents } from '../tableOfContents';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
import { InternalLinkTarget, LinkData, MdLinkProvider } from './documentLinkProvider';
|
||||
import { MdWorkspaceCache } from './workspaceCache';
|
||||
|
||||
|
||||
export class MdReferencesProvider extends Disposable implements vscode.ReferenceProvider {
|
||||
|
||||
private readonly _linkCache: MdWorkspaceCache<Promise<LinkData[]>>;
|
||||
|
||||
public constructor(
|
||||
linkProvider: MdLinkProvider,
|
||||
workspaceContents: MdWorkspaceContents,
|
||||
private readonly engine: MarkdownEngine,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkProvider.getInlineLinks(doc.getText(), doc)));
|
||||
}
|
||||
|
||||
async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
|
||||
const toc = await TableOfContents.create(this.engine, document);
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const header = toc.entries.find(entry => entry.line === position.line);
|
||||
if (!header) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const links = await Promise.all(await this._linkCache.getAll());
|
||||
return links
|
||||
.flat()
|
||||
.filter(link => {
|
||||
return link.target.kind === 'internal'
|
||||
&& link.target.path.fsPath === document.uri.fsPath
|
||||
&& link.target.fragment === header.slug.value;
|
||||
})
|
||||
.map(link => {
|
||||
const target = link.target as InternalLinkTarget;
|
||||
return new vscode.Location(target.fromResource, link.sourceRange);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { Lazy, lazy } from '../util/lazy';
|
||||
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
||||
|
||||
/**
|
||||
* Cache of information for markdown files in the workspace.
|
||||
*/
|
||||
export class MdWorkspaceCache<T> extends Disposable {
|
||||
|
||||
private readonly _cache = new Map<string, Lazy<T>>();
|
||||
private _hasPopulatedCache = false;
|
||||
|
||||
public constructor(
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly getValue: (document: SkinnyTextDocument) => T,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getAll(): Promise<T[]> {
|
||||
if (!this._hasPopulatedCache) {
|
||||
await this.populateSymbolCache();
|
||||
this._hasPopulatedCache = true;
|
||||
|
||||
this.workspaceContents.onDidChangeMarkdownDocument(this.onDidChangeDocument, this, this._disposables);
|
||||
this.workspaceContents.onDidCreateMarkdownDocument(this.onDidChangeDocument, this, this._disposables);
|
||||
this.workspaceContents.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this, this._disposables);
|
||||
}
|
||||
|
||||
return Array.from(this._cache.values(), x => x.value);
|
||||
}
|
||||
|
||||
private async populateSymbolCache(): Promise<void> {
|
||||
const markdownDocumentUris = await this.workspaceContents.getAllMarkdownDocuments();
|
||||
for (const document of markdownDocumentUris) {
|
||||
this._cache.set(document.uri.toString(), this.update(document));
|
||||
}
|
||||
}
|
||||
|
||||
private update(document: SkinnyTextDocument): Lazy<T> {
|
||||
return lazy(() => this.getValue(document));
|
||||
}
|
||||
|
||||
private onDidChangeDocument(document: SkinnyTextDocument) {
|
||||
this._cache.set(document.uri.toString(), this.update(document));
|
||||
}
|
||||
|
||||
private onDidDeleteDocument(resource: vscode.Uri) {
|
||||
this._cache.delete(resource.toString());
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { Lazy, lazy } from '../util/lazy';
|
||||
import { SkinnyTextDocument, MdWorkspaceContents } from '../workspaceContents';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
import { MdDocumentSymbolProvider } from './documentSymbolProvider';
|
||||
|
||||
import { MdWorkspaceCache } from './workspaceCache';
|
||||
|
||||
export class MdWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider {
|
||||
|
||||
@@ -29,51 +28,3 @@ export class MdWorkspaceSymbolProvider extends Disposable implements vscode.Work
|
||||
return allSymbols.filter(symbolInformation => symbolInformation.name.toLowerCase().indexOf(query.toLowerCase()) !== -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache of information for markdown files in the workspace.
|
||||
*/
|
||||
class MdWorkspaceCache<T> extends Disposable {
|
||||
|
||||
private readonly _cache = new Map<string, Lazy<T>>();
|
||||
private _hasPopulatedCache = false;
|
||||
|
||||
public constructor(
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly getValue: (document: SkinnyTextDocument) => T,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getAll(): Promise<T[]> {
|
||||
if (!this._hasPopulatedCache) {
|
||||
await this.populateSymbolCache();
|
||||
this._hasPopulatedCache = true;
|
||||
|
||||
this.workspaceContents.onDidChangeMarkdownDocument(this.onDidChangeDocument, this, this._disposables);
|
||||
this.workspaceContents.onDidCreateMarkdownDocument(this.onDidChangeDocument, this, this._disposables);
|
||||
this.workspaceContents.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this, this._disposables);
|
||||
}
|
||||
|
||||
return Array.from(this._cache.values(), x => x.value);
|
||||
}
|
||||
|
||||
private async populateSymbolCache(): Promise<void> {
|
||||
const markdownDocumentUris = await this.workspaceContents.getAllMarkdownDocuments();
|
||||
for (const document of markdownDocumentUris) {
|
||||
this._cache.set(document.uri.toString(), this.update(document));
|
||||
}
|
||||
}
|
||||
|
||||
private update(document: SkinnyTextDocument): Lazy<T> {
|
||||
return lazy(() => this.getValue(document));
|
||||
}
|
||||
|
||||
private onDidChangeDocument(document: SkinnyTextDocument) {
|
||||
this._cache.set(document.uri.toString(), this.update(document));
|
||||
}
|
||||
|
||||
private onDidDeleteDocument(resource: vscode.Uri) {
|
||||
this._cache.delete(resource.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
|
||||
|
||||
export class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
|
||||
private readonly _documents = new Map<string, vscode.TextDocument>();
|
||||
|
||||
constructor(documents: vscode.TextDocument[]) {
|
||||
for (const doc of documents) {
|
||||
this._documents.set(doc.fileName, doc);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllMarkdownDocuments() {
|
||||
return Array.from(this._documents.values());
|
||||
}
|
||||
|
||||
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.TextDocument>();
|
||||
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;
|
||||
|
||||
private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.TextDocument>();
|
||||
public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event;
|
||||
|
||||
private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event;
|
||||
|
||||
public updateDocument(document: vscode.TextDocument) {
|
||||
this._documents.set(document.fileName, document);
|
||||
this._onDidChangeMarkdownDocumentEmitter.fire(document);
|
||||
}
|
||||
|
||||
public createDocument(document: vscode.TextDocument) {
|
||||
assert.ok(!this._documents.has(document.uri.fsPath));
|
||||
|
||||
this._documents.set(document.uri.fsPath, document);
|
||||
this._onDidCreateMarkdownDocumentEmitter.fire(document);
|
||||
}
|
||||
|
||||
public deleteDocument(resource: vscode.Uri) {
|
||||
this._documents.delete(resource.fsPath);
|
||||
this._onDidDeleteMarkdownDocumentEmitter.fire(resource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
|
||||
import { MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryDocument } from './inMemoryDocument';
|
||||
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
|
||||
import { joinLines, noopToken, workspaceFile } from './util';
|
||||
|
||||
|
||||
function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = new MdLinkProvider(engine);
|
||||
const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine);
|
||||
return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
|
||||
}
|
||||
|
||||
suite.only('markdown header references', () => {
|
||||
test('Should not return references when not on header', async () => {
|
||||
const doc = new InMemoryDocument(workspaceFile('doc.md'), joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
`text`,
|
||||
));
|
||||
|
||||
{
|
||||
const refs = await getReferences(doc, new vscode.Position(1, 0), new InMemoryWorkspaceMarkdownDocuments([doc]));
|
||||
assert.deepStrictEqual(refs, undefined);
|
||||
}
|
||||
{
|
||||
const refs = await getReferences(doc, new vscode.Position(3, 2), new InMemoryWorkspaceMarkdownDocuments([doc]));
|
||||
assert.deepStrictEqual(refs, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should find simple references within same file', async () => {
|
||||
const doc = new InMemoryDocument(workspaceFile('doc.md'), joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
`[not link](#noabc)`,
|
||||
`[link 2](#abc)`,
|
||||
));
|
||||
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([doc]));
|
||||
|
||||
assert.deepStrictEqual(refs!.length, 2);
|
||||
|
||||
{
|
||||
const ref1 = refs![0];
|
||||
assert.deepStrictEqual(ref1.range.start.line, 2);
|
||||
}
|
||||
{
|
||||
const ref2 = refs![1];
|
||||
assert.deepStrictEqual(ref2.range.start.line, 4);
|
||||
}
|
||||
});
|
||||
|
||||
test('Should find simple references across files', async () => {
|
||||
const docUri = workspaceFile('doc.md');
|
||||
const other1Uri = workspaceFile('sub', 'other.md');
|
||||
const other2Uri = workspaceFile('other2.md');
|
||||
|
||||
const doc = new InMemoryDocument(docUri, joinLines(
|
||||
`# abc`,
|
||||
``,
|
||||
`[link 1](#abc)`,
|
||||
));
|
||||
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([
|
||||
doc,
|
||||
new InMemoryDocument(other1Uri, joinLines(
|
||||
`[not link](#abc)`,
|
||||
`[not link](/doc.md#abz)`,
|
||||
`[link](/doc.md#abc)`,
|
||||
)),
|
||||
new InMemoryDocument(other2Uri, joinLines(
|
||||
`[not link](#abc)`,
|
||||
`[not link](./doc.md#abz)`,
|
||||
`[link](./doc.md#abc)`,
|
||||
))
|
||||
]));
|
||||
|
||||
assert.deepStrictEqual(refs!.length, 3);
|
||||
|
||||
{
|
||||
const ref1 = refs![0];
|
||||
assert.deepStrictEqual(ref1.uri.toString(), docUri.toString());
|
||||
assert.deepStrictEqual(ref1.range.start.line, 2);
|
||||
}
|
||||
{
|
||||
const ref2 = refs![1];
|
||||
assert.deepStrictEqual(ref2.uri.toString(), other1Uri.toString());
|
||||
assert.deepStrictEqual(ref2.range.start.line, 2);
|
||||
}
|
||||
{
|
||||
const ref3 = refs![2];
|
||||
assert.deepStrictEqual(ref3.uri.toString(), other2Uri.toString());
|
||||
assert.deepStrictEqual(ref3.range.start.line, 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -31,3 +31,7 @@ export function getCursorPositions(contents: string, doc: InMemoryDocument): vsc
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
export function workspaceFile(...segments: string[]): vscode.Uri {
|
||||
return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider';
|
||||
import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbolProvider';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryDocument } from './inMemoryDocument';
|
||||
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
|
||||
|
||||
|
||||
const symbolProvider = new MdDocumentSymbolProvider(createNewMarkdownEngine());
|
||||
@@ -100,44 +100,3 @@ suite('markdown.WorkspaceSymbolProvider', () => {
|
||||
assert.strictEqual(newSymbols.length, 3);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
|
||||
private readonly _documents = new Map<string, vscode.TextDocument>();
|
||||
|
||||
constructor(documents: vscode.TextDocument[]) {
|
||||
for (const doc of documents) {
|
||||
this._documents.set(doc.fileName, doc);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllMarkdownDocuments() {
|
||||
return Array.from(this._documents.values());
|
||||
}
|
||||
|
||||
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.TextDocument>();
|
||||
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;
|
||||
|
||||
private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.TextDocument>();
|
||||
public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event;
|
||||
|
||||
private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event;
|
||||
|
||||
public updateDocument(document: vscode.TextDocument) {
|
||||
this._documents.set(document.fileName, document);
|
||||
this._onDidChangeMarkdownDocumentEmitter.fire(document);
|
||||
}
|
||||
|
||||
public createDocument(document: vscode.TextDocument) {
|
||||
assert.ok(!this._documents.has(document.uri.fsPath));
|
||||
|
||||
this._documents.set(document.uri.fsPath, document);
|
||||
this._onDidCreateMarkdownDocumentEmitter.fire(document);
|
||||
}
|
||||
|
||||
public deleteDocument(resource: vscode.Uri) {
|
||||
this._documents.delete(resource.fsPath);
|
||||
this._onDidDeleteMarkdownDocumentEmitter.fire(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ export interface SkinnyTextDocument {
|
||||
readonly version: number;
|
||||
readonly lineCount: number;
|
||||
|
||||
lineAt(line: number): SkinnyTextLine;
|
||||
getText(): string;
|
||||
lineAt(line: number): SkinnyTextLine;
|
||||
positionAt(offset: number): vscode.Position;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +151,13 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace
|
||||
},
|
||||
getText: () => {
|
||||
return text;
|
||||
},
|
||||
positionAt(offset: number): vscode.Position {
|
||||
const before = text.slice(0, offset);
|
||||
const newLines = before.match(/\r\n|\n/g);
|
||||
const line = newLines ? newLines.length : 0;
|
||||
const preCharacters = before.match(/(?<=\r\n|\n|^).*$/g);
|
||||
return new vscode.Position(line, preCharacters ? preCharacters[0].length : 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user