223 lines
6.1 KiB
TypeScript
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;
|
|
}
|