Skip to content
Merged
Show file tree
Hide file tree
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
585 changes: 585 additions & 0 deletions src/config/vector-db.config.ts

Large diffs are not rendered by default.

300 changes: 174 additions & 126 deletions src/extension.ts

Large diffs are not rendered by default.

199 changes: 199 additions & 0 deletions src/infrastructure/configuration-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import * as vscode from "vscode";
import { Logger, LogLevel } from "../infrastructure/logger/logger";

/**
* Generic observer interface
*/
export interface Observer<T> {
update(data: T): void;
}

/**
* Observable configuration manager using the Observer pattern
*/
export class ConfigurationObservable<T> implements vscode.Disposable {
private observers: Observer<T>[] = [];
private logger: Logger;
private disposables: vscode.Disposable[] = [];

constructor(private configurationSection: string) {
this.logger = Logger.initialize("ConfigurationObservable", {
minLevel: LogLevel.INFO,
});

// Watch for configuration changes
const configWatcher = vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration(this.configurationSection)) {
this.notifyObservers();
}
});

this.disposables.push(configWatcher);
}

/**
* Subscribe an observer to configuration changes
*/
subscribe(observer: Observer<T>): vscode.Disposable {
this.observers.push(observer);
this.logger.info(`Observer subscribed to ${this.configurationSection} changes`);

return {
dispose: () => {
this.unsubscribe(observer);
},
};
}

/**
* Unsubscribe an observer from configuration changes
*/
unsubscribe(observer: Observer<T>): void {
const index = this.observers.indexOf(observer);
if (index >= 0) {
this.observers.splice(index, 1);
this.logger.info(`Observer unsubscribed from ${this.configurationSection} changes`);
}
}

/**
* Notify all observers of configuration changes
*/
private notifyObservers(): void {
const config = this.getCurrentConfiguration();
this.logger.info(`Notifying ${this.observers.length} observers of configuration change`);

for (const observer of this.observers) {
try {
observer.update(config);
} catch (error) {
this.logger.error("Error in configuration observer:", error);
}
}
}

/**
* Get current configuration - to be overridden by subclasses
*/
protected getCurrentConfiguration(): T {
const config = vscode.workspace.getConfiguration(this.configurationSection);
return config as unknown as T;
}

/**
* Manually trigger notification (useful for testing or forced updates)
*/
notify(): void {
this.notifyObservers();
}

/**
* Get the number of current observers
*/
getObserverCount(): number {
return this.observers.length;
}

/**
* Dispose of all resources
*/
dispose(): void {
this.logger.info("Disposing configuration observable");
this.observers.length = 0;
this.disposables.forEach((d) => d.dispose());
this.disposables.length = 0;
}
}

/**
* Specific observable for Vector Database Configuration
*/
export class VectorDbConfigObservable extends ConfigurationObservable<any> {
constructor() {
super("codebuddy.vectorDb");
}

protected getCurrentConfiguration(): any {
const config = vscode.workspace.getConfiguration("codebuddy.vectorDb");

return {
enabled: config.get("enabled", true),
embeddingModel: config.get("embeddingModel", "gemini"),
maxTokens: config.get("maxTokens", 6000),
batchSize: config.get("batchSize", 10),
searchResultLimit: config.get("searchResultLimit", 8),
enableBackgroundProcessing: config.get("enableBackgroundProcessing", true),
enableProgressNotifications: config.get("enableProgressNotifications", true),
progressLocation: config.get("progressLocation", "notification"),
debounceDelay: config.get("debounceDelay", 1000),
performanceMode: config.get("performanceMode", "balanced"),
fallbackToKeywordSearch: config.get("fallbackToKeywordSearch", true),
cacheEnabled: config.get("cacheEnabled", true),
logLevel: config.get("logLevel", "info"),
slowSearchThreshold: config.get("slowSearchThreshold", 2000),
};
}
}

/**
* Configuration change listener that implements Observer pattern
*/
export class ConfigurationChangeListener implements Observer<any> {
private logger: Logger;

constructor(
private name: string,
private callback: (config: any) => void
) {
this.logger = Logger.initialize(`ConfigListener_${name}`, {
minLevel: LogLevel.INFO,
});
}

update(config: any): void {
this.logger.info(`Configuration changed for ${this.name}`);
try {
this.callback(config);
} catch (error) {
this.logger.error(`Error in configuration callback for ${this.name}:`, error);
}
}
}

