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:
2026-02-13 18:23:55 -05:00
parent ebc0bdb109
commit a6eb349d59
14 changed files with 1437 additions and 9 deletions

View 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();
}
}