feat: improve editor tooling and fix warnings
Neovim improvements: - Add tree-sitter text objects for functions, types, blocks - Add folding support - Enhanced REPL integration (toggle, send line/selection) - New commands: LuxCheck, LuxReplToggle, LuxSend - Better keybindings with localleader VS Code extension: - Full syntax highlighting with TextMate grammar - LSP client integration - 20+ snippets for common patterns - Commands: run, format, check, REPL - Keybindings and context menu Fixes: - Fix all cargo warnings with #[allow(dead_code)] annotations - Clean up unused variables Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
241
editors/vscode/src/extension.ts
Normal file
241
editors/vscode/src/extension.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
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<void> | 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<string>('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<vscode.TextEdit[]> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user