import * as path from 'path'; import * as fs from 'fs'; import * as vscode from 'vscode'; import { workspace, ExtensionContext } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, Trace } from 'vscode-languageclient/node'; import { AssistantPanelController } from './AssistantPanelController'; let client: LanguageClient | undefined; function executableExists(command: string): boolean { if (path.isAbsolute(command)) { return fs.existsSync(command); } const dirs = (process.env.PATH || '').split(path.delimiter); return dirs.some(dir => fs.existsSync(path.join(dir, command))); } async function promptForServerPath(): Promise { const openSettings = 'Open Settings'; const action = await vscode.window.showWarningMessage( 'Pterodactyl language server not found. Please configure the path to the server executable.', openSettings ); if (action === openSettings) { await vscode.commands.executeCommand('workbench.action.openSettings', 'pterodactyl.serverPath'); } } async function startServer(assistantPanelController: AssistantPanelController): Promise { if (client) { await client.stop(); client = undefined; } const serverCommand = workspace.getConfiguration('pterodactyl').get('serverPath')!; if (!executableExists(serverCommand)) { promptForServerPath(); return; } const serverOptions: ServerOptions = { command: serverCommand, args: [], transport: TransportKind.stdio }; const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'pterodactyl' }], synchronize: { fileEvents: workspace.createFileSystemWatcher('**/*.ptero') }, outputChannel: vscode.window.createOutputChannel("Pterodactyl Server"), traceOutputChannel: vscode.window.createOutputChannel('LSP Trace'), }; client = new LanguageClient( 'pterodactylLanguageServer', 'Pterodactyl Language Server', serverOptions, clientOptions ); client.setTrace(Trace.Verbose); client.start(); client.onTelemetry((data: any) => { if (data?.type === 'assistantPanel' && typeof data.uri === 'string' && typeof data.html === 'string') { assistantPanelController.updateHtml(data.uri, data.html); } }); } export function activate(context: ExtensionContext) { let assistantPanelController = new AssistantPanelController(context); // Update panel whenever the active editor changes context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(editor => assistantPanelController.update(editor)) ); // Populate panel for the editor open at activation assistantPanelController.update(vscode.window.activeTextEditor); // Serialize startServer calls so that a rapid config change can't orphan a client. let startServerQueue: Promise = Promise.resolve(); const enqueueStartServer = () => { startServerQueue = startServerQueue.then(() => startServer(assistantPanelController)); }; enqueueStartServer(); context.subscriptions.push( workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('pterodactyl.serverPath')) { enqueueStartServer(); } }) ); context.subscriptions.push( vscode.commands.registerCommand('pterodactyl.refreshDocument', async () => { if (!client) { return; } const editor = vscode.window.activeTextEditor; if (!editor) { return; } const uri = editor.document.uri.toString(); await client.sendRequest('workspace/executeCommand', { command: 'refreshDocument', arguments: [uri] }); }) ); context.subscriptions.push( vscode.commands.registerCommand('pterodactyl.refreshWorkspace', async () => { if (!client) { return; } const editor = vscode.window.activeTextEditor; if (!editor) { return; } const uri = editor.document.uri.toString(); await client.sendRequest('workspace/executeCommand', { command: 'refreshWorkspace', arguments: [uri] }); }) ); } export function deactivate(): Thenable | undefined { if (!client) { return undefined; } return client.stop(); }