/**
* Factory for creating configuration observers
*/
export class ConfigurationObserverFactory {
private static observables = new Map<string, ConfigurationObservable<any>>();

/**
* Get or create an observable for a configuration section
*/
static getObservable<T>(section: string): ConfigurationObservable<T> {
if (!this.observables.has(section)) {
if (section === "codebuddy.vectorDb") {
this.observables.set(section, new VectorDbConfigObservable());
} else {
this.observables.set(section, new ConfigurationObservable<T>(section));
}
}
return this.observables.get(section) as ConfigurationObservable<T>;
}

/**
* Create a configuration listener
*/
static createListener<T>(name: string, callback: (config: T) => void): Observer<T> {
return new ConfigurationChangeListener(name, callback);
}

/**
* Dispose of all observables
*/
static disposeAll(): void {
for (const observable of this.observables.values()) {
observable.dispose();
}
this.observables.clear();
}
}
143 changes: 143 additions & 0 deletions src/infrastructure/dependency-container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { IDependencyContainer } from "../interfaces/services.interface";
import { Logger, LogLevel } from "../infrastructure/logger/logger";

/**
* Simple dependency injection container for vector database services
*/
export class DependencyContainer implements IDependencyContainer {
private services = new Map<string, { factory: () => any; singleton: boolean; instance?: any }>();
private logger: Logger;

constructor() {
this.logger = Logger.initialize("DependencyContainer", {
minLevel: LogLevel.INFO,
});
}

/**
* Register a transient service (new instance each time)
*/
register<T>(token: string, factory: () => T): void {
this.services.set(token, { factory, singleton: false });
this.logger.info(`Registered transient service: ${token}`);
}

/**
* Register a singleton service (same instance always)
*/
registerSingleton<T>(token: string, factory: () => T): void {
this.services.set(token, { factory, singleton: true });
this.logger.info(`Registered singleton service: ${token}`);
}

/**
* Resolve a service from the container
*/
resolve<T>(token: string): T {
const serviceInfo = this.services.get(token);
if (!serviceInfo) {
throw new Error(`Service '${token}' is not registered in the container`);
}

if (serviceInfo.singleton) {
if (!serviceInfo.instance) {
serviceInfo.instance = serviceInfo.factory();
this.logger.info(`Created singleton instance for: ${token}`);
}
return serviceInfo.instance;
} else {
this.logger.debug(`Creating new instance for: ${token}`);
return serviceInfo.factory();
}
}

/**
* Check if a service is registered
*/
isRegistered(token: string): boolean {
return this.services.has(token);
}

/**
* Get all registered service tokens
*/
getRegisteredServices(): string[] {
return Array.from(this.services.keys());
}

/**
* Dispose of all singleton instances and clear the container
*/
dispose(): void {
this.logger.info("Disposing dependency container...");

// Dispose of singleton instances that have a dispose method
for (const [token, serviceInfo] of this.services) {
if (serviceInfo.singleton && serviceInfo.instance) {
const instance = serviceInfo.instance;
if (typeof instance.dispose === "function") {
try {
instance.dispose();
this.logger.info(`Disposed singleton service: ${token}`);
} catch (error) {
this.logger.error(`Error disposing service ${token}:`, error);
}
}
}
}

this.services.clear();
this.logger.info("Dependency container disposed");
}
}

/**
* Service tokens for dependency injection
*/
export const ServiceTokens = {
// Core services
VECTOR_DATABASE_SERVICE: "IVectorDatabaseService",
VECTOR_DB_WORKER_MANAGER: "IVectorDbWorkerManager",
VECTOR_DB_SYNC_SERVICE: "IVectorDbSync",

// Context and search
SMART_CONTEXT_EXTRACTOR: "ISmartContextExtractor",
CODE_INDEXER: "ICodeIndexer",

// User interaction
USER_FEEDBACK_SERVICE: "IUserFeedbackService",
CONFIGURATION_MANAGER: "IConfigurationManager",

// Orchestration
EMBEDDING_ORCHESTRATOR: "IEmbeddingOrchestrator",

// External dependencies
EXTENSION_CONTEXT: "vscode.ExtensionContext",
API_KEY: "ApiKey",
LOGGER: "Logger",
} as const;

/**
* Global dependency container instance
*/
let globalContainer: DependencyContainer | null = null;

/**
* Get or create the global dependency container
*/
export function getContainer(): DependencyContainer {
if (!globalContainer) {
globalContainer = new DependencyContainer();
}
return globalContainer;
}

/**
* Dispose of the global container
*/
export function disposeContainer(): void {
if (globalContainer) {
globalContainer.dispose();
globalContainer = null;
}
}
Loading