Dynamic commands
This commit is contained in:
@@ -1,358 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { CommandParser, CommandInfo, CommandParameter } from './commandParser';
|
||||
|
||||
// ASHES commands with their descriptions and parameters
|
||||
const ASHES_COMMANDS = [
|
||||
{
|
||||
name: 'accept_input',
|
||||
description: 'Accept specific input types',
|
||||
parameters: ['input_type']
|
||||
},
|
||||
{
|
||||
name: 'anim',
|
||||
description: 'Play animation on object',
|
||||
parameters: ['object_id', 'animation_name']
|
||||
},
|
||||
{
|
||||
name: 'anim_block',
|
||||
description: 'Play animation and wait for completion',
|
||||
parameters: ['object_id', 'animation_name']
|
||||
},
|
||||
{
|
||||
name: 'block_say',
|
||||
description: 'Start a block of say commands',
|
||||
parameters: []
|
||||
},
|
||||
{
|
||||
name: 'camera_push',
|
||||
description: 'Push camera to new position',
|
||||
parameters: ['x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'camera_push_block',
|
||||
description: 'Push camera and wait for completion',
|
||||
parameters: ['x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_limits',
|
||||
description: 'Set camera movement limits',
|
||||
parameters: ['left', 'top', 'right', 'bottom']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_pos',
|
||||
description: 'Set camera position',
|
||||
parameters: ['x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_pos_block',
|
||||
description: 'Set camera position and wait',
|
||||
parameters: ['x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_target',
|
||||
description: 'Set camera target',
|
||||
parameters: ['object_id']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_target_block',
|
||||
description: 'Set camera target and wait',
|
||||
parameters: ['object_id']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_zoom',
|
||||
description: 'Set camera zoom level',
|
||||
parameters: ['zoom_level']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_zoom_block',
|
||||
description: 'Set camera zoom and wait',
|
||||
parameters: ['zoom_level']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_zoom_height',
|
||||
description: 'Set camera zoom height',
|
||||
parameters: ['height']
|
||||
},
|
||||
{
|
||||
name: 'camera_set_zoom_height_block',
|
||||
description: 'Set camera zoom height and wait',
|
||||
parameters: ['height']
|
||||
},
|
||||
{
|
||||
name: 'camera_shift',
|
||||
description: 'Shift camera position',
|
||||
parameters: ['x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'camera_shift_block',
|
||||
description: 'Shift camera and wait',
|
||||
parameters: ['x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'change_scene',
|
||||
description: 'Change to a different scene',
|
||||
parameters: ['scene_path', 'enable_transition', 'run_events']
|
||||
},
|
||||
{
|
||||
name: 'custom',
|
||||
description: 'Execute custom command',
|
||||
parameters: ['command_name', '...args']
|
||||
},
|
||||
{
|
||||
name: 'dec_global',
|
||||
description: 'Decrement global variable',
|
||||
parameters: ['variable_name']
|
||||
},
|
||||
{
|
||||
name: 'enable_terrain',
|
||||
description: 'Enable/disable terrain',
|
||||
parameters: ['terrain_name', 'enabled']
|
||||
},
|
||||
{
|
||||
name: 'end_block_say',
|
||||
description: 'End a block of say commands',
|
||||
parameters: []
|
||||
},
|
||||
{
|
||||
name: 'hide_menu',
|
||||
description: 'Hide menu',
|
||||
parameters: ['menu_name']
|
||||
},
|
||||
{
|
||||
name: 'inc_global',
|
||||
description: 'Increment global variable',
|
||||
parameters: ['variable_name']
|
||||
},
|
||||
{
|
||||
name: 'inventory_add',
|
||||
description: 'Add item to inventory',
|
||||
parameters: ['item_id']
|
||||
},
|
||||
{
|
||||
name: 'inventory_remove',
|
||||
description: 'Remove item from inventory',
|
||||
parameters: ['item_id']
|
||||
},
|
||||
{
|
||||
name: 'item_count_add',
|
||||
description: 'Add to item count',
|
||||
parameters: ['item_id', 'count']
|
||||
},
|
||||
{
|
||||
name: 'play_lib_snd',
|
||||
description: 'Play library sound',
|
||||
parameters: ['filename', 'namespace']
|
||||
},
|
||||
{
|
||||
name: 'play_snd',
|
||||
description: 'Play sound file',
|
||||
parameters: ['sound_path', 'type']
|
||||
},
|
||||
{
|
||||
name: 'play_video',
|
||||
description: 'Play video file',
|
||||
parameters: ['video_path']
|
||||
},
|
||||
{
|
||||
name: 'print',
|
||||
description: 'Print debug message',
|
||||
parameters: ['message']
|
||||
},
|
||||
{
|
||||
name: 'print_internal',
|
||||
description: 'Print internal message',
|
||||
parameters: ['message']
|
||||
},
|
||||
{
|
||||
name: 'queue_event',
|
||||
description: 'Queue event for later execution',
|
||||
parameters: ['object_id', 'event_name']
|
||||
},
|
||||
{
|
||||
name: 'queue_resource',
|
||||
description: 'Queue resource for loading',
|
||||
parameters: ['resource_path']
|
||||
},
|
||||
{
|
||||
name: 'rand_global',
|
||||
description: 'Set random value to global',
|
||||
parameters: ['variable_name', 'min', 'max']
|
||||
},
|
||||
{
|
||||
name: 'repeat',
|
||||
description: 'Repeat command',
|
||||
parameters: ['count', 'command']
|
||||
},
|
||||
{
|
||||
name: 'save_game',
|
||||
description: 'Save game state',
|
||||
parameters: ['save_name']
|
||||
},
|
||||
{
|
||||
name: 'say',
|
||||
description: 'Display dialog text',
|
||||
parameters: ['speaker', 'text', 'translation_key', 'type']
|
||||
},
|
||||
{
|
||||
name: 'say_last_dialog_option',
|
||||
description: 'Say the last dialog option',
|
||||
parameters: []
|
||||
},
|
||||
{
|
||||
name: 'say_random',
|
||||
description: 'Say random text from list',
|
||||
parameters: ['speaker', 'list_id', 'length']
|
||||
},
|
||||
{
|
||||
name: 'say_sequence',
|
||||
description: 'Say text sequence',
|
||||
parameters: ['speaker', 'list_id', 'length', 'loop']
|
||||
},
|
||||
{
|
||||
name: 'sched_event',
|
||||
description: 'Schedule event for later',
|
||||
parameters: ['delay', 'object_id', 'event_name']
|
||||
},
|
||||
{
|
||||
name: 'set_active',
|
||||
description: 'Set object active/inactive',
|
||||
parameters: ['object_id', 'active']
|
||||
},
|
||||
{
|
||||
name: 'set_active_if_exists',
|
||||
description: 'Set object active if it exists',
|
||||
parameters: ['object_id', 'active']
|
||||
},
|
||||
{
|
||||
name: 'set_angle',
|
||||
description: 'Set object angle',
|
||||
parameters: ['object_id', 'angle']
|
||||
},
|
||||
{
|
||||
name: 'set_animations',
|
||||
description: 'Set object animations',
|
||||
parameters: ['object_id', 'animations']
|
||||
},
|
||||
{
|
||||
name: 'set_direction',
|
||||
description: 'Set object direction',
|
||||
parameters: ['object_id', 'direction']
|
||||
},
|
||||
{
|
||||
name: 'set_global',
|
||||
description: 'Set global variable',
|
||||
parameters: ['variable_name', 'value', 'force']
|
||||
},
|
||||
{
|
||||
name: 'set_globals',
|
||||
description: 'Set multiple global variables',
|
||||
parameters: ['variables_dict']
|
||||
},
|
||||
{
|
||||
name: 'set_gui_visible',
|
||||
description: 'Set GUI visibility',
|
||||
parameters: ['visible']
|
||||
},
|
||||
{
|
||||
name: 'set_interactive',
|
||||
description: 'Set object interactive state',
|
||||
parameters: ['object_id', 'interactive']
|
||||
},
|
||||
{
|
||||
name: 'set_item_custom_data',
|
||||
description: 'Set item custom data',
|
||||
parameters: ['item_id', 'key', 'value']
|
||||
},
|
||||
{
|
||||
name: 'set_speed',
|
||||
description: 'Set object speed',
|
||||
parameters: ['object_id', 'speed']
|
||||
},
|
||||
{
|
||||
name: 'set_state',
|
||||
description: 'Set object state',
|
||||
parameters: ['object_id', 'state']
|
||||
},
|
||||
{
|
||||
name: 'set_tooltip',
|
||||
description: 'Set object tooltip',
|
||||
parameters: ['object_id', 'action', 'text']
|
||||
},
|
||||
{
|
||||
name: 'show_menu',
|
||||
description: 'Show menu',
|
||||
parameters: ['menu_name']
|
||||
},
|
||||
{
|
||||
name: 'slide',
|
||||
description: 'Slide object to position',
|
||||
parameters: ['object_id', 'x', 'y', 'duration']
|
||||
},
|
||||
{
|
||||
name: 'slide_block',
|
||||
description: 'Slide object and wait',
|
||||
parameters: ['object_id', 'x', 'y', 'duration']
|
||||
},
|
||||
{
|
||||
name: 'spawn',
|
||||
description: 'Spawn object',
|
||||
parameters: ['object_id', 'x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'stop',
|
||||
description: 'Stop current event',
|
||||
parameters: []
|
||||
},
|
||||
{
|
||||
name: 'stop_snd',
|
||||
description: 'Stop sound',
|
||||
parameters: ['sound_type']
|
||||
},
|
||||
{
|
||||
name: 'teleport',
|
||||
description: 'Teleport object to target',
|
||||
parameters: ['object_id', 'target_id']
|
||||
},
|
||||
{
|
||||
name: 'teleport_pos',
|
||||
description: 'Teleport object to position',
|
||||
parameters: ['object_id', 'x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'transition',
|
||||
description: 'Play transition effect',
|
||||
parameters: ['transition_type', 'duration']
|
||||
},
|
||||
{
|
||||
name: 'turn_to',
|
||||
description: 'Turn object to face target',
|
||||
parameters: ['object_id', 'target_id']
|
||||
},
|
||||
{
|
||||
name: 'wait',
|
||||
description: 'Wait for specified time',
|
||||
parameters: ['duration']
|
||||
},
|
||||
{
|
||||
name: 'walk',
|
||||
description: 'Walk object to target',
|
||||
parameters: ['object_id', 'target_id']
|
||||
},
|
||||
{
|
||||
name: 'walk_block',
|
||||
description: 'Walk object and wait',
|
||||
parameters: ['object_id', 'target_id']
|
||||
},
|
||||
{
|
||||
name: 'walk_to_pos',
|
||||
description: 'Walk object to position',
|
||||
parameters: ['object_id', 'x', 'y']
|
||||
},
|
||||
{
|
||||
name: 'walk_to_pos_block',
|
||||
description: 'Walk object to position and wait',
|
||||
parameters: ['object_id', 'x', 'y']
|
||||
}
|
||||
];
|
||||
// Cache for dynamically loaded commands
|
||||
let ASHES_COMMANDS: Array<{name: string, description: string, parameters: CommandParameter[]}> = [];
|
||||
let COMMAND_CACHE_TIMESTAMP = 0;
|
||||
|
||||
// Built-in variables
|
||||
const BUILTIN_VARIABLES = [
|
||||
@@ -369,6 +21,41 @@ const KEYWORDS = [
|
||||
'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[]}> {
|
||||
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[]}> {
|
||||
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!');
|
||||
|
||||
@@ -379,14 +66,52 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
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
|
||||
ASHES_COMMANDS.forEach(command => {
|
||||
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) => {
|
||||
const required = param.required ? '**' : '';
|
||||
const optional = param.required ? '' : ' (optional)';
|
||||
const defaultValue = param.defaultValue ? ` (default: ${param.defaultValue})` : '';
|
||||
paramDocs += `- ${required}${param.name}${required} (${param.type})${optional}${defaultValue}\n`;
|
||||
|
||||
// Create example parameter
|
||||
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${command.description}\n\n**Parameters:** ${command.parameters.join(', ')}`
|
||||
`## ${command.name}\n\n---\n\n${command.description}${paramDocs}${exampleUsage}`
|
||||
);
|
||||
completion.insertText = new vscode.SnippetString(`${command.name}($1)`);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
@@ -417,12 +142,46 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
{
|
||||
provideHover(document: vscode.TextDocument, position: vscode.Position) {
|
||||
const word = document.getText(document.getWordRangeAtPosition(position));
|
||||
const command = ASHES_COMMANDS.find(cmd => cmd.name === word);
|
||||
|
||||
// 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) => {
|
||||
const required = param.required ? '**' : '';
|
||||
const optional = param.required ? '' : ' (optional)';
|
||||
const defaultValue = param.defaultValue ? ` (default: ${param.defaultValue})` : '';
|
||||
paramDocs += `- ${required}${param.name}${required} (${param.type})${optional}${defaultValue}\n`;
|
||||
|
||||
// Create example parameter
|
||||
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${command.description}\n\n**Parameters:** ${command.parameters.join(', ')}`
|
||||
`## ${command.name}\n\n---\n\n${command.description}${paramDocs}${exampleUsage}`
|
||||
)
|
||||
);
|
||||
return hover;
|
||||
@@ -450,13 +209,65 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
{}
|
||||
);
|
||||
|
||||
const commandsHtml = ASHES_COMMANDS.map(command =>
|
||||
`<tr>
|
||||
// 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 = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: var(--vscode-font-family); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ASHES Command Reference</h1>
|
||||
<p>No workspace found. Please open a workspace to view commands.</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get dynamic commands
|
||||
const commands = getCommands(workspaceRoot);
|
||||
|
||||
const commandsHtml = commands.map(command => {
|
||||
let paramInfo = '';
|
||||
let exampleUsage = '';
|
||||
|
||||
if (command.parameters && command.parameters.length > 0) {
|
||||
const exampleParams: string[] = [];
|
||||
|
||||
paramInfo = command.parameters.map(param => {
|
||||
const required = param.required ? '<strong>' : '';
|
||||
const requiredEnd = param.required ? '</strong>' : '';
|
||||
const optional = param.required ? '' : ' (optional)';
|
||||
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 `${required}${param.name}${requiredEnd} (${param.type})${optional}${defaultValue}`;
|
||||
}).join('<br>');
|
||||
|
||||
// Create example usage
|
||||
exampleUsage = `<br><br><strong>Example:</strong><br><code>${command.name}(${exampleParams.join(', ')})</code>`;
|
||||
}
|
||||
|
||||
return `<tr>
|
||||
<td><code>${command.name}</code></td>
|
||||
<td>${command.description}</td>
|
||||
<td><code>${command.parameters.join(', ')}</code></td>
|
||||
</tr>`
|
||||
).join('');
|
||||
<td>${command.description}${exampleUsage}</td>
|
||||
<td>${paramInfo}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
panel.webview.html = `
|
||||
<!DOCTYPE html>
|
||||
@@ -489,7 +300,16 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
`;
|
||||
});
|
||||
|
||||
context.subscriptions.push(completionProvider, hoverProvider, showCommandReference);
|
||||
// Register command for refreshing command cache
|
||||
const refreshCommands = vscode.commands.registerCommand('ashes.refreshCommands', () => {
|
||||
// Clear cache to force reload
|
||||
ASHES_COMMANDS = [];
|
||||
COMMAND_CACHE_TIMESTAMP = 0;
|
||||
|
||||
vscode.window.showInformationMessage('ASHES commands cache refreshed!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(completionProvider, hoverProvider, showCommandReference, refreshCommands);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
Reference in New Issue
Block a user