import * as vscode from 'vscode';
import { DaemonClient } from './daemon/client';
import { DaemonProcess } from './daemon/process';
import { ChatViewProvider } from './chat/provider';
import { StatusBarManager } from './status';
import { AtomCodeActionProvider } from './editor/actions';
import { DiffContentProvider } from './editor/diff';
import { getEditorContext, buildContextualPrompt } from './editor/context';
import { getConfig, DEFAULT_PORT } from './config';
class ExtensionState {
client!: DaemonClient;
daemonProcess!: DaemonProcess;
chatProvider!: ChatViewProvider;
statusBar!: StatusBarManager;
healthCheckInterval: ReturnType<typeof setInterval> | null = null;
}
const extensionState = new ExtensionState();
export async function activate(context: vscode.ExtensionContext) {
const config = getConfig();
extensionState.client = new DaemonClient(config.daemonPort);
extensionState.daemonProcess = new DaemonProcess(extensionState.client, context.extensionUri, {
defaultPort: config.daemonPort,
binaryPath: config.binaryPath,
autoStart: config.autoStart,
});
let reconnecting: Promise<boolean> | null = null;
extensionState.client.onConnectionLost = async () => {
if (!reconnecting) {
reconnecting = extensionState.daemonProcess.ensureRunning().finally(() => { reconnecting = null; });
}
return reconnecting;
};
extensionState.statusBar = new StatusBarManager();
context.subscriptions.push({ dispose: () => extensionState.statusBar.dispose() });
extensionState.chatProvider = new ChatViewProvider(context.extensionUri, extensionState.client);
context.subscriptions.push({ dispose: () => extensionState.chatProvider.dispose() });
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(ChatViewProvider.viewType, extensionState.chatProvider, {
webviewOptions: { retainContextWhenHidden: true },
})
);
const diffProvider = new DiffContentProvider();
context.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider('atomcode-original', diffProvider)
);
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider('*', new AtomCodeActionProvider(), {
providedCodeActionKinds: AtomCodeActionProvider.providedCodeActionKinds,
})
);
const cmds = [
vscode.commands.registerCommand('atomcode.openSidebar', async () => {
await runCommand('open AtomCode sidebar', () => extensionState.chatProvider.openInSidebar());
}),
vscode.commands.registerCommand('atomcode.openTab', () => {
extensionState.chatProvider.openInTab();
}),
vscode.commands.registerCommand('atomcode.openPreferredLocation', async () => {
await runCommand('open AtomCode', () => extensionState.chatProvider.openPreferredLocation());
}),
vscode.commands.registerCommand('atomcode.focusInput', async () => {
await runCommand('focus AtomCode input', () => extensionState.chatProvider.focusInput());
}),
vscode.commands.registerCommand('atomcode.newConversation', async () => {
await runCommand('start a new AtomCode conversation', () => extensionState.chatProvider.newConversation());
}),
vscode.commands.registerCommand('atomcode.stop', () => {
extensionState.chatProvider.stopGeneration();
}),
vscode.commands.registerCommand('atomcode.explain', async () => {
const ctx = getEditorContext();
const prompt = buildContextualPrompt('Please explain this code. What does it do, and why?', ctx);
await runCommand('explain the selected code', () => extensionState.chatProvider.sendEditorCommandMessage(prompt));
}),
vscode.commands.registerCommand('atomcode.fix', async () => {
const ctx = getEditorContext();
const prompt = buildContextualPrompt('Please fix any bugs or issues in this code.', ctx);
await runCommand('fix the selected code', () => extensionState.chatProvider.sendEditorCommandMessage(prompt));
}),
vscode.commands.registerCommand('atomcode.optimize', async () => {
const ctx = getEditorContext();
const prompt = buildContextualPrompt('Please optimize this code for better performance and readability.', ctx);
await runCommand('optimize the selected code', () => extensionState.chatProvider.sendEditorCommandMessage(prompt));
}),
vscode.commands.registerCommand('atomcode.addToChat', async () => {
const ctx = getEditorContext();
if (!ctx.selection || !ctx.filePath) return;
await runCommand('add selection to chat', () => extensionState.chatProvider.addToChat({
path: ctx.filePath!,
fileName: ctx.fileName!,
language: ctx.language,
selection: ctx.selection,
startLine: ctx.startLine,
endLine: ctx.endLine,
}));
}),
];
context.subscriptions.push(...cmds);
context.subscriptions.push(
vscode.window.registerWebviewPanelSerializer('atomcode.chatTab', {
async deserializeWebviewPanel(panel: vscode.WebviewPanel, state: any) {
const sessionId = state?.sessionId as string | undefined;
const projectHash = state?.projectHash as string | undefined;
extensionState.chatProvider.setupPanelForRestore(panel, sessionId, projectHash);
},
})
);
context.subscriptions.push(
vscode.commands.registerCommand('atomcode.openSessionInTab', async (sessionId?: string, projectHash?: string) => {
await extensionState.chatProvider.openSessionInTab(sessionId, projectHash);
})
);
void initializeDaemon();
extensionState.chatProvider.onModelSelected = (model: string) => {
extensionState.statusBar.update(true, model);
};
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor(() => {
extensionState.chatProvider.sendEditorContext();
})
);
extensionState.healthCheckInterval = setInterval(async () => {
const isRunning = await extensionState.client.isRunning();
extensionState.statusBar.update(isRunning, undefined, undefined);
}, 30000);
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('atomcode')) {
const newConfig = vscode.workspace.getConfiguration('atomcode');
const newPort = newConfig.get<number>('daemon.port', 13456);
if (newPort !== config.daemonPort) {
vscode.window.showInformationMessage('AtomCode: Restart VS Code to apply port change.');
}
}
})
);
}
async function runCommand(label: string, command: () => Thenable<unknown> | Promise<unknown> | unknown) {
try {
await command();
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
vscode.window.showErrorMessage(`AtomCode failed to ${label}: ${message}`);
}
}
async function initializeDaemon() {
try {
const connected = await extensionState.daemonProcess.ensureRunning();
extensionState.statusBar.update(connected);
if (connected) {
try {
const models = await extensionState.client.listModels();
const defaultModel = models.find(m => m.is_default);
if (defaultModel) extensionState.statusBar.update(true, defaultModel.model);
} catch {
}
}
} catch (e) {
extensionState.statusBar.update(false);
const message = e instanceof Error ? e.message : String(e);
vscode.window.showWarningMessage(`AtomCode daemon startup failed: ${message}`);
}
}
export function deactivate() {
if (extensionState.healthCheckInterval) {
clearInterval(extensionState.healthCheckInterval);
extensionState.healthCheckInterval = null;
}
extensionState.daemonProcess.dispose();
}