223 lines
6.1 KiB
TypeScript

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<void> {
await stopClient();
}
async function startClient(context: vscode.ExtensionContext): Promise<void> {
clearReconnectTimer();
if (client) {
output.appendLine("Prometeu LSP client is already running.");
return;
}
const config = vscode.workspace.getConfiguration("prometeu.studio");
const host = config.get<string>("host", "127.0.0.1");
const port = config.get<number>("port", 7775);
output.show(true);
output.appendLine(`Connecting to Prometeu Studio LSP at ${host}:${port}...`);
const serverOptions: ServerOptions = async (): Promise<StreamInfo> => {
return new Promise<StreamInfo>((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<void> {
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;
}