1use crate::{
7 Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8 RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12pub struct DioxusProvider {
14 priority: u8,
15}
16
17impl DioxusProvider {
18 pub fn new() -> Self {
19 Self {
20 priority: 90, }
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 match &context.project_type {
38 ProjectType::Dioxus => true,
39 ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Dioxus),
40 _ => {
41 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 commands.extend(self.development_commands(context));
57
58 commands.extend(self.platform_commands(context));
60
61 commands.extend(self.test_commands(context));
63
64 commands.extend(self.context_aware_commands(context));
66
67 commands.extend(self.deployment_commands(context));
69
70 Ok(commands)
71 }
72}
73
74impl DioxusProvider {
75 fn detect_platform(&self, context: &ProjectContext) -> String {
77 if let Ok(platform_override) = std::env::var("RAZ_PLATFORM_OVERRIDE") {
79 return platform_override;
80 }
81
82 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 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 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 "desktop".to_string()
127 }
128 }
129
130 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 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 fn platform_commands(&self, context: &ProjectContext) -> Vec<Command> {
216 let mut commands = vec![
217 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 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 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 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 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 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 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 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 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 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 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 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 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 assert!(commands.iter().any(|c| c.id == "dx-serve"));
583 assert!(commands.iter().any(|c| c.id == "dx-run"));
584
585 assert!(commands.iter().any(|c| c.id == "dx-build-web"));
587 assert!(commands.iter().any(|c| c.id == "dx-build-desktop"));
588
589 assert!(commands.iter().any(|c| c.id == "cargo-test"));
591 assert!(commands.iter().any(|c| c.id == "dx-check"));
592
593 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 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 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 let serve_cmd = commands.iter().find(|c| c.id == "dx-serve").unwrap();
627 assert_eq!(serve_cmd.priority, 95);
628
629 assert!(
631 commands
632 .iter()
633 .all(|c| c.tags.contains(&"dioxus".to_string()))
634 );
635 }
636}