Add config() Go to Definition support to VS Code extension
Always include params in window.rsxapp to reduce state variations Add request params to window.rsxapp global Enhance module creation commands with clear nomenclature guidance Add module/submodule/feature nomenclature clarification to docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ class Module_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'rsx:app:module:create
|
||||
protected $signature = 'rsx:app:module:create
|
||||
{name : Module name (lowercase with underscores)}';
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ class Module_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new RSX application module with default index feature';
|
||||
protected $description = 'Create a new module (top-level section with shared layout). Example: frontend, admin';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
|
||||
@@ -12,7 +12,7 @@ class Module_Feature_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'rsx:app:module:feature:create
|
||||
protected $signature = 'rsx:app:module:feature:create
|
||||
{module : Module name (must exist)}
|
||||
{feature : Feature name (lowercase with underscores)}';
|
||||
|
||||
@@ -21,7 +21,7 @@ class Module_Feature_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new feature within an RSX module';
|
||||
protected $description = 'Create a feature (CRUD page group) within a module. Example: clients, tasks';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
@@ -47,7 +47,14 @@ class Module_Feature_Create_Command extends Command
|
||||
// Check if module exists
|
||||
$module_path = base_path("rsx/app/{$module_name}");
|
||||
if (!is_dir($module_path)) {
|
||||
$this->error("Module '{$module_name}' does not exist. Create it first with: php artisan rsx:app:module:create {$module_name}");
|
||||
$this->error("Module '{$module_name}' does not exist.");
|
||||
$this->line('');
|
||||
$this->line('NOMENCLATURE:');
|
||||
$this->line(' Module = Top-level section with shared layout (e.g., frontend, admin)');
|
||||
$this->line(' Feature = CRUD page group within a module (e.g., clients, tasks)');
|
||||
$this->line('');
|
||||
$this->line('Create the module first:');
|
||||
$this->info(" php artisan rsx:app:module:create {$module_name}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class Submodule_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new submodule within an RSX module with embedded layout and default index feature';
|
||||
protected $description = 'Create a submodule (page group with own layout within a module). Example: settings within frontend';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
@@ -47,7 +47,14 @@ class Submodule_Create_Command extends Command
|
||||
// Check if module exists
|
||||
$module_path = base_path("rsx/app/{$module_name}");
|
||||
if (!is_dir($module_path)) {
|
||||
$this->error("Module '{$module_name}' does not exist. Create it first with: php artisan rsx:app:module:create {$module_name}");
|
||||
$this->error("Module '{$module_name}' does not exist.");
|
||||
$this->line('');
|
||||
$this->line('NOMENCLATURE:');
|
||||
$this->line(' Module = Top-level section with shared layout (e.g., frontend, admin)');
|
||||
$this->line(' Submodule = Page group with own layout within a module (e.g., settings within frontend)');
|
||||
$this->line('');
|
||||
$this->line('Create the module first:');
|
||||
$this->info(" php artisan rsx:app:module:create {$module_name}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class Submodule_Feature_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new feature within an RSX submodule';
|
||||
protected $description = 'Create a feature (CRUD page group) within a submodule';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
@@ -54,14 +54,24 @@ class Submodule_Feature_Create_Command extends Command
|
||||
// Check if module exists
|
||||
$module_path = base_path("rsx/app/{$module_name}");
|
||||
if (!is_dir($module_path)) {
|
||||
$this->error("Module '{$module_name}' does not exist. Create it first with: php artisan rsx:app:module:create {$module_name}");
|
||||
$this->error("Module '{$module_name}' does not exist.");
|
||||
$this->line('');
|
||||
$this->line('Create the module first:');
|
||||
$this->info(" php artisan rsx:app:module:create {$module_name}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check if submodule exists
|
||||
$submodule_path = "{$module_path}/{$submodule_name}";
|
||||
if (!is_dir($submodule_path)) {
|
||||
$this->error("Submodule '{$submodule_name}' does not exist. Create it first with: php artisan rsx:app:submodule:create {$module_name} {$submodule_name}");
|
||||
$this->error("Submodule '{$submodule_name}' does not exist in module '{$module_name}'.");
|
||||
$this->line('');
|
||||
$this->line('NOMENCLATURE:');
|
||||
$this->line(' Submodule = Page group with own layout within a module');
|
||||
$this->line(' Feature = CRUD page group within a submodule');
|
||||
$this->line('');
|
||||
$this->line('Create the submodule first:');
|
||||
$this->info(" php artisan rsx:app:submodule:create {$module_name} {$submodule_name}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class Submodule_Subfeature_Create_Command extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new subfeature within an RSX submodule feature';
|
||||
protected $description = 'Create a subfeature (individual CRUD operation like edit, view) within a feature';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
|
||||
@@ -267,6 +267,10 @@ abstract class Rsx_Bundle_Abstract
|
||||
$rsxapp_data['is_auth'] = Session::is_logged_in();
|
||||
$rsxapp_data['ajax_disable_batching'] = config('rsx.development.ajax_disable_batching', false);
|
||||
|
||||
// Add current params (always set to reduce state variations)
|
||||
$current_params = \App\RSpade\Core\Rsx::get_current_params();
|
||||
$rsxapp_data['params'] = $current_params ?? [];
|
||||
|
||||
// Add user, site, and csrf data from session
|
||||
$rsxapp_data['user'] = Session::get_user();
|
||||
$rsxapp_data['site'] = Session::get_site();
|
||||
|
||||
@@ -246,7 +246,7 @@ class Dispatcher
|
||||
Debugger::console_debug('DISPATCH', 'Matched route to ' . $handler_class . '::' . $handler_method . ' params: ' . json_encode($params));
|
||||
|
||||
// Set current controller and action in Rsx for tracking
|
||||
\App\RSpade\Core\Rsx::_set_current_controller_action($handler_class, $handler_method);
|
||||
\App\RSpade\Core\Rsx::_set_current_controller_action($handler_class, $handler_method, $params);
|
||||
|
||||
// Load and validate handler class
|
||||
static::__load_handler_class($handler_class);
|
||||
@@ -574,7 +574,7 @@ class Dispatcher
|
||||
}
|
||||
|
||||
// Set current controller and action for tracking
|
||||
Rsx::_set_current_controller_action($class_name, $method_name);
|
||||
Rsx::_set_current_controller_action($class_name, $method_name, $params);
|
||||
|
||||
// Check if this is a controller (all methods are static)
|
||||
if (static::__is_controller($class_name)) {
|
||||
|
||||
@@ -35,13 +35,20 @@ class Rsx
|
||||
*/
|
||||
protected static $current_action = null;
|
||||
|
||||
/**
|
||||
* Current request params
|
||||
* @var array|null
|
||||
*/
|
||||
protected static $current_params = null;
|
||||
|
||||
/**
|
||||
* Set the current controller and action being executed
|
||||
*
|
||||
* @param string $controller_class The controller class name
|
||||
* @param string $action_method The action method name
|
||||
* @param array $params Optional request params to store
|
||||
*/
|
||||
public static function _set_current_controller_action($controller_class, $action_method)
|
||||
public static function _set_current_controller_action($controller_class, $action_method, array $params = [])
|
||||
{
|
||||
// Extract just the class name without namespace
|
||||
$parts = explode('\\', $controller_class);
|
||||
@@ -49,6 +56,7 @@ class Rsx
|
||||
|
||||
static::$current_controller = $class_name;
|
||||
static::$current_action = $action_method;
|
||||
static::$current_params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +79,16 @@ class Rsx
|
||||
return static::$current_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current request params
|
||||
*
|
||||
* @return array|null The current request params or null if not set
|
||||
*/
|
||||
public static function get_current_params()
|
||||
{
|
||||
return static::$current_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current controller and action tracking
|
||||
*/
|
||||
@@ -78,6 +96,7 @@ class Rsx
|
||||
{
|
||||
static::$current_controller = null;
|
||||
static::$current_action = null;
|
||||
static::$current_params = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
234
app/RSpade/man/module_organization.txt
Executable file
234
app/RSpade/man/module_organization.txt
Executable file
@@ -0,0 +1,234 @@
|
||||
MODULE_ORGANIZATION(3) RSX Framework Manual MODULE_ORGANIZATION(3)
|
||||
|
||||
NAME
|
||||
Module Organization - RSX application structure and nomenclature
|
||||
|
||||
SYNOPSIS
|
||||
php artisan rsx:app:module:create <name>
|
||||
php artisan rsx:app:module:feature:create <module> <feature>
|
||||
php artisan rsx:app:submodule:create <module> <submodule>
|
||||
php artisan rsx:app:submodule:feature:create <module> <submodule> <feature>
|
||||
php artisan rsx:app:submodule:subfeature:create <module> <submodule> <feature> <subfeature>
|
||||
|
||||
DESCRIPTION
|
||||
RSX applications organize code into a hierarchical structure:
|
||||
Module > Submodule > Feature > Subfeature
|
||||
|
||||
This structure enforces clear separation of concerns and predictable
|
||||
file organization for both developers and AI assistants.
|
||||
|
||||
NOMENCLATURE
|
||||
|
||||
Module
|
||||
Top-level section with shared layout and common UI elements.
|
||||
|
||||
Examples: frontend, admin, api
|
||||
|
||||
Rule: If pages share a layout, they belong in the same module.
|
||||
|
||||
Created with: rsx:app:module:create <name>
|
||||
|
||||
Structure:
|
||||
rsx/app/frontend/
|
||||
frontend_bundle.php
|
||||
frontend_layout.blade.php
|
||||
frontend_index_controller.php
|
||||
frontend_index.blade.php
|
||||
...
|
||||
|
||||
Submodule
|
||||
Group of pages within a module with its own embedded layout.
|
||||
|
||||
Examples: settings within frontend, reports within admin
|
||||
|
||||
Rule: If pages within a module need their own layout and common
|
||||
navigation, create a submodule.
|
||||
|
||||
Created with: rsx:app:submodule:create <module> <submodule>
|
||||
|
||||
Structure:
|
||||
rsx/app/frontend/settings/
|
||||
frontend_settings_layout.blade.php
|
||||
frontend_settings.scss
|
||||
frontend_settings_index_controller.php
|
||||
...
|
||||
|
||||
Feature
|
||||
CRUD page group for a specific entity or concept.
|
||||
|
||||
Examples: clients, tasks, users, projects
|
||||
|
||||
Rule: If pages perform CRUD operations on a single entity type,
|
||||
they are a feature group.
|
||||
|
||||
Created with:
|
||||
rsx:app:module:feature:create <module> <feature>
|
||||
rsx:app:submodule:feature:create <module> <submodule> <feature>
|
||||
|
||||
Structure:
|
||||
rsx/app/frontend/clients/
|
||||
frontend_clients_controller.php
|
||||
frontend_clients.blade.php
|
||||
frontend_clients.js
|
||||
frontend_clients.scss
|
||||
|
||||
Subfeature
|
||||
Individual CRUD operation within a feature.
|
||||
|
||||
Examples: edit, view, delete, export
|
||||
|
||||
Rule: Each distinct operation on an entity is a subfeature.
|
||||
|
||||
Created with:
|
||||
rsx:app:submodule:subfeature:create <module> <submodule> <feature> <subfeature>
|
||||
|
||||
Structure:
|
||||
rsx/app/frontend/settings/profile/edit/
|
||||
frontend_settings_profile_edit_controller.php
|
||||
frontend_settings_profile_edit.blade.php
|
||||
frontend_settings_profile_edit.js
|
||||
frontend_settings_profile_edit.scss
|
||||
|
||||
EXAMPLES
|
||||
|
||||
Basic Module with Feature
|
||||
Structure: frontend/clients/edit
|
||||
Hierarchy: module, feature, subfeature
|
||||
|
||||
Commands:
|
||||
php artisan rsx:app:module:create frontend
|
||||
php artisan rsx:app:module:feature:create frontend clients
|
||||
(edit subfeature created automatically with clients feature)
|
||||
|
||||
Module with Submodule
|
||||
Structure: frontend/settings/profile/edit
|
||||
Hierarchy: module, submodule, feature, subfeature
|
||||
|
||||
Commands:
|
||||
php artisan rsx:app:module:create frontend
|
||||
php artisan rsx:app:submodule:create frontend settings
|
||||
php artisan rsx:app:submodule:feature:create frontend settings profile
|
||||
php artisan rsx:app:submodule:subfeature:create frontend settings profile edit
|
||||
|
||||
Simple Admin Module
|
||||
Structure: admin/sites
|
||||
Hierarchy: module, feature
|
||||
|
||||
Commands:
|
||||
php artisan rsx:app:module:create admin
|
||||
php artisan rsx:app:module:feature:create admin sites
|
||||
|
||||
DIRECTORY STRUCTURE
|
||||
|
||||
Module Level
|
||||
rsx/app/{module}/
|
||||
{module}_bundle.php - Asset bundle definition
|
||||
{module}_layout.blade.php - Shared layout for module
|
||||
{module}_index_controller.php
|
||||
{module}_index.blade.php
|
||||
{module}_index.js
|
||||
{module}_index.scss
|
||||
|
||||
Feature Level
|
||||
rsx/app/{module}/{feature}/
|
||||
{module}_{feature}_controller.php
|
||||
{module}_{feature}.blade.php
|
||||
{module}_{feature}.js
|
||||
{module}_{feature}.scss
|
||||
|
||||
Submodule Level
|
||||
rsx/app/{module}/{submodule}/
|
||||
{module}_{submodule}_layout.blade.php - Embedded layout
|
||||
{module}_{submodule}.scss
|
||||
{module}_{submodule}_index_controller.php
|
||||
...
|
||||
|
||||
Subfeature Level
|
||||
rsx/app/{module}/{submodule}/{feature}/{subfeature}/
|
||||
{module}_{submodule}_{feature}_{subfeature}_controller.php
|
||||
{module}_{submodule}_{feature}_{subfeature}.blade.php
|
||||
{module}_{submodule}_{feature}_{subfeature}.js
|
||||
{module}_{submodule}_{feature}_{subfeature}.scss
|
||||
|
||||
ROUTING
|
||||
|
||||
Routes are automatically generated from the hierarchy:
|
||||
|
||||
Module index: /{module}
|
||||
Module feature: /{module}/{feature}
|
||||
Submodule index: /{module}/{submodule}
|
||||
Submodule feature: /{module}/{submodule}/{feature}
|
||||
Submodule subfeature: /{module}/{submodule}/{feature}/{subfeature}
|
||||
|
||||
Controllers use #[Route] attributes to define routes:
|
||||
|
||||
#[Route('/{module}/{feature}', name: 'module.feature.index')]
|
||||
public static function index(Request $request, array $params = [])
|
||||
|
||||
DECISION GUIDE
|
||||
|
||||
When creating new pages, ask:
|
||||
|
||||
1. Does it share a layout with existing pages?
|
||||
YES -> Add to existing module
|
||||
NO -> Create new module
|
||||
|
||||
2. Does it need its own layout within a module?
|
||||
YES -> Create submodule
|
||||
NO -> Create feature in module
|
||||
|
||||
3. Is it a CRUD operation group?
|
||||
YES -> Create feature
|
||||
NO -> May need submodule or different organization
|
||||
|
||||
4. Is it a single CRUD operation?
|
||||
YES -> Create subfeature
|
||||
NO -> Create feature with multiple subfeatures
|
||||
|
||||
COMMON MISTAKES
|
||||
|
||||
Creating Module Instead of Feature
|
||||
WRONG: php artisan rsx:app:module:create tasks
|
||||
RIGHT: php artisan rsx:app:module:feature:create frontend tasks
|
||||
|
||||
Reason: tasks shares the frontend layout, so it's a feature not a module.
|
||||
|
||||
Creating Feature Before Module
|
||||
WRONG: php artisan rsx:app:module:feature:create tasks edit
|
||||
RIGHT: php artisan rsx:app:module:create frontend
|
||||
php artisan rsx:app:module:feature:create frontend tasks
|
||||
|
||||
Error: "Module 'tasks' does not exist"
|
||||
|
||||
Solution: Create the module first, then add features to it.
|
||||
|
||||
Confusing Submodule with Feature
|
||||
Submodule: Has embedded layout, groups related features
|
||||
Feature: CRUD page group, uses parent layout
|
||||
|
||||
Use submodule for: Settings, Reports, Admin sections
|
||||
Use feature for: Clients, Tasks, Users, Projects
|
||||
|
||||
NAMING CONVENTIONS
|
||||
|
||||
All names use lowercase with underscores only: [a-z_]+
|
||||
|
||||
Valid: frontend, client_portal, user_management
|
||||
Invalid: Frontend, client-portal, userManagement, User_Management
|
||||
|
||||
FILE NAMING
|
||||
|
||||
All files follow predictable patterns:
|
||||
|
||||
Controller: {prefix}_controller.php
|
||||
View: {prefix}.blade.php
|
||||
JavaScript: {prefix}.js
|
||||
SCSS: {prefix}.scss
|
||||
Layout: {prefix}_layout.blade.php
|
||||
Bundle: {prefix}_bundle.php
|
||||
|
||||
Where prefix is: {module}_{submodule}_{feature}_{subfeature}
|
||||
(omitting parts not applicable)
|
||||
|
||||
SEE ALSO
|
||||
rsx_architecture(3), routing(3), bundle_api(3), controller(3)
|
||||
@@ -154,6 +154,15 @@ class RspadeDefinitionProvider {
|
||||
if (routeResult) {
|
||||
return routeResult;
|
||||
}
|
||||
// Check for config() pattern - works in PHP and Blade files
|
||||
if (['php', 'blade', 'html'].includes(languageId) ||
|
||||
fileName.endsWith('.php') ||
|
||||
fileName.endsWith('.blade.php')) {
|
||||
const configResult = await this.handleConfigPattern(document, position);
|
||||
if (configResult) {
|
||||
return configResult;
|
||||
}
|
||||
}
|
||||
// Check for href="/" pattern in Blade/Jqhtml files
|
||||
if (fileName.endsWith('.blade.php') || fileName.endsWith('.jqhtml')) {
|
||||
const hrefResult = await this.handleHrefPattern(document, position);
|
||||
@@ -283,6 +292,101 @@ class RspadeDefinitionProvider {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Handle config() pattern in PHP and Blade files
|
||||
* Detects patterns like:
|
||||
* - config('rsx.default_user.email')
|
||||
* - config("app.name")
|
||||
*
|
||||
* Searches in both system/config/ and rsx/resource/config/
|
||||
* (rsx/resource/config/ takes precedence)
|
||||
*/
|
||||
async handleConfigPattern(document, position) {
|
||||
const line = document.lineAt(position.line).text;
|
||||
// Match config('key.path') or config("key.path")
|
||||
const configPattern = /config\s*\(\s*(['"])([a-zA-Z0-9_.]+)\1\s*\)/g;
|
||||
let match;
|
||||
while ((match = configPattern.exec(line)) !== null) {
|
||||
const fullMatch = match[0];
|
||||
const configKey = match[2]; // e.g., "rsx.default_user.email"
|
||||
const keyStart = match.index + match[0].indexOf(configKey);
|
||||
const keyEnd = keyStart + configKey.length;
|
||||
// Check if cursor is on the config key
|
||||
if (position.character >= keyStart && position.character < keyEnd) {
|
||||
// Parse the config key
|
||||
const keyParts = configKey.split('.');
|
||||
const configFile = keyParts[0]; // e.g., "rsx"
|
||||
const nestedPath = keyParts.slice(1); // e.g., ["default_user", "email"]
|
||||
// Get the RSpade project root
|
||||
const rspade_root = this.find_rspade_root();
|
||||
if (!rspade_root) {
|
||||
return undefined;
|
||||
}
|
||||
// Search for config file (prioritize rsx/resource/config/)
|
||||
const rsxConfigPath = path.join(rspade_root, 'rsx', 'resource', 'config', `${configFile}.php`);
|
||||
const systemConfigPath = path.join(rspade_root, 'system', 'config', `${configFile}.php`);
|
||||
let configFilePath;
|
||||
// Check rsx/resource/config first
|
||||
if (fs.existsSync(rsxConfigPath)) {
|
||||
configFilePath = rsxConfigPath;
|
||||
}
|
||||
else if (fs.existsSync(systemConfigPath)) {
|
||||
configFilePath = systemConfigPath;
|
||||
}
|
||||
if (!configFilePath) {
|
||||
return undefined;
|
||||
}
|
||||
// Read the config file
|
||||
try {
|
||||
const configContent = fs.readFileSync(configFilePath, 'utf8');
|
||||
// Find the line containing the nested key
|
||||
const location = this.findConfigKeyInFile(configContent, nestedPath, configFilePath);
|
||||
if (location) {
|
||||
this.clear_status_bar();
|
||||
return location;
|
||||
}
|
||||
// If we can't find the specific key, just return the file
|
||||
const fileUri = vscode.Uri.file(configFilePath);
|
||||
const filePosition = new vscode.Position(0, 0);
|
||||
this.clear_status_bar();
|
||||
return new vscode.Location(fileUri, filePosition);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error reading config file:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Find a nested config key in a PHP config file
|
||||
* Returns the location of the key definition if found
|
||||
*/
|
||||
findConfigKeyInFile(content, nestedPath, filePath) {
|
||||
if (nestedPath.length === 0) {
|
||||
// No nested path, return start of file
|
||||
const fileUri = vscode.Uri.file(filePath);
|
||||
return new vscode.Location(fileUri, new vscode.Position(0, 0));
|
||||
}
|
||||
// Split content into lines
|
||||
const lines = content.split('\n');
|
||||
// Search for the key in the file
|
||||
// For nested keys like ["default_user", "email"], we need to find:
|
||||
// 1. First, find 'default_user' => [
|
||||
// 2. Then find 'email' => value
|
||||
// Simple approach: search for the last key in quotes
|
||||
const targetKey = nestedPath[nestedPath.length - 1];
|
||||
const keyPattern = new RegExp(`['"]${targetKey}['"]\\s*=>`, 'i');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (keyPattern.test(lines[i])) {
|
||||
const fileUri = vscode.Uri.file(filePath);
|
||||
const position = new vscode.Position(i, 0);
|
||||
return new vscode.Location(fileUri, position);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Handle href="/" pattern in Blade/Jqhtml files
|
||||
* Detects when cursor is on "/" within href attribute
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"name": "rspade-framework",
|
||||
"displayName": "RSpade Framework Support",
|
||||
"description": "VS Code extension for RSpade framework with code folding, formatting, and namespace management",
|
||||
"version": "0.1.212",
|
||||
"version": "0.1.214",
|
||||
"publisher": "rspade",
|
||||
"engines": {
|
||||
"vscode": "^1.74.0"
|
||||
|
||||
BIN
app/RSpade/resource/vscode_extension/rspade-framework.vsix
Executable file → Normal file
BIN
app/RSpade/resource/vscode_extension/rspade-framework.vsix
Executable file → Normal file
Binary file not shown.
@@ -160,6 +160,16 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
return routeResult;
|
||||
}
|
||||
|
||||
// Check for config() pattern - works in PHP and Blade files
|
||||
if (['php', 'blade', 'html'].includes(languageId) ||
|
||||
fileName.endsWith('.php') ||
|
||||
fileName.endsWith('.blade.php')) {
|
||||
const configResult = await this.handleConfigPattern(document, position);
|
||||
if (configResult) {
|
||||
return configResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for href="/" pattern in Blade/Jqhtml files
|
||||
if (fileName.endsWith('.blade.php') || fileName.endsWith('.jqhtml')) {
|
||||
const hrefResult = await this.handleHrefPattern(document, position);
|
||||
@@ -304,6 +314,126 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle config() pattern in PHP and Blade files
|
||||
* Detects patterns like:
|
||||
* - config('rsx.default_user.email')
|
||||
* - config("app.name")
|
||||
*
|
||||
* Searches in both system/config/ and rsx/resource/config/
|
||||
* (rsx/resource/config/ takes precedence)
|
||||
*/
|
||||
private async handleConfigPattern(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): Promise<vscode.Definition | undefined> {
|
||||
const line = document.lineAt(position.line).text;
|
||||
|
||||
// Match config('key.path') or config("key.path")
|
||||
const configPattern = /config\s*\(\s*(['"])([a-zA-Z0-9_.]+)\1\s*\)/g;
|
||||
let match;
|
||||
|
||||
while ((match = configPattern.exec(line)) !== null) {
|
||||
const fullMatch = match[0];
|
||||
const configKey = match[2]; // e.g., "rsx.default_user.email"
|
||||
const keyStart = match.index + match[0].indexOf(configKey);
|
||||
const keyEnd = keyStart + configKey.length;
|
||||
|
||||
// Check if cursor is on the config key
|
||||
if (position.character >= keyStart && position.character < keyEnd) {
|
||||
// Parse the config key
|
||||
const keyParts = configKey.split('.');
|
||||
const configFile = keyParts[0]; // e.g., "rsx"
|
||||
const nestedPath = keyParts.slice(1); // e.g., ["default_user", "email"]
|
||||
|
||||
// Get the RSpade project root
|
||||
const rspade_root = this.find_rspade_root();
|
||||
if (!rspade_root) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Search for config file (prioritize rsx/resource/config/)
|
||||
const rsxConfigPath = path.join(rspade_root, 'rsx', 'resource', 'config', `${configFile}.php`);
|
||||
const systemConfigPath = path.join(rspade_root, 'system', 'config', `${configFile}.php`);
|
||||
|
||||
let configFilePath: string | undefined;
|
||||
|
||||
// Check rsx/resource/config first
|
||||
if (fs.existsSync(rsxConfigPath)) {
|
||||
configFilePath = rsxConfigPath;
|
||||
} else if (fs.existsSync(systemConfigPath)) {
|
||||
configFilePath = systemConfigPath;
|
||||
}
|
||||
|
||||
if (!configFilePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Read the config file
|
||||
try {
|
||||
const configContent = fs.readFileSync(configFilePath, 'utf8');
|
||||
|
||||
// Find the line containing the nested key
|
||||
const location = this.findConfigKeyInFile(configContent, nestedPath, configFilePath);
|
||||
if (location) {
|
||||
this.clear_status_bar();
|
||||
return location;
|
||||
}
|
||||
|
||||
// If we can't find the specific key, just return the file
|
||||
const fileUri = vscode.Uri.file(configFilePath);
|
||||
const filePosition = new vscode.Position(0, 0);
|
||||
this.clear_status_bar();
|
||||
return new vscode.Location(fileUri, filePosition);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error reading config file:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a nested config key in a PHP config file
|
||||
* Returns the location of the key definition if found
|
||||
*/
|
||||
private findConfigKeyInFile(
|
||||
content: string,
|
||||
nestedPath: string[],
|
||||
filePath: string
|
||||
): vscode.Location | undefined {
|
||||
if (nestedPath.length === 0) {
|
||||
// No nested path, return start of file
|
||||
const fileUri = vscode.Uri.file(filePath);
|
||||
return new vscode.Location(fileUri, new vscode.Position(0, 0));
|
||||
}
|
||||
|
||||
// Split content into lines
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Search for the key in the file
|
||||
// For nested keys like ["default_user", "email"], we need to find:
|
||||
// 1. First, find 'default_user' => [
|
||||
// 2. Then find 'email' => value
|
||||
|
||||
// Simple approach: search for the last key in quotes
|
||||
const targetKey = nestedPath[nestedPath.length - 1];
|
||||
const keyPattern = new RegExp(`['"]${targetKey}['"]\\s*=>`, 'i');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (keyPattern.test(lines[i])) {
|
||||
const fileUri = vscode.Uri.file(filePath);
|
||||
const position = new vscode.Position(i, 0);
|
||||
return new vscode.Location(fileUri, position);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle href="/" pattern in Blade/Jqhtml files
|
||||
* Detects when cursor is on "/" within href attribute
|
||||
|
||||
@@ -663,6 +663,8 @@ rsx:app:module:feature:create <m> <f> # /m/f
|
||||
rsx:app:component:create --name=x # Component
|
||||
```
|
||||
|
||||
**Nomenclature**: Module (top-level section with shared layout), Submodule (page group within module), Feature (CRUD group), Subfeature (individual CRUD operation). Example: `frontend/clients/edit` = module, feature, subfeature.
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
@@ -1085,6 +1087,19 @@ class DataService {
|
||||
| rsx:app:submodule:create <m> <s> | Submodule + layout | /m/s |
|
||||
| rsx:app:component:create --name=x | jqhtml component | N/A |
|
||||
|
||||
**Nomenclature**:
|
||||
- **Module**: Top-level section sharing layout and common UI (frontend, admin)
|
||||
- **Submodule**: Group of pages within a module (settings within frontend)
|
||||
- **Feature**: CRUD page group (clients, tasks, users)
|
||||
- **Subfeature**: Individual CRUD operation (edit, view, delete)
|
||||
|
||||
**Examples**:
|
||||
- `frontend/clients/edit` = module, feature, subfeature
|
||||
- `frontend/settings/profile/edit` = module, submodule, feature, subfeature
|
||||
- `admin/sites` = module, feature
|
||||
|
||||
**Rule of thumb**: If it shares a layout, it's a module. If it's a group of pages within a module, it's a submodule. If it's CRUD operations, it's feature + subfeatures.
|
||||
|
||||
### Migration Commands
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user