Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
110 changes: 110 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@
"VisualStudioExptTeam.vscodeintellicode"
],
"dependencies": {
"@github/copilot-language-server": "^1.316.0",
"@iconify-icons/codicon": "1.2.8",
"@iconify/react": "^1.1.4",
"@reduxjs/toolkit": "^1.8.6",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ export async function toggleAwtDevelopmentHandler(context: vscode.ExtensionConte

fetchInitProps(context);
vscode.window.showInformationMessage(`Java AWT development is ${enable ? "enabled" : "disabled"}.`);
}
}
211 changes: 211 additions & 0 deletions src/copilot/context/contextCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*---------------------------------------------------------------------------------------------
* 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 crypto from 'crypto';
import { INodeImportClass } from './copilotHelper';

/**
* Cache entry interface for storing import data with timestamp
*/
interface CacheEntry {
value: INodeImportClass[];
timestamp: number;
}

/**
* Configuration options for the context cache
*/
interface ContextCacheOptions {
/** Cache expiry time in milliseconds. Default: 5 minutes */
expiryTime?: number;
/** Enable automatic cleanup interval. Default: true */
enableAutoCleanup?: boolean;
/** Enable file watching for cache invalidation. Default: true */
enableFileWatching?: boolean;
}

/**
* Context cache manager for storing and managing Java import contexts
*/
export class ContextCache {
private readonly cache = new Map<string, CacheEntry>();
private readonly expiryTime: number;
private readonly enableAutoCleanup: boolean;
private readonly enableFileWatching: boolean;

private cleanupInterval?: NodeJS.Timeout;
private fileWatcher?: vscode.FileSystemWatcher;

constructor(options: ContextCacheOptions = {}) {
this.expiryTime = options.expiryTime ?? 5 * 60 * 1000; // 5 minutes default
this.enableAutoCleanup = options.enableAutoCleanup ?? true;
this.enableFileWatching = options.enableFileWatching ?? true;
}

/**
* Initialize the cache with VS Code extension context
* @param context VS Code extension context for managing disposables
*/
public initialize(context: vscode.ExtensionContext): void {
if (this.enableAutoCleanup) {
this.startPeriodicCleanup();
}

if (this.enableFileWatching) {
this.setupFileWatcher();
}

// Register cleanup on extension disposal
context.subscriptions.push(
new vscode.Disposable(() => {
this.dispose();
})
);

if (this.fileWatcher) {
context.subscriptions.push(this.fileWatcher);
}
}

/**
* Generate a hash for the document URI to use as cache key
* @param uri Document URI
* @returns Hashed URI string
*/
private generateCacheKey(uri: vscode.Uri): string {
return crypto.createHash('md5').update(uri.toString()).digest('hex');
}

/**
* Get cached imports for a document URI
* @param uri Document URI
* @returns Cached imports or null if not found/expired
*/
public get(uri: vscode.Uri): INodeImportClass[] | null {
const key = this.generateCacheKey(uri);
const cached = this.cache.get(key);

if (!cached) {
return null;
}

// Check if cache is expired
if (this.isExpired(cached)) {
this.cache.delete(key);
return null;
}

return cached.value;
}

/**
* Set cached imports for a document URI
* @param uri Document URI
* @param imports Import class array to cache
*/
public set(uri: vscode.Uri, imports: INodeImportClass[]): void {
const key = this.generateCacheKey(uri);
this.cache.set(key, {
value: imports,
timestamp: Date.now()
});
}

/**
* Check if a cache entry is expired
* @param entry Cache entry to check
* @returns True if expired, false otherwise
*/
private isExpired(entry: CacheEntry): boolean {
return Date.now() - entry.timestamp > this.expiryTime;
}

/**
* Clear expired cache entries
*/
public clearExpired(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.expiryTime) {
this.cache.delete(key);
}
}
}

/**
* Clear all cache entries
*/
public clear(): void {
this.cache.clear();
}

/**
* Invalidate cache for specific URI
* @param uri URI to invalidate
*/
public invalidate(uri: vscode.Uri): void {
const key = this.generateCacheKey(uri);
if (this.cache.has(key)) {
this.cache.delete(key);
console.log('Cache invalidated for:', uri.toString());
}
}

/**
* Get cache statistics
* @returns Object containing cache size and other statistics
*/
public getStats(): { size: number; expiryTime: number } {
return {
size: this.cache.size,
expiryTime: this.expiryTime
};
}

/**
* Start periodic cleanup of expired cache entries
*/
private startPeriodicCleanup(): void {
this.cleanupInterval = setInterval(() => {
this.clearExpired();
}, this.expiryTime);
}

/**
* Setup file system watcher for Java files to invalidate cache on changes
*/
private setupFileWatcher(): void {
this.fileWatcher = vscode.workspace.createFileSystemWatcher('**/*.java');

const invalidateHandler = (uri: vscode.Uri) => {
this.invalidate(uri);
};

this.fileWatcher.onDidChange(invalidateHandler);
this.fileWatcher.onDidDelete(invalidateHandler);
}

/**
* Dispose of all resources (intervals, watchers, etc.)
*/
public dispose(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = undefined;
}

if (this.fileWatcher) {
this.fileWatcher.dispose();
this.fileWatcher = undefined;
}

this.clear();
}
}

/**
* Default context cache instance
*/
export const contextCache = new ContextCache();
28 changes: 28 additions & 0 deletions src/copilot/context/copilotHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import { commands, Uri } from "vscode";
import { logger } from "../utils";

export interface INodeImportClass {
uri: string;
className: string; // Changed from 'class' to 'className' to match Java code
}
/**
* Helper class for Copilot integration to analyze Java project dependencies
*/
export namespace CopilotHelper {
/**
* Resolves all local project types imported by the given file
* @param fileUri The URI of the Java file to analyze
* @returns Array of strings in format "type:fully.qualified.name" where type is class|interface|enum|annotation
*/
export async function resolveLocalImports(fileUri: Uri): Promise<INodeImportClass[]> {
try {
return await commands.executeCommand("java.execute.workspaceCommand", "java.project.getImportClassContent", fileUri) || [];
} catch (error) {
logger.error("Error resolving copilot request:", error);
return [];
}
}
}
Loading