raz_core/providers/
dioxus.rs

1//! Dioxus framework provider for cross-platform app development
2//!
3//! Provides intelligent command suggestions for Dioxus projects including
4//! desktop, web, mobile development and testing commands.
5
6use crate::{
7    Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8    RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12/// Provider for Dioxus cross-platform framework commands
13pub struct DioxusProvider {
14    priority: u8,
15}
16
17impl DioxusProvider {
18    pub fn new() -> Self {
19        Self {
20            priority: 90, // High priority for Dioxus projects
21        }
22    }
23}
24
25#[async_trait]
26impl CommandProvider for DioxusProvider {
27    fn name(&self) -> &str {
28        "dioxus"
29    }
30
31    fn priority(&self) -> u8 {
32        self.priority
33    }
34
35    fn can_handle(&self, context: &ProjectContext) -> bool {
36        // Check if this is a Dioxus project
37        match &context.project_type {
38            ProjectType::Dioxus => true,
39            ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Dioxus),
40            _ => {
41                // Also check dependencies for dioxus
42                context.dependencies.iter().any(|dep| {
43                    dep.name == "dioxus"
44                        || dep.name == "dioxus-web"
45                        || dep.name == "dioxus-desktop"
46                        || dep.name == "dioxus-mobile"
47                })
48            }
49        }
50    }
51
52    async fn commands(&self, context: &ProjectContext) -> RazResult<Vec<Command>> {
53        let mut commands = Vec::new();
54
55        // Development commands
56        commands.extend(self.development_commands(context));
57
58        // Platform-specific build commands
59        commands.extend(self.platform_commands(context));
60
61        // Testing commands
62        commands.extend(self.test_commands(context));
63
64        // Context-aware commands
65        commands.extend(self.context_aware_commands(context));
66
67        // Bundle and deployment commands
68        commands.extend(self.deployment_commands(context));
69
70        Ok(commands)
71    }
72}
73
74impl DioxusProvider {
75    /// Detect the default platform for the Dioxus project
76    fn detect_platform(&self, context: &ProjectContext) -> String {
77        // 0. Check for environment variable override (highest priority)
78        if let Ok(platform_override) = std::env::var("RAZ_PLATFORM_OVERRIDE") {
79            return platform_override;
80        }
81
82        // 0.5. Check for saved command override in config
83        // TODO: We need access to the config manager here to check for saved overrides
84        // For now, the environment variable approach will work
85
86        // 1. Check Dioxus.toml for default_platform
87        if let Some(dioxus_config) = self.read_dioxus_config(context) {
88            if let Some(platform) = dioxus_config.get("default_platform") {
89                return platform.as_str().unwrap_or("desktop").to_string();
90            }
91        }
92
93        // 2. Check Cargo.toml features to auto-detect platform
94        let has_web = context
95            .dependencies
96            .iter()
97            .any(|dep| dep.name == "dioxus-web" || dep.features.iter().any(|f| f == "web"));
98
99        let has_desktop = context
100            .dependencies
101            .iter()
102            .any(|dep| dep.name == "dioxus-desktop" || dep.features.iter().any(|f| f == "desktop"));
103
104        let has_mobile = context
105            .dependencies
106            .iter()
107            .any(|dep| dep.name == "dioxus-mobile" || dep.features.iter().any(|f| f == "mobile"));
108
109        let has_fullstack = context.dependencies.iter().any(|dep| {
110            dep.features
111                .iter()
112                .any(|f| f == "fullstack" || f == "server")
113        });
114
115        // Prioritize platforms based on dependencies
116        if has_fullstack {
117            "fullstack".to_string()
118        } else if has_web {
119            "web".to_string()
120        } else if has_desktop {
121            "desktop".to_string()
122        } else if has_mobile {
123            "mobile".to_string()
124        } else {
125            // Default fallback - desktop is most common for Dioxus users
126            "desktop".to_string()
127        }
128    }
129
130    /// Read Dioxus.toml configuration file
131    fn read_dioxus_config(&self, context: &ProjectContext) -> Option<toml::Value> {
132        let dioxus_toml_path = context.workspace_root.join("Dioxus.toml");
133        if dioxus_toml_path.exists() {
134            if let Ok(content) = std::fs::read_to_string(&dioxus_toml_path) {
135                if let Ok(config) = content.parse::<toml::Value>() {
136                    return config.get("application").cloned();
137                }
138            }
139        }
140        None
141    }
142
143    /// Development server and watch commands
144    fn development_commands(&self, context: &ProjectContext) -> Vec<Command> {
145        let platform = self.detect_platform(context);
146
147        vec![
148            CommandBuilder::new("dx-serve", "dx")
149                .label(format!("Dioxus Dev Server ({platform})"))
150                .description(format!(
151                    "Build, watch & serve with hot-reload for {platform} platform"
152                ))
153                .arg("serve")
154                .arg("--platform")
155                .arg(&platform)
156                .category(CommandCategory::Run)
157                .priority(95)
158                .tag("dev")
159                .tag("serve")
160                .tag("hot-reload")
161                .tag("dioxus")
162                .tag(&platform)
163                .estimated_duration(5)
164                .build(),
165            CommandBuilder::new("dx-serve-open", "dx")
166                .label(format!("Dioxus Serve & Open ({platform})"))
167                .description(format!(
168                    "Start dev server and open browser for {platform} platform"
169                ))
170                .arg("serve")
171                .arg("--platform")
172                .arg(&platform)
173                .arg("--open")
174                .category(CommandCategory::Run)
175                .priority(93)
176                .tag("dev")
177                .tag("serve")
178                .tag("browser")
179                .tag("dioxus")
180                .tag(&platform)
181                .estimated_duration(5)
182                .build(),
183            CommandBuilder::new("dx-serve-release", "dx")
184                .label(format!("Dioxus Serve Release ({platform})"))
185                .description(format!("Serve in release mode for {platform} platform"))
186                .arg("serve")
187                .arg("--platform")
188                .arg(&platform)
189                .arg("--release")
190                .category(CommandCategory::Run)
191                .priority(85)
192                .tag("serve")
193                .tag("release")
194                .tag("dioxus")
195                .tag(&platform)
196                .estimated_duration(30)
197                .build(),
198            CommandBuilder::new("dx-run", "dx")
199                .label(format!("Dioxus Run ({platform})"))
200                .description(format!("Run without hot-reload for {platform} platform"))
201                .arg("run")
202                .arg("--platform")
203                .arg(&platform)
204                .category(CommandCategory::Run)
205                .priority(80)
206                .tag("run")
207                .tag("dioxus")
208                .tag(&platform)
209                .estimated_duration(5)
210                .build(),
211        ]
212    }
213
214    /// Platform-specific build commands
215    fn platform_commands(&self, context: &ProjectContext) -> Vec<Command> {
216        let mut commands = vec![
217            // Web platform
218            CommandBuilder::new("dx-build-web", "dx")
219                .label("Build for Web")
220                .description("Build Dioxus app for web deployment")
221                .arg("build")
222                .arg("--platform")
223                .arg("web")
224                .category(CommandCategory::Build)
225                .priority(85)
226                .tag("build")
227                .tag("web")
228                .tag("dioxus")
229                .estimated_duration(30)
230                .build(),
231            // Desktop platform
232            CommandBuilder::new("dx-build-desktop", "dx")
233                .label("Build for Desktop")
234                .description("Build Dioxus desktop application")
235                .arg("build")
236                .arg("--platform")
237                .arg("desktop")
238                .category(CommandCategory::Build)
239                .priority(80)
240                .tag("build")
241                .tag("desktop")
242                .tag("dioxus")
243                .estimated_duration(45)
244                .build(),
245            // Release builds
246            CommandBuilder::new("dx-build-release", "dx")
247                .label("Build Release")
248                .description("Build optimized release version")
249                .arg("build")
250                .arg("--release")
251                .category(CommandCategory::Build)
252                .priority(80)
253                .tag("build")
254                .tag("release")
255                .tag("dioxus")
256                .estimated_duration(60)
257                .build(),
258            // Fullstack build
259            CommandBuilder::new("dx-build-fullstack", "dx")
260                .label("Build Fullstack")
261                .description("Build fullstack app with server and client")
262                .arg("build")
263                .arg("--fullstack")
264                .category(CommandCategory::Build)
265                .priority(78)
266                .tag("build")
267                .tag("fullstack")
268                .tag("dioxus")
269                .estimated_duration(45)
270                .build(),
271            // SSG build
272            CommandBuilder::new("dx-build-ssg", "dx")
273                .label("Build Static Site")
274                .description("Generate static site")
275                .arg("build")
276                .arg("--ssg")
277                .category(CommandCategory::Build)
278                .priority(77)
279                .tag("build")
280                .tag("ssg")
281                .tag("static")
282                .tag("dioxus")
283                .estimated_duration(40)
284                .build(),
285        ];
286
287        // Add mobile commands if we detect mobile dependencies
288        if context
289            .dependencies
290            .iter()
291            .any(|d| d.name.contains("mobile"))
292        {
293            commands.extend(vec![
294                CommandBuilder::new("dx-build-ios", "dx")
295                    .label("Build for iOS")
296                    .description("Build Dioxus app for iOS")
297                    .arg("build")
298                    .arg("--platform")
299                    .arg("ios")
300                    .category(CommandCategory::Build)
301                    .priority(75)
302                    .tag("build")
303                    .tag("ios")
304                    .tag("mobile")
305                    .tag("dioxus")
306                    .estimated_duration(90)
307                    .build(),
308                CommandBuilder::new("dx-build-android", "dx")
309                    .label("Build for Android")
310                    .description("Build Dioxus app for Android")
311                    .arg("build")
312                    .arg("--platform")
313                    .arg("android")
314                    .category(CommandCategory::Build)
315                    .priority(75)
316                    .tag("build")
317                    .tag("android")
318                    .tag("mobile")
319                    .tag("dioxus")
320                    .estimated_duration(90)
321                    .build(),
322            ]);
323        }
324
325        commands
326    }
327
328    /// Testing commands
329    fn test_commands(&self, _context: &ProjectContext) -> Vec<Command> {
330        vec![
331            CommandBuilder::new("cargo-test", "cargo")
332                .label("Run Tests")
333                .description("Run all tests")
334                .arg("test")
335                .category(CommandCategory::Test)
336                .priority(75)
337                .tag("test")
338                .tag("dioxus")
339                .estimated_duration(20)
340                .build(),
341            CommandBuilder::new("cargo-test-workspace", "cargo")
342                .label("Test Workspace")
343                .description("Run all workspace tests")
344                .arg("test")
345                .arg("--workspace")
346                .category(CommandCategory::Test)
347                .priority(70)
348                .tag("test")
349                .tag("workspace")
350                .tag("dioxus")
351                .estimated_duration(30)
352                .build(),
353            CommandBuilder::new("dx-check", "dx")
354                .label("Check Project")
355                .description("Check project for issues")
356                .arg("check")
357                .category(CommandCategory::Lint)
358                .priority(72)
359                .tag("check")
360                .tag("lint")
361                .tag("dioxus")
362                .estimated_duration(10)
363                .build(),
364            CommandBuilder::new("dx-fmt", "dx")
365                .label("Format RSX")
366                .description("Automatically format RSX code")
367                .arg("fmt")
368                .category(CommandCategory::Format)
369                .priority(70)
370                .tag("format")
371                .tag("rsx")
372                .tag("dioxus")
373                .estimated_duration(5)
374                .build(),
375        ]
376    }
377
378    /// Context-aware commands based on cursor position
379    fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
380        let mut commands = Vec::new();
381
382        if let Some(file_context) = &context.current_file {
383            if let Some(symbol) = &file_context.cursor_symbol {
384                match symbol.kind {
385                    SymbolKind::Function => {
386                        // Component-related commands for PascalCase functions (Dioxus components)
387                        if symbol.name.chars().next().is_some_and(|c| c.is_uppercase()) {
388                            commands.push(
389                                CommandBuilder::new("dx-component-preview", "dx")
390                                    .label("Preview Component")
391                                    .description(format!("Preview component: {}", symbol.name))
392                                    .arg("serve")
393                                    .arg("--component")
394                                    .arg(&symbol.name)
395                                    .category(CommandCategory::Run)
396                                    .priority(85)
397                                    .tag("component")
398                                    .tag("preview")
399                                    .tag("dioxus")
400                                    .estimated_duration(10)
401                                    .build(),
402                            );
403                        }
404
405                        // Test function commands
406                        if symbol.name.starts_with("test_")
407                            || symbol.modifiers.contains(&"test".to_string())
408                        {
409                            commands.push(
410                                CommandBuilder::new("dx-test-current", "dx")
411                                    .label("Test Current Function")
412                                    .description(format!("Run test: {}", symbol.name))
413                                    .arg("test")
414                                    .arg(&symbol.name)
415                                    .category(CommandCategory::Test)
416                                    .priority(90)
417                                    .tag("test")
418                                    .tag("current")
419                                    .tag("dioxus")
420                                    .estimated_duration(5)
421                                    .build(),
422                            );
423                        }
424                    }
425                    SymbolKind::Struct => {
426                        // State management related commands
427                        if symbol.name.to_lowercase().contains("state") {
428                            commands.push(
429                                CommandBuilder::new("dx-check-state", "cargo")
430                                    .label("Check State Management")
431                                    .description(format!("Analyze state struct: {}", symbol.name))
432                                    .arg("check")
433                                    .category(CommandCategory::Lint)
434                                    .priority(70)
435                                    .tag("state")
436                                    .tag("check")
437                                    .tag("dioxus")
438                                    .estimated_duration(8)
439                                    .build(),
440                            );
441                        }
442                    }
443                    _ => {}
444                }
445            }
446
447            // File-based commands
448            if file_context.path.to_string_lossy().contains("component") {
449                commands.push(
450                    CommandBuilder::new("dx-build-components", "dx")
451                        .label("Build Components")
452                        .description("Build and validate all components")
453                        .arg("build")
454                        .arg("--components-only")
455                        .category(CommandCategory::Build)
456                        .priority(80)
457                        .tag("components")
458                        .tag("build")
459                        .tag("dioxus")
460                        .estimated_duration(20)
461                        .build(),
462                );
463            }
464        }
465
466        commands
467    }
468
469    /// Bundle and deployment commands
470    fn deployment_commands(&self, _context: &ProjectContext) -> Vec<Command> {
471        vec![
472            CommandBuilder::new("dx-bundle", "dx")
473                .label("Bundle Application")
474                .description("Bundle the Dioxus app into a shippable object")
475                .arg("bundle")
476                .category(CommandCategory::Build)
477                .priority(75)
478                .tag("bundle")
479                .tag("production")
480                .tag("dioxus")
481                .estimated_duration(60)
482                .build(),
483            CommandBuilder::new("dx-bundle-release", "dx")
484                .label("Bundle Release")
485                .description("Bundle optimized release version")
486                .arg("bundle")
487                .arg("--release")
488                .category(CommandCategory::Deploy)
489                .priority(72)
490                .tag("bundle")
491                .tag("release")
492                .tag("deploy")
493                .tag("dioxus")
494                .estimated_duration(75)
495                .build(),
496            CommandBuilder::new("dx-clean", "dx")
497                .label("Clean Artifacts")
498                .description("Clean output artifacts")
499                .arg("clean")
500                .category(CommandCategory::Clean)
501                .priority(65)
502                .tag("clean")
503                .tag("dioxus")
504                .estimated_duration(5)
505                .build(),
506            CommandBuilder::new("dx-new", "dx")
507                .label("New Project")
508                .description("Create a new Dioxus project")
509                .arg("new")
510                .category(CommandCategory::Generate)
511                .priority(60)
512                .tag("new")
513                .tag("create")
514                .tag("dioxus")
515                .estimated_duration(10)
516                .build(),
517        ]
518    }
519}
520
521impl Default for DioxusProvider {
522    fn default() -> Self {
523        Self::new()
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530    use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
531    use std::collections::HashMap;
532    use std::path::PathBuf;
533
534    fn create_dioxus_context() -> ProjectContext {
535        ProjectContext {
536            workspace_root: PathBuf::from("/test"),
537            current_file: None,
538            cursor_position: None,
539            project_type: ProjectType::Dioxus,
540            dependencies: vec![Dependency {
541                name: "dioxus".to_string(),
542                version: "0.4".to_string(),
543                features: vec!["web".to_string(), "desktop".to_string()],
544                optional: false,
545                dev_dependency: false,
546            }],
547            workspace_members: vec![WorkspaceMember {
548                name: "dioxus-app".to_string(),
549                path: PathBuf::from("/test"),
550                package_type: ProjectType::Dioxus,
551            }],
552            build_targets: vec![BuildTarget {
553                name: "main".to_string(),
554                target_type: TargetType::Binary,
555                path: PathBuf::from("/test/src/main.rs"),
556            }],
557            active_features: vec!["web".to_string()],
558            env_vars: HashMap::new(),
559        }
560    }
561
562    #[tokio::test]
563    async fn test_dioxus_provider_can_handle() {
564        let provider = DioxusProvider::new();
565        let context = create_dioxus_context();
566
567        assert!(provider.can_handle(&context));
568        assert_eq!(provider.name(), "dioxus");
569        assert_eq!(provider.priority(), 90);
570    }
571
572    #[tokio::test]
573    async fn test_dioxus_commands_generation() {
574        let provider = DioxusProvider::new();
575        let context = create_dioxus_context();
576
577        let commands = provider.commands(&context).await.unwrap();
578
579        assert!(!commands.is_empty());
580
581        // Should have development commands
582        assert!(commands.iter().any(|c| c.id == "dx-serve"));
583        assert!(commands.iter().any(|c| c.id == "dx-run"));
584
585        // Should have platform-specific builds
586        assert!(commands.iter().any(|c| c.id == "dx-build-web"));
587        assert!(commands.iter().any(|c| c.id == "dx-build-desktop"));
588
589        // Should have test/check commands
590        assert!(commands.iter().any(|c| c.id == "cargo-test"));
591        assert!(commands.iter().any(|c| c.id == "dx-check"));
592
593        // Should have bundle commands
594        assert!(commands.iter().any(|c| c.id == "dx-bundle"));
595    }
596
597    #[tokio::test]
598    async fn test_dioxus_mobile_commands() {
599        let provider = DioxusProvider::new();
600        let mut context = create_dioxus_context();
601
602        // Add mobile dependency
603        context.dependencies.push(Dependency {
604            name: "dioxus-mobile".to_string(),
605            version: "0.4".to_string(),
606            features: vec![],
607            optional: false,
608            dev_dependency: false,
609        });
610
611        let commands = provider.commands(&context).await.unwrap();
612
613        // Should have mobile-specific commands
614        assert!(commands.iter().any(|c| c.id == "dx-build-ios"));
615        assert!(commands.iter().any(|c| c.id == "dx-build-android"));
616    }
617
618    #[tokio::test]
619    async fn test_dioxus_command_priorities() {
620        let provider = DioxusProvider::new();
621        let context = create_dioxus_context();
622
623        let commands = provider.commands(&context).await.unwrap();
624
625        // Development server should have highest priority
626        let serve_cmd = commands.iter().find(|c| c.id == "dx-serve").unwrap();
627        assert_eq!(serve_cmd.priority, 95);
628
629        // All commands should have dioxus tag
630        assert!(
631            commands
632                .iter()
633                .all(|c| c.tags.contains(&"dioxus".to_string()))
634        );
635    }
636}