Skip to content

Commit 4184566

Browse files
committed
feat: update
1 parent bc8eba7 commit 4184566

File tree

2 files changed

+147
-84
lines changed

2 files changed

+147
-84
lines changed

src/copilot/contextProvider.ts

Lines changed: 147 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import {
99
type ContextProvider,
1010
} from '@github/copilot-language-server';
1111
import * as vscode from 'vscode';
12-
import { CopilotHelper } from '../copilotHelper';
12+
import { CopilotHelper, INodeImportClass } from '../copilotHelper';
1313
import { TreatmentVariables } from '../ext/treatmentVariables';
1414
import { getExpService } from '../ext/ExperimentationService';
1515
import { sendInfo } from "vscode-extension-telemetry-wrapper";
16+
import * as crypto from 'crypto';
1617

1718
export enum NodeKind {
1819
Workspace = 1,
@@ -27,6 +28,71 @@ export enum NodeKind {
2728
File = 10,
2829
}
2930

31+
// Global cache for storing resolveLocalImports results
32+
interface CacheEntry {
33+
value: INodeImportClass[];
34+
timestamp: number;
35+
}
36+
37+
const globalImportsCache = new Map<string, CacheEntry>();
38+
const CACHE_EXPIRY_TIME = 5 * 60 * 1000; // 5 minutes
39+
40+
/**
41+
* Generate a hash for the document URI to use as cache key
42+
* @param uri Document URI
43+
* @returns Hashed URI string
44+
*/
45+
function generateCacheKey(uri: vscode.Uri): string {
46+
return crypto.createHash('md5').update(uri.toString()).digest('hex');
47+
}
48+
49+
/**
50+
* Get cached imports for a document URI
51+
* @param uri Document URI
52+
* @returns Cached imports or null if not found/expired
53+
*/
54+
function getCachedImports(uri: vscode.Uri): INodeImportClass[] | null {
55+
const key = generateCacheKey(uri);
56+
const cached = globalImportsCache.get(key);
57+
58+
if (!cached) {
59+
return null;
60+
}
61+
62+
// Check if cache is expired
63+
if (Date.now() - cached.timestamp > CACHE_EXPIRY_TIME) {
64+
globalImportsCache.delete(key);
65+
return null;
66+
}
67+
68+
return cached.value;
69+
}
70+
71+
/**
72+
* Set cached imports for a document URI
73+
* @param uri Document URI
74+
* @param imports Import class array to cache
75+
*/
76+
function setCachedImports(uri: vscode.Uri, imports: INodeImportClass[]): void {
77+
const key = generateCacheKey(uri);
78+
globalImportsCache.set(key, {
79+
value: imports,
80+
timestamp: Date.now()
81+
});
82+
}
83+
84+
/**
85+
* Clear expired cache entries
86+
*/
87+
function clearExpiredCache(): void {
88+
const now = Date.now();
89+
for (const [key, entry] of globalImportsCache.entries()) {
90+
if (now - entry.timestamp > CACHE_EXPIRY_TIME) {
91+
globalImportsCache.delete(key);
92+
}
93+
}
94+
}
95+
3096
export async function registerCopilotContextProviders(
3197
context: vscode.ExtensionContext
3298
) {
@@ -40,37 +106,90 @@ export async function registerCopilotContextProviders(
40106
sendInfo("", {
41107
"contextProviderEnabled": "true",
42108
});
109+
110+
// Start periodic cache cleanup
111+
const cacheCleanupInterval = setInterval(() => {
112+
clearExpiredCache();
113+
}, CACHE_EXPIRY_TIME); // Clean up every 5 minutes
114+
115+
// Monitor file changes to invalidate cache
116+
const fileWatcher = vscode.workspace.createFileSystemWatcher('**/*.java');
117+
118+
const invalidateCache = (uri: vscode.Uri) => {
119+
const key = generateCacheKey(uri);
120+
if (globalImportsCache.has(key)) {
121+
globalImportsCache.delete(key);
122+
console.log('======== Cache invalidated for:', uri.toString());
123+
}
124+
};
125+
126+
fileWatcher.onDidChange(invalidateCache);
127+
fileWatcher.onDidDelete(invalidateCache);
128+
129+
// Dispose the interval and file watcher when extension is deactivated
130+
context.subscriptions.push(
131+
new vscode.Disposable(() => {
132+
clearInterval(cacheCleanupInterval);
133+
globalImportsCache.clear(); // Clear all cache on disposal
134+
}),
135+
fileWatcher
136+
);
137+
43138
try {
44139
const copilotClientApi = await getCopilotClientApi();
45140
const copilotChatApi = await getCopilotChatApi();
46-
if (!copilotClientApi && !copilotChatApi) {
47-
console.log('Failed to find compatible version of GitHub Copilot extension installed. Skip registration of Copilot context provider.');
141+
if (!copilotClientApi || !copilotChatApi) {
142+
console.error('Failed to find compatible version of GitHub Copilot extension installed. Skip registration of Copilot context provider.');
48143
return;
49144
}
50145
// Register the Java completion context provider
51-
const javaCompletionProvider = new JavaCopilotCompletionContextProvider();
52-
let completionProviderInstallCount = 0;
146+
const provider: ContextProvider<SupportedContextItem> = {
147+
id: 'vscjava.vscode-java-pack', // use extension id as provider id for now
148+
selector: [{ language: "java" }],
149+
resolver: {
150+
resolve: async (request, token) => {
151+
// Check if we have a cached result for the current active editor
152+
const activeEditor = vscode.window.activeTextEditor;
153+
if (activeEditor && activeEditor.document.languageId === 'java') {
154+
const cachedImports = getCachedImports(activeEditor.document.uri);
155+
if (cachedImports) {
156+
console.log('======== Using cached imports, cache size:', cachedImports.length);
157+
// Return cached result as context items
158+
return cachedImports.map(cls => ({
159+
uri: cls.uri,
160+
value: cls.className,
161+
importance: 70,
162+
origin: 'request' as const
163+
}));
164+
}
165+
}
166+
167+
return await resolveJavaContext(request, token);
168+
}
169+
}
170+
};
53171

172+
let installCount = 0;
54173
if (copilotClientApi) {
55-
const disposable = await installContextProvider(copilotClientApi, javaCompletionProvider);
174+
const disposable = await installContextProvider(copilotClientApi, provider);
56175
if (disposable) {
57176
context.subscriptions.push(disposable);
58-
completionProviderInstallCount++;
177+
installCount++;
59178
}
60179
}
61180
if (copilotChatApi) {
62-
const disposable = await installContextProvider(copilotChatApi, javaCompletionProvider);
181+
const disposable = await installContextProvider(copilotChatApi, provider);
63182
if (disposable) {
64183
context.subscriptions.push(disposable);
65-
completionProviderInstallCount++;
184+
installCount++;
66185
}
67186
}
68187

69-
if (completionProviderInstallCount > 0) {
70-
console.log('Registration of Java completion context provider for GitHub Copilot extension succeeded.');
71-
} else {
72-
console.log('Failed to register Java completion context provider for GitHub Copilot extension.');
188+
if (installCount === 0) {
189+
console.log('Incompatible GitHub Copilot extension installed. Skip registration of Java context providers.');
190+
return;
73191
}
192+
console.log('Registration of Java context provider for GitHub Copilot extension succeeded.');
74193
}
75194
catch (error) {
76195
console.log('Error occurred while registering Java context provider for GitHub Copilot extension:', error);
@@ -89,11 +208,6 @@ async function resolveJavaContext(_request: ResolveRequest, _token: vscode.Cance
89208

90209
const document = activeEditor.document;
91210

92-
// const position = activeEditor.selection.active;
93-
// const currentRange = activeEditor.selection.isEmpty
94-
// ? new vscode.Range(position, position)
95-
// : activeEditor.selection;
96-
97211
// 1. Project basic information (High importance)
98212
const projectContext = await collectProjectContext(document);
99213
const packageName = await getPackageName(document);
@@ -122,7 +236,17 @@ async function resolveJavaContext(_request: ResolveRequest, _token: vscode.Cance
122236
origin: 'request'
123237
});
124238

125-
const importClass = await CopilotHelper.resolveLocalImports(document.uri);
239+
// Try to get cached imports first
240+
let importClass = getCachedImports(document.uri);
241+
if (!importClass) {
242+
// If not cached, resolve and cache the result
243+
importClass = await CopilotHelper.resolveLocalImports(document.uri);
244+
setCachedImports(document.uri, importClass);
245+
console.log('======== Cached new imports, cache size:', importClass.length);
246+
} else {
247+
console.log('======== Using cached imports in resolveJavaContext, cache size:', importClass.length);
248+
}
249+
126250
for (const cls of importClass) {
127251
items.push({
128252
uri: cls.uri,
@@ -145,16 +269,16 @@ async function resolveJavaContext(_request: ResolveRequest, _token: vscode.Cance
145269
origin: 'request'
146270
});
147271
}
148-
console.log('Total context resolution time:', performance.now() - start);
149-
console.log('===== Size of context items:', items.length);
272+
console.log('Total context resolution time:', performance.now() - start, 'ms', ' ,size:', items.length);
273+
console.log('Context items:', items);
150274
return items;
151275
}
152276

153277
async function collectProjectContext(document: vscode.TextDocument): Promise<{ javaVersion: string }> {
154278
try {
155-
return await vscode.commands.executeCommand("java.project.getSettings", document.uri, ["java.home"]);
279+
return await vscode.commands.executeCommand("java.project.getSettings", document.uri, ["java.compliance", "java.source", "java.target"]);
156280
} catch (error) {
157-
console.log('Failed to get Java version:', error);
281+
console.error('Failed to get Java version:', error);
158282
return { javaVersion: 'unknown' };
159283
}
160284
}
@@ -218,63 +342,3 @@ async function installContextProvider(
218342
}
219343
return undefined;
220344
}
221-
222-
/**
223-
* Java-specific Copilot completion context provider
224-
* Similar to CopilotCompletionContextProvider but tailored for Java language
225-
*/
226-
export class JavaCopilotCompletionContextProvider implements ContextProvider<SupportedContextItem> {
227-
public readonly id = 'java-completion';
228-
public readonly selector = [{ language: 'java' }];
229-
public readonly resolver = this.resolve.bind(this);
230-
231-
// Cache for completion contexts with timeout
232-
private cache = new Map<string, { context: SupportedContextItem[]; timestamp: number }>();
233-
private readonly cacheTimeout = 30000; // 30 seconds
234-
235-
public async resolve(request: ResolveRequest, cancellationToken: vscode.CancellationToken): Promise<SupportedContextItem[]> {
236-
// Access document through request properties
237-
const docUri = request.documentContext?.uri?.toString();
238-
const docOffset = request.documentContext?.offset;
239-
240-
// Only process Java files
241-
if (!docUri || !docUri.endsWith('.java')) {
242-
return [];
243-
}
244-
245-
const cacheKey = `${docUri}:${docOffset}`;
246-
const cached = this.cache.get(cacheKey);
247-
248-
// Return cached result if still valid
249-
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
250-
return cached.context;
251-
}
252-
253-
try {
254-
const context = await resolveJavaContext(request, cancellationToken);
255-
256-
// Cache the result
257-
this.cache.set(cacheKey, {
258-
context,
259-
timestamp: Date.now()
260-
});
261-
262-
// Clean up old cache entries
263-
this.cleanCache();
264-
265-
return context;
266-
} catch (error) {
267-
console.error('Error generating Java completion context:', error);
268-
return [];
269-
}
270-
}
271-
272-
private cleanCache(): void {
273-
const now = Date.now();
274-
for (const [key, value] of this.cache.entries()) {
275-
if (now - value.timestamp > this.cacheTimeout) {
276-
this.cache.delete(key);
277-
}
278-
}
279-
}
280-
}

src/extension.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { commands, Diagnostic, Extension, ExtensionContext, extensions, language
66
Range, tasks, TextDocument, TextEditor, Uri, window, workspace } from "vscode";
77
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation, instrumentOperationAsVsCodeCommand, sendInfo } from "vscode-extension-telemetry-wrapper";
88
import { Commands, contextManager } from "../extension.bundle";
9-
import { CopilotHelper } from "./copilotHelper";
109
import { BuildTaskProvider } from "./tasks/build/buildTaskProvider";
1110
import { buildFiles, Context, ExtensionName } from "./constants";
1211
import { LibraryController } from "./controllers/libraryController";

0 commit comments

Comments
 (0)