1use crate::file_detection::{
7 EntryPoint, EntryPointType, ExecutionCapabilities, FileExecutionContext, FileRole,
8 RustProjectType, SingleFileType,
9};
10
11use crate::framework_detection::{FrameworkType, PreciseFrameworkDetector};
12#[cfg(feature = "tree-sitter-support")]
13#[allow(unused_imports)] use crate::tree_sitter_test_detector::TreeSitterTestDetector;
15use crate::{Command, CommandCategory, Position, ProjectContext, RazResult};
16use raz_common::parse::parse_option;
17use raz_config::{CommandOverride, OverrideMode};
18use raz_override::OptionValue;
19use raz_override::{FunctionContext, OverrideSystem};
20use std::collections::HashMap;
21use std::path::Path;
22
23pub struct UniversalCommandGenerator;
25
26impl UniversalCommandGenerator {
27 pub fn generate_commands(
29 context: &FileExecutionContext,
30 cursor: Option<Position>,
31 ) -> RazResult<Vec<Command>> {
32 Self::generate_commands_with_overrides(context, cursor, None, None)
33 }
34
35 pub fn generate_commands_with_overrides(
37 context: &FileExecutionContext,
38 cursor: Option<Position>,
39 workspace: Option<&Path>,
40 _override_key: Option<&str>,
41 ) -> RazResult<Vec<Command>> {
42 let mut commands = Self::generate_commands_internal(context, cursor)?;
44
45 if let Some(workspace_path) = workspace {
47 let function_context = if let Some(pos) = cursor {
49 let test_name =
51 Self::find_test_at_cursor(&context.entry_points, pos).map(|ep| ep.name.clone());
52
53 FunctionContext {
54 file_path: context.file_path.clone(),
55 function_name: test_name,
56 line_number: pos.line as usize,
57 context: Some(format!("column:{}", pos.column)),
58 }
59 } else {
60 FunctionContext {
61 file_path: context.file_path.clone(),
62 function_name: None,
63 line_number: 0,
64 context: None,
65 }
66 };
67
68 let mut override_system = OverrideSystem::new(workspace_path).map_err(|e| {
70 crate::error::RazError::Config {
71 message: format!("Failed to create override system: {e}"),
72 }
73 })?;
74
75 if let Some(override_config) = override_system
76 .resolve_override(&function_context)
77 .map_err(|e| crate::error::RazError::Config {
78 message: format!("Failed to load override: {e}"),
79 })?
80 {
81 for cmd in &mut commands {
83 Self::apply_override_to_command(cmd, &override_config);
84 }
85 }
86 }
89
90 Ok(commands)
91 }
92
93 fn generate_commands_internal(
95 context: &FileExecutionContext,
96 cursor: Option<Position>,
97 ) -> RazResult<Vec<Command>> {
98 let mut commands = Vec::new();
99
100 let (cursor_in_test, cursor_on_main, cursor_in_doc_test) = if let Some(cursor_pos) = cursor
108 {
109 let test_entry = Self::find_test_at_cursor(&context.entry_points, cursor_pos);
110 let main_entry = Self::find_main_at_cursor(&context.entry_points, cursor_pos);
111 let in_doc_test = test_entry
112 .as_ref()
113 .is_some_and(|entry| matches!(entry.entry_type, EntryPointType::DocTest));
114 (test_entry.is_some(), main_entry.is_some(), in_doc_test)
115 } else {
116 (false, false, false)
117 };
118
119 if context.capabilities.can_test && !cursor_on_main {
121 let mut test_commands = Self::generate_test_commands(context, cursor)?;
122
123 for cmd in &mut test_commands {
125 cmd.priority = 100; if cursor_in_test {
129 cmd.priority = cmd.priority.saturating_add(50);
130 }
131
132 if cursor_in_doc_test && !cmd.id.contains("doc-test") {
134 cmd.priority = cmd.priority.saturating_sub(25);
135 }
136 }
137
138 commands.extend(test_commands);
139 }
140
141 if context.capabilities.can_doc_test {
143 let has_doc_tests = context
144 .entry_points
145 .iter()
146 .any(|ep| ep.entry_type == crate::file_detection::EntryPointType::DocTest);
147
148 if cursor.is_none() || cursor_in_doc_test || has_doc_tests {
153 let mut doc_test_commands = Self::generate_doc_test_commands(context)?;
154
155 for cmd in &mut doc_test_commands {
157 cmd.priority = 95;
158
159 if cursor_in_doc_test {
161 cmd.priority = cmd.priority.saturating_add(30);
162 }
163 }
164
165 commands.extend(doc_test_commands);
166 }
167 }
168
169 if context.capabilities.can_run {
171 let mut run_commands = Self::generate_run_commands(context)?;
172
173 let run_priority = if cursor_on_main {
175 150 } else if context.capabilities.can_test && cursor_in_test {
177 50 } else if context.capabilities.can_test {
179 75 } else {
181 90 };
183
184 for cmd in &mut run_commands {
185 cmd.priority = run_priority;
186 }
187
188 commands.extend(run_commands);
189
190 let mut framework_commands = Self::generate_advanced_framework_commands(context)?;
192 for cmd in &mut framework_commands {
193 let framework_priority = if cursor_on_main && Self::is_web_framework_command(cmd) {
197 160 } else if Self::is_web_framework_command(cmd) {
199 120 } else {
201 run_priority };
203 cmd.priority = framework_priority;
204 }
205 commands.extend(framework_commands);
206 }
207
208 if matches!(
210 &context.file_role,
211 crate::file_detection::FileRole::BuildScript
212 ) {
213 let mut build_commands = Self::generate_build_script_commands(context)?;
214
215 for cmd in &mut build_commands {
216 cmd.priority = 80; }
218
219 commands.extend(build_commands);
220 }
221
222 if context.capabilities.can_bench {
224 let mut bench_commands = Self::generate_bench_commands(context)?;
225
226 for cmd in &mut bench_commands {
227 cmd.priority = 70; }
229
230 commands.extend(bench_commands);
231 }
232
233 commands.sort_by(|a, b| b.priority.cmp(&a.priority));
235
236 Ok(commands)
237 }
238
239 pub fn generate_commands_with_runtime_override(
241 context: &FileExecutionContext,
242 cursor: Option<Position>,
243 _workspace: Option<&Path>,
244 _override_key: Option<&str>,
245 override_input: &str,
246 ) -> RazResult<Vec<Command>> {
247 let mut commands = Self::generate_commands(context, cursor)?;
249
250 let command = if !commands.is_empty() {
252 match commands[0].command.as_str() {
254 "cargo" => {
255 commands[0]
257 .args
258 .first()
259 .map(|s| s.as_str())
260 .unwrap_or("run")
261 }
262 _ => "run",
263 }
264 } else {
265 "run"
266 };
267
268 let parsed_override = raz_override::parse_override_to_command(command, override_input)
269 .map_err(|e| crate::error::RazError::Config {
270 message: format!("Failed to parse override: {e}"),
271 })?;
272
273 for cmd in &mut commands {
275 Self::apply_override_to_command(cmd, &parsed_override);
276 }
277
278 Ok(commands)
279 }
280
281 fn apply_override_to_command(command: &mut Command, override_config: &CommandOverride) {
283 let is_append = matches!(override_config.mode, OverrideMode::Append);
284
285 if !override_config.env.is_empty() {
287 if is_append {
288 for (key, value) in &override_config.env {
290 command.env.insert(key.clone(), value.clone());
291 }
292 } else {
293 command.env.clear();
295 for (key, value) in &override_config.env {
296 command.env.insert(key.clone(), value.clone());
297 }
298 }
299 }
300
301 if !override_config.cargo_options.is_empty() {
303 let mut all_cargo_parts = Vec::new();
305 for option in &override_config.cargo_options {
306 match parse_option(option) {
307 Ok(parts) if !parts.is_empty() => {
308 if is_append {
309 if !command.args.contains(&parts[0]) {
311 all_cargo_parts.extend(parts);
312 }
313 } else {
314 all_cargo_parts.extend(parts);
316 }
317 }
318 Ok(_) => {} Err(e) => {
320 eprintln!("Warning: Failed to parse cargo option '{option}': {e}");
321 }
322 }
323 }
324
325 if !all_cargo_parts.is_empty() {
327 let separator_pos = command.args.iter().position(|arg| arg == "--");
329
330 if let Some(sep_pos) = separator_pos {
331 for (i, part) in all_cargo_parts.into_iter().enumerate() {
333 command.args.insert(sep_pos + i, part);
334 }
335 } else {
336 command.args.extend(all_cargo_parts);
338 }
339 }
340 }
341
342 if !override_config.rustc_options.is_empty() {
344 let separator_pos = command.args.iter().position(|arg| arg == "--");
346 if separator_pos.is_none()
347 && (!override_config.rustc_options.is_empty() || !override_config.args.is_empty())
348 {
349 command.args.push("--".to_string());
350 }
351
352 for option in &override_config.rustc_options {
354 command.args.push(option.clone());
355 }
356 }
357
358 if !override_config.args.is_empty() {
360 let is_test_command = command.category == CommandCategory::Test
362 || command.args.iter().any(|arg| arg == "test");
363
364 if is_test_command {
365 if is_append {
367 if let Some(separator_pos) = command.args.iter().position(|arg| arg == "--") {
369 let existing_args: Vec<String> = command.args[separator_pos + 1..].to_vec();
371
372 let mut args_to_add = Vec::new();
374 for arg in &override_config.args {
375 if !existing_args.contains(arg) {
376 args_to_add.push(arg.clone());
377 }
378 }
379
380 let insert_pos = separator_pos + 1;
382 command.args.splice(insert_pos..insert_pos, args_to_add);
383 } else {
384 command.args.push("--".to_string());
386 command.args.extend(override_config.args.clone());
387 }
388 } else {
389 if let Some(separator_pos) = command.args.iter().position(|arg| arg == "--") {
391 let existing_args: Vec<String> = command.args[separator_pos + 1..].to_vec();
392
393 let mut preserved_test_name = None;
396 if let Some(first_arg) = existing_args.first() {
397 if !first_arg.starts_with("--") && !first_arg.is_empty() {
398 preserved_test_name = Some(first_arg.clone());
400 }
401 }
402
403 command.args.truncate(separator_pos + 1);
405
406 if let Some(test_name) = preserved_test_name {
408 command.args.push(test_name);
409 }
410
411 command.args.extend(override_config.args.clone());
413 } else {
414 command.args.push("--".to_string());
416 command.args.extend(override_config.args.clone());
417 }
418 }
419 } else {
420 if is_append {
422 command.args.extend(override_config.args.clone());
423 } else {
424 command.args.extend(override_config.args.clone());
427 }
428 }
429 }
430 }
431
432 #[allow(dead_code)]
433 fn apply_options_to_command(
435 command: &mut Command,
436 options: &HashMap<String, OptionValue>,
437 append_mode: bool,
438 ) {
439 use crate::rustc_options::translate_cargo_to_rustc;
440
441 let is_rustc = command.command == "rustc"
443 || (command.command == "sh" && command.args.iter().any(|arg| arg.contains("rustc")));
444
445 for (option, value) in options {
446 match value {
447 OptionValue::Flag(true) => {
448 let actual_option = if is_rustc {
450 if let Some(rustc_opt) = translate_cargo_to_rustc(option) {
451 rustc_opt.to_string()
452 } else {
453 continue;
455 }
456 } else {
457 option.clone()
458 };
459
460 if !command.args.contains(&actual_option) {
462 if command.command == "sh"
464 && command.args.first() == Some(&"-c".to_string())
465 {
466 if let Some(cmd_string) = command.args.get_mut(1) {
467 if cmd_string.contains("rustc")
469 && !cmd_string.contains(&actual_option)
470 {
471 if option == "--release" && is_rustc {
473 let opt_flags = "-O -C lto=yes -C codegen-units=1";
474 *cmd_string = cmd_string
475 .replace("rustc", &format!("rustc {opt_flags}"));
476 } else {
477 *cmd_string = cmd_string
478 .replace("rustc", &format!("rustc {actual_option}"));
479 }
480 }
481 }
482 } else {
483 let insert_pos = command
485 .args
486 .iter()
487 .position(|arg| arg == "--")
488 .unwrap_or(command.args.len());
489
490 if option == "--release" && is_rustc {
492 command.args.insert(insert_pos, "-O".to_string());
493 command.args.insert(insert_pos + 1, "-C".to_string());
494 command.args.insert(insert_pos + 2, "lto=yes".to_string());
495 command.args.insert(insert_pos + 3, "-C".to_string());
496 command
497 .args
498 .insert(insert_pos + 4, "codegen-units=1".to_string());
499 } else {
500 command.args.insert(insert_pos, actual_option);
501 }
502 }
503 }
504 }
505 OptionValue::Flag(false) => {
506 command.args.retain(|arg| arg != option);
508 }
509 OptionValue::Single(val) => {
510 if let Some(opt_idx) = command.args.iter().position(|arg| arg == option) {
511 if append_mode && opt_idx + 1 < command.args.len() {
512 command.args[opt_idx + 1] = val.clone();
515 } else {
516 if opt_idx + 1 < command.args.len() {
518 command.args[opt_idx + 1] = val.clone();
519 } else {
520 command.args.push(val.clone());
521 }
522 }
523 } else {
524 let insert_pos = command
526 .args
527 .iter()
528 .position(|arg| arg == "--")
529 .unwrap_or(command.args.len());
530 command.args.insert(insert_pos, option.clone());
531 command.args.insert(insert_pos + 1, val.clone());
532 }
533 }
534 OptionValue::Multiple(values) => {
535 let value_str = values.join(",");
536 if let Some(opt_idx) = command.args.iter().position(|arg| arg == option) {
537 if append_mode && opt_idx + 1 < command.args.len() {
538 let existing = &command.args[opt_idx + 1];
540 let mut all_values: Vec<String> =
541 existing.split(',').map(|s| s.to_string()).collect();
542 all_values.extend(values.clone());
543 command.args[opt_idx + 1] = all_values.join(",");
544 } else {
545 if opt_idx + 1 < command.args.len() {
547 command.args[opt_idx + 1] = value_str;
548 } else {
549 command.args.push(value_str);
550 }
551 }
552 } else {
553 let insert_pos = command
555 .args
556 .iter()
557 .position(|arg| arg == "--")
558 .unwrap_or(command.args.len());
559 command.args.insert(insert_pos, option.clone());
560 command.args.insert(insert_pos + 1, value_str);
561 }
562 }
563 }
564 }
565 }
566
567 fn generate_run_commands(context: &FileExecutionContext) -> RazResult<Vec<Command>> {
569 let mut commands = Vec::new();
570
571 match &context.project_type {
572 RustProjectType::CargoWorkspace { root, members } => {
573 commands.extend(Self::generate_cargo_workspace_run_commands(
574 root,
575 members,
576 &context.file_role,
577 &context.capabilities,
578 )?);
579 }
580 RustProjectType::CargoPackage { root, package_name } => {
581 commands.extend(Self::generate_cargo_package_run_commands(
582 root,
583 package_name,
584 &context.file_role,
585 &context.capabilities,
586 )?);
587 }
588 RustProjectType::CargoScript { file_path, .. } => {
589 commands.extend(Self::generate_cargo_script_run_commands(file_path)?);
590 }
591 RustProjectType::SingleFile {
592 file_path,
593 file_type,
594 } => {
595 commands.extend(Self::generate_single_file_run_commands(
596 file_path, file_type,
597 )?);
598 }
599 }
600
601 Ok(commands)
602 }
603
604 fn generate_test_commands(
606 context: &FileExecutionContext,
607 cursor: Option<Position>,
608 ) -> RazResult<Vec<Command>> {
609 let mut commands = Vec::new();
610
611 if let Some(cursor_pos) = cursor {
613 if let Some(test_entry) = Self::find_test_at_cursor(&context.entry_points, cursor_pos) {
614 commands.extend(Self::generate_specific_test_commands(context, test_entry)?);
616 for cmd in &mut commands {
618 if matches!(test_entry.entry_type, EntryPointType::DocTest) {
619 cmd.priority = cmd.priority.saturating_add(50);
620 }
621 }
622 }
623 }
624
625 match &context.project_type {
627 RustProjectType::CargoWorkspace { root, members } => {
628 commands.extend(Self::generate_cargo_workspace_test_commands(
629 root,
630 members,
631 &context.file_role,
632 )?);
633 }
634 RustProjectType::CargoPackage { root, package_name } => {
635 commands.extend(Self::generate_cargo_package_test_commands(
636 root,
637 package_name,
638 &context.file_role,
639 )?);
640 }
641 RustProjectType::CargoScript { file_path, .. } => {
642 commands.extend(Self::generate_cargo_script_test_commands(file_path)?);
643 }
644 RustProjectType::SingleFile {
645 file_path,
646 file_type,
647 } => {
648 commands.extend(Self::generate_single_file_test_commands(
649 file_path, file_type,
650 )?);
651 }
652 }
653
654 Ok(commands)
655 }
656
657 fn generate_bench_commands(context: &FileExecutionContext) -> RazResult<Vec<Command>> {
659 let mut commands = Vec::new();
660
661 match &context.project_type {
662 RustProjectType::CargoWorkspace { root, members } => {
663 commands.extend(Self::generate_cargo_workspace_bench_commands(
664 root,
665 members,
666 &context.file_role,
667 )?);
668 }
669 RustProjectType::CargoPackage { root, package_name } => {
670 commands.extend(Self::generate_cargo_package_bench_commands(
671 root,
672 package_name,
673 &context.file_role,
674 )?);
675 }
676 RustProjectType::SingleFile { file_path, .. } => {
677 commands.extend(Self::generate_single_file_bench_commands(file_path)?);
678 }
679 _ => {} }
681
682 Ok(commands)
683 }
684
685 fn generate_doc_test_commands(context: &FileExecutionContext) -> RazResult<Vec<Command>> {
687 let mut commands = Vec::new();
688
689 match &context.project_type {
690 RustProjectType::CargoWorkspace { root, members } => {
691 commands.extend(Self::generate_cargo_workspace_doc_test_commands(
692 root,
693 members,
694 &context.file_role,
695 )?);
696 }
697 RustProjectType::CargoPackage { root, package_name } => {
698 commands.extend(Self::generate_cargo_package_doc_test_commands(
699 root,
700 package_name,
701 &context.file_role,
702 )?);
703 }
704 RustProjectType::SingleFile { file_path, .. } => {
705 let has_doc_tests = context
707 .entry_points
708 .iter()
709 .any(|ep| ep.entry_type == EntryPointType::DocTest);
710 if has_doc_tests {
711 commands.extend(Self::generate_single_file_doc_test_commands(file_path)?);
712 }
713 }
714 _ => {} }
716
717 Ok(commands)
718 }
719
720 fn generate_build_script_commands(context: &FileExecutionContext) -> RazResult<Vec<Command>> {
722 let mut commands = Vec::new();
723
724 match &context.project_type {
725 RustProjectType::CargoWorkspace { root, .. }
726 | RustProjectType::CargoPackage { root, .. } => {
727 commands.push(Command {
729 id: "build-script-trigger".to_string(),
730 label: "Run build script".to_string(),
731 description: Some("Trigger build script execution via cargo build".to_string()),
732 command: "cargo".to_string(),
733 args: vec!["build".to_string()],
734 env: HashMap::new(),
735 cwd: Some(root.to_path_buf()),
736 category: CommandCategory::Build,
737 priority: 80,
738 conditions: Vec::new(),
739 tags: vec!["build".to_string(), "script".to_string()],
740 requires_input: false,
741 estimated_duration: Some(10),
742 });
743
744 if context.capabilities.can_run {
746 commands.push(Command {
747 id: "build-script-direct".to_string(),
748 label: "Execute build script directly".to_string(),
749 description: Some(
750 "Compile and run build script as standalone executable".to_string(),
751 ),
752 command: "sh".to_string(),
753 args: vec![
754 "-c".to_string(),
755 format!("rustc {} && ./build", root.join("build.rs").display()),
756 ],
757 env: HashMap::new(),
758 cwd: Some(root.to_path_buf()),
759 category: CommandCategory::Build,
760 priority: 60,
761 conditions: Vec::new(),
762 tags: vec![
763 "build".to_string(),
764 "script".to_string(),
765 "direct".to_string(),
766 ],
767 requires_input: false,
768 estimated_duration: Some(5),
769 });
770 }
771 }
772 RustProjectType::SingleFile { file_path, .. } => {
773 commands.extend(Self::generate_standalone_build_script_commands(file_path)?);
775 }
776 _ => {} }
778
779 Ok(commands)
780 }
781
782 fn generate_cargo_workspace_run_commands(
785 root: &Path,
786 members: &[crate::file_detection::WorkspaceMember],
787 file_role: &FileRole,
788 _capabilities: &ExecutionCapabilities,
789 ) -> RazResult<Vec<Command>> {
790 let mut commands = Vec::new();
791
792 match file_role {
793 FileRole::MainBinary { binary_name } | FileRole::AdditionalBinary { binary_name } => {
794 if let Some(package_name) = Self::find_package_for_file(members, root) {
796 commands.push(Self::create_cargo_run_command(
797 root,
798 Some(&package_name),
799 Some(binary_name),
800 100,
801 ));
802 }
803 }
804 FileRole::FrontendLibrary { framework } => {
805 commands.extend(Self::generate_framework_commands(root, framework)?);
806 }
807 FileRole::Example { example_name } => {
808 if let Some(package_name) = Self::find_package_for_file(members, root) {
809 commands.push(Self::create_cargo_example_command(
810 root,
811 Some(&package_name),
812 example_name,
813 90,
814 ));
815 }
816 }
817 _ => {}
818 }
819
820 Ok(commands)
821 }
822
823 fn generate_cargo_workspace_test_commands(
824 root: &Path,
825 members: &[crate::file_detection::WorkspaceMember],
826 file_role: &FileRole,
827 ) -> RazResult<Vec<Command>> {
828 let mut commands = Vec::new();
829
830 if let Some(package_name) = Self::find_package_for_file(members, root) {
831 match file_role {
832 FileRole::IntegrationTest { test_name } => {
833 commands.push(Self::create_cargo_test_command(
834 root,
835 Some(&package_name),
836 Some(&format!("--test {test_name}")),
837 "integration test",
838 95,
839 ));
840 }
841 FileRole::LibraryRoot => {
842 commands.push(Self::create_cargo_test_command(
843 root,
844 Some(&package_name),
845 Some("--lib"),
846 "library tests",
847 90,
848 ));
849 }
850 FileRole::Module => {
851 commands.push(Self::create_cargo_test_command(
853 root,
854 Some(&package_name),
855 Some("--lib"),
856 "module tests",
857 90,
858 ));
859 }
860 _ => {
861 commands.push(Self::create_cargo_test_command(
862 root,
863 Some(&package_name),
864 None,
865 "all tests",
866 85,
867 ));
868 }
869 }
870 }
871
872 Ok(commands)
873 }
874
875 fn generate_cargo_workspace_bench_commands(
876 root: &Path,
877 members: &[crate::file_detection::WorkspaceMember],
878 file_role: &FileRole,
879 ) -> RazResult<Vec<Command>> {
880 let mut commands = Vec::new();
881
882 if let Some(package_name) = Self::find_package_for_file(members, root) {
883 match file_role {
884 FileRole::Benchmark { bench_name } => {
885 commands.push(Self::create_cargo_bench_command(
886 root,
887 Some(&package_name),
888 Some(&format!("--bench {bench_name}")),
889 "specific benchmark",
890 95,
891 ));
892 }
893 _ => {
894 commands.push(Self::create_cargo_bench_command(
895 root,
896 Some(&package_name),
897 None,
898 "all benchmarks",
899 85,
900 ));
901 }
902 }
903 }
904
905 Ok(commands)
906 }
907
908 fn generate_cargo_workspace_doc_test_commands(
909 root: &Path,
910 members: &[crate::file_detection::WorkspaceMember],
911 _file_role: &FileRole,
912 ) -> RazResult<Vec<Command>> {
913 let mut commands = Vec::new();
914
915 if let Some(package_name) = Self::find_package_for_file(members, root) {
916 commands.push(Self::create_cargo_doc_test_command(
917 root,
918 Some(&package_name),
919 "doc tests",
920 85,
921 ));
922 }
923
924 Ok(commands)
925 }
926
927 fn generate_cargo_package_run_commands(
930 root: &Path,
931 package_name: &str,
932 file_role: &FileRole,
933 _capabilities: &ExecutionCapabilities,
934 ) -> RazResult<Vec<Command>> {
935 let mut commands = Vec::new();
936
937 match file_role {
938 FileRole::MainBinary { binary_name } | FileRole::AdditionalBinary { binary_name } => {
939 commands.push(Self::create_cargo_run_command(
940 root,
941 Some(package_name),
942 Some(binary_name),
943 100,
944 ));
945 }
946 FileRole::FrontendLibrary { framework } => {
947 commands.extend(Self::generate_framework_commands(root, framework)?);
948 }
949 FileRole::Example { example_name } => {
950 commands.push(Self::create_cargo_example_command(
951 root,
952 Some(package_name),
953 example_name,
954 90,
955 ));
956 }
957 FileRole::BuildScript => {
958 }
961 FileRole::Standalone => {
962 }
965 _ => {}
966 }
967
968 Ok(commands)
969 }
970
971 fn generate_cargo_package_test_commands(
972 root: &Path,
973 package_name: &str,
974 file_role: &FileRole,
975 ) -> RazResult<Vec<Command>> {
976 let mut commands = Vec::new();
977
978 match file_role {
979 FileRole::IntegrationTest { test_name } => {
980 commands.push(Self::create_cargo_test_command(
981 root,
982 Some(package_name),
983 Some(&format!("--test {test_name}")),
984 "integration test",
985 95,
986 ));
987 }
988 FileRole::LibraryRoot => {
989 commands.push(Self::create_cargo_test_command(
990 root,
991 Some(package_name),
992 Some("--lib"),
993 "library tests",
994 90,
995 ));
996 }
997 FileRole::Module => {
998 commands.push(Self::create_cargo_test_command(
1000 root,
1001 Some(package_name),
1002 Some("--lib"),
1003 "module tests",
1004 90,
1005 ));
1006 }
1007 _ => {
1008 commands.push(Self::create_cargo_test_command(
1009 root,
1010 Some(package_name),
1011 None,
1012 "all tests",
1013 85,
1014 ));
1015 }
1016 }
1017
1018 Ok(commands)
1019 }
1020
1021 fn generate_cargo_package_bench_commands(
1022 root: &Path,
1023 package_name: &str,
1024 file_role: &FileRole,
1025 ) -> RazResult<Vec<Command>> {
1026 let mut commands = Vec::new();
1027
1028 match file_role {
1029 FileRole::Benchmark { bench_name } => {
1030 commands.push(Self::create_cargo_bench_command(
1031 root,
1032 Some(package_name),
1033 Some(&format!("--bench {bench_name}")),
1034 "specific benchmark",
1035 95,
1036 ));
1037 }
1038 _ => {
1039 commands.push(Self::create_cargo_bench_command(
1040 root,
1041 Some(package_name),
1042 None,
1043 "all benchmarks",
1044 85,
1045 ));
1046 }
1047 }
1048
1049 Ok(commands)
1050 }
1051
1052 fn generate_cargo_package_doc_test_commands(
1053 root: &Path,
1054 package_name: &str,
1055 _file_role: &FileRole,
1056 ) -> RazResult<Vec<Command>> {
1057 Ok(vec![Self::create_cargo_doc_test_command(
1058 root,
1059 Some(package_name),
1060 "doc tests",
1061 85,
1062 )])
1063 }
1064
1065 fn generate_cargo_script_run_commands(file_path: &Path) -> RazResult<Vec<Command>> {
1068 let mut commands = Vec::new();
1069
1070 let file_name = file_path.to_string_lossy();
1071
1072 commands.push(Command {
1073 id: "cargo-script-run".to_string(),
1074 label: "Run cargo script".to_string(),
1075 description: Some("Execute the cargo script".to_string()),
1076 command: "cargo".to_string(),
1077 args: vec![
1078 "+nightly".to_string(),
1079 "-Zscript".to_string(),
1080 file_name.to_string(),
1081 ],
1082 env: HashMap::new(),
1083 cwd: file_path.parent().map(|p| p.to_path_buf()),
1084 category: CommandCategory::Run,
1085 priority: 100,
1086 conditions: Vec::new(),
1087 tags: vec!["script".to_string(), "nightly".to_string()],
1088 requires_input: false,
1089 estimated_duration: Some(3),
1090 });
1091
1092 Ok(commands)
1093 }
1094
1095 fn generate_cargo_script_test_commands(file_path: &Path) -> RazResult<Vec<Command>> {
1096 let mut commands = Vec::new();
1097
1098 let file_name = file_path.to_string_lossy();
1099
1100 commands.push(Command {
1101 id: "cargo-script-test".to_string(),
1102 label: "Test cargo script".to_string(),
1103 description: Some("Run tests in the cargo script".to_string()),
1104 command: "cargo".to_string(),
1105 args: vec![
1106 "+nightly".to_string(),
1107 "-Zscript".to_string(),
1108 file_name.to_string(),
1109 "--".to_string(),
1110 "--test".to_string(),
1111 ],
1112 env: HashMap::new(),
1113 cwd: file_path.parent().map(|p| p.to_path_buf()),
1114 category: CommandCategory::Test,
1115 priority: 90,
1116 conditions: Vec::new(),
1117 tags: vec![
1118 "script".to_string(),
1119 "test".to_string(),
1120 "nightly".to_string(),
1121 ],
1122 requires_input: false,
1123 estimated_duration: Some(5),
1124 });
1125
1126 Ok(commands)
1127 }
1128
1129 fn generate_standalone_build_script_commands(file_path: &Path) -> RazResult<Vec<Command>> {
1133 let mut commands = Vec::new();
1134
1135 let file_name = file_path.to_string_lossy();
1136 let parent_dir = file_path.parent().unwrap_or_else(|| Path::new("."));
1137
1138 if parent_dir.join("Cargo.toml").exists() {
1140 commands.push(Command {
1142 id: "build-script-cargo".to_string(),
1143 label: "Run build script".to_string(),
1144 description: Some("Trigger build script execution via cargo build".to_string()),
1145 command: "cargo".to_string(),
1146 args: vec!["build".to_string()],
1147 env: HashMap::new(),
1148 cwd: Some(parent_dir.to_path_buf()),
1149 category: CommandCategory::Build,
1150 priority: 90,
1151 conditions: Vec::new(),
1152 tags: vec!["build".to_string(), "script".to_string()],
1153 requires_input: false,
1154 estimated_duration: Some(10),
1155 });
1156 }
1157
1158 let executable_name = file_path
1160 .file_stem()
1161 .and_then(|s| s.to_str())
1162 .unwrap_or("build");
1163
1164 commands.push(Command {
1165 id: "build-script-direct".to_string(),
1166 label: "Execute build script directly".to_string(),
1167 description: Some("Compile and run build script as standalone executable".to_string()),
1168 command: "sh".to_string(),
1169 args: vec![
1170 "-c".to_string(),
1171 format!("rustc {} && ./{}", file_name, executable_name),
1172 ],
1173 env: HashMap::new(),
1174 cwd: Some(parent_dir.to_path_buf()),
1175 category: CommandCategory::Build,
1176 priority: 80,
1177 conditions: Vec::new(),
1178 tags: vec![
1179 "build".to_string(),
1180 "script".to_string(),
1181 "direct".to_string(),
1182 ],
1183 requires_input: false,
1184 estimated_duration: Some(5),
1185 });
1186
1187 Ok(commands)
1188 }
1189
1190 fn generate_single_file_run_commands(
1191 file_path: &Path,
1192 file_type: &SingleFileType,
1193 ) -> RazResult<Vec<Command>> {
1194 let mut commands = Vec::new();
1195
1196 if let SingleFileType::Executable = file_type {
1197 let file_name = file_path.to_string_lossy();
1198 let executable_name = file_path
1199 .file_stem()
1200 .and_then(|s| s.to_str())
1201 .unwrap_or("program");
1202
1203 commands.push(Command {
1204 id: "rustc-run".to_string(),
1205 label: format!("Compile and run {executable_name}"),
1206 description: Some("Compile with rustc and execute".to_string()),
1207 command: "sh".to_string(),
1208 args: vec![
1209 "-c".to_string(),
1210 format!("rustc '{}' && './{}'", file_name, executable_name),
1211 ],
1212 env: HashMap::new(),
1213 cwd: file_path.parent().map(|p| p.to_path_buf()),
1214 category: CommandCategory::Run,
1215 priority: 100,
1216 conditions: Vec::new(),
1217 tags: vec!["rustc".to_string(), "standalone".to_string()],
1218 requires_input: false,
1219 estimated_duration: Some(10),
1220 });
1221
1222 commands.push(Command {
1224 id: "rustc-run-optimized".to_string(),
1225 label: format!("Compile and run {executable_name} (optimized)"),
1226 description: Some("Compile with optimizations and execute".to_string()),
1227 command: "sh".to_string(),
1228 args: vec![
1229 "-c".to_string(),
1230 format!("rustc -O '{}' && './{}'", file_name, executable_name),
1231 ],
1232 env: HashMap::new(),
1233 cwd: file_path.parent().map(|p| p.to_path_buf()),
1234 category: CommandCategory::Run,
1235 priority: 80,
1236 conditions: Vec::new(),
1237 tags: vec![
1238 "rustc".to_string(),
1239 "optimized".to_string(),
1240 "standalone".to_string(),
1241 ],
1242 requires_input: false,
1243 estimated_duration: Some(15),
1244 });
1245 }
1246 Ok(commands)
1249 }
1250
1251 fn generate_single_file_test_commands(
1252 file_path: &Path,
1253 _file_type: &SingleFileType,
1254 ) -> RazResult<Vec<Command>> {
1255 let mut commands = Vec::new();
1256
1257 let file_name = file_path.to_string_lossy();
1258 let test_name = file_path
1259 .file_stem()
1260 .and_then(|s| s.to_str())
1261 .unwrap_or("test");
1262
1263 commands.push(Command {
1264 id: "rustc-test".to_string(),
1265 label: format!("Test all ({test_name})"),
1266 description: Some("Compile and run all tests with rustc".to_string()),
1267 command: "sh".to_string(),
1268 args: vec![
1269 "-c".to_string(),
1270 format!(
1271 "rustc --test '{}' -o './{}_test' && './{}_test'",
1272 file_name, test_name, test_name
1273 ),
1274 ],
1275 env: HashMap::new(),
1276 cwd: file_path.parent().map(|p| p.to_path_buf()),
1277 category: CommandCategory::Test,
1278 priority: 90,
1279 conditions: Vec::new(),
1280 tags: vec![
1281 "rustc".to_string(),
1282 "test".to_string(),
1283 "standalone".to_string(),
1284 ],
1285 requires_input: false,
1286 estimated_duration: Some(8),
1287 });
1288
1289 Ok(commands)
1290 }
1291
1292 fn generate_single_file_bench_commands(file_path: &Path) -> RazResult<Vec<Command>> {
1293 let mut commands = Vec::new();
1294
1295 let file_name = file_path.to_string_lossy();
1296 let bench_name = file_path
1297 .file_stem()
1298 .and_then(|s| s.to_str())
1299 .unwrap_or("bench");
1300
1301 commands.push(Command {
1302 id: "rustc-bench".to_string(),
1303 label: format!("Benchmark {bench_name}"),
1304 description: Some("Compile and run benchmarks with rustc".to_string()),
1305 command: "sh".to_string(),
1306 args: vec![
1307 "-c".to_string(),
1308 format!("rustc --test '{}' && './{}'", file_name, bench_name),
1309 ],
1310 env: HashMap::new(),
1311 cwd: file_path.parent().map(|p| p.to_path_buf()),
1312 category: CommandCategory::Custom("benchmark".to_string()),
1313 priority: 85,
1314 conditions: Vec::new(),
1315 tags: vec![
1316 "rustc".to_string(),
1317 "bench".to_string(),
1318 "standalone".to_string(),
1319 ],
1320 requires_input: false,
1321 estimated_duration: Some(15),
1322 });
1323
1324 Ok(commands)
1325 }
1326
1327 fn generate_single_file_doc_test_commands(file_path: &Path) -> RazResult<Vec<Command>> {
1328 let mut commands = Vec::new();
1329
1330 let file_name = file_path.to_string_lossy();
1331
1332 commands.push(Command {
1333 id: "rustdoc-test".to_string(),
1334 label: "Test documentation examples".to_string(),
1335 description: Some("Run documentation tests with rustdoc".to_string()),
1336 command: "rustdoc".to_string(),
1337 args: vec!["--test".to_string(), file_name.to_string()],
1338 env: HashMap::new(),
1339 cwd: file_path.parent().map(|p| p.to_path_buf()),
1340 category: CommandCategory::Test,
1341 priority: 80,
1342 conditions: Vec::new(),
1343 tags: vec![
1344 "rustdoc".to_string(),
1345 "doctest".to_string(),
1346 "standalone".to_string(),
1347 ],
1348 requires_input: false,
1349 estimated_duration: Some(5),
1350 });
1351
1352 Ok(commands)
1353 }
1354
1355 fn generate_framework_commands(root: &Path, framework: &str) -> RazResult<Vec<Command>> {
1358 let mut commands = Vec::new();
1359
1360 match framework {
1361 "leptos" => {
1362 commands.push(Command {
1363 id: "leptos-watch".to_string(),
1364 label: "Leptos Dev Server".to_string(),
1365 description: Some("Run Leptos development server with hot reload".to_string()),
1366 command: "cargo".to_string(),
1367 args: vec!["leptos".to_string(), "watch".to_string()],
1368 env: HashMap::new(),
1369 cwd: Some(root.to_path_buf()),
1370 category: CommandCategory::Run,
1371 priority: 100,
1372 conditions: Vec::new(),
1373 tags: vec!["leptos".to_string(), "dev".to_string(), "watch".to_string()],
1374 requires_input: false,
1375 estimated_duration: Some(5),
1376 });
1377 }
1378 "dioxus" => {
1379 commands.push(Command {
1380 id: "dioxus-serve".to_string(),
1381 label: "Dioxus Dev Server".to_string(),
1382 description: Some("Run Dioxus development server".to_string()),
1383 command: "dx".to_string(),
1384 args: vec!["serve".to_string()],
1385 env: HashMap::new(),
1386 cwd: Some(root.to_path_buf()),
1387 category: CommandCategory::Run,
1388 priority: 100,
1389 conditions: Vec::new(),
1390 tags: vec!["dioxus".to_string(), "dev".to_string(), "serve".to_string()],
1391 requires_input: false,
1392 estimated_duration: Some(5),
1393 });
1394 }
1395 "yew" => {
1396 commands.push(Command {
1397 id: "trunk-serve".to_string(),
1398 label: "Trunk Dev Server".to_string(),
1399 description: Some("Run Yew app with Trunk dev server".to_string()),
1400 command: "trunk".to_string(),
1401 args: vec!["serve".to_string()],
1402 env: HashMap::new(),
1403 cwd: Some(root.to_path_buf()),
1404 category: CommandCategory::Run,
1405 priority: 100,
1406 conditions: Vec::new(),
1407 tags: vec!["yew".to_string(), "trunk".to_string(), "serve".to_string()],
1408 requires_input: false,
1409 estimated_duration: Some(5),
1410 });
1411 }
1412 _ => {}
1413 }
1414
1415 Ok(commands)
1416 }
1417
1418 fn create_cargo_run_command(
1421 root: &Path,
1422 package: Option<&str>,
1423 binary: Option<&str>,
1424 priority: u8,
1425 ) -> Command {
1426 let mut args = vec!["run".to_string()];
1427
1428 if let Some(pkg) = package {
1429 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1430 }
1431
1432 if let Some(bin) = binary {
1433 args.extend(vec!["--bin".to_string(), bin.to_string()]);
1434 }
1435
1436 let label = match (package, binary) {
1437 (Some(pkg), Some(bin)) => format!("Run {bin} ({pkg})"),
1438 (Some(pkg), None) => format!("Run {pkg}"),
1439 (None, Some(bin)) => format!("Run {bin}"),
1440 (None, None) => "Run".to_string(),
1441 };
1442
1443 Command {
1444 id: "cargo-run".to_string(),
1445 label,
1446 description: Some("Execute the binary with cargo run".to_string()),
1447 command: "cargo".to_string(),
1448 args,
1449 env: HashMap::new(),
1450 cwd: Some(root.to_path_buf()),
1451 category: CommandCategory::Run,
1452 priority,
1453 conditions: Vec::new(),
1454 tags: vec!["cargo".to_string(), "run".to_string()],
1455 requires_input: false,
1456 estimated_duration: Some(5),
1457 }
1458 }
1459
1460 fn create_cargo_test_command(
1461 root: &Path,
1462 package: Option<&str>,
1463 extra_args: Option<&str>,
1464 description_suffix: &str,
1465 priority: u8,
1466 ) -> Command {
1467 let mut args = vec!["test".to_string()];
1468
1469 if let Some(pkg) = package {
1470 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1471 }
1472
1473 if let Some(extra) = extra_args {
1474 match parse_option(extra) {
1475 Ok(parts) => args.extend(parts),
1476 Err(e) => eprintln!("Warning: Failed to parse extra args '{extra}': {e}"),
1477 }
1478 }
1479
1480 Command {
1481 id: "cargo-test".to_string(),
1482 label: format!("Test {description_suffix}"),
1483 description: Some(format!("Run {description_suffix} with cargo test")),
1484 command: "cargo".to_string(),
1485 args,
1486 env: HashMap::new(),
1487 cwd: Some(root.to_path_buf()),
1488 category: CommandCategory::Test,
1489 priority,
1490 conditions: Vec::new(),
1491 tags: vec!["cargo".to_string(), "test".to_string()],
1492 requires_input: false,
1493 estimated_duration: Some(10),
1494 }
1495 }
1496
1497 fn create_cargo_binary_test_command(
1498 root: &Path,
1499 package: Option<&str>,
1500 binary_name: &str,
1501 test_filter: Option<&str>,
1502 description_suffix: &str,
1503 priority: u8,
1504 ) -> Command {
1505 let mut args = vec!["test".to_string()];
1506
1507 if let Some(pkg) = package {
1508 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1509 }
1510
1511 args.extend(vec!["--bin".to_string(), binary_name.to_string()]);
1513
1514 if let Some(filter) = test_filter {
1516 args.push("--".to_string());
1517 match parse_option(filter) {
1518 Ok(parts) => args.extend(parts),
1519 Err(e) => eprintln!("Warning: Failed to parse test filter '{filter}': {e}"),
1520 }
1521 }
1522
1523 Command {
1524 id: "cargo-test-binary".to_string(),
1525 label: format!("Test {description_suffix}"),
1526 description: Some(format!("Run {description_suffix} in binary {binary_name}")),
1527 command: "cargo".to_string(),
1528 args,
1529 env: HashMap::new(),
1530 cwd: Some(root.to_path_buf()),
1531 category: CommandCategory::Test,
1532 priority,
1533 conditions: Vec::new(),
1534 tags: vec![
1535 "cargo".to_string(),
1536 "test".to_string(),
1537 "binary".to_string(),
1538 ],
1539 requires_input: false,
1540 estimated_duration: Some(10),
1541 }
1542 }
1543
1544 fn create_cargo_bench_command(
1545 root: &Path,
1546 package: Option<&str>,
1547 extra_args: Option<&str>,
1548 description_suffix: &str,
1549 priority: u8,
1550 ) -> Command {
1551 let mut args = vec!["bench".to_string()];
1552
1553 if let Some(pkg) = package {
1554 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1555 }
1556
1557 if let Some(extra) = extra_args {
1558 match parse_option(extra) {
1559 Ok(parts) => args.extend(parts),
1560 Err(e) => eprintln!("Warning: Failed to parse extra args '{extra}': {e}"),
1561 }
1562 }
1563
1564 Command {
1565 id: "cargo-bench".to_string(),
1566 label: format!("Benchmark {description_suffix}"),
1567 description: Some(format!("Run {description_suffix} with cargo bench")),
1568 command: "cargo".to_string(),
1569 args,
1570 env: HashMap::new(),
1571 cwd: Some(root.to_path_buf()),
1572 category: CommandCategory::Custom("benchmark".to_string()),
1573 priority,
1574 conditions: Vec::new(),
1575 tags: vec!["cargo".to_string(), "bench".to_string()],
1576 requires_input: false,
1577 estimated_duration: Some(30),
1578 }
1579 }
1580
1581 fn create_cargo_doc_test_command(
1582 root: &Path,
1583 package: Option<&str>,
1584 description_suffix: &str,
1585 priority: u8,
1586 ) -> Command {
1587 let mut args = vec!["test".to_string(), "--doc".to_string()];
1588
1589 if let Some(pkg) = package {
1590 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1591 }
1592
1593 Command {
1594 id: "cargo-doc-test".to_string(),
1595 label: format!("Doc test {description_suffix}"),
1596 description: Some("Run documentation tests".to_string()),
1597 command: "cargo".to_string(),
1598 args,
1599 env: HashMap::new(),
1600 cwd: Some(root.to_path_buf()),
1601 category: CommandCategory::Test,
1602 priority,
1603 conditions: Vec::new(),
1604 tags: vec!["cargo".to_string(), "doctest".to_string()],
1605 requires_input: false,
1606 estimated_duration: Some(8),
1607 }
1608 }
1609
1610 fn create_cargo_doc_test_command_with_filter(
1611 root: &Path,
1612 package: Option<&str>,
1613 function_name: &str,
1614 description_suffix: &str,
1615 priority: u8,
1616 ) -> Command {
1617 let mut args = vec!["test".to_string(), "--doc".to_string()];
1618
1619 if let Some(pkg) = package {
1620 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1621 }
1622
1623 args.extend(vec![
1625 "--".to_string(),
1626 function_name.to_string(),
1627 "--show-output".to_string(),
1628 ]);
1629
1630 Command {
1631 id: "cargo-doc-test-specific".to_string(),
1632 label: format!("Doc test {description_suffix}"),
1633 description: Some(format!(
1634 "Run documentation test for function {function_name}"
1635 )),
1636 command: "cargo".to_string(),
1637 args,
1638 env: HashMap::new(),
1639 cwd: Some(root.to_path_buf()),
1640 category: CommandCategory::Test,
1641 priority,
1642 conditions: Vec::new(),
1643 tags: vec![
1644 "cargo".to_string(),
1645 "doctest".to_string(),
1646 "specific".to_string(),
1647 ],
1648 requires_input: false,
1649 estimated_duration: Some(8),
1650 }
1651 }
1652
1653 fn create_cargo_example_command(
1654 root: &Path,
1655 package: Option<&str>,
1656 example_name: &str,
1657 priority: u8,
1658 ) -> Command {
1659 let mut args = vec!["run".to_string()];
1660
1661 if let Some(pkg) = package {
1662 args.extend(vec!["--package".to_string(), pkg.to_string()]);
1663 }
1664
1665 args.extend(vec!["--example".to_string(), example_name.to_string()]);
1666
1667 Command {
1668 id: "cargo-example".to_string(),
1669 label: format!("Run example {example_name}"),
1670 description: Some("Execute the example".to_string()),
1671 command: "cargo".to_string(),
1672 args,
1673 env: HashMap::new(),
1674 cwd: Some(root.to_path_buf()),
1675 category: CommandCategory::Run,
1676 priority,
1677 conditions: Vec::new(),
1678 tags: vec!["cargo".to_string(), "example".to_string()],
1679 requires_input: false,
1680 estimated_duration: Some(5),
1681 }
1682 }
1683
1684 fn find_package_for_file(
1687 members: &[crate::file_detection::WorkspaceMember],
1688 _root: &Path,
1689 ) -> Option<String> {
1690 members.first().map(|m| m.name.clone())
1693 }
1694
1695 fn find_main_at_cursor(entry_points: &[EntryPoint], cursor: Position) -> Option<&EntryPoint> {
1696 let cursor_line = cursor.line + 1; for ep in entry_points {
1700 if ep.entry_type == EntryPointType::Main {
1701 if cursor_line >= ep.line.saturating_sub(1) && cursor_line <= ep.line + 2 {
1703 return Some(ep);
1704 }
1705 }
1706 }
1707
1708 None
1709 }
1710
1711 fn find_doctest_for_function_at_cursor(
1714 entry_points: &[EntryPoint],
1715 cursor: Position,
1716 ) -> Option<&EntryPoint> {
1717 #[cfg(feature = "tree-sitter-support")]
1718 {
1719 if let Some(_context) = entry_points.first().and({
1721 None::<()> }) {
1725 }
1728 }
1729
1730 let cursor_line = cursor.line + 1; let in_test_module = entry_points.iter().any(|ep| {
1735 ep.entry_type == EntryPointType::TestModule
1736 && cursor_line >= ep.line
1737 && cursor_line <= ep.line_range.1
1738 });
1739
1740 if in_test_module {
1741 return None; }
1743
1744 let near_test_function = entry_points.iter().any(|ep| {
1746 ep.entry_type == EntryPointType::Test && ep.line.abs_diff(cursor_line) <= 10 });
1748
1749 if near_test_function {
1750 return None; }
1752
1753 let mut closest_doctest: Option<&EntryPoint> = None;
1755 let mut closest_distance = u32::MAX;
1756
1757 for doctest_ep in entry_points {
1758 if doctest_ep.entry_type == EntryPointType::DocTest {
1759 let doctest_end = doctest_ep.line_range.1;
1760
1761 if cursor_line > doctest_end && cursor_line <= doctest_end + 20 {
1764 let distance = cursor_line - doctest_end;
1765 if distance < closest_distance {
1766 closest_distance = distance;
1767 closest_doctest = Some(doctest_ep);
1768 }
1769 }
1770 }
1771 }
1772
1773 closest_doctest
1774 }
1775
1776 pub fn find_test_at_cursor(
1777 entry_points: &[EntryPoint],
1778 cursor: Position,
1779 ) -> Option<&EntryPoint> {
1780 let cursor_line = cursor.line + 1; for ep in entry_points {
1784 match ep.entry_type {
1785 EntryPointType::Test | EntryPointType::DocTest => {
1786 if cursor_line >= ep.line_range.0 && cursor_line <= ep.line_range.1 {
1788 return Some(ep);
1789 }
1790 }
1791 _ => {}
1792 }
1793 }
1794
1795 if let Some(doctest_for_function) =
1797 Self::find_doctest_for_function_at_cursor(entry_points, cursor)
1798 {
1799 return Some(doctest_for_function);
1800 }
1801
1802 let mut best_module: Option<&EntryPoint> = None;
1805 let mut smallest_range = u32::MAX;
1806
1807 for ep in entry_points {
1808 if ep.entry_type == EntryPointType::TestModule {
1809 if cursor_line >= ep.line && cursor_line <= ep.line_range.1 {
1811 let range_size = ep.line_range.1 - ep.line;
1812 if range_size < smallest_range {
1813 smallest_range = range_size;
1814 best_module = Some(ep);
1815 }
1816 }
1817 }
1818 }
1819
1820 if let Some(module) = best_module {
1821 return Some(module);
1822 }
1823
1824 let in_test_module = entry_points.iter().any(|ep| {
1827 ep.entry_type == EntryPointType::TestModule
1828 && cursor_line >= ep.line
1829 && cursor_line <= ep.line_range.1
1830 });
1831
1832 if !in_test_module {
1833 let mut closest_test: Option<&EntryPoint> = None;
1834 let mut closest_distance = u32::MAX;
1835
1836 for ep in entry_points {
1837 match ep.entry_type {
1838 EntryPointType::Test | EntryPointType::DocTest => {
1839 let distance = ep.line.abs_diff(cursor_line);
1840
1841 if distance <= 5 && distance < closest_distance {
1843 closest_distance = distance;
1844 closest_test = Some(ep);
1845 }
1846 }
1847 _ => {}
1848 }
1849 }
1850
1851 return closest_test;
1852 }
1853
1854 None
1855 }
1856
1857 fn generate_specific_test_commands(
1858 context: &FileExecutionContext,
1859 test_entry: &EntryPoint,
1860 ) -> RazResult<Vec<Command>> {
1861 let mut commands = Vec::new();
1862
1863 match test_entry.entry_type {
1864 EntryPointType::DocTest => {
1865 let function_name = test_entry.full_path.as_ref().unwrap_or(&test_entry.name);
1867
1868 match &context.project_type {
1869 RustProjectType::CargoWorkspace { root, members } => {
1870 if let Some(package_name) = Self::find_package_for_file(members, root) {
1871 commands.push(Self::create_cargo_doc_test_command_with_filter(
1872 root,
1873 Some(&package_name),
1874 function_name,
1875 &format!("doc test for {function_name}"),
1876 150, ));
1878 }
1879 }
1880 RustProjectType::CargoPackage { root, package_name } => {
1881 commands.push(Self::create_cargo_doc_test_command_with_filter(
1882 root,
1883 Some(package_name),
1884 function_name,
1885 &format!("doc test for {function_name}"),
1886 150, ));
1888 }
1889 RustProjectType::SingleFile { file_path, .. } => {
1890 let file_name = file_path.to_string_lossy();
1892 commands.push(Command {
1893 id: "rustdoc-test-specific".to_string(),
1894 label: format!("Doc test for {function_name}"),
1895 description: Some(format!(
1896 "Run documentation test for function {function_name}"
1897 )),
1898 command: "rustdoc".to_string(),
1899 args: vec!["--test".to_string(), file_name.to_string()],
1900 env: HashMap::new(),
1901 cwd: file_path.parent().map(|p| p.to_path_buf()),
1902 category: CommandCategory::Test,
1903 priority: 150,
1904 conditions: Vec::new(),
1905 tags: vec![
1906 "rustdoc".to_string(),
1907 "doctest".to_string(),
1908 "specific".to_string(),
1909 ],
1910 requires_input: false,
1911 estimated_duration: Some(5),
1912 });
1913 }
1914 _ => {}
1915 }
1916 }
1917 EntryPointType::TestModule => {
1918 let module_name = test_entry.full_path.as_ref().unwrap_or(&test_entry.name);
1920
1921 match &context.project_type {
1922 RustProjectType::CargoWorkspace { root, members } => {
1923 if let Some(package_name) = Self::find_package_for_file(members, root) {
1924 if let FileRole::AdditionalBinary { binary_name }
1926 | FileRole::MainBinary { binary_name } = &context.file_role
1927 {
1928 let test_path = if module_name.contains("::") {
1930 module_name
1932 .split("::")
1933 .skip(2)
1934 .collect::<Vec<_>>()
1935 .join("::")
1936 } else {
1937 module_name.clone()
1938 };
1939
1940 commands.push(Self::create_cargo_binary_test_command(
1941 root,
1942 Some(&package_name),
1943 binary_name,
1944 Some(&format!("{test_path}::")),
1945 &format!("all tests in module: {test_path}"),
1946 100,
1947 ));
1948 } else {
1949 commands.push(Self::create_cargo_test_command(
1951 root,
1952 Some(&package_name),
1953 Some(&format!("-- {module_name}::")),
1954 &format!("all tests in module: {module_name}"),
1955 100,
1956 ));
1957 }
1958 }
1959 }
1960 RustProjectType::CargoPackage { root, package_name } => {
1961 if let FileRole::AdditionalBinary { binary_name }
1963 | FileRole::MainBinary { binary_name } = &context.file_role
1964 {
1965 let test_path = if module_name.contains("::") {
1967 module_name
1969 .split("::")
1970 .skip(2)
1971 .collect::<Vec<_>>()
1972 .join("::")
1973 } else {
1974 module_name.clone()
1975 };
1976
1977 commands.push(Self::create_cargo_binary_test_command(
1978 root,
1979 Some(package_name),
1980 binary_name,
1981 Some(&format!("{test_path}::")),
1982 &format!("all tests in module: {test_path}"),
1983 100,
1984 ));
1985 } else {
1986 commands.push(Self::create_cargo_test_command(
1988 root,
1989 Some(package_name),
1990 Some(&format!("-- {module_name}::")),
1991 &format!("all tests in module: {module_name}"),
1992 100,
1993 ));
1994 }
1995 }
1996 RustProjectType::SingleFile { file_path, .. } => {
1997 let file_name = file_path.to_string_lossy();
1998 let test_binary = file_path
1999 .file_stem()
2000 .and_then(|s| s.to_str())
2001 .unwrap_or("test");
2002
2003 commands.push(Command {
2004 id: "rustc-test-module".to_string(),
2005 label: format!("Test module: {module_name}"),
2006 description: Some(format!(
2007 "Compile and run all tests in module: {module_name}"
2008 )),
2009 command: "sh".to_string(),
2010 args: vec![
2011 "-c".to_string(),
2012 format!("rustc --test '{}' && './{}'", file_name, test_binary),
2013 ],
2014 env: HashMap::new(),
2015 cwd: file_path.parent().map(|p| p.to_path_buf()),
2016 category: CommandCategory::Test,
2017 priority: 100,
2018 conditions: Vec::new(),
2019 tags: vec![
2020 "rustc".to_string(),
2021 "test".to_string(),
2022 "module".to_string(),
2023 ],
2024 requires_input: false,
2025 estimated_duration: Some(8),
2026 });
2027 }
2028 _ => {}
2029 }
2030 }
2031 _ => {
2032 let test_name = test_entry.full_path.as_ref().unwrap_or(&test_entry.name);
2034
2035 match &context.project_type {
2036 RustProjectType::CargoWorkspace { root, members } => {
2037 if let Some(package_name) = Self::find_package_for_file(members, root) {
2038 if let FileRole::AdditionalBinary { binary_name }
2040 | FileRole::MainBinary { binary_name } = &context.file_role
2041 {
2042 let test_path = if test_name.starts_with("bin::") {
2044 test_name.split("::").skip(2).collect::<Vec<_>>().join("::")
2046 } else {
2047 test_name.clone()
2048 };
2049
2050 commands.push(Self::create_cargo_binary_test_command(
2051 root,
2052 Some(&package_name),
2053 binary_name,
2054 Some(&Self::build_test_args_with_defaults(
2055 &test_path, true, true,
2056 )),
2057 &format!("specific test: {test_path}"),
2058 100,
2059 ));
2060 } else {
2061 if let FileRole::IntegrationTest {
2063 test_name: integration_test_name,
2064 } = &context.file_role
2065 {
2066 commands.push(Self::create_cargo_test_command(
2068 root,
2069 Some(&package_name),
2070 Some(&format!(
2071 "--test {} -- {}",
2072 integration_test_name,
2073 Self::build_test_args_with_defaults(
2074 test_name, true, false
2075 )
2076 )),
2077 &format!("specific test: {test_name}"),
2078 100,
2079 ));
2080 } else {
2081 commands.push(Self::create_cargo_test_command(
2083 root,
2084 Some(&package_name),
2085 Some(&format!(
2086 "--lib -- {}",
2087 Self::build_test_args_with_defaults(
2088 test_name, true, false
2089 )
2090 )),
2091 &format!("specific test: {test_name}"),
2092 100,
2093 ));
2094 }
2095 }
2096 }
2097 }
2098 RustProjectType::CargoPackage { root, package_name } => {
2099 if let FileRole::AdditionalBinary { binary_name }
2101 | FileRole::MainBinary { binary_name } = &context.file_role
2102 {
2103 let test_path = if test_name.starts_with("bin::") {
2105 test_name.split("::").skip(2).collect::<Vec<_>>().join("::")
2107 } else {
2108 test_name.clone()
2109 };
2110
2111 commands.push(Self::create_cargo_binary_test_command(
2112 root,
2113 Some(package_name),
2114 binary_name,
2115 Some(&Self::build_test_args_with_defaults(&test_path, true, true)),
2116 &format!("specific test: {test_path}"),
2117 100,
2118 ));
2119 } else {
2120 if let FileRole::IntegrationTest {
2122 test_name: integration_test_name,
2123 } = &context.file_role
2124 {
2125 commands.push(Self::create_cargo_test_command(
2127 root,
2128 Some(package_name),
2129 Some(&format!(
2130 "--test {} -- {}",
2131 integration_test_name,
2132 Self::build_test_args_with_defaults(test_name, true, false)
2133 )),
2134 &format!("specific test: {test_name}"),
2135 100,
2136 ));
2137 } else {
2138 commands.push(Self::create_cargo_test_command(
2140 root,
2141 Some(package_name),
2142 Some(&format!(
2143 "--lib -- {}",
2144 Self::build_test_args_with_defaults(test_name, true, false)
2145 )),
2146 &format!("specific test: {test_name}"),
2147 100,
2148 ));
2149 }
2150 }
2151 }
2152 RustProjectType::SingleFile { file_path, .. } => {
2153 let file_name = file_path.to_string_lossy();
2156 let test_binary = file_path
2157 .file_stem()
2158 .and_then(|s| s.to_str())
2159 .unwrap_or("test");
2160
2161 commands.push(Command {
2162 id: "rustc-test-specific".to_string(),
2163 label: format!("Test {test_name}"),
2164 description: Some(format!(
2165 "Compile and run specific test: {test_name}"
2166 )),
2167 command: "sh".to_string(),
2168 args: vec![
2169 "-c".to_string(),
2170 format!(
2171 "rustc --test '{}' -o './{}_test' && './{}_test' '{}'",
2172 file_name, test_binary, test_binary, test_name
2173 ),
2174 ],
2175 env: HashMap::new(),
2176 cwd: file_path.parent().map(|p| p.to_path_buf()),
2177 category: CommandCategory::Test,
2178 priority: 140, conditions: Vec::new(),
2180 tags: vec![
2181 "rustc".to_string(),
2182 "test".to_string(),
2183 "specific".to_string(),
2184 ],
2185 requires_input: false,
2186 estimated_duration: Some(8),
2187 });
2188 }
2189 _ => {}
2190 }
2191 }
2192 }
2193
2194 Ok(commands)
2195 }
2196
2197 fn generate_advanced_framework_commands(
2199 context: &FileExecutionContext,
2200 ) -> RazResult<Vec<Command>> {
2201 let mut commands = Vec::new();
2202
2203 if let Some(project_context) = Self::convert_to_project_context(context) {
2205 let detector = PreciseFrameworkDetector::new();
2206 let framework_scores = detector.detect(&project_context, Some(&context.file_path));
2207
2208 for score in framework_scores.iter() {
2210 let confidence_threshold = match score.framework {
2211 FrameworkType::YewWeb => 6.0, _ => 8.0, };
2214 if score.confidence >= confidence_threshold {
2215 match &score.framework {
2216 FrameworkType::DioxusDesktop
2217 | FrameworkType::DioxusWeb
2218 | FrameworkType::DioxusMobile => {
2219 commands
2220 .extend(Self::generate_dioxus_commands(context, &score.framework)?);
2221 }
2222 FrameworkType::LeptosSSR | FrameworkType::LeptosCSR => {
2223 commands
2224 .extend(Self::generate_leptos_commands(context, &score.framework)?);
2225 }
2226 FrameworkType::YewWeb => {
2227 commands
2228 .extend(Self::generate_yew_commands(context, &score.framework)?);
2229 }
2230 FrameworkType::TauriDesktop => {
2231 commands
2232 .extend(Self::generate_tauri_commands(context, &score.framework)?);
2233 }
2234 _ => continue, }
2236 break; }
2238 }
2239 }
2240
2241 Ok(commands)
2242 }
2243
2244 fn convert_to_project_context(context: &FileExecutionContext) -> Option<ProjectContext> {
2246 match &context.project_type {
2247 RustProjectType::CargoWorkspace { root, .. }
2248 | RustProjectType::CargoPackage { root, .. } => {
2249 let cargo_toml = root.join("Cargo.toml");
2254 if cargo_toml.exists() {
2255 if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
2256 let dependencies = Self::parse_dependencies_from_toml(&content);
2257 return Some(ProjectContext {
2258 workspace_root: root.clone(),
2259 current_file: None,
2260 cursor_position: None,
2261 project_type: crate::ProjectType::Binary, dependencies,
2263 workspace_members: Vec::new(),
2264 build_targets: Vec::new(),
2265 active_features: Vec::new(),
2266 env_vars: std::collections::HashMap::new(),
2267 });
2268 }
2269 }
2270 }
2271 _ => {}
2272 }
2273 None
2274 }
2275
2276 fn parse_dependencies_from_toml(content: &str) -> Vec<crate::Dependency> {
2278 let mut dependencies = Vec::new();
2279
2280 if let Ok(value) = content.parse::<toml::Value>() {
2281 if let Some(deps) = value.get("dependencies").and_then(|d| d.as_table()) {
2282 for (name, dep_value) in deps {
2283 let mut features = Vec::new();
2284
2285 if let Some(dep_table) = dep_value.as_table() {
2287 if let Some(feat_array) =
2288 dep_table.get("features").and_then(|f| f.as_array())
2289 {
2290 features = feat_array
2291 .iter()
2292 .filter_map(|v| v.as_str().map(|s| s.to_string()))
2293 .collect();
2294 }
2295 }
2296
2297 dependencies.push(crate::Dependency {
2298 name: name.clone(),
2299 version: "unknown".to_string(), features,
2301 optional: false,
2302 dev_dependency: false,
2303 });
2304 }
2305 }
2306 }
2307
2308 dependencies
2309 }
2310
2311 fn generate_dioxus_commands(
2313 context: &FileExecutionContext,
2314 framework: &FrameworkType,
2315 ) -> RazResult<Vec<Command>> {
2316 let mut commands = Vec::new();
2317
2318 let (root, platform) = match &context.project_type {
2319 RustProjectType::CargoWorkspace { root, .. }
2320 | RustProjectType::CargoPackage { root, .. } => {
2321 let platform = Self::detect_dioxus_platform(root, framework);
2323 (root.clone(), platform)
2324 }
2325 _ => return Ok(commands),
2326 };
2327
2328 commands.push(Command {
2330 id: "dx-serve".to_string(),
2331 label: format!("Dioxus Serve ({platform})"),
2332 description: Some(format!("Start Dioxus dev server for {platform} platform")),
2333 command: "dx".to_string(),
2334 args: vec![
2335 "serve".to_string(),
2336 "--platform".to_string(),
2337 platform.clone(),
2338 ],
2339 env: std::collections::HashMap::new(),
2340 cwd: Some(root.clone()),
2341 category: CommandCategory::Run,
2342 priority: 110, conditions: Vec::new(),
2344 tags: vec!["dioxus".to_string(), "serve".to_string(), platform.clone()],
2345 requires_input: false,
2346 estimated_duration: Some(5),
2347 });
2348
2349 Ok(commands)
2350 }
2351
2352 fn generate_leptos_commands(
2354 context: &FileExecutionContext,
2355 _framework: &FrameworkType,
2356 ) -> RazResult<Vec<Command>> {
2357 let mut commands = Vec::new();
2358
2359 let root = match &context.project_type {
2360 RustProjectType::CargoWorkspace { root, .. }
2361 | RustProjectType::CargoPackage { root, .. } => root.clone(),
2362 _ => return Ok(commands),
2363 };
2364
2365 commands.push(Command {
2367 id: "leptos-watch".to_string(),
2368 label: "Leptos Watch".to_string(),
2369 description: Some("Start Leptos development server with hot reload".to_string()),
2370 command: "cargo".to_string(),
2371 args: vec!["leptos".to_string(), "watch".to_string()],
2372 env: std::collections::HashMap::new(),
2373 cwd: Some(root),
2374 category: CommandCategory::Run,
2375 priority: 110, conditions: Vec::new(),
2377 tags: vec!["leptos".to_string(), "watch".to_string()],
2378 requires_input: false,
2379 estimated_duration: Some(5),
2380 });
2381
2382 Ok(commands)
2383 }
2384
2385 fn generate_yew_commands(
2387 context: &FileExecutionContext,
2388 _framework: &FrameworkType,
2389 ) -> RazResult<Vec<Command>> {
2390 let mut commands = Vec::new();
2391
2392 let root = match &context.project_type {
2393 RustProjectType::CargoWorkspace { root, .. }
2394 | RustProjectType::CargoPackage { root, .. } => root.clone(),
2395 _ => return Ok(commands),
2396 };
2397
2398 commands.push(Command {
2400 id: "yew-serve".to_string(),
2401 label: "Yew Dev Server".to_string(),
2402 description: Some("Start Yew development server with hot reload".to_string()),
2403 command: "trunk".to_string(),
2404 args: vec!["serve".to_string()],
2405 env: std::collections::HashMap::new(),
2406 cwd: Some(root.clone()),
2407 category: CommandCategory::Run,
2408 priority: 110, conditions: Vec::new(),
2410 tags: vec!["yew".to_string(), "serve".to_string(), "trunk".to_string()],
2411 requires_input: false,
2412 estimated_duration: Some(5),
2413 });
2414
2415 commands.push(Command {
2417 id: "yew-serve-open".to_string(),
2418 label: "Yew Dev Server + Open".to_string(),
2419 description: Some("Start Yew development server and open browser".to_string()),
2420 command: "trunk".to_string(),
2421 args: vec!["serve".to_string(), "--open".to_string()],
2422 env: std::collections::HashMap::new(),
2423 cwd: Some(root.clone()),
2424 category: CommandCategory::Run,
2425 priority: 105, conditions: Vec::new(),
2427 tags: vec![
2428 "yew".to_string(),
2429 "serve".to_string(),
2430 "open".to_string(),
2431 "trunk".to_string(),
2432 ],
2433 requires_input: false,
2434 estimated_duration: Some(5),
2435 });
2436
2437 commands.push(Command {
2439 id: "yew-build".to_string(),
2440 label: "Yew Build".to_string(),
2441 description: Some("Build Yew application for production".to_string()),
2442 command: "trunk".to_string(),
2443 args: vec!["build".to_string(), "--release".to_string()],
2444 env: std::collections::HashMap::new(),
2445 cwd: Some(root),
2446 category: CommandCategory::Build,
2447 priority: 85,
2448 conditions: Vec::new(),
2449 tags: vec!["yew".to_string(), "build".to_string(), "trunk".to_string()],
2450 requires_input: false,
2451 estimated_duration: Some(30),
2452 });
2453
2454 Ok(commands)
2455 }
2456
2457 fn generate_tauri_commands(
2459 context: &FileExecutionContext,
2460 _framework: &FrameworkType,
2461 ) -> RazResult<Vec<Command>> {
2462 let mut commands = Vec::new();
2463
2464 let root = match &context.project_type {
2465 RustProjectType::CargoWorkspace { root, .. }
2466 | RustProjectType::CargoPackage { root, .. } => root.clone(),
2467 _ => return Ok(commands),
2468 };
2469
2470 let tauri_dir = if root.ends_with("src-tauri") {
2474 root.clone()
2475 } else if root.join("src-tauri").exists() {
2476 root.join("src-tauri")
2477 } else {
2478 root.clone()
2479 };
2480
2481 commands.push(Command {
2483 id: "tauri-dev".to_string(),
2484 label: "Tauri Dev".to_string(),
2485 description: Some("Start Tauri development server with hot reload".to_string()),
2486 command: "cargo".to_string(),
2487 args: vec!["tauri".to_string(), "dev".to_string()],
2488 env: std::collections::HashMap::new(),
2489 cwd: Some(tauri_dir.clone()),
2490 category: CommandCategory::Run,
2491 priority: 110, conditions: Vec::new(),
2493 tags: vec!["tauri".to_string(), "dev".to_string()],
2494 requires_input: false,
2495 estimated_duration: Some(10),
2496 });
2497
2498 commands.push(Command {
2500 id: "tauri-build".to_string(),
2501 label: "Tauri Build".to_string(),
2502 description: Some("Build Tauri application for production".to_string()),
2503 command: "cargo".to_string(),
2504 args: vec!["tauri".to_string(), "build".to_string()],
2505 env: std::collections::HashMap::new(),
2506 cwd: Some(tauri_dir.clone()),
2507 category: CommandCategory::Build,
2508 priority: 85,
2509 conditions: Vec::new(),
2510 tags: vec!["tauri".to_string(), "build".to_string()],
2511 requires_input: false,
2512 estimated_duration: Some(60),
2513 });
2514
2515 commands.push(Command {
2517 id: "tauri-build-debug".to_string(),
2518 label: "Tauri Build (Debug)".to_string(),
2519 description: Some("Build Tauri application with debug information".to_string()),
2520 command: "cargo".to_string(),
2521 args: vec![
2522 "tauri".to_string(),
2523 "build".to_string(),
2524 "--debug".to_string(),
2525 ],
2526 env: std::collections::HashMap::new(),
2527 cwd: Some(tauri_dir),
2528 category: CommandCategory::Build,
2529 priority: 80,
2530 conditions: Vec::new(),
2531 tags: vec![
2532 "tauri".to_string(),
2533 "build".to_string(),
2534 "debug".to_string(),
2535 ],
2536 requires_input: false,
2537 estimated_duration: Some(45),
2538 });
2539
2540 Ok(commands)
2541 }
2542
2543 fn detect_dioxus_platform(root: &Path, _framework: &FrameworkType) -> String {
2545 if let Ok(platform_override) = std::env::var("RAZ_PLATFORM_OVERRIDE") {
2547 return platform_override;
2548 }
2549
2550 let dioxus_toml = root.join("Dioxus.toml");
2552 if dioxus_toml.exists() {
2553 if let Ok(content) = std::fs::read_to_string(&dioxus_toml) {
2554 if let Ok(config) = content.parse::<toml::Value>() {
2555 if let Some(app_config) = config.get("application") {
2556 if let Some(default_platform) = app_config.get("default_platform") {
2557 if let Some(platform_str) = default_platform.as_str() {
2558 return platform_str.to_string();
2559 }
2560 }
2561 }
2562 }
2563 }
2564 }
2565
2566 "desktop".to_string()
2568 }
2569
2570 fn is_web_framework_command(cmd: &Command) -> bool {
2572 let is_serve_dev = cmd.tags.contains(&"serve".to_string())
2574 || cmd.tags.contains(&"dev".to_string())
2575 || cmd.tags.contains(&"watch".to_string());
2576
2577 let is_framework_cmd = cmd
2579 .args
2580 .iter()
2581 .any(|arg| arg == "leptos" || arg == "dioxus" || arg == "tauri" || arg == "trunk")
2582 || cmd.tags.contains(&"yew".to_string())
2583 || cmd.tags.contains(&"tauri".to_string());
2584
2585 is_serve_dev || is_framework_cmd
2586 }
2587
2588 fn build_test_args_with_defaults(
2591 test_name: &str,
2592 add_exact: bool,
2593 add_show_output: bool,
2594 ) -> String {
2595 let mut args = vec![test_name.to_string()];
2596
2597 if add_exact {
2599 args.push("--exact".to_string());
2600 }
2601 if add_show_output {
2602 args.push("--show-output".to_string());
2603 }
2604
2605 args.join(" ")
2606 }
2607}
2608
2609#[cfg(test)]
2610mod tests {
2611 use super::*;
2612 use crate::file_detection::{
2613 ExecutionCapabilities, FileExecutionContext, FileRole, RustProjectType, SingleFileType,
2614 };
2615 use std::path::PathBuf;
2616
2617 #[test]
2618 fn test_single_file_executable_commands() {
2619 let context = FileExecutionContext {
2620 project_type: RustProjectType::SingleFile {
2621 file_path: PathBuf::from("/tmp/hello.rs"),
2622 file_type: SingleFileType::Executable,
2623 },
2624 file_role: FileRole::Standalone,
2625 entry_points: vec![],
2626 capabilities: ExecutionCapabilities {
2627 can_run: true,
2628 can_test: false,
2629 can_bench: false,
2630 can_doc_test: false,
2631 requires_framework: None,
2632 },
2633 file_path: PathBuf::from("/tmp/hello.rs"),
2634 };
2635
2636 let commands = UniversalCommandGenerator::generate_commands(&context, None).unwrap();
2637 assert!(!commands.is_empty());
2638 assert!(commands.iter().any(|c| c.id == "rustc-run"));
2639 assert!(commands.iter().any(|c| c.command == "sh"));
2640 }
2641
2642 #[test]
2643 fn test_cargo_script_commands() {
2644 let context = FileExecutionContext {
2645 project_type: RustProjectType::CargoScript {
2646 file_path: PathBuf::from("/tmp/script.rs"),
2647 manifest: None,
2648 },
2649 file_role: FileRole::MainBinary {
2650 binary_name: "script".to_string(),
2651 },
2652 entry_points: vec![],
2653 capabilities: ExecutionCapabilities {
2654 can_run: true,
2655 can_test: true,
2656 can_bench: false,
2657 can_doc_test: false,
2658 requires_framework: None,
2659 },
2660 file_path: PathBuf::from("/tmp/script.rs"),
2661 };
2662
2663 let commands = UniversalCommandGenerator::generate_commands(&context, None).unwrap();
2664 assert!(!commands.is_empty());
2665 assert!(commands.iter().any(|c| c.id == "cargo-script-run"));
2666 assert!(
2667 commands
2668 .iter()
2669 .any(|c| c.args.contains(&"+nightly".to_string()))
2670 );
2671 }
2672
2673 #[test]
2674 fn test_frontend_library_commands() {
2675 let context = FileExecutionContext {
2676 project_type: RustProjectType::CargoPackage {
2677 root: PathBuf::from("/tmp/project"),
2678 package_name: "frontend".to_string(),
2679 },
2680 file_role: FileRole::FrontendLibrary {
2681 framework: "leptos".to_string(),
2682 },
2683 entry_points: vec![],
2684 capabilities: ExecutionCapabilities {
2685 can_run: true,
2686 can_test: false,
2687 can_bench: false,
2688 can_doc_test: true,
2689 requires_framework: Some("leptos".to_string()),
2690 },
2691 file_path: PathBuf::from("/tmp/project/src/lib.rs"),
2692 };
2693
2694 let commands = UniversalCommandGenerator::generate_commands(&context, None).unwrap();
2695 assert!(!commands.is_empty());
2696 assert!(commands.iter().any(|c| c.id == "leptos-watch"));
2697 assert!(
2698 commands
2699 .iter()
2700 .any(|c| c.command == "cargo" && c.args.contains(&"leptos".to_string()))
2701 );
2702 }
2703}