import * as vscode from 'vscode'; import * as path from 'path'; import { CommandParser, CommandInfo, CommandParameter } from './commandParser'; import { ASHESDiagnosticProvider } from './diagnosticProvider'; // Cache for dynamically loaded commands let ASHES_COMMANDS: Array<{name: string, description: string, parameters: CommandParameter[], filePath?: string}> = []; let COMMAND_CACHE_TIMESTAMP = 0; // Built-in variables const BUILTIN_VARIABLES = [ 'CURRENT_PLAYER', 'ESC_LAST_SCENE', 'ESC_CURRENT_SCENE', 'FORCE_LAST_SCENE_NULL', 'ANIMATION_RESOURCES' ]; // Keywords const KEYWORDS = [ 'var', 'global', 'if', 'elif', 'else', 'while', 'break', 'done', 'stop', 'pass', 'true', 'false', 'nil', 'and', 'or', 'not', 'in', 'is', 'active' ]; /** * Load commands dynamically from the project */ function loadCommands(workspaceRoot: string): Array<{name: string, description: string, parameters: CommandParameter[], filePath?: string}> { try { const parser = new CommandParser(workspaceRoot); const commands = parser.getCommandsForExtension(); // Update cache timestamp COMMAND_CACHE_TIMESTAMP = Date.now(); return commands; } catch (error) { console.error('Error loading commands:', error); // Return empty array if loading fails return []; } } /** * Get commands, using cache if available and not too old */ function getCommands(workspaceRoot: string): Array<{name: string, description: string, parameters: CommandParameter[], filePath?: string}> { const now = Date.now(); const cacheAge = now - COMMAND_CACHE_TIMESTAMP; const maxCacheAge = 5 * 60 * 1000; // 5 minutes // Reload commands if cache is empty or too old if (ASHES_COMMANDS.length === 0 || cacheAge > maxCacheAge) { ASHES_COMMANDS = loadCommands(workspaceRoot); } return ASHES_COMMANDS; } export function activate(context: vscode.ExtensionContext) { console.log('ASHES Language Support extension is now active!'); // Initialize diagnostic provider for syntax error detection const diagnosticProvider = new ASHESDiagnosticProvider(); diagnosticProvider.initialize(context); // Register completion provider const completionProvider = vscode.languages.registerCompletionItemProvider( 'ashes', { provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) { const completions: vscode.CompletionItem[] = []; // Get workspace root const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath; if (!workspaceRoot) { return completions; } // Get dynamic commands const commands = getCommands(workspaceRoot); // Add command completions commands.forEach(command => { const completion = new vscode.CompletionItem(command.name, vscode.CompletionItemKind.Function); completion.detail = command.description; // Create detailed parameter documentation let paramDocs = ''; let exampleUsage = ''; if (command.parameters && command.parameters.length > 0) { paramDocs = '\n\n**Parameters:**\n'; const exampleParams: string[] = []; command.parameters.forEach((param, index) => { // Enhanced parameter formatting with clear indicators const requiredIndicator = param.required ? '**Required**' : '**Optional**'; const typeIndicator = `\`${param.type}\``; const nameIndicator = `**${param.name}**`; const defaultValue = param.defaultValue ? ` *(default: \`${param.defaultValue}\`)*` : ''; paramDocs += `- ${requiredIndicator} ${nameIndicator} (${typeIndicator})${defaultValue}\n`; // Create example parameter with clear formatting const exampleParam = param.required ? `${param.name}: ${param.type}` : `[${param.name}: ${param.type}]`; exampleParams.push(exampleParam); }); // Create example usage exampleUsage = `\n\n**Example:**\n\`${command.name}(${exampleParams.join(', ')})\``; } completion.documentation = new vscode.MarkdownString( `## ${command.name}\n\n---\n\n${command.description}${paramDocs}${exampleUsage}` ); // Create snippet with parameter placeholders const snippetParams = command.parameters.map((param, index) => `\${${index + 1}:${param.name}}`).join(', '); completion.insertText = new vscode.SnippetString(`${command.name}(${snippetParams})`); completions.push(completion); }); // Add built-in variable completions BUILTIN_VARIABLES.forEach(variable => { const completion = new vscode.CompletionItem(variable, vscode.CompletionItemKind.Variable); completion.detail = 'Built-in variable'; completion.documentation = new vscode.MarkdownString(`Built-in ASHES variable: **${variable}**`); completions.push(completion); }); // Add keyword completions KEYWORDS.forEach(keyword => { const completion = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); completion.detail = 'ASHES keyword'; completions.push(completion); }); return completions; } }, ' ', '(', '$' // Trigger characters ); // Register hover provider for commands const hoverProvider = vscode.languages.registerHoverProvider( 'ashes', { provideHover(document: vscode.TextDocument, position: vscode.Position) { const word = document.getText(document.getWordRangeAtPosition(position)); // Get workspace root const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath; if (!workspaceRoot) { return null; } // Get dynamic commands const commands = getCommands(workspaceRoot); const command = commands.find(cmd => cmd.name === word); if (command) { // Create detailed parameter documentation let paramDocs = ''; let exampleUsage = ''; if (command.parameters && command.parameters.length > 0) { paramDocs = '\n\n**Parameters:**\n'; const exampleParams: string[] = []; command.parameters.forEach((param, index) => { // Enhanced parameter formatting with clear indicators const requiredIndicator = param.required ? '**Required**' : '**Optional**'; const typeIndicator = `\`${param.type}\``; const nameIndicator = `**${param.name}**`; const defaultValue = param.defaultValue ? ` *(default: \`${param.defaultValue}\`)*` : ''; paramDocs += `- ${requiredIndicator} ${nameIndicator} (${typeIndicator})${defaultValue}\n`; // Create example parameter with clear formatting const exampleParam = param.required ? `${param.name}: ${param.type}` : `[${param.name}: ${param.type}]`; exampleParams.push(exampleParam); }); // Create example usage exampleUsage = `\n\n**Example:**\n\`${command.name}(${exampleParams.join(', ')})\``; } const hover = new vscode.Hover( new vscode.MarkdownString( `## ${command.name}\n\n---\n\n${command.description}${paramDocs}${exampleUsage}` ) ); return hover; } const builtinVar = BUILTIN_VARIABLES.find(variable => variable === word); if (builtinVar) { const hover = new vscode.Hover( new vscode.MarkdownString(`Built-in ASHES variable: **${builtinVar}**`) ); return hover; } return null; } } ); // Register definition provider for Ctrl+click navigation const definitionProvider = vscode.languages.registerDefinitionProvider( 'ashes', { provideDefinition(document: vscode.TextDocument, position: vscode.Position) { const word = document.getText(document.getWordRangeAtPosition(position)); // Get workspace root const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath; if (!workspaceRoot) { return null; } // Get dynamic commands const commands = getCommands(workspaceRoot); const command = commands.find(cmd => cmd.name === word); if (command && command.filePath) { // Create a location pointing to the command file const uri = vscode.Uri.file(command.filePath); // Try to find the class definition line in the file try { const fs = require('fs'); const content = fs.readFileSync(command.filePath, 'utf8'); const lines = content.split('\n'); // Look for the class definition line for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.includes('class_name') && line.includes(command.name)) { return new vscode.Location(uri, new vscode.Position(i, 0)); } } // If no class definition found, return the beginning of the file return new vscode.Location(uri, new vscode.Position(0, 0)); } catch (error) { console.error('Error reading command file:', error); return new vscode.Location(uri, new vscode.Position(0, 0)); } } return null; } } ); // Register command for showing command reference const showCommandReference = vscode.commands.registerCommand('ashes.showCommandReference', () => { const panel = vscode.window.createWebviewPanel( 'ashesCommandReference', 'ASHES Command Reference', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true } ); // Get workspace root from active editor const activeEditor = vscode.window.activeTextEditor; const workspaceRoot = activeEditor ? vscode.workspace.getWorkspaceFolder(activeEditor.document.uri)?.uri.fsPath : vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (!workspaceRoot) { panel.webview.html = `

