Skip to content

Commit 86c38e4

Browse files
committed
feat: context provider api support for java
1 parent fb829cd commit 86c38e4

File tree

7 files changed

+562
-0
lines changed

7 files changed

+562
-0
lines changed

package-lock.json

Lines changed: 110 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@
381381
"VisualStudioExptTeam.vscodeintellicode"
382382
],
383383
"dependencies": {
384+
"@github/copilot-language-server": "^1.316.0",
384385
"@iconify-icons/codicon": "1.2.8",
385386
"@iconify/react": "^1.1.4",
386387
"@reduxjs/toolkit": "^1.8.6",

src/commands/handler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,7 @@ export async function toggleAwtDevelopmentHandler(context: vscode.ExtensionConte
127127
fetchInitProps(context);
128128
vscode.window.showInformationMessage(`Java AWT development is ${enable ? "enabled" : "disabled"}.`);
129129
}
130+
131+
export async function getImportClassContent(uri: string): Promise<INodeImportClass[]> {
132+
return await vscode.commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_GETIMPORTCLASSCONTENT, uri) || [];
133+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
import * as vscode from 'vscode';
6+
import * as crypto from 'crypto';
7+
import { INodeImportClass } from './copilotHelper';
8+
9+
/**
10+
* Cache entry interface for storing import data with timestamp
11+
*/
12+
interface CacheEntry {
13+
value: INodeImportClass[];
14+
timestamp: number;
15+
}
16+
17+
/**
18+
* Configuration options for the context cache
19+
*/
20+
interface ContextCacheOptions {
21+
/** Cache expiry time in milliseconds. Default: 5 minutes */
22+
expiryTime?: number;
23+
/** Enable automatic cleanup interval. Default: true */
24+
enableAutoCleanup?: boolean;
25+
/** Enable file watching for cache invalidation. Default: true */
26+
enableFileWatching?: boolean;
27+
}
28+
29+
/**
30+
* Context cache manager for storing and managing Java import contexts
31+
*/
32+
export class ContextCache {
33+
private readonly cache = new Map<string, CacheEntry>();
34+
private readonly expiryTime: number;
35+
private readonly enableAutoCleanup: boolean;
36+
private readonly enableFileWatching: boolean;
37+
38+
private cleanupInterval?: NodeJS.Timeout;
39+
private fileWatcher?: vscode.FileSystemWatcher;
40+
41+
constructor(options: ContextCacheOptions = {}) {
42+
this.expiryTime = options.expiryTime ?? 5 * 60 * 1000; // 5 minutes default
43+
this.enableAutoCleanup = options.enableAutoCleanup ?? true;
44+
this.enableFileWatching = options.enableFileWatching ?? true;
45+
}
46+
47+
/**
48+
* Initialize the cache with VS Code extension context
49+
* @param context VS Code extension context for managing disposables
50+
*/
51+
public initialize(context: vscode.ExtensionContext): void {
52+
if (this.enableAutoCleanup) {
53+
this.startPeriodicCleanup();
54+
}
55+
56+
if (this.enableFileWatching) {
57+
this.setupFileWatcher();
58+
}
59+
60+
// Register cleanup on extension disposal
61+
context.subscriptions.push(
62+
new vscode.Disposable(() => {
63+
this.dispose();
64+
})
65+
);
66+
67+
if (this.fileWatcher) {
68+
context.subscriptions.push(this.fileWatcher);
69+
}
70+
}
71+
72+
/**
73+
* Generate a hash for the document URI to use as cache key
74+
* @param uri Document URI
75+
* @returns Hashed URI string
76+
*/
77+
private generateCacheKey(uri: vscode.Uri): string {
78+
return crypto.createHash('md5').update(uri.toString()).digest('hex');
79+
}
80+
81+
/**
82+
* Get cached imports for a document URI
83+
* @param uri Document URI
84+
* @returns Cached imports or null if not found/expired
85+
*/
86+
public get(uri: vscode.Uri): INodeImportClass[] | null {
87+
const key = this.generateCacheKey(uri);
88+
const cached = this.cache.get(key);
89+
90+
if (!cached) {
91+
return null;
92+
}
93+
94+
// Check if cache is expired
95+
if (this.isExpired(cached)) {
96+
this.cache.delete(key);
97+
return null;
98+
}
99+
100+
return cached.value;
101+
}
102+
103+
/**
104+
* Set cached imports for a document URI
105+
* @param uri Document URI
106+
* @param imports Import class array to cache
107+
*/
108+
public set(uri: vscode.Uri, imports: INodeImportClass[]): void {
109+
const key = this.generateCacheKey(uri);
110+
this.cache.set(key, {
111+
value: imports,
112+
timestamp: Date.now()
113+
});
114+
}
115+
116+
/**
117+
* Check if a cache entry is expired
118+
* @param entry Cache entry to check
119+
* @returns True if expired, false otherwise
120+
*/
121+
private isExpired(entry: CacheEntry): boolean {
122+
return Date.now() - entry.timestamp > this.expiryTime;
123+
}
124+
125+
/**
126+
* Clear expired cache entries
127+
*/
128+
public clearExpired(): void {
129+
const now = Date.now();
130+
for (const [key, entry] of this.cache.entries()) {
131+
if (now - entry.timestamp > this.expiryTime) {
132+
this.cache.delete(key);
133+
}
134+
}
135+
}
136+
137+
/**
138+
* Clear all cache entries
139+
*/
140+
public clear(): void {
141+
this.cache.clear();
142+
}
143+
144+
/**
145+
* Invalidate cache for specific URI
146+
* @param uri URI to invalidate
147+
*/
148+
public invalidate(uri: vscode.Uri): void {
149+
const key = this.generateCacheKey(uri);
150+
if (this.cache.has(key)) {
151+
this.cache.delete(key);
152+
console.log('======== Cache invalidated for:', uri.toString());
153+
}
154+
}
155+
156+
/**
157+
* Get cache statistics
158+
* @returns Object containing cache size and other statistics
159+
*/
160+
public getStats(): { size: number; expiryTime: number } {
161+
return {
162+
size: this.cache.size,
163+
expiryTime: this.expiryTime
164+
};
165+
}
166+
167+
/**
168+
* Start periodic cleanup of expired cache entries
169+
*/
170+
private startPeriodicCleanup(): void {
171+
this.cleanupInterval = setInterval(() => {
172+
this.clearExpired();
173+
}, this.expiryTime);
174+
}
175+
176+
/**
177+
* Setup file system watcher for Java files to invalidate cache on changes
178+
*/
179+
private setupFileWatcher(): void {
180+
this.fileWatcher = vscode.workspace.createFileSystemWatcher('**/*.java');
181+
182+
const invalidateHandler = (uri: vscode.Uri) => {
183+
this.invalidate(uri);
184+
};
185+
186+
this.fileWatcher.onDidChange(invalidateHandler);
187+
this.fileWatcher.onDidDelete(invalidateHandler);
188+
}
189+
190+
/**
191+
* Dispose of all resources (intervals, watchers, etc.)
192+
*/
193+
public dispose(): void {
194+
if (this.cleanupInterval) {
195+
clearInterval(this.cleanupInterval);
196+
this.cleanupInterval = undefined;
197+
}
198+
199+
if (this.fileWatcher) {
200+
this.fileWatcher.dispose();
201+
this.fileWatcher = undefined;
202+
}
203+
204+
this.clear();
205+
}
206+
}
207+
208+
/**
209+
* Default context cache instance
210+
*/
211+
export const contextCache = new ContextCache();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import { commands, Uri } from "vscode";
5+
import { logger } from "../utils";
6+
7+
export interface INodeImportClass {
8+
uri: string;
9+
className: string; // Changed from 'class' to 'className' to match Java code
10+
}
11+
/**
12+
* Helper class for Copilot integration to analyze Java project dependencies
13+
*/
14+
export namespace CopilotHelper {
15+
/**
16+
* Resolves all local project types imported by the given file
17+
* @param fileUri The URI of the Java file to analyze
18+
* @returns Array of strings in format "type:fully.qualified.name" where type is class|interface|enum|annotation
19+
*/
20+
export async function resolveLocalImports(fileUri: Uri): Promise<INodeImportClass[]> {
21+
try {
22+
return await commands.executeCommand("java.execute.workspaceCommand", "java.project.getImportClassContent", fileUri) || [];
23+
} catch (error) {
24+
logger.error("Error resolving copilot request:", error);
25+
return [];
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)