mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 05:19:39 +00:00
parent
111b58221c
commit
0496c2b3a7
|
@ -29,6 +29,7 @@
|
|||
"onCommand:markdown.showPreviewSecuritySelector",
|
||||
"onCommand:markdown.api.render",
|
||||
"onCommand:markdown.api.reloadPlugins",
|
||||
"onCommand:markdown.findAllFileReferences",
|
||||
"onWebviewPanel:markdown.preview",
|
||||
"onCustomEditor:vscode.markdown.preview.editor"
|
||||
],
|
||||
|
@ -169,6 +170,11 @@
|
|||
"command": "markdown.preview.toggleLock",
|
||||
"title": "%markdown.preview.toggleLock.title%",
|
||||
"category": "Markdown"
|
||||
},
|
||||
{
|
||||
"command": "markdown.findAllFileReferences",
|
||||
"title": "%markdown.findAllFileReferences%",
|
||||
"category": "Markdown"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
@ -205,6 +211,11 @@
|
|||
"command": "markdown.showPreview",
|
||||
"when": "resourceLangId == markdown && !hasCustomMarkdownPreview",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "markdown.findAllFileReferences",
|
||||
"when": "resourceLangId == markdown",
|
||||
"group": "4_search"
|
||||
}
|
||||
],
|
||||
"editor/title/context": [
|
||||
|
@ -212,6 +223,10 @@
|
|||
"command": "markdown.showPreview",
|
||||
"when": "resourceLangId == markdown && !hasCustomMarkdownPreview",
|
||||
"group": "1_open"
|
||||
},
|
||||
{
|
||||
"command": "markdown.findAllFileReferences",
|
||||
"when": "resourceLangId == markdown"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
|
@ -254,6 +269,10 @@
|
|||
{
|
||||
"command": "markdown.preview.refresh",
|
||||
"when": "markdownPreviewFocus"
|
||||
},
|
||||
{
|
||||
"command": "markdown.findAllFileReferences",
|
||||
"when": "editorLangId == markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"markdown.trace.desc": "Enable debug logging for the Markdown extension.",
|
||||
"markdown.preview.refresh.title": "Refresh Preview",
|
||||
"markdown.preview.toggleLock.title": "Toggle Preview Locking",
|
||||
"markdown.findAllFileReferences": "Find File References",
|
||||
"configuration.markdown.preview.openMarkdownLinks.description": "Controls how links to other Markdown files in the Markdown preview should be opened.",
|
||||
"configuration.markdown.preview.openMarkdownLinks.inEditor": "Try to open links in the editor.",
|
||||
"configuration.markdown.preview.openMarkdownLinks.inPreview": "Try to open links in the Markdown preview.",
|
||||
|
|
|
@ -21,9 +21,11 @@ export class CommandManager {
|
|||
this.commands.clear();
|
||||
}
|
||||
|
||||
public register<T extends Command>(command: T): T {
|
||||
public register<T extends Command>(command: T): vscode.Disposable {
|
||||
this.registerCommand(command.id, command.execute, command);
|
||||
return command;
|
||||
return new vscode.Disposable(() => {
|
||||
this.commands.delete(command.id);
|
||||
});
|
||||
}
|
||||
|
||||
private registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) {
|
||||
|
@ -33,4 +35,4 @@ export class CommandManager {
|
|||
|
||||
this.commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import * as commands from './commands/index';
|
|||
import { MdLinkProvider } from './languageFeatures/documentLinkProvider';
|
||||
import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvider';
|
||||
import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor';
|
||||
import { registerFindFileReferences } from './languageFeatures/fileReferences';
|
||||
import { MdFoldingProvider } from './languageFeatures/foldingProvider';
|
||||
import { MdPathCompletionProvider } from './languageFeatures/pathCompletions';
|
||||
import { MdReferencesProvider } from './languageFeatures/references';
|
||||
|
@ -36,14 +37,15 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState);
|
||||
const engine = new MarkdownEngine(contributions, githubSlugifier);
|
||||
const logger = new Logger();
|
||||
const commandManager = new CommandManager();
|
||||
|
||||
const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger);
|
||||
const symbolProvider = new MdDocumentSymbolProvider(engine);
|
||||
const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, engine);
|
||||
context.subscriptions.push(previewManager);
|
||||
|
||||
context.subscriptions.push(registerMarkdownLanguageFeatures(symbolProvider, engine));
|
||||
context.subscriptions.push(registerMarkdownCommands(previewManager, telemetryReporter, cspArbiter, engine));
|
||||
context.subscriptions.push(registerMarkdownLanguageFeatures(commandManager, symbolProvider, engine));
|
||||
context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine));
|
||||
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
|
||||
logger.updateConfiguration();
|
||||
|
@ -52,6 +54,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
}
|
||||
|
||||
function registerMarkdownLanguageFeatures(
|
||||
commandManager: CommandManager,
|
||||
symbolProvider: MdDocumentSymbolProvider,
|
||||
engine: MarkdownEngine
|
||||
): vscode.Disposable {
|
||||
|
@ -71,10 +74,12 @@ function registerMarkdownLanguageFeatures(
|
|||
vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, githubSlugifier)),
|
||||
MdPathCompletionProvider.register(selector, engine, linkProvider),
|
||||
registerDropIntoEditor(selector),
|
||||
registerFindFileReferences(commandManager, referencesProvider),
|
||||
);
|
||||
}
|
||||
|
||||
function registerMarkdownCommands(
|
||||
commandManager: CommandManager,
|
||||
previewManager: MarkdownPreviewManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
cspArbiter: ContentSecurityPolicyArbiter,
|
||||
|
@ -82,7 +87,6 @@ function registerMarkdownCommands(
|
|||
): vscode.Disposable {
|
||||
const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager);
|
||||
|
||||
const commandManager = new CommandManager();
|
||||
commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter));
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { Command, CommandManager } from '../commandManager';
|
||||
import { MdReferencesProvider } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class FindFileReferencesCommand implements Command {
|
||||
|
||||
public readonly id = 'markdown.findAllFileReferences';
|
||||
|
||||
constructor(
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
) { }
|
||||
|
||||
public async execute(resource?: vscode.Uri) {
|
||||
if (!resource) {
|
||||
resource = vscode.window.activeTextEditor?.document.uri;
|
||||
}
|
||||
|
||||
if (!resource) {
|
||||
vscode.window.showErrorMessage(localize('error.noResource', "Find file references failed. No resource provided."));
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Window,
|
||||
title: localize('progress.title', "Finding file references")
|
||||
}, async (_progress, token) => {
|
||||
const references = await this.referencesProvider.getAllReferencesToFile(resource!, token);
|
||||
const locations = references.map(ref => ref.location);
|
||||
|
||||
const config = vscode.workspace.getConfiguration('references');
|
||||
const existingSetting = config.inspect<string>('preferredLocation');
|
||||
|
||||
await config.update('preferredLocation', 'view');
|
||||
try {
|
||||
await vscode.commands.executeCommand('editor.action.showReferences', resource, new vscode.Position(0, 0), locations);
|
||||
} finally {
|
||||
await config.update('preferredLocation', existingSetting?.workspaceFolderValue ?? existingSetting?.workspaceValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function registerFindFileReferences(commandManager: CommandManager, referencesProvider: MdReferencesProvider): vscode.Disposable {
|
||||
return commandManager.register(new FindFileReferencesCommand(referencesProvider));
|
||||
}
|
|
@ -87,14 +87,14 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
}
|
||||
|
||||
async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
|
||||
const allRefs = await this.getAllReferences(document, position, token);
|
||||
const allRefs = await this.getAllReferencesAtPosition(document, position, token);
|
||||
|
||||
return allRefs
|
||||
.filter(ref => context.includeDeclaration || !ref.isDefinition)
|
||||
.map(ref => ref.location);
|
||||
}
|
||||
|
||||
public async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
public async getAllReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
const toc = await TableOfContents.create(this.engine, document);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
|
@ -124,7 +124,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
|
||||
for (const link of links) {
|
||||
if (link.href.kind === 'internal'
|
||||
&& this.looksLikeLinkToDoc(link.href, document)
|
||||
&& this.looksLikeLinkToDoc(link.href, document.uri)
|
||||
&& this.slugifier.fromHeading(link.href.fragment).value === header.slug.value
|
||||
) {
|
||||
references.push({
|
||||
|
@ -203,32 +203,14 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
}
|
||||
}
|
||||
|
||||
for (const link of allLinksInWorkspace) {
|
||||
if (link.href.kind !== 'internal') {
|
||||
continue;
|
||||
}
|
||||
if (sourceLink.href.fragment) {
|
||||
for (const link of allLinksInWorkspace) {
|
||||
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, targetDoc.uri)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.looksLikeLinkToDoc(link.href, targetDoc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
||||
|
||||
if (sourceLink.href.fragment) {
|
||||
if (this.slugifier.fromHeading(link.href.fragment).equals(this.slugifier.fromHeading(sourceLink.href.fragment))) {
|
||||
references.push({
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
link,
|
||||
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
||||
fragmentLocation: getFragmentLocation(link),
|
||||
});
|
||||
}
|
||||
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
|
||||
|
||||
// But exclude cases where the file is implicitly referencing itself
|
||||
if (!link.source.text.startsWith('#') || link.source.resource.fsPath !== targetDoc.uri.fsPath) {
|
||||
const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
||||
references.push({
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
|
@ -239,14 +221,42 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
});
|
||||
}
|
||||
}
|
||||
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
|
||||
references.push(...this.findAllLinksToFile(targetDoc.uri, allLinksInWorkspace, sourceLink));
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
private looksLikeLinkToDoc(href: InternalHref, targetDoc: SkinnyTextDocument) {
|
||||
return href.path.fsPath === targetDoc.uri.fsPath
|
||||
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.uri.fsPath;
|
||||
private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) {
|
||||
return href.path.fsPath === targetDoc.fsPath
|
||||
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath;
|
||||
}
|
||||
|
||||
public async getAllReferencesToFile(resource: vscode.Uri, _token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
const allLinksInWorkspace = (await this._linkCache.getAll()).flat();
|
||||
return Array.from(this.findAllLinksToFile(resource, allLinksInWorkspace, undefined));
|
||||
}
|
||||
|
||||
private *findAllLinksToFile(resource: vscode.Uri, allLinksInWorkspace: readonly MdLink[], sourceLink: MdLink | undefined): Iterable<MdReference> {
|
||||
for (const link of allLinksInWorkspace) {
|
||||
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exclude cases where the file is implicitly referencing itself
|
||||
if (!link.source.text.startsWith('#') || link.source.resource.fsPath !== resource.fsPath) {
|
||||
const isTriggerLocation = !!sourceLink && sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
||||
yield {
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
link,
|
||||
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
||||
fragmentLocation: getFragmentLocation(link),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private *getReferencesToLinkReference(allLinks: Iterable<MdLink>, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable<MdReference> {
|
||||
|
|
|
@ -115,7 +115,7 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
return this.cachedRefs;
|
||||
}
|
||||
|
||||
const references = await this.referencesProvider.getAllReferences(document, position, token);
|
||||
const references = await this.referencesProvider.getAllReferencesAtPosition(document, position, token);
|
||||
const triggerRef = references.find(ref => ref.isTriggerLocation);
|
||||
if (!triggerRef) {
|
||||
return undefined;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { MdReference, MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { githubSlugifier } from '../slugify';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
|
||||
import { joinLines, noopToken, workspacePath } from './util';
|
||||
|
||||
|
||||
function getFileReferences(resource: vscode.Uri, workspaceContents: MdWorkspaceContents) {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = new MdLinkProvider(engine);
|
||||
const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
|
||||
return provider.getAllReferencesToFile(resource, noopToken);
|
||||
}
|
||||
|
||||
function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) {
|
||||
assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`);
|
||||
|
||||
for (let i = 0; i < actualRefs.length; ++i) {
|
||||
const actual = actualRefs[i].location;
|
||||
const expected = expectedRefs[i];
|
||||
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
|
||||
assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`);
|
||||
assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`);
|
||||
}
|
||||
}
|
||||
|
||||
suite('markdown: find file references', () => {
|
||||
|
||||
test('Should find basic references', async () => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
|
||||
const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([
|
||||
new InMemoryDocument(docUri, joinLines(
|
||||
`# header`,
|
||||
`[link 1](./other.md)`,
|
||||
`[link 2](./other.md)`,
|
||||
)),
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`,
|
||||
`pre`,
|
||||
`[link 3](./other.md)`,
|
||||
`post`,
|
||||
)),
|
||||
]));
|
||||
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: otherUri, line: 2 },
|
||||
);
|
||||
});
|
||||
|
||||
test('Should find references with and without file extensions', async () => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
|
||||
const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([
|
||||
new InMemoryDocument(docUri, joinLines(
|
||||
`# header`,
|
||||
`[link 1](./other.md)`,
|
||||
`[link 2](./other)`,
|
||||
)),
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`,
|
||||
`pre`,
|
||||
`[link 3](./other.md)`,
|
||||
`[link 4](./other)`,
|
||||
`post`,
|
||||
)),
|
||||
]));
|
||||
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: otherUri, line: 2 },
|
||||
{ uri: otherUri, line: 3 },
|
||||
);
|
||||
});
|
||||
|
||||
test('Should find references with headers on links', async () => {
|
||||
const docUri = workspacePath('doc.md');
|
||||
const otherUri = workspacePath('other.md');
|
||||
|
||||
const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([
|
||||
new InMemoryDocument(docUri, joinLines(
|
||||
`# header`,
|
||||
`[link 1](./other.md#sub-bla)`,
|
||||
`[link 2](./other#sub-bla)`,
|
||||
)),
|
||||
new InMemoryDocument(otherUri, joinLines(
|
||||
`# header`,
|
||||
`pre`,
|
||||
`[link 3](./other.md#sub-bla)`,
|
||||
`[link 4](./other#sub-bla)`,
|
||||
`post`,
|
||||
)),
|
||||
]));
|
||||
|
||||
assertReferencesEqual(refs!,
|
||||
{ uri: docUri, line: 1 },
|
||||
{ uri: docUri, line: 2 },
|
||||
{ uri: otherUri, line: 2 },
|
||||
{ uri: otherUri, line: 3 },
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue