Dynamic commands
This commit is contained in:
76
vscode-extension-ashes/DYNAMIC_COMMANDS.md
Normal file
76
vscode-extension-ashes/DYNAMIC_COMMANDS.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Dynamic Command Generation
|
||||
|
||||
The ASHES VSCode extension now supports dynamic command generation by parsing the `project.godot` file and extracting command information from the specified command directories.
|
||||
|
||||
## How it Works
|
||||
|
||||
1. **Project.godot Parsing**: The extension reads the `project.godot` file and looks for the `main/command_directories` setting in the `[escoria]` section.
|
||||
|
||||
2. **Command Discovery**: It scans each directory specified in `command_directories` for `.gd` files (excluding `.gd.uid` files).
|
||||
|
||||
3. **Command Parsing**: For each command file, it extracts:
|
||||
- Command name (from filename)
|
||||
- Description (from comment blocks)
|
||||
- Parameters (from comment documentation and `configure()` method)
|
||||
- Examples (if available)
|
||||
|
||||
4. **Caching**: Commands are cached for 5 minutes to improve performance.
|
||||
|
||||
## Configuration
|
||||
|
||||
The extension automatically detects command directories from your `project.godot` file:
|
||||
|
||||
```ini
|
||||
[escoria]
|
||||
main/command_directories=["res://addons/escoria-core/game/core-scripts/esc/commands", "res://addons/escoria-ui-return-monkey-island/esc/commands", "res://addons/escoria-ui-return-monkey-island-dialog-simple/commands"]
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-completion**: Commands are automatically available in IntelliSense
|
||||
- **Hover Documentation**: Hover over commands to see descriptions and parameters
|
||||
- **Command Reference**: Use `Ctrl+Shift+P` → "ASHES: Show ASHES Command Reference" to see all commands
|
||||
- **Cache Refresh**: Use `Ctrl+Shift+P` → "ASHES: Refresh ASHES Commands" to reload commands
|
||||
|
||||
## Command File Format
|
||||
|
||||
Commands should follow the standard Escoria format:
|
||||
|
||||
```gdscript
|
||||
# `command_name param1 param2 [optional_param]`
|
||||
#
|
||||
# Description of what the command does.
|
||||
#
|
||||
# **Parameters**
|
||||
#
|
||||
# - *param1*: Description of parameter 1
|
||||
# - *param2*: Description of parameter 2
|
||||
# - *optional_param*: Description of optional parameter (default: default_value)
|
||||
#
|
||||
# Example: `command_name("value1", "value2")`
|
||||
#
|
||||
# @ESC
|
||||
extends ESCBaseCommand
|
||||
class_name CommandNameCommand
|
||||
|
||||
func configure() -> ESCCommandArgumentDescriptor:
|
||||
return ESCCommandArgumentDescriptor.new(
|
||||
2, # Number of required parameters
|
||||
[TYPE_STRING, TYPE_STRING, TYPE_STRING], # Parameter types
|
||||
[null, null, "default_value"], # Default values
|
||||
[true, true, false] # Required flags
|
||||
)
|
||||
```
|
||||
|
||||
## Fallback Behavior
|
||||
|
||||
If the extension cannot find or parse the `project.godot` file, it falls back to the default command directories:
|
||||
- `res://addons/escoria-core/game/core-scripts/esc/commands`
|
||||
- `res://addons/escoria-ui-return-monkey-island/esc/commands`
|
||||
- `res://addons/escoria-ui-return-monkey-island-dialog-simple/commands`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Commands not appearing**: Make sure your `project.godot` file has the correct `main/command_directories` setting
|
||||
- **Outdated commands**: Use the "Refresh ASHES Commands" command to reload the cache
|
||||
- **Missing descriptions**: Ensure your command files have proper comment documentation
|
||||
355
vscode-extension-ashes/HOWTO.md
Normal file
355
vscode-extension-ashes/HOWTO.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# ASHES Language Support - HowTo Guide
|
||||
|
||||
This guide provides comprehensive instructions for configuring and using the ASHES Language Support extension for Visual Studio Code.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Installation & Configuration](#installation--configuration)
|
||||
2. [Available Commands & Functionality](#available-commands--functionality)
|
||||
3. [Language Features](#language-features)
|
||||
4. [Code Snippets](#code-snippets)
|
||||
5. [Auto-completion & IntelliSense](#auto-completion--intellisense)
|
||||
6. [Syntax Highlighting](#syntax-highlighting)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Installation & Configuration
|
||||
|
||||
### Quick Installation
|
||||
|
||||
1. **Copy the extension folder** to your VS Code extensions directory:
|
||||
- **Linux**: `~/.vscode/extensions/`
|
||||
- **macOS**: `~/.vscode/extensions/`
|
||||
- **Windows**: `%USERPROFILE%\.vscode\extensions\`
|
||||
|
||||
2. **Rename the folder** to `ashes-language-support-0.1.0`
|
||||
|
||||
3. **Reload VS Code** or restart the application
|
||||
|
||||
4. **Verify installation** by opening any `.esc` file - you should see "ASHES" in the language selector (bottom-right corner)
|
||||
|
||||
### Development Mode Installation
|
||||
|
||||
1. **Open VS Code** in the extension directory:
|
||||
```bash
|
||||
cd vscode-extension-ashes
|
||||
code .
|
||||
```
|
||||
|
||||
2. **Install dependencies**:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Compile TypeScript**:
|
||||
```bash
|
||||
npm run compile
|
||||
```
|
||||
|
||||
4. **Press F5** to run the extension in a new Extension Development Host window
|
||||
|
||||
### Configuration Options
|
||||
|
||||
The extension automatically configures itself when you open `.esc` files. No additional configuration is required, but you can customize:
|
||||
|
||||
- **File associations**: Ensure `.esc` files are associated with the ASHES language
|
||||
- **Editor settings**: Configure indentation, word wrap, etc. in VS Code settings
|
||||
- **Theme compatibility**: The extension works with all VS Code themes
|
||||
|
||||
## Available Commands & Functionality
|
||||
|
||||
### VSCode Commands
|
||||
|
||||
#### `ASHES: Show Command Reference`
|
||||
- **Access**: `Ctrl+Shift+P` → Type "ASHES: Show Command Reference"
|
||||
- **Description**: Opens a webview panel with a complete reference of all ASHES commands
|
||||
- **Features**:
|
||||
- Searchable table of commands
|
||||
- Parameter information
|
||||
- Command descriptions
|
||||
|
||||
### ASHES Language Commands
|
||||
|
||||
The extension supports **60+ ASHES commands** organized by category:
|
||||
|
||||
#### Animation Commands
|
||||
- `anim(object_id, animation_name)` - Play animation on object
|
||||
- `anim_block(object_id, animation_name)` - Play animation and wait for completion
|
||||
- `set_animations(object_id, animations)` - Set object animations
|
||||
|
||||
#### Camera Commands
|
||||
- `camera_push(x, y)` - Push camera to new position
|
||||
- `camera_set_pos(x, y)` - Set camera position
|
||||
- `camera_set_target(object_id)` - Set camera target
|
||||
- `camera_set_zoom(zoom_level)` - Set camera zoom level
|
||||
- `camera_shift(x, y)` - Shift camera position
|
||||
|
||||
#### Dialog & Text Commands
|
||||
- `say(speaker, text, translation_key, type)` - Display dialog text
|
||||
- `say_random(speaker, list_id, length)` - Say random text from list
|
||||
- `say_sequence(speaker, list_id, length, loop)` - Say text sequence
|
||||
- `block_say()` - Start a block of say commands
|
||||
- `end_block_say()` - End a block of say commands
|
||||
|
||||
#### Game State Commands
|
||||
- `change_scene(scene_path, enable_transition, run_events)` - Change to a different scene
|
||||
- `save_game(save_name)` - Save game state
|
||||
- `set_global(variable_name, value, force)` - Set global variable
|
||||
- `set_globals(variables_dict)` - Set multiple global variables
|
||||
- `inc_global(variable_name)` - Increment global variable
|
||||
- `dec_global(variable_name)` - Decrement global variable
|
||||
|
||||
#### Inventory Commands
|
||||
- `inventory_add(item_id)` - Add item to inventory
|
||||
- `inventory_remove(item_id)` - Remove item from inventory
|
||||
- `item_count_add(item_id, count)` - Add to item count
|
||||
- `set_item_custom_data(item_id, key, value)` - Set item custom data
|
||||
|
||||
#### Object Manipulation Commands
|
||||
- `set_active(object_id, active)` - Set object active/inactive
|
||||
- `set_interactive(object_id, interactive)` - Set object interactive state
|
||||
- `set_state(object_id, state)` - Set object state
|
||||
- `set_tooltip(object_id, action, text)` - Set object tooltip
|
||||
- `teleport(object_id, target_id)` - Teleport object to target
|
||||
- `teleport_pos(object_id, x, y)` - Teleport object to position
|
||||
- `walk(object_id, target_id)` - Walk object to target
|
||||
- `walk_to_pos(object_id, x, y)` - Walk object to position
|
||||
|
||||
#### Audio/Video Commands
|
||||
- `play_snd(sound_path, type)` - Play sound file
|
||||
- `play_lib_snd(filename, namespace)` - Play library sound
|
||||
- `play_video(video_path)` - Play video file
|
||||
- `stop_snd(sound_type)` - Stop sound
|
||||
|
||||
#### Utility Commands
|
||||
- `print(message)` - Print debug message
|
||||
- `wait(duration)` - Wait for specified time
|
||||
- `transition(transition_type, duration)` - Play transition effect
|
||||
- `custom(command_name, ...args)` - Execute custom command
|
||||
|
||||
### Built-in Variables
|
||||
|
||||
The extension provides auto-completion for these built-in variables:
|
||||
- `CURRENT_PLAYER` - Current player object
|
||||
- `ESC_LAST_SCENE` - Last scene identifier
|
||||
- `ESC_CURRENT_SCENE` - Current scene identifier
|
||||
- `FORCE_LAST_SCENE_NULL` - Force last scene to null
|
||||
- `ANIMATION_RESOURCES` - Animation resources
|
||||
|
||||
### Keywords
|
||||
|
||||
Auto-completion for ASHES keywords:
|
||||
- `var` - Local variable declaration
|
||||
- `global` - Global variable declaration
|
||||
- `if`, `elif`, `else` - Conditional statements
|
||||
- `while` - Loop statement
|
||||
- `break`, `done` - Loop control
|
||||
- `stop`, `pass` - Flow control
|
||||
- `true`, `false`, `nil` - Boolean values
|
||||
- `and`, `or`, `not` - Logical operators
|
||||
- `in`, `is`, `active` - Special operators
|
||||
|
||||
## Language Features
|
||||
|
||||
### Events
|
||||
```ashes
|
||||
:event_name
|
||||
# Event code here
|
||||
|
||||
:event_with_target "target_id"
|
||||
# Event code here
|
||||
|
||||
:event_with_flags | NO_UI | NO_TT
|
||||
# Event code here
|
||||
```
|
||||
|
||||
### Variables
|
||||
```ashes
|
||||
# Local variables
|
||||
var my_variable = "value"
|
||||
|
||||
# Global variables
|
||||
global game_state = "playing"
|
||||
|
||||
# Global IDs (with $ prefix)
|
||||
set_active($player, true)
|
||||
```
|
||||
|
||||
### Control Flow
|
||||
```ashes
|
||||
if condition:
|
||||
# Code here
|
||||
elif other_condition:
|
||||
# Code here
|
||||
else:
|
||||
# Code here
|
||||
|
||||
while condition:
|
||||
# Code here
|
||||
break # Exit loop
|
||||
# or
|
||||
done # Continue to next iteration
|
||||
```
|
||||
|
||||
### Dialog System
|
||||
```ashes
|
||||
?!
|
||||
- "Choice 1"
|
||||
# Code for choice 1
|
||||
- "Choice 2" [condition]
|
||||
# Code for choice 2 (only if condition is true)
|
||||
```
|
||||
|
||||
### Comments
|
||||
```ashes
|
||||
# This is a line comment
|
||||
```
|
||||
|
||||
## Code Snippets
|
||||
|
||||
The extension provides **20+ code snippets** for common ASHES patterns:
|
||||
|
||||
### Event Snippets
|
||||
- `event` → Create new event
|
||||
- `eventtarget` → Create event with target
|
||||
- `eventflags` → Create event with flags
|
||||
- `eventflagstarget` → Create event with flags and target
|
||||
|
||||
### Command Snippets
|
||||
- `say` → Say command
|
||||
- `setglobal` → Set global variable
|
||||
- `changescene` → Change scene
|
||||
- `setactive` → Set object active
|
||||
- `teleport` → Teleport object
|
||||
- `walk` → Walk object
|
||||
- `playsnd` → Play sound
|
||||
- `playvideo` → Play video
|
||||
- `inventoryadd` → Add to inventory
|
||||
- `inventoryremove` → Remove from inventory
|
||||
|
||||
### Control Flow Snippets
|
||||
- `if` → If statement
|
||||
- `ifelse` → If-else statement
|
||||
- `while` → While loop
|
||||
|
||||
### Dialog Snippets
|
||||
- `dialog` → Dialog block with choice
|
||||
- `dialogif` → Dialog choice with condition
|
||||
|
||||
### Utility Snippets
|
||||
- `var` → Local variable declaration
|
||||
- `global` → Global variable declaration
|
||||
- `comment` → Add comment
|
||||
- `print` → Print debug message
|
||||
|
||||
### Using Snippets
|
||||
1. Type the snippet prefix (e.g., `event`)
|
||||
2. Press `Tab` to expand
|
||||
3. Use `Tab` to navigate between placeholders
|
||||
4. Press `Enter` or `Escape` to finish
|
||||
|
||||
## Auto-completion & IntelliSense
|
||||
|
||||
### Trigger Characters
|
||||
Auto-completion is triggered by:
|
||||
- Space character (` `)
|
||||
- Opening parenthesis (`(`)
|
||||
- Dollar sign (`$`) for global IDs
|
||||
|
||||
### Command Completion
|
||||
- Type any ASHES command and press `Ctrl+Space`
|
||||
- Commands show with descriptions and parameter hints
|
||||
- Parameters are automatically inserted with placeholders
|
||||
|
||||
### Variable Completion
|
||||
- Built-in variables are automatically suggested
|
||||
- Global IDs with `$` prefix are suggested
|
||||
- Keywords are suggested in appropriate contexts
|
||||
|
||||
### Hover Information
|
||||
- Hover over any command to see detailed information
|
||||
- Shows command description and parameter list
|
||||
- Works for built-in variables and keywords
|
||||
|
||||
## Syntax Highlighting
|
||||
|
||||
The extension provides comprehensive syntax highlighting:
|
||||
|
||||
### Color Scheme
|
||||
- **Events** (`:event_name`) - Blue
|
||||
- **Commands** (`say`, `set_global`, etc.) - Green
|
||||
- **Variables** (`var`, `global`) - Orange
|
||||
- **Strings** (`"text"`) - Yellow
|
||||
- **Comments** (`# comment`) - Gray
|
||||
- **Dialog blocks** (`?!`) - Special highlighting
|
||||
- **Global IDs** (`$object_id`) - Distinct color
|
||||
|
||||
### Smart Indentation
|
||||
- Automatic indentation for events, control flow, and dialog blocks
|
||||
- Proper outdenting for `break`, `done`, `else`, etc.
|
||||
- Configurable through VS Code settings
|
||||
|
||||
### Code Folding
|
||||
- Events can be folded for better organization
|
||||
- Dialog blocks can be folded
|
||||
- Folding markers: `:event_name` (start), empty line (end)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension Not Loading
|
||||
1. Check that the folder is in the correct extensions directory
|
||||
2. Restart VS Code completely
|
||||
3. Check the Developer Console for errors (`Help > Toggle Developer Tools`)
|
||||
|
||||
### Syntax Highlighting Not Working
|
||||
1. Ensure the file has a `.esc` extension
|
||||
2. Check that the language is set to "ASHES" in the bottom-right corner
|
||||
3. Reload the window (`Ctrl+Shift+P` → "Developer: Reload Window")
|
||||
|
||||
### Auto-completion Not Working
|
||||
1. Press `Ctrl+Space` to manually trigger completion
|
||||
2. Check that the extension is activated (should show in the Extensions panel)
|
||||
3. Ensure you're typing in a `.esc` file
|
||||
|
||||
### Snippets Not Working
|
||||
1. Make sure you're in a `.esc` file
|
||||
2. Type the snippet prefix and press `Tab`
|
||||
3. Check that the language is set to "ASHES"
|
||||
|
||||
### Performance Issues
|
||||
1. Large files may cause slower auto-completion
|
||||
2. Consider splitting large `.esc` files into smaller ones
|
||||
3. Disable other extensions if needed
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Commands
|
||||
The extension supports custom commands through the `custom()` function:
|
||||
```ashes
|
||||
custom("my_custom_command", "param1", "param2")
|
||||
```
|
||||
|
||||
### File Organization
|
||||
- Use multiple `.esc` files for different scenes/characters
|
||||
- Organize events logically within files
|
||||
- Use comments to document complex logic
|
||||
|
||||
### Integration with Escoria
|
||||
- The extension is designed specifically for the Escoria framework
|
||||
- Commands correspond to Escoria's ASHES implementation
|
||||
- Global IDs should match your Escoria project structure
|
||||
|
||||
## Contributing
|
||||
|
||||
To contribute to the extension:
|
||||
1. Fork the repository
|
||||
2. Make your changes
|
||||
3. Test thoroughly with `.esc` files
|
||||
4. Submit a pull request
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check this HowTo guide first
|
||||
2. Review the main README.md
|
||||
3. Check the installation guide (INSTALL.md)
|
||||
4. Report issues through the project's issue tracker
|
||||
@@ -10,6 +10,7 @@ A Visual Studio Code extension that provides syntax highlighting and IntelliSens
|
||||
- **Code Snippets**: Pre-built snippets for common ASHES patterns
|
||||
- **Command Reference**: Built-in command reference panel
|
||||
- **Smart Indentation**: Proper indentation rules for ASHES code structure
|
||||
- **Dynamic Command Generation**: Automatically discovers and loads commands from your project's `project.godot` configuration - [Learn more about dynamic commands](DYNAMIC_COMMANDS.md)
|
||||
|
||||
## ASHES Language Features Supported
|
||||
|
||||
|
||||
@@ -54,12 +54,20 @@
|
||||
"command": "ashes.showCommandReference",
|
||||
"title": "Show ASHES Command Reference",
|
||||
"category": "ASHES"
|
||||
},
|
||||
{
|
||||
"command": "ashes.refreshCommands",
|
||||
"title": "Refresh ASHES Commands",
|
||||
"category": "ASHES"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "ashes.showCommandReference"
|
||||
},
|
||||
{
|
||||
"command": "ashes.refreshCommands"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
407
vscode-extension-ashes/src/commandParser.ts
Normal file
407
vscode-extension-ashes/src/commandParser.ts
Normal file
@@ -0,0 +1,407 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface CommandParameter {
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export interface CommandInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: CommandParameter[];
|
||||
example?: string;
|
||||
}
|
||||
|
||||
export class CommandParser {
|
||||
private projectRoot: string;
|
||||
|
||||
constructor(projectRoot: string) {
|
||||
this.projectRoot = projectRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse project.godot file to extract command directories
|
||||
*/
|
||||
private parseProjectGodot(): string[] {
|
||||
const projectGodotPath = path.join(this.projectRoot, 'project.godot');
|
||||
|
||||
if (!fs.existsSync(projectGodotPath)) {
|
||||
console.warn('project.godot not found, using default command directories');
|
||||
return [
|
||||
'res://addons/escoria-core/game/core-scripts/esc/commands',
|
||||
'res://addons/escoria-ui-return-monkey-island/esc/commands',
|
||||
'res://addons/escoria-ui-return-monkey-island-dialog-simple/commands'
|
||||
];
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(projectGodotPath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let inEscoriaSection = false;
|
||||
let commandDirectories: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
if (trimmedLine === '[escoria]') {
|
||||
inEscoriaSection = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inEscoriaSection && trimmedLine.startsWith('[')) {
|
||||
break; // End of escoria section
|
||||
}
|
||||
|
||||
if (inEscoriaSection && trimmedLine.startsWith('main/command_directories=')) {
|
||||
// Parse the array format: ["path1", "path2", "path3"]
|
||||
const arrayMatch = trimmedLine.match(/main\/command_directories=\[(.*)\]/);
|
||||
if (arrayMatch) {
|
||||
const pathsString = arrayMatch[1];
|
||||
// Extract quoted paths
|
||||
const pathMatches = pathsString.match(/"([^"]+)"/g);
|
||||
if (pathMatches) {
|
||||
commandDirectories = pathMatches.map(match => match.slice(1, -1)); // Remove quotes
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return commandDirectories.length > 0 ? commandDirectories : [
|
||||
'res://addons/escoria-core/game/core-scripts/esc/commands',
|
||||
'res://addons/escoria-ui-return-monkey-island/esc/commands',
|
||||
'res://addons/escoria-ui-return-monkey-island-dialog-simple/commands'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert res:// path to actual filesystem path
|
||||
*/
|
||||
private resPathToFsPath(resPath: string): string {
|
||||
if (resPath.startsWith('res://')) {
|
||||
return path.join(this.projectRoot, resPath.substring(6));
|
||||
}
|
||||
return path.join(this.projectRoot, resPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single command file to extract command information
|
||||
*/
|
||||
private parseCommandFile(filePath: string): CommandInfo | null {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Extract command name from filename
|
||||
const fileName = path.basename(filePath, '.gd');
|
||||
const commandName = fileName;
|
||||
|
||||
// Parse description from comments
|
||||
let description = '';
|
||||
let parameters: CommandParameter[] = [];
|
||||
let example = '';
|
||||
|
||||
let inParametersSection = false;
|
||||
let inExampleSection = false;
|
||||
let foundFirstDescription = false;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
// Skip empty lines
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract main description (first comment block)
|
||||
if (line.startsWith('# `') && line.includes('`')) {
|
||||
const match = line.match(/# `([^`]+)`/);
|
||||
if (match) {
|
||||
description = match[1];
|
||||
foundFirstDescription = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for description in subsequent comment lines (before parameters section)
|
||||
if (line.startsWith('#') && !inParametersSection && !inExampleSection) {
|
||||
const cleanLine = line.replace(/^#\s*/, '');
|
||||
|
||||
// Skip lines that are clearly not part of the main description
|
||||
if (cleanLine.startsWith('**') || cleanLine.startsWith('@') ||
|
||||
cleanLine.startsWith('Example:') || cleanLine.startsWith('e.g.') ||
|
||||
cleanLine.includes('*') && cleanLine.includes(':') ||
|
||||
cleanLine.includes('Constructor') || cleanLine.includes('Use look-ahead') ||
|
||||
cleanLine.includes('Return the descriptor') || cleanLine.includes('Validate whether') ||
|
||||
cleanLine.includes('Run the command') || cleanLine.includes('Function called when')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we haven't found the first description yet, this might be it
|
||||
if (!foundFirstDescription && cleanLine) {
|
||||
description = cleanLine;
|
||||
foundFirstDescription = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have a description and this line looks like continuation
|
||||
if (foundFirstDescription && cleanLine && !cleanLine.includes('*')) {
|
||||
// Stop if we hit code-related content
|
||||
if (cleanLine.includes('Constructor') || cleanLine.includes('Use look-ahead') ||
|
||||
cleanLine.includes('Return the descriptor') || cleanLine.includes('Validate whether') ||
|
||||
cleanLine.includes('Run the command') || cleanLine.includes('Function called when')) {
|
||||
break;
|
||||
}
|
||||
description += ' ' + cleanLine;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for parameters section
|
||||
if (line.includes('**Parameters**')) {
|
||||
inParametersSection = true;
|
||||
inExampleSection = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for example section
|
||||
if (line.includes('Example:')) {
|
||||
inParametersSection = false;
|
||||
inExampleSection = true;
|
||||
const exampleMatch = line.match(/Example:\s*(.+)/);
|
||||
if (exampleMatch) {
|
||||
example = exampleMatch[1];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse parameters (support both # - *param*: and # * *param*: formats)
|
||||
if (inParametersSection && (line.includes('# - *') || line.includes('# * *'))) {
|
||||
const paramMatch = line.match(/#\s*[-*]\s*\*([^*]+)\*:\s*(.+)/);
|
||||
if (paramMatch) {
|
||||
const paramName = paramMatch[1].trim();
|
||||
const paramDesc = paramMatch[2].trim();
|
||||
|
||||
// Try to determine if parameter is required
|
||||
const isRequired = !paramDesc.includes('default:') && !paramDesc.includes('(default:');
|
||||
|
||||
// Extract default value if present
|
||||
let defaultValue: string | undefined;
|
||||
const defaultMatch = paramDesc.match(/\(default:\s*([^)]+)\)/);
|
||||
if (defaultMatch) {
|
||||
defaultValue = defaultMatch[1].trim();
|
||||
}
|
||||
|
||||
// Determine parameter type from description
|
||||
let paramType = 'string';
|
||||
if (paramDesc.includes('boolean') || paramDesc.includes('true') || paramDesc.includes('false')) {
|
||||
paramType = 'boolean';
|
||||
} else if (paramDesc.includes('number') || paramDesc.includes('int') || paramDesc.includes('float')) {
|
||||
paramType = 'number';
|
||||
}
|
||||
|
||||
parameters.push({
|
||||
name: paramName,
|
||||
type: paramType,
|
||||
required: isRequired,
|
||||
defaultValue: defaultValue
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse configure() method to get more accurate parameter info
|
||||
if (line.includes('func configure()') && i + 5 < lines.length) {
|
||||
// Look for ESCCommandArgumentDescriptor in the next few lines
|
||||
for (let j = i + 1; j < Math.min(i + 15, lines.length); j++) {
|
||||
const configLine = lines[j].trim();
|
||||
if (configLine.includes('ESCCommandArgumentDescriptor.new(')) {
|
||||
// Parse the descriptor parameters
|
||||
const descriptorMatch = configLine.match(/ESCCommandArgumentDescriptor\.new\(\s*(\d+)/);
|
||||
if (descriptorMatch) {
|
||||
const minArgs = parseInt(descriptorMatch[1]);
|
||||
|
||||
// Look for type arrays, defaults, and required flags in subsequent lines
|
||||
let types: string[] = [];
|
||||
let defaults: string[] = [];
|
||||
let requiredFlags: boolean[] = [];
|
||||
|
||||
for (let k = j + 1; k < Math.min(j + 10, lines.length); k++) {
|
||||
const typeLine = lines[k].trim();
|
||||
|
||||
// Extract types array
|
||||
if (typeLine.includes('TYPE_') && typeLine.includes('[')) {
|
||||
const typeMatch = typeLine.match(/\[([^\]]+)\]/);
|
||||
if (typeMatch) {
|
||||
types = typeMatch[1].split(',').map(t => t.trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Extract defaults array
|
||||
if (typeLine.includes('null') && typeLine.includes('[') && !typeLine.includes('TYPE_')) {
|
||||
const defaultMatch = typeLine.match(/\[([^\]]+)\]/);
|
||||
if (defaultMatch) {
|
||||
defaults = defaultMatch[1].split(',').map(d => d.trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Extract required flags array (look for true/false pattern)
|
||||
if (typeLine.includes('true') && typeLine.includes('false') && typeLine.includes('[') && !typeLine.includes('TYPE_')) {
|
||||
const requiredMatch = typeLine.match(/\[([^\]]+)\]/);
|
||||
if (requiredMatch) {
|
||||
requiredFlags = requiredMatch[1].split(',').map(f => f.trim() === 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update or create parameters with accurate information
|
||||
const maxParams = Math.max(types.length, defaults.length);
|
||||
const newParameters: CommandParameter[] = [];
|
||||
|
||||
// Store original parameters from comments for name preservation
|
||||
const originalParams = [...parameters];
|
||||
|
||||
for (let p = 0; p < maxParams; p++) {
|
||||
const type = types[p] || 'TYPE_STRING';
|
||||
const defaultValue = defaults[p] || 'null';
|
||||
// If requiredFlags array is not provided, use minArgs to determine required parameters
|
||||
const isRequired = p < minArgs || (requiredFlags[p] !== undefined ? requiredFlags[p] : p < minArgs);
|
||||
|
||||
// Convert Godot types to readable types
|
||||
let paramType = 'string';
|
||||
if (type.includes('TYPE_BOOL')) {
|
||||
paramType = 'boolean';
|
||||
} else if (type.includes('TYPE_INT') || type.includes('TYPE_FLOAT')) {
|
||||
paramType = 'number';
|
||||
} else if (type.includes('TYPE_STRING')) {
|
||||
paramType = 'string';
|
||||
}
|
||||
|
||||
// Try to get parameter name from comments if available, otherwise generate one
|
||||
let paramName = `param${p + 1}`;
|
||||
if (p < originalParams.length && originalParams[p] && originalParams[p].name !== `param${p + 1}`) {
|
||||
paramName = originalParams[p].name;
|
||||
}
|
||||
|
||||
newParameters.push({
|
||||
name: paramName,
|
||||
type: paramType,
|
||||
required: isRequired,
|
||||
defaultValue: defaultValue !== 'null' ? defaultValue : undefined
|
||||
});
|
||||
}
|
||||
|
||||
// Replace parameters with the new ones from configure method
|
||||
parameters = newParameters;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't parse parameters from comments, try to infer from configure method
|
||||
if (parameters.length === 0) {
|
||||
// Look for configure method and try to extract parameter count
|
||||
const configureMatch = content.match(/func configure\(\)[^}]*ESCCommandArgumentDescriptor\.new\(\s*(\d+)/);
|
||||
if (configureMatch) {
|
||||
const paramCount = parseInt(configureMatch[1]);
|
||||
for (let i = 0; i < paramCount; i++) {
|
||||
parameters.push({
|
||||
name: `param${i + 1}`,
|
||||
type: 'string',
|
||||
required: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up description - remove extra whitespace and fix common issues
|
||||
if (description) {
|
||||
description = description.trim();
|
||||
|
||||
// Remove extra whitespace
|
||||
description = description.replace(/\s+/g, ' ');
|
||||
|
||||
// Truncate if too long (keep first sentence or first 200 chars)
|
||||
const firstSentence = description.split('.')[0];
|
||||
if (firstSentence.length < 200 && firstSentence.length > 20) {
|
||||
description = firstSentence + '.';
|
||||
} else if (description.length > 200) {
|
||||
description = description.substring(0, 200).trim() + '...';
|
||||
}
|
||||
|
||||
// If description is just the command name, try to get a better one
|
||||
if (description === commandName || description === `${commandName} command`) {
|
||||
description = `${commandName} command`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: commandName,
|
||||
description: description || `${commandName} command`,
|
||||
parameters: parameters,
|
||||
example: example
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error parsing command file ${filePath}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all command files from the specified directories
|
||||
*/
|
||||
public parseCommands(): CommandInfo[] {
|
||||
const commandDirectories = this.parseProjectGodot();
|
||||
const commands: CommandInfo[] = [];
|
||||
|
||||
for (const dir of commandDirectories) {
|
||||
const fsPath = this.resPathToFsPath(dir);
|
||||
|
||||
if (!fs.existsSync(fsPath)) {
|
||||
console.warn(`Command directory not found: ${fsPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(fsPath);
|
||||
const gdFiles = files.filter(file => file.endsWith('.gd') && !file.endsWith('.gd.uid'));
|
||||
|
||||
for (const file of gdFiles) {
|
||||
const filePath = path.join(fsPath, file);
|
||||
const commandInfo = this.parseCommandFile(filePath);
|
||||
|
||||
if (commandInfo) {
|
||||
commands.push(commandInfo);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading directory ${fsPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort commands alphabetically
|
||||
commands.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commands in the format expected by the VSCode extension
|
||||
*/
|
||||
public getCommandsForExtension(): Array<{name: string, description: string, parameters: CommandParameter[]}> {
|
||||
const commands = this.parseCommands();
|
||||
|
||||
return commands.map(cmd => ({
|
||||
name: cmd.name,
|
||||
description: cmd.description,
|
||||
parameters: cmd.parameters
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
7
vscode-extension-ashes/test.esc
Normal file
7
vscode-extension-ashes/test.esc
Normal file
@@ -0,0 +1,7 @@
|
||||
# Test ASHES file for the VSCode extension
|
||||
|
||||
# Test some commands
|
||||
say player "Hello world!"
|
||||
walk player target
|
||||
set_global test_var 42
|
||||
play_snd "sound.ogg" sfx
|
||||
Reference in New Issue
Block a user