Skip to content

Commit

Permalink
(feat) Implement find file references (#1491)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jojoshua committed May 30, 2022
1 parent fa5eb0f commit 5a6eba1
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/language-server/src/ls-config.ts
Expand Up @@ -14,6 +14,7 @@ const defaultLSConfig: LSConfig = {
completions: { enable: true },
definitions: { enable: true },
findReferences: { enable: true },
fileReferences: { enable: true },
documentSymbols: { enable: true },
codeActions: { enable: true },
rename: { enable: true },
Expand Down Expand Up @@ -97,6 +98,9 @@ export interface LSTypescriptConfig {
findReferences: {
enable: boolean;
};
fileReferences: {
enable: boolean;
};
definitions: {
enable: boolean;
};
Expand Down
4 changes: 4 additions & 0 deletions packages/language-server/src/plugins/PluginHost.ts
Expand Up @@ -389,6 +389,10 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
);
}

async fileReferences(uri: string): Promise<Location[] | null> {
return await this.execute<any>('fileReferences', [uri], ExecuteMode.FirstNonNull, 'high');
}

async getSignatureHelp(
textDocument: TextDocumentIdentifier,
position: Position,
Expand Down
5 changes: 5 additions & 0 deletions packages/language-server/src/plugins/interfaces.ts
Expand Up @@ -143,6 +143,10 @@ export interface FindReferencesProvider {
): Promise<Location[] | null>;
}

export interface FileReferencesProvider {
fileReferences(uri: string): Promise<Location[] | null>;
}

export interface SignatureHelpProvider {
getSignatureHelp(
document: Document,
Expand Down Expand Up @@ -199,6 +203,7 @@ type ProviderBase = DiagnosticsProvider &
UpdateImportsProvider &
CodeActionsProvider &
FindReferencesProvider &
FileReferencesProvider &
RenameProvider &
SignatureHelpProvider &
SemanticTokensProvider &
Expand Down
Expand Up @@ -36,6 +36,7 @@ import {
DocumentSymbolsProvider,
FileRename,
FindReferencesProvider,
FileReferencesProvider,
HoverProvider,
ImplementationProvider,
OnWatchFileChanges,
Expand All @@ -54,6 +55,7 @@ import {
CompletionsProviderImpl
} from './features/CompletionProvider';
import { DiagnosticsProviderImpl } from './features/DiagnosticsProvider';
import { FindFileReferencesProviderImpl } from './features/FindFileReferencesProvider';
import { FindReferencesProviderImpl } from './features/FindReferencesProvider';
import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions';
import { HoverProviderImpl } from './features/HoverProvider';
Expand Down Expand Up @@ -85,6 +87,7 @@ export class TypeScriptPlugin
UpdateImportsProvider,
RenameProvider,
FindReferencesProvider,
FileReferencesProvider,
SelectionRangeProvider,
SignatureHelpProvider,
SemanticTokensProvider,
Expand All @@ -104,6 +107,8 @@ export class TypeScriptPlugin
private readonly renameProvider: RenameProviderImpl;
private readonly hoverProvider: HoverProviderImpl;
private readonly findReferencesProvider: FindReferencesProviderImpl;
private readonly findFileReferencesProvider: FindFileReferencesProviderImpl;

private readonly selectionRangeProvider: SelectionRangeProviderImpl;
private readonly signatureHelpProvider: SignatureHelpProviderImpl;
private readonly semanticTokensProvider: SemanticTokensProviderImpl;
Expand All @@ -130,6 +135,9 @@ export class TypeScriptPlugin
this.renameProvider = new RenameProviderImpl(this.lsAndTsDocResolver, configManager);
this.hoverProvider = new HoverProviderImpl(this.lsAndTsDocResolver);
this.findReferencesProvider = new FindReferencesProviderImpl(this.lsAndTsDocResolver);
this.findFileReferencesProvider = new FindFileReferencesProviderImpl(
this.lsAndTsDocResolver
);
this.selectionRangeProvider = new SelectionRangeProviderImpl(this.lsAndTsDocResolver);
this.signatureHelpProvider = new SignatureHelpProviderImpl(this.lsAndTsDocResolver);
this.semanticTokensProvider = new SemanticTokensProviderImpl(this.lsAndTsDocResolver);
Expand Down Expand Up @@ -423,6 +431,14 @@ export class TypeScriptPlugin
return this.findReferencesProvider.findReferences(document, position, context);
}

async fileReferences(uri: string): Promise<Location[] | null> {
if (!this.featureEnabled('fileReferences')) {
return null;
}

return this.findFileReferencesProvider.fileReferences(uri);
}

async onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): Promise<void> {
let doneUpdateProjectFiles = false;

Expand Down
@@ -0,0 +1,51 @@
import { Location } from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { pathToUrl } from '../../../utils';
import { FileReferencesProvider } from '../../interfaces';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertToLocationRange, hasNonZeroRange } from '../utils';
import { SnapshotFragmentMap } from './utils';

export class FindFileReferencesProviderImpl implements FileReferencesProvider {
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}

async fileReferences(uri: string): Promise<Location[] | null> {
const u = URI.parse(uri);
const fileName = u.fsPath;

const lang = await this.getLSForPath(fileName);
const tsDoc = await this.getSnapshotForPath(fileName);
const fragment = tsDoc.getFragment();

const references = lang.getFileReferences(fileName);

if (!references) {
return null;
}

const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
docs.set(tsDoc.filePath, { fragment, snapshot: tsDoc });

const locations = await Promise.all(
references.map(async (ref) => {
const defDoc = await docs.retrieveFragment(ref.fileName);

return Location.create(
pathToUrl(ref.fileName),
convertToLocationRange(defDoc, ref.textSpan)
);
})
);
// Some references are in generated code but not wrapped with explicit ignore comments.
// These show up as zero-length ranges, so filter them out.
return locations.filter(hasNonZeroRange);
}

private async getLSForPath(path: string) {
return this.lsAndTsDocResolver.getLSForPath(path);
}

private async getSnapshotForPath(path: string) {
return this.lsAndTsDocResolver.getSnapshot(path);
}
}
4 changes: 4 additions & 0 deletions packages/language-server/src/server.ts
Expand Up @@ -426,6 +426,10 @@ export function startServer(options?: LSOptions) {
pluginHost.updateImports(fileRename)
);

connection.onRequest('$/getFileReferences', async (uri: string) => {
return pluginHost.fileReferences(uri);
});

connection.onRequest('$/getCompiledCode', async (uri: DocumentUri) => {
const doc = docManager.get(uri);
if (!doc) {
Expand Down
@@ -0,0 +1,57 @@
import * as assert from 'assert';
import * as path from 'path';
import ts from 'typescript';
import { Location, Position, Range } from 'vscode-languageserver';
import { Document, DocumentManager } from '../../../../src/lib/documents';
import { LSConfigManager } from '../../../../src/ls-config';
import { FindFileReferencesProviderImpl } from '../../../../src/plugins/typescript/features/FindFileReferencesProvider';
import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver';
import { pathToUrl } from '../../../../src/utils';

const testDir = path.join(__dirname, '..');

describe('FindFileReferencesProvider', () => {
function getFullPath(filename: string) {
return path.join(testDir, 'testfiles', filename);
}
function getUri(filename: string) {
const filePath = path.join(testDir, 'testfiles', filename);
return pathToUrl(filePath);
}

function setup(filename: string) {
const docManager = new DocumentManager(
(textDocument) => new Document(textDocument.uri, textDocument.text)
);
const lsConfigManager = new LSConfigManager();
const lsAndTsDocResolver = new LSAndTSDocResolver(docManager, [testDir], lsConfigManager);
const provider = new FindFileReferencesProviderImpl(lsAndTsDocResolver);
const document = openDoc(filename);
return { provider, document, openDoc };

function openDoc(filename: string) {
const filePath = getFullPath(filename);
const doc = docManager.openDocument(<any>{
uri: pathToUrl(filePath),
text: ts.sys.readFile(filePath) || ''
});
return doc;
}
}

it('finds file references', async () => {
const { provider, document, openDoc } = setup('find-file-references-child.svelte');
//Make known all the associated files
openDoc('find-file-references-parent.svelte');

const results = await provider.fileReferences(document.uri.toString());
const expectedResults = [
Location.create(
getUri('find-file-references-parent.svelte'),
Range.create(Position.create(1, 37), Position.create(1, 72))
)
];

assert.deepStrictEqual(results, expectedResults);
});
});
@@ -0,0 +1,6 @@
<script>
const findMe = true;
if (findMe) {
findMe;
}
</script>
@@ -0,0 +1,10 @@
<script>
import FindFileReferencesChild from "./find-file-references-child.svelte";
const findMe = true;
if (findMe) {
findMe;
}
</script>

<FindFileReferencesChild></FindFileReferencesChild>
26 changes: 26 additions & 0 deletions packages/svelte-vscode/package.json
Expand Up @@ -534,13 +534,21 @@
{
"command": "svelte.extractComponent",
"title": "Svelte: Extract Component"
},
{
"command": "svelte.typescript.findAllFileReferences",
"title": "Svelte: Find File References"
}
],
"menus": {
"commandPalette": [
{
"command": "svelte.showCompiledCodeToSide",
"when": "editorLangId == svelte"
},
{
"command": "svelte.typescript.findAllFileReferences",
"when": "editorLangId == svelte && resourceScheme == file"
}
],
"editor/title": [
Expand All @@ -550,11 +558,29 @@
"group": "navigation"
}
],
"editor/title/context": [
{
"command": "svelte.typescript.findAllFileReferences",
"when": "resourceLangId == svelte && resourceScheme == file"
}
],
"editor/context": [
{
"command": "svelte.extractComponent",
"when": "editorLangId == svelte",
"group": "1_modification"
},
{
"command": "svelte.typescript.findAllFileReferences",
"when": "editorLangId == svelte",
"group": "4_search"
}
],
"explorer/context": [
{
"command": "svelte.typescript.findAllFileReferences",
"when": "resourceLangId == svelte",
"group": "4_search"
}
]
},
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte-vscode/src/extension.ts
Expand Up @@ -29,6 +29,7 @@ import CompiledCodeContentProvider from './CompiledCodeContentProvider';
import { activateTagClosing } from './html/autoClose';
import { EMPTY_ELEMENTS } from './html/htmlEmptyTagsShared';
import { TsPlugin } from './tsplugin';
import { addFindFileReferencesListener } from './typescript/findFileReferences';

namespace TagCloseRequest {
export const type: RequestType<TextDocumentPositionParams, string, any> = new RequestType(
Expand Down Expand Up @@ -223,6 +224,8 @@ export function activateSvelteLanguageServer(context: ExtensionContext) {

addDidChangeTextDocumentListener(getLS);

addFindFileReferencesListener(getLS, context);

addRenameFileListener(getLS);

addCompilePreviewCommand(getLS, context);
Expand Down
84 changes: 84 additions & 0 deletions packages/svelte-vscode/src/typescript/findFileReferences.ts
@@ -0,0 +1,84 @@
import {
commands,
ExtensionContext,
ProgressLocation,
Uri,
window,
workspace,
Position,
Location,
Range
} from 'vscode';
import { LanguageClient } from 'vscode-languageclient/node';
import { Location as LSLocation } from 'vscode-languageclient';

/**
* adopted from https://github.com/microsoft/vscode/blob/5f3e9c120a4407de3e55465588ce788618526eb0/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts
*/
export async function addFindFileReferencesListener(
getLS: () => LanguageClient,
context: ExtensionContext
) {
const disposable = commands.registerCommand('svelte.typescript.findAllFileReferences', handler);

context.subscriptions.push(disposable);

async function handler(resource?: Uri) {
if (!resource) {
resource = window.activeTextEditor?.document.uri;
}

if (!resource || resource.scheme !== 'file') {
return;
}

const document = await workspace.openTextDocument(resource);

await window.withProgress(
{
location: ProgressLocation.Window,
title: 'Finding file references'
},
async (_, token) => {
const lsLocations = await getLS().sendRequest<LSLocation[] | null>(
'$/getFileReferences',
document.uri.toString(),
token
);

if (!lsLocations) {
return;
}

const config = workspace.getConfiguration('references');
const existingSetting = config.inspect<string>('preferredLocation');

await config.update('preferredLocation', 'view');
try {
await commands.executeCommand(
'editor.action.showReferences',
resource,
new Position(0, 0),
lsLocations.map(
(ref) =>
new Location(
Uri.parse(ref.uri),
new Range(
ref.range.start.line,
ref.range.start.character,
ref.range.end.line,
ref.range.end.character
)
)
)
);
} finally {
await config.update(
'preferredLocation',
existingSetting?.workspaceFolderValue ?? existingSetting?.workspaceValue
);
}
}
);
}
}

0 comments on commit 5a6eba1

Please sign in to comment.