import * as vscode from 'vscode'; import * as path from 'path'; import { spawn, ChildProcess } from 'child_process'; import { LanguageClient, LanguageClientOptions, ServerOptions, } from 'vscode-languageclient/node'; let client: LanguageClient | undefined; let replTerminal: vscode.Terminal | undefined; export function activate(context: vscode.ExtensionContext) { console.log('Lux extension activated'); // Start LSP if enabled const config = vscode.workspace.getConfiguration('lux'); if (config.get('lsp.enabled', true)) { startLspClient(context); } // Register commands context.subscriptions.push( vscode.commands.registerCommand('lux.run', runCurrentFile), vscode.commands.registerCommand('lux.format', formatDocument), vscode.commands.registerCommand('lux.check', checkFile), vscode.commands.registerCommand('lux.startRepl', startRepl), vscode.commands.registerCommand('lux.sendToRepl', sendToRepl), vscode.commands.registerCommand('lux.restartLsp', () => restartLspClient(context)) ); // Format on save if enabled if (config.get('format.onSave', false)) { context.subscriptions.push( vscode.workspace.onWillSaveTextDocument((event) => { if (event.document.languageId === 'lux') { event.waitUntil(formatDocumentAsync(event.document)); } }) ); } } export function deactivate(): Thenable | undefined { if (replTerminal) { replTerminal.dispose(); } if (client) { return client.stop(); } return undefined; } function getLuxBinary(): string { const config = vscode.workspace.getConfiguration('lux'); const customPath = config.get('lspPath'); if (customPath && customPath.trim() !== '') { return customPath; } // Try common locations const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; const possiblePaths = [ workspaceFolder ? path.join(workspaceFolder, 'result', 'bin', 'lux') : null, workspaceFolder ? path.join(workspaceFolder, 'target', 'release', 'lux') : null, workspaceFolder ? path.join(workspaceFolder, 'target', 'debug', 'lux') : null, ].filter(Boolean) as string[]; // Check if any exist for (const p of possiblePaths) { try { require('fs').accessSync(p, require('fs').constants.X_OK); return p; } catch { // Continue to next } } // Fall back to PATH return 'lux'; } function startLspClient(context: vscode.ExtensionContext) { const luxBinary = getLuxBinary(); const serverOptions: ServerOptions = { command: luxBinary, args: ['lsp'], }; const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'lux' }], synchronize: { fileEvents: vscode.workspace.createFileSystemWatcher('**/*.lux'), }, }; client = new LanguageClient( 'luxLanguageServer', 'Lux Language Server', serverOptions, clientOptions ); client.start(); } async function restartLspClient(context: vscode.ExtensionContext) { if (client) { await client.stop(); } startLspClient(context); vscode.window.showInformationMessage('Lux language server restarted'); } async function runCurrentFile() { const editor = vscode.window.activeTextEditor; if (!editor || editor.document.languageId !== 'lux') { vscode.window.showErrorMessage('No Lux file is active'); return; } // Save the file first await editor.document.save(); const filePath = editor.document.uri.fsPath; const luxBinary = getLuxBinary(); // Create terminal and run const terminal = vscode.window.createTerminal('Lux Run'); terminal.show(); terminal.sendText(`${luxBinary} "${filePath}"`); } async function formatDocument() { const editor = vscode.window.activeTextEditor; if (!editor || editor.document.languageId !== 'lux') { vscode.window.showErrorMessage('No Lux file is active'); return; } await editor.document.save(); const formatted = await formatDocumentAsync(editor.document); if (formatted.length > 0) { const edit = new vscode.WorkspaceEdit(); edit.set(editor.document.uri, formatted); await vscode.workspace.applyEdit(edit); } } async function formatDocumentAsync(document: vscode.TextDocument): Promise { const luxBinary = getLuxBinary(); const filePath = document.uri.fsPath; return new Promise((resolve) => { const process = spawn(luxBinary, ['fmt', filePath]); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data; }); process.stderr.on('data', (data) => { stderr += data; }); process.on('close', (code) => { if (code === 0) { // File was formatted, need to reload resolve([]); } else { vscode.window.showErrorMessage(`Format error: ${stderr}`); resolve([]); } }); }); } async function checkFile() { const editor = vscode.window.activeTextEditor; if (!editor || editor.document.languageId !== 'lux') { vscode.window.showErrorMessage('No Lux file is active'); return; } await editor.document.save(); const filePath = editor.document.uri.fsPath; const luxBinary = getLuxBinary(); const process = spawn(luxBinary, ['check', filePath]); let stderr = ''; process.stderr.on('data', (data) => { stderr += data; }); process.on('close', (code) => { if (code === 0) { vscode.window.showInformationMessage('No type errors found'); } else { vscode.window.showErrorMessage(`Type check failed:\n${stderr}`); } }); } function startRepl() { if (replTerminal) { replTerminal.dispose(); } const luxBinary = getLuxBinary(); replTerminal = vscode.window.createTerminal({ name: 'Lux REPL', shellPath: luxBinary, }); replTerminal.show(); } async function sendToRepl() { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showErrorMessage('No active editor'); return; } // Get selection or current line let text: string; if (editor.selection.isEmpty) { text = editor.document.lineAt(editor.selection.active.line).text; } else { text = editor.document.getText(editor.selection); } // Start REPL if not running if (!replTerminal) { startRepl(); // Wait a bit for REPL to start await new Promise(resolve => setTimeout(resolve, 500)); } if (replTerminal) { replTerminal.sendText(text); replTerminal.show(); } }