A Visual Studio Code extension for Pterodactyl
1import * as path from 'path';
2import * as fs from 'fs';
3import * as vscode from 'vscode';
4import { workspace, ExtensionContext } from 'vscode';
5import {
6 LanguageClient,
7 LanguageClientOptions,
8 ServerOptions,
9 TransportKind,
10 Trace
11} from 'vscode-languageclient/node';
12
13import { AssistantPanelController } from './AssistantPanelController';
14
15let client: LanguageClient | undefined;
16
17function executableExists(command: string): boolean {
18 if (path.isAbsolute(command)) {
19 return fs.existsSync(command);
20 }
21 const dirs = (process.env.PATH || '').split(path.delimiter);
22 return dirs.some(dir => fs.existsSync(path.join(dir, command)));
23}
24
25async function promptForServerPath(): Promise<void> {
26 const openSettings = 'Open Settings';
27 const action = await vscode.window.showWarningMessage(
28 'Pterodactyl language server not found. Please configure the path to the server executable.',
29 openSettings
30 );
31 if (action === openSettings) {
32 await vscode.commands.executeCommand('workbench.action.openSettings', 'pterodactyl.serverPath');
33 }
34}
35
36async function startServer(assistantPanelController: AssistantPanelController): Promise<void> {
37 if (client) {
38 await client.stop();
39 client = undefined;
40 }
41
42 const serverCommand = workspace.getConfiguration('pterodactyl').get<string>('serverPath')!;
43
44 if (!executableExists(serverCommand)) {
45 promptForServerPath();
46 return;
47 }
48
49 const serverOptions: ServerOptions = {
50 command: serverCommand,
51 args: [],
52 transport: TransportKind.stdio
53 };
54
55 const clientOptions: LanguageClientOptions = {
56 documentSelector: [{ scheme: 'file', language: 'pterodactyl' }],
57 synchronize: {
58 fileEvents: workspace.createFileSystemWatcher('**/*.ptero')
59 },
60 outputChannel: vscode.window.createOutputChannel("Pterodactyl Server"),
61 traceOutputChannel: vscode.window.createOutputChannel('LSP Trace'),
62 };
63
64 client = new LanguageClient(
65 'pterodactylLanguageServer',
66 'Pterodactyl Language Server',
67 serverOptions,
68 clientOptions
69 );
70
71 client.setTrace(Trace.Verbose);
72 client.start();
73
74 client.onTelemetry((data: any) => {
75 if (data?.type === 'assistantPanel' && typeof data.uri === 'string' && typeof data.html === 'string') {
76 assistantPanelController.updateHtml(data.uri, data.html);
77 }
78 });
79}
80
81export function activate(context: ExtensionContext) {
82 let assistantPanelController = new AssistantPanelController(context);
83
84 // Update panel whenever the active editor changes
85 context.subscriptions.push(
86 vscode.window.onDidChangeActiveTextEditor(editor => assistantPanelController.update(editor))
87 );
88
89 // Populate panel for the editor open at activation
90 assistantPanelController.update(vscode.window.activeTextEditor);
91
92 // Serialize startServer calls so that a rapid config change can't orphan a client.
93 let startServerQueue: Promise<void> = Promise.resolve();
94 const enqueueStartServer = () => {
95 startServerQueue = startServerQueue.then(() => startServer(assistantPanelController));
96 };
97
98 enqueueStartServer();
99
100 context.subscriptions.push(
101 workspace.onDidChangeConfiguration(e => {
102 if (e.affectsConfiguration('pterodactyl.serverPath')) {
103 enqueueStartServer();
104 }
105 })
106 );
107
108 context.subscriptions.push(
109 vscode.commands.registerCommand('pterodactyl.refreshDocument', async () => {
110 if (!client) {
111 return;
112 }
113 const editor = vscode.window.activeTextEditor;
114 if (!editor) {
115 return;
116 }
117 const uri = editor.document.uri.toString();
118 await client.sendRequest('workspace/executeCommand', {
119 command: 'refreshDocument',
120 arguments: [uri]
121 });
122 })
123 );
124
125 context.subscriptions.push(
126 vscode.commands.registerCommand('pterodactyl.refreshWorkspace', async () => {
127 if (!client) {
128 return;
129 }
130 const editor = vscode.window.activeTextEditor;
131 if (!editor) {
132 return;
133 }
134 const uri = editor.document.uri.toString();
135 await client.sendRequest('workspace/executeCommand', {
136 command: 'refreshWorkspace',
137 arguments: [uri]
138 });
139 })
140 );
141}
142
143export function deactivate(): Thenable<void> | undefined {
144 if (!client) {
145 return undefined;
146 }
147 return client.stop();
148}