import * as net from "node:net"; import * as vscode from "vscode"; import { CloseAction, ErrorAction, LanguageClient, LanguageClientOptions, ServerOptions, State, StreamInfo } from "vscode-languageclient/node"; let client: LanguageClient | undefined; let output: vscode.OutputChannel; let reconnectTimer: NodeJS.Timeout | undefined; let reconnectContext: vscode.ExtensionContext | undefined; let stoppingClient = false; const RECONNECT_DELAY_MS = 2000; export function activate(context: vscode.ExtensionContext): void { reconnectContext = context; output = vscode.window.createOutputChannel("Prometeu LSP"); output.appendLine("Prometeu VS Code extension activated."); const connectCommand = vscode.commands.registerCommand( "prometeu.connectStudioLsp", async () => { await startClient(context); } ); const restartCommand = vscode.commands.registerCommand( "prometeu.restartStudioLsp", async () => { await stopClient(); await startClient(context); } ); context.subscriptions.push(output, connectCommand, restartCommand); void startClient(context); } export async function deactivate(): Promise { await stopClient(); } async function startClient(context: vscode.ExtensionContext): Promise { clearReconnectTimer(); if (client) { output.appendLine("Prometeu LSP client is already running."); return; } const config = vscode.workspace.getConfiguration("prometeu.studio"); const host = config.get("host", "127.0.0.1"); const port = config.get("port", 7775); output.show(true); output.appendLine(`Connecting to Prometeu Studio LSP at ${host}:${port}...`); const serverOptions: ServerOptions = async (): Promise => { return new Promise((resolve, reject) => { const socket = net.createConnection({ host, port }); let settled = false; const connectTimeout = setTimeout(() => { if (settled) { return; } settled = true; socket.destroy(); reject(new Error("Connection timed out.")); }, 3000); socket.once("connect", () => { if (settled) { return; } settled = true; clearTimeout(connectTimeout); output.appendLine("TCP connection established with Prometeu Studio."); resolve({ reader: socket, writer: socket }); }); socket.once("error", (error) => { if (settled) { return; } settled = true; clearTimeout(connectTimeout); reject(error); }); }); }; const clientOptions: LanguageClientOptions = { documentSelector: [ { scheme: "file", language: "pbs" } ], synchronize: { fileEvents: [ vscode.workspace.createFileSystemWatcher("**/*.pbs") ] }, initializationOptions: { clientKind: "vscode", prometeuClientVersion: context.extension.packageJSON.version }, outputChannel: output, errorHandler: { error: (error) => { output.appendLine(`Prometeu LSP transport error: ${error.message}`); return { action: ErrorAction.Continue }; }, closed: () => { output.appendLine("Prometeu LSP transport closed."); return { action: CloseAction.DoNotRestart }; } } }; const nextClient = new LanguageClient( "prometeuStudioLsp", "Prometeu Studio LSP", serverOptions, clientOptions ); client = nextClient; nextClient.onDidChangeState((event) => { output.appendLine(`Prometeu LSP state changed: ${State[event.oldState]} -> ${State[event.newState]}`); if (event.newState !== State.Stopped) { return; } if (client === nextClient) { client = undefined; } if (!stoppingClient) { scheduleReconnect(); } }); try { await nextClient.start(); output.appendLine("Prometeu Studio LSP client started."); vscode.window.showInformationMessage("Connected to Prometeu Studio LSP."); } catch (error) { if (client === nextClient) { client = undefined; } const message = error instanceof Error ? error.message : String(error); output.appendLine(`Failed to start Prometeu LSP client: ${message}`); vscode.window.showErrorMessage(`Could not connect to Prometeu Studio LSP: ${message}`); scheduleReconnect(); } } async function stopClient(): Promise { clearReconnectTimer(); if (!client) { return; } const currentClient = client; client = undefined; stoppingClient = true; output.appendLine("Stopping Prometeu Studio LSP client..."); try { await currentClient.stop(); output.appendLine("Prometeu Studio LSP client stopped."); } catch (error) { const message = error instanceof Error ? error.message : String(error); output.appendLine(`Failed to stop Prometeu LSP client cleanly: ${message}`); } finally { stoppingClient = false; } } function scheduleReconnect(): void { if (!reconnectContext) { return; } if (reconnectTimer || client) { return; } output.appendLine(`Scheduling Prometeu LSP reconnect in ${RECONNECT_DELAY_MS}ms...`); reconnectTimer = setTimeout(() => { reconnectTimer = undefined; void startClient(reconnectContext!); }, RECONNECT_DELAY_MS); } function clearReconnectTimer(): void { if (!reconnectTimer) { return; } clearTimeout(reconnectTimer); reconnectTimer = undefined; }