ASHES Command Reference

No workspace found. Please open a workspace to view commands.

`; return; } // Get dynamic commands const commands = getCommands(workspaceRoot); // Handle messages from the webview panel.webview.onDidReceiveMessage( message => { switch (message.command) { case 'navigateToCommand': if (message.commandName && workspaceRoot) { const command = commands.find(cmd => cmd.name === message.commandName); if (command && command.filePath) { const uri = vscode.Uri.file(command.filePath); // Try to find the class definition line try { const fs = require('fs'); const content = fs.readFileSync(command.filePath, 'utf8'); const lines = content.split('\n'); // Look for the class definition line for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.includes('class_name') && line.includes(command.name)) { vscode.window.showTextDocument(uri, { selection: new vscode.Range(i, 0, i, 0) }); return; } } // If no class definition found, open at the beginning vscode.window.showTextDocument(uri); } catch (error) { console.error('Error opening command file:', error); vscode.window.showTextDocument(uri); } } } return; } }, undefined, context.subscriptions ); const commandsHtml = commands.map(command => { let paramInfo = ''; let exampleUsage = ''; if (command.parameters && command.parameters.length > 0) { const exampleParams: string[] = []; paramInfo = command.parameters.map(param => { // Enhanced parameter formatting with clear indicators const requiredIndicator = param.required ? 'Required' : 'Optional'; const typeIndicator = `${param.type}`; const nameIndicator = `${param.name}`; const defaultValue = param.defaultValue ? ` (default: ${param.defaultValue})` : ''; // Create example parameter const exampleParam = param.required ? `${param.name}: ${param.type}` : `[${param.name}: ${param.type}]`; exampleParams.push(exampleParam); return `${requiredIndicator} ${nameIndicator} (${typeIndicator})${defaultValue}`; }).join('
'); // Create example usage exampleUsage = `

Example:
${command.name}(${exampleParams.join(', ')})`; } return ` ${command.name} ${command.description}${exampleUsage} ${paramInfo} `; }).join(''); panel.webview.html = `

ASHES Command Reference

💡 Tip: Click on any command name to navigate to its source file!
${commandsHtml}
Command Description Parameters
`; }); // Register command for refreshing command cache const refreshCommands = vscode.commands.registerCommand('ashes.refreshCommands', () => { // Clear cache to force reload ASHES_COMMANDS = []; COMMAND_CACHE_TIMESTAMP = 0; // Also refresh diagnostics diagnosticProvider.refreshAllDiagnostics(); vscode.window.showInformationMessage('ASHES commands cache and diagnostics refreshed!'); }); // Register command for refreshing diagnostics only const refreshDiagnostics = vscode.commands.registerCommand('ashes.refreshDiagnostics', () => { diagnosticProvider.refreshAllDiagnostics(); vscode.window.showInformationMessage('ASHES diagnostics refreshed!'); }); context.subscriptions.push(completionProvider, hoverProvider, definitionProvider, showCommandReference, refreshCommands, refreshDiagnostics); } export function deactivate() {}