Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions src/extension/linkify/vscode-node/findWord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export async function findWordInReferences(
word: string,
options: FindWordOptions,
token: CancellationToken,
documentCache?: Map<string, Promise<SimpleTextDocument | undefined>>,
): Promise<vscode.Location[]> {
const parserService = accessor.get(IParserService);

Expand All @@ -64,11 +65,11 @@ export async function findWordInReferences(

let loc: ResolvedWordLocation | undefined;
if (isUriComponents(ref.anchor)) {
loc = await findWordInDoc(parserService, word, ref.anchor, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), options, token);
loc = await findWordInDoc(parserService, word, ref.anchor, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), options, token, documentCache);
} else if ('range' in ref.anchor) {
loc = await findWordInDoc(parserService, word, ref.anchor.uri, ref.anchor.range, options, token);
loc = await findWordInDoc(parserService, word, ref.anchor.uri, ref.anchor.range, options, token, documentCache);
} else if ('value' in ref.anchor && URI.isUri(ref.anchor.value)) {
loc = await findWordInDoc(parserService, word, ref.anchor.value, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), options, token);
loc = await findWordInDoc(parserService, word, ref.anchor.value, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), options, token, documentCache);
}

if (loc) {
Expand All @@ -85,8 +86,15 @@ export async function findWordInReferences(
.slice(0, options.maxResultCount);
}

async function findWordInDoc(parserService: IParserService, word: string, uri: vscode.Uri, range: vscode.Range, options: FindWordOptions, token: vscode.CancellationToken): Promise<ResolvedWordLocation | undefined> {
const doc = await openDocument(uri);
async function findWordInDoc(parserService: IParserService, word: string, uri: vscode.Uri, range: vscode.Range, options: FindWordOptions, token: vscode.CancellationToken, documentCache?: Map<string, Promise<SimpleTextDocument | undefined>>): Promise<ResolvedWordLocation | undefined> {
if (options.symbolMatchesOnly) {
const languageId = getLanguageForResource(uri).languageId;
if (!getWasmLanguage(languageId)) {
return;
}
}

const doc = await openDocument(uri, documentCache);
if (!doc || token.isCancellationRequested) {
return;
}
Expand Down Expand Up @@ -142,12 +150,28 @@ interface SimpleTextDocument {
}


async function openDocument(uri: vscode.Uri): Promise<SimpleTextDocument | undefined> {
async function openDocument(uri: vscode.Uri, documentCache?: Map<string, Promise<SimpleTextDocument | undefined>>): Promise<SimpleTextDocument | undefined> {
const vsCodeDoc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString());
if (vsCodeDoc) {
return vsCodeDoc;
}

if (documentCache) {
const key = uri.toString();
const existing = documentCache.get(key);
if (existing) {
return existing;
}

const pending = doOpenDocument(uri);
documentCache.set(key, pending);
return pending;
}

return doOpenDocument(uri);
}

async function doOpenDocument(uri: vscode.Uri): Promise<SimpleTextDocument | undefined> {
try {
const contents = await vscode.workspace.fs.readFile(uri);
const languageId = getLanguageForResource(uri).languageId;
Expand Down Expand Up @@ -190,6 +214,7 @@ async function getSymbolsInRange(parserService: IParserService, doc: SimpleTextD
export class ReferencesSymbolResolver {
/** Symbols which we have already tried to resolve */
private readonly cache = new Map<string, Promise<vscode.Location[] | undefined>>();
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider adding a JSDoc comment to document the purpose of the documentCache field. For example:

/** Cache of documents read during symbol resolution to avoid redundant file I/O within a single response. */
private readonly documentCache = new Map<string, Promise<SimpleTextDocument | undefined>>();

This would help future maintainers understand that the cache is per-resolver instance (and thus per-response) and is meant to avoid reading the same file multiple times during linkification of a single response.

Suggested change
private readonly cache = new Map<string, Promise<vscode.Location[] | undefined>>();
private readonly cache = new Map<string, Promise<vscode.Location[] | undefined>>();
/**
* Cache of documents read during symbol resolution to avoid redundant file I/O within a single response.
* This cache is per-resolver instance (and thus per-response) and is meant to avoid reading the same file
* multiple times during linkification of a single response.
*/

Copilot uses AI. Check for mistakes.
private readonly documentCache = new Map<string, Promise<SimpleTextDocument | undefined>>();

constructor(
private readonly findWordOptions: FindWordOptions,
Expand All @@ -213,7 +238,7 @@ export class ReferencesSymbolResolver {

private async doResolve(codeText: string, references: readonly PromptReference[], token: CancellationToken): Promise<vscode.Location[] | undefined> {
// Prefer exact match
let wordMatches = await this.instantiationService.invokeFunction(accessor => findWordInReferences(accessor, references, codeText, this.findWordOptions, token));
let wordMatches = await this.instantiationService.invokeFunction(accessor => findWordInReferences(accessor, references, codeText, this.findWordOptions, token, this.documentCache));
if (token.isCancellationRequested) {
return;
}
Expand All @@ -234,7 +259,7 @@ export class ReferencesSymbolResolver {
const classMatches = await this.instantiationService.invokeFunction(accessor => findWordInReferences(accessor, references, firstPart, {
symbolMatchesOnly: true,
maxResultCount: this.findWordOptions.maxResultCount,
}, token));
}, token, this.documentCache));

// If we found the class, we'll rely on the click-time resolution to find the method
if (classMatches.length) {
Expand All @@ -254,7 +279,7 @@ export class ReferencesSymbolResolver {
wordMatches = await this.instantiationService.invokeFunction(accessor => findWordInReferences(accessor, references, lastPart, {
symbolMatchesOnly: true,
maxResultCount: this.findWordOptions.maxResultCount,
}, token));
}, token, this.documentCache));
}
}
}
Expand Down
Loading