1use crate::{
7 Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8 RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12pub struct BevyProvider {
14 priority: u8,
15}
16
17impl BevyProvider {
18 pub fn new() -> Self {
19 Self {
20 priority: 85, }
22 }
23}
24
25#[async_trait]
26impl CommandProvider for BevyProvider {
27 fn name(&self) -> &str {
28 "bevy"
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::Bevy => true,
39 ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Bevy),
40 _ => {
41 context
43 .dependencies
44 .iter()
45 .any(|dep| dep.name == "bevy" || dep.name.starts_with("bevy_"))
46 }
47 }
48 }
49
50 async fn commands(&self, context: &ProjectContext) -> RazResult<Vec<Command>> {
51 let mut commands = Vec::new();
52
53 commands.extend(self.development_commands(context));
55
56 commands.extend(self.build_commands(context));
58
59 commands.extend(self.test_commands(context));
61
62 commands.extend(self.context_aware_commands(context));
64
65 commands.extend(self.asset_commands(context));
67
68 Ok(commands)
69 }
70}
71
72impl BevyProvider {
73 fn development_commands(&self, _context: &ProjectContext) -> Vec<Command> {
75 vec![
76 CommandBuilder::new("bevy-run", "cargo")
77 .label("Run Bevy Game")
78 .description("Run the Bevy game in development mode")
79 .arg("run")
80 .category(CommandCategory::Run)
81 .priority(95)
82 .tag("dev")
83 .tag("run")
84 .tag("bevy")
85 .estimated_duration(3)
86 .build(),
87 CommandBuilder::new("bevy-run-release", "cargo")
88 .label("Run Release Build")
89 .description("Run optimized release build of the game")
90 .arg("run")
91 .arg("--release")
92 .category(CommandCategory::Run)
93 .priority(85)
94 .tag("run")
95 .tag("release")
96 .tag("bevy")
97 .estimated_duration(5)
98 .build(),
99 CommandBuilder::new("bevy-run-features", "cargo")
100 .label("Run with Dynamic Features")
101 .description("Run with dynamic linking for faster iteration")
102 .arg("run")
103 .arg("--features")
104 .arg("bevy/dynamic_linking")
105 .category(CommandCategory::Run)
106 .priority(88)
107 .tag("dev")
108 .tag("dynamic")
109 .tag("bevy")
110 .estimated_duration(4)
111 .build(),
112 CommandBuilder::new("bevy-check", "cargo")
113 .label("Check Bevy Code")
114 .description("Fast compile check without building")
115 .arg("check")
116 .category(CommandCategory::Lint)
117 .priority(90)
118 .tag("check")
119 .tag("fast")
120 .tag("bevy")
121 .estimated_duration(8)
122 .build(),
123 ]
124 }
125
126 fn build_commands(&self, context: &ProjectContext) -> Vec<Command> {
128 let mut commands = vec![
129 CommandBuilder::new("bevy-build", "cargo")
130 .label("Build Bevy Game")
131 .description("Build the game for the current platform")
132 .arg("build")
133 .category(CommandCategory::Build)
134 .priority(80)
135 .tag("build")
136 .tag("bevy")
137 .estimated_duration(45)
138 .build(),
139 CommandBuilder::new("bevy-build-release", "cargo")
140 .label("Build Release")
141 .description("Build optimized release version")
142 .arg("build")
143 .arg("--release")
144 .category(CommandCategory::Build)
145 .priority(85)
146 .tag("build")
147 .tag("release")
148 .tag("bevy")
149 .estimated_duration(90)
150 .build(),
151 ];
152
153 if context.dependencies.iter().any(|d| d.name.contains("wasm")) {
155 commands.push(
156 CommandBuilder::new("bevy-build-wasm", "cargo")
157 .label("Build for WASM")
158 .description("Build Bevy game for web (WebAssembly)")
159 .arg("build")
160 .arg("--target")
161 .arg("wasm32-unknown-unknown")
162 .arg("--release")
163 .category(CommandCategory::Build)
164 .priority(75)
165 .tag("build")
166 .tag("wasm")
167 .tag("web")
168 .tag("bevy")
169 .estimated_duration(120)
170 .build(),
171 );
172 }
173
174 commands.push(
176 CommandBuilder::new("bevy-build-windows", "cargo")
177 .label("Build for Windows")
178 .description("Cross-compile for Windows")
179 .arg("build")
180 .arg("--target")
181 .arg("x86_64-pc-windows-gnu")
182 .arg("--release")
183 .category(CommandCategory::Build)
184 .priority(70)
185 .tag("build")
186 .tag("windows")
187 .tag("cross")
188 .tag("bevy")
189 .estimated_duration(100)
190 .build(),
191 );
192
193 commands
194 }
195
196 fn test_commands(&self, _context: &ProjectContext) -> Vec<Command> {
198 vec![
199 CommandBuilder::new("bevy-test", "cargo")
200 .label("Run Bevy Tests")
201 .description("Run unit and integration tests")
202 .arg("test")
203 .category(CommandCategory::Test)
204 .priority(75)
205 .tag("test")
206 .tag("bevy")
207 .estimated_duration(20)
208 .build(),
209 CommandBuilder::new("bevy-test-headless", "cargo")
210 .label("Headless Tests")
211 .description("Run tests without rendering (headless)")
212 .arg("test")
213 .arg("--features")
214 .arg("bevy/headless")
215 .category(CommandCategory::Test)
216 .priority(80)
217 .tag("test")
218 .tag("headless")
219 .tag("bevy")
220 .estimated_duration(15)
221 .build(),
222 CommandBuilder::new("bevy-test-doc", "cargo")
223 .label("Test Documentation")
224 .description("Test code examples in documentation")
225 .arg("test")
226 .arg("--doc")
227 .category(CommandCategory::Test)
228 .priority(65)
229 .tag("test")
230 .tag("doc")
231 .tag("bevy")
232 .estimated_duration(25)
233 .build(),
234 ]
235 }
236
237 fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
239 let mut commands = Vec::new();
240
241 if let Some(file_context) = &context.current_file {
242 if let Some(symbol) = &file_context.cursor_symbol {
243 match symbol.kind {
244 SymbolKind::Function => {
245 if symbol.name.ends_with("_system")
247 || symbol.name.contains("update")
248 || symbol.name.contains("spawn")
249 {
250 commands.push(
251 CommandBuilder::new("bevy-run-system", "cargo")
252 .label("Run with System Focus")
253 .description(format!(
254 "Run game focusing on system: {}",
255 symbol.name
256 ))
257 .arg("run")
258 .arg("--features")
259 .arg("bevy/trace")
260 .category(CommandCategory::Run)
261 .priority(85)
262 .tag("system")
263 .tag("debug")
264 .tag("bevy")
265 .estimated_duration(8)
266 .build(),
267 );
268 }
269
270 if symbol.name.starts_with("test_")
272 || symbol.modifiers.contains(&"test".to_string())
273 {
274 commands.push(
275 CommandBuilder::new("bevy-test-current", "cargo")
276 .label("Test Current System")
277 .description(format!("Run test: {}", symbol.name))
278 .arg("test")
279 .arg(&symbol.name)
280 .arg("--")
281 .arg("--nocapture")
282 .category(CommandCategory::Test)
283 .priority(90)
284 .tag("test")
285 .tag("current")
286 .tag("bevy")
287 .estimated_duration(5)
288 .build(),
289 );
290 }
291 }
292 SymbolKind::Struct => {
293 if symbol.name.ends_with("Component")
295 || symbol.modifiers.contains(&"Component".to_string())
296 {
297 commands.push(
298 CommandBuilder::new("bevy-inspect-component", "cargo")
299 .label("Inspect Component")
300 .description(format!(
301 "Run with component inspector: {}",
302 symbol.name
303 ))
304 .arg("run")
305 .arg("--features")
306 .arg("bevy-inspector-egui/default")
307 .category(CommandCategory::Run)
308 .priority(80)
309 .tag("component")
310 .tag("inspector")
311 .tag("bevy")
312 .estimated_duration(10)
313 .build(),
314 );
315 }
316
317 if symbol.name.ends_with("Resource") {
319 commands.push(
320 CommandBuilder::new("bevy-debug-resource", "cargo")
321 .label("Debug Resource")
322 .description(format!(
323 "Run with resource debugging: {}",
324 symbol.name
325 ))
326 .arg("run")
327 .arg("--features")
328 .arg("bevy/trace")
329 .category(CommandCategory::Run)
330 .priority(75)
331 .tag("resource")
332 .tag("debug")
333 .tag("bevy")
334 .estimated_duration(8)
335 .build(),
336 );
337 }
338 }
339 _ => {}
340 }
341 }
342
343 if file_context.path.to_string_lossy().contains("system") {
345 commands.push(
346 CommandBuilder::new("bevy-check-systems", "cargo")
347 .label("Check Systems")
348 .description("Check all game systems for compilation errors")
349 .arg("check")
350 .arg("--features")
351 .arg("bevy/trace")
352 .category(CommandCategory::Lint)
353 .priority(80)
354 .tag("systems")
355 .tag("check")
356 .tag("bevy")
357 .estimated_duration(12)
358 .build(),
359 );
360 }
361
362 if file_context.path.to_string_lossy().contains("asset") {
363 commands.push(
364 CommandBuilder::new("bevy-check-assets", "cargo")
365 .label("Validate Assets")
366 .description("Check asset loading and validation")
367 .arg("run")
368 .arg("--features")
369 .arg("bevy/filesystem_watcher")
370 .category(CommandCategory::Run)
371 .priority(75)
372 .tag("assets")
373 .tag("validate")
374 .tag("bevy")
375 .estimated_duration(15)
376 .build(),
377 );
378 }
379 }
380
381 commands
382 }
383
384 fn asset_commands(&self, _context: &ProjectContext) -> Vec<Command> {
386 vec![
387 CommandBuilder::new("bevy-assets-watch", "cargo")
388 .label("Watch Assets")
389 .description("Run with asset hot-reloading enabled")
390 .arg("run")
391 .arg("--features")
392 .arg("bevy/file_watcher")
393 .category(CommandCategory::Run)
394 .priority(85)
395 .tag("assets")
396 .tag("watch")
397 .tag("bevy")
398 .estimated_duration(5)
399 .build(),
400 CommandBuilder::new("bevy-optimize-assets", "cargo")
401 .label("Optimize Assets")
402 .description("Build with optimized asset processing")
403 .arg("build")
404 .arg("--release")
405 .arg("--features")
406 .arg("bevy/serialize")
407 .category(CommandCategory::Build)
408 .priority(70)
409 .tag("assets")
410 .tag("optimize")
411 .tag("bevy")
412 .estimated_duration(60)
413 .build(),
414 CommandBuilder::new("bevy-bundle-assets", "cargo")
415 .label("Bundle Assets")
416 .description("Create asset bundle for distribution")
417 .arg("build")
418 .arg("--release")
419 .arg("--features")
420 .arg("bevy/embedded_watcher")
421 .category(CommandCategory::Deploy)
422 .priority(65)
423 .tag("bundle")
424 .tag("assets")
425 .tag("deploy")
426 .tag("bevy")
427 .estimated_duration(90)
428 .build(),
429 ]
430 }
431}
432
433impl Default for BevyProvider {
434 fn default() -> Self {
435 Self::new()
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
443 use std::collections::HashMap;
444 use std::path::PathBuf;
445
446 fn create_bevy_context() -> ProjectContext {
447 ProjectContext {
448 workspace_root: PathBuf::from("/test"),
449 current_file: None,
450 cursor_position: None,
451 project_type: ProjectType::Bevy,
452 dependencies: vec![Dependency {
453 name: "bevy".to_string(),
454 version: "0.12".to_string(),
455 features: vec!["dynamic_linking".to_string()],
456 optional: false,
457 dev_dependency: false,
458 }],
459 workspace_members: vec![WorkspaceMember {
460 name: "bevy-game".to_string(),
461 path: PathBuf::from("/test"),
462 package_type: ProjectType::Bevy,
463 }],
464 build_targets: vec![BuildTarget {
465 name: "main".to_string(),
466 target_type: TargetType::Binary,
467 path: PathBuf::from("/test/src/main.rs"),
468 }],
469 active_features: vec!["dynamic_linking".to_string()],
470 env_vars: HashMap::new(),
471 }
472 }
473
474 #[tokio::test]
475 async fn test_bevy_provider_can_handle() {
476 let provider = BevyProvider::new();
477 let context = create_bevy_context();
478
479 assert!(provider.can_handle(&context));
480 assert_eq!(provider.name(), "bevy");
481 assert_eq!(provider.priority(), 85);
482 }
483
484 #[tokio::test]
485 async fn test_bevy_commands_generation() {
486 let provider = BevyProvider::new();
487 let context = create_bevy_context();
488
489 let commands = provider.commands(&context).await.unwrap();
490
491 assert!(!commands.is_empty());
492
493 assert!(commands.iter().any(|c| c.id == "bevy-run"));
495 assert!(commands.iter().any(|c| c.id == "bevy-run-features"));
496
497 assert!(commands.iter().any(|c| c.id == "bevy-build"));
499 assert!(commands.iter().any(|c| c.id == "bevy-build-release"));
500
501 assert!(commands.iter().any(|c| c.id == "bevy-test"));
503 assert!(commands.iter().any(|c| c.id == "bevy-test-headless"));
504
505 assert!(commands.iter().any(|c| c.id == "bevy-assets-watch"));
507 }
508
509 #[tokio::test]
510 async fn test_bevy_wasm_commands() {
511 let provider = BevyProvider::new();
512 let mut context = create_bevy_context();
513
514 context.dependencies.push(Dependency {
516 name: "wasm-bindgen".to_string(),
517 version: "0.2".to_string(),
518 features: vec![],
519 optional: false,
520 dev_dependency: false,
521 });
522
523 let commands = provider.commands(&context).await.unwrap();
524
525 assert!(commands.iter().any(|c| c.id == "bevy-build-wasm"));
527 }
528
529 #[tokio::test]
530 async fn test_bevy_command_priorities() {
531 let provider = BevyProvider::new();
532 let context = create_bevy_context();
533
534 let commands = provider.commands(&context).await.unwrap();
535
536 let run_cmd = commands.iter().find(|c| c.id == "bevy-run").unwrap();
538 assert_eq!(run_cmd.priority, 95);
539
540 assert!(
542 commands
543 .iter()
544 .all(|c| c.tags.contains(&"bevy".to_string()))
545 );
546 }
547
548 #[tokio::test]
549 async fn test_bevy_dependency_detection() {
550 let provider = BevyProvider::new();
551 let mut context = create_bevy_context();
552 context.project_type = ProjectType::Binary; assert!(provider.can_handle(&context));
556 }
557}