raz_core/
universal_command_generator.rs

1//! Universal command generator for all Rust execution patterns
2//!
3//! This module generates appropriate commands for any Rust file based on
4//! stateless detection of project type and file role.
5
6use 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)] // TODO: Use this when implementing tree-sitter doctest detection
14use 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
23/// Universal command generator
24pub struct UniversalCommandGenerator;
25
26impl UniversalCommandGenerator {
27    /// Generate all appropriate commands for the given execution context
28    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    /// Generate commands with optional override support
36    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        // First generate base commands
43        let mut commands = Self::generate_commands_internal(context, cursor)?;
44
45        // Load and apply saved overrides using the new override system
46        if let Some(workspace_path) = workspace {
47            // Create function context
48            let function_context = if let Some(pos) = cursor {
49                // Try to find the test function at the cursor to get its name
50                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            // Try to load override using the new system
69            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                // Apply the saved override to all commands
82                for cmd in &mut commands {
83                    Self::apply_override_to_command(cmd, &override_config);
84                }
85            }
86            // Note: Legacy ConfigManager fallback removed as the new override system
87            // handles all override cases. Use `raz override migrate` for legacy data.
88        }
89
90        Ok(commands)
91    }
92
93    /// Internal command generation without override loading
94    fn generate_commands_internal(
95        context: &FileExecutionContext,
96        cursor: Option<Position>,
97    ) -> RazResult<Vec<Command>> {
98        let mut commands = Vec::new();
99
100        // Core Algorithm: Priority order based on content and cursor
101        // 1. TEST (highest priority) - mod test, #[test] macros, doc tests
102        // 2. RUN - main functions, binaries
103        // 3. BUILD - build.rs files
104        // 4. BENCH - benchmarks
105
106        // Check if cursor is targeting a specific test or main function
107        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        // 1. TEST COMMANDS - Highest priority if tests exist AND cursor is not on main
120        if context.capabilities.can_test && !cursor_on_main {
121            let mut test_commands = Self::generate_test_commands(context, cursor)?;
122
123            // Set base priority for tests (highest)
124            for cmd in &mut test_commands {
125                cmd.priority = 100; // Base test priority
126
127                // Extra boost if cursor is specifically on a test
128                if cursor_in_test {
129                    cmd.priority = cmd.priority.saturating_add(50);
130                }
131
132                // Lower priority if cursor is on doc test (doc tests should take precedence)
133                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        // 2. DOC TEST COMMANDS - Only generate if doc tests exist or cursor is in doc test
142        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            // Only generate doc test commands if:
149            // 1. No cursor provided (general case), OR
150            // 2. Cursor is specifically in a doc test, OR
151            // 3. File has doc test entry points
152            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                // Set priority just below regular tests
156                for cmd in &mut doc_test_commands {
157                    cmd.priority = 95;
158
159                    // Boost priority if cursor is on a doc test
160                    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        // 3. RUN COMMANDS - High priority if cursor is on main, otherwise lower
170        if context.capabilities.can_run {
171            let mut run_commands = Self::generate_run_commands(context)?;
172
173            // Set run priority based on cursor position and test existence
174            let run_priority = if cursor_on_main {
175                150 // Highest priority when cursor is on main function
176            } else if context.capabilities.can_test && cursor_in_test {
177                50 // Lower priority if tests exist and cursor is in test
178            } else if context.capabilities.can_test {
179                75 // Medium priority if tests exist but cursor not in test
180            } else {
181                90 // High priority if no tests exist
182            };
183
184            for cmd in &mut run_commands {
185                cmd.priority = run_priority;
186            }
187
188            commands.extend(run_commands);
189
190            // Framework-specific commands with enhanced priority logic
191            let mut framework_commands = Self::generate_advanced_framework_commands(context)?;
192            for cmd in &mut framework_commands {
193                // Give framework commands higher priority when:
194                // 1. Cursor is on main.rs (framework entry point)
195                // 2. It's a serve/dev command for web frameworks
196                let framework_priority = if cursor_on_main && Self::is_web_framework_command(cmd) {
197                    160 // Highest priority for main.rs framework commands (higher than cargo run)
198                } else if Self::is_web_framework_command(cmd) {
199                    120 // High priority for framework serve/dev commands
200                } else {
201                    run_priority // Default to run priority for other framework commands
202                };
203                cmd.priority = framework_priority;
204            }
205            commands.extend(framework_commands);
206        }
207
208        // 4. BUILD SCRIPT COMMANDS - Special handling for build.rs files
209        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; // Medium priority
217            }
218
219            commands.extend(build_commands);
220        }
221
222        // 5. BENCHMARK COMMANDS - Lower priority
223        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; // Lower than run, higher than build
228            }
229
230            commands.extend(bench_commands);
231        }
232
233        // Sort by priority (highest first)
234        commands.sort_by(|a, b| b.priority.cmp(&a.priority));
235
236        Ok(commands)
237    }
238
239    /// Generate commands with runtime override input
240    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        // First generate base commands
248        let mut commands = Self::generate_commands(context, cursor)?;
249
250        // Parse the runtime override
251        let command = if !commands.is_empty() {
252            // Detect command from first generated command
253            match commands[0].command.as_str() {
254                "cargo" => {
255                    // Look at first arg to determine subcommand
256                    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        // Apply the runtime override to all commands
274        for cmd in &mut commands {
275            Self::apply_override_to_command(cmd, &parsed_override);
276        }
277
278        Ok(commands)
279    }
280
281    /// Apply command override to a command
282    fn apply_override_to_command(command: &mut Command, override_config: &CommandOverride) {
283        let is_append = matches!(override_config.mode, OverrideMode::Append);
284
285        // Apply environment variables
286        if !override_config.env.is_empty() {
287            if is_append {
288                // Append mode - merge with existing
289                for (key, value) in &override_config.env {
290                    command.env.insert(key.clone(), value.clone());
291                }
292            } else {
293                // Replace mode - clear and set new
294                command.env.clear();
295                for (key, value) in &override_config.env {
296                    command.env.insert(key.clone(), value.clone());
297                }
298            }
299        }
300
301        // Apply cargo options
302        if !override_config.cargo_options.is_empty() {
303            // Collect all cargo option parts first
304            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                            // Add if not already present
310                            if !command.args.contains(&parts[0]) {
311                                all_cargo_parts.extend(parts);
312                            }
313                        } else {
314                            // Just add the options
315                            all_cargo_parts.extend(parts);
316                        }
317                    }
318                    Ok(_) => {} // Empty parts, skip
319                    Err(e) => {
320                        eprintln!("Warning: Failed to parse cargo option '{option}': {e}");
321                    }
322                }
323            }
324
325            // Now insert all cargo options before the -- separator if it exists
326            if !all_cargo_parts.is_empty() {
327                // Find the separator position
328                let separator_pos = command.args.iter().position(|arg| arg == "--");
329
330                if let Some(sep_pos) = separator_pos {
331                    // Insert all cargo options before the -- separator
332                    for (i, part) in all_cargo_parts.into_iter().enumerate() {
333                        command.args.insert(sep_pos + i, part);
334                    }
335                } else {
336                    // No separator, add at the end
337                    command.args.extend(all_cargo_parts);
338                }
339            }
340        }
341
342        // Apply rustc options
343        if !override_config.rustc_options.is_empty() {
344            // Find or add -- separator
345            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            // Add rustc options after --
353            for option in &override_config.rustc_options {
354                command.args.push(option.clone());
355            }
356        }
357
358        // Apply extra arguments
359        if !override_config.args.is_empty() {
360            // For test commands, handle -- separator specially
361            let is_test_command = command.category == CommandCategory::Test
362                || command.args.iter().any(|arg| arg == "test");
363
364            if is_test_command {
365                // Test commands need special handling for --
366                if is_append {
367                    // In append mode, intelligently merge test arguments
368                    if let Some(separator_pos) = command.args.iter().position(|arg| arg == "--") {
369                        // Get existing args after separator
370                        let existing_args: Vec<String> = command.args[separator_pos + 1..].to_vec();
371
372                        // Remove duplicates: don't add flags that already exist
373                        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                        // Insert new args after the separator
381                        let insert_pos = separator_pos + 1;
382                        command.args.splice(insert_pos..insert_pos, args_to_add);
383                    } else {
384                        // No separator found, add one and then the args
385                        command.args.push("--".to_string());
386                        command.args.extend(override_config.args.clone());
387                    }
388                } else {
389                    // In replace mode for test commands, preserve the test function name
390                    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                        // Try to preserve the test function name if it exists
394                        // Test function names don't start with -- and are usually the first arg after --
395                        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                                // This looks like a test function name, preserve it
399                                preserved_test_name = Some(first_arg.clone());
400                            }
401                        }
402
403                        // Remove everything after -- and rebuild with preserved test name + new args
404                        command.args.truncate(separator_pos + 1);
405
406                        // Add preserved test name first if it exists
407                        if let Some(test_name) = preserved_test_name {
408                            command.args.push(test_name);
409                        }
410
411                        // Then add override args
412                        command.args.extend(override_config.args.clone());
413                    } else {
414                        // No separator found, add one and then the args
415                        command.args.push("--".to_string());
416                        command.args.extend(override_config.args.clone());
417                    }
418                }
419            } else {
420                // For non-test commands, just append the args directly
421                if is_append {
422                    command.args.extend(override_config.args.clone());
423                } else {
424                    // In replace mode for non-test commands, we might want to preserve some core args
425                    // For now, just extend the args
426                    command.args.extend(override_config.args.clone());
427                }
428            }
429        }
430    }
431
432    #[allow(dead_code)]
433    /// Apply options to a command  
434    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        // Check if this is a rustc command
442        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                    // For rustc commands, translate the option
449                    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                            // Skip invalid rustc options
454                            continue;
455                        }
456                    } else {
457                        option.clone()
458                    };
459
460                    // Add flag if not present
461                    if !command.args.contains(&actual_option) {
462                        // For rustc commands in sh -c, we need to insert the option in the rustc part
463                        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                                // Parse and modify the shell command
468                                if cmd_string.contains("rustc")
469                                    && !cmd_string.contains(&actual_option)
470                                {
471                                    // For release builds, add full optimization flags
472                                    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                            // Normal insertion
484                            let insert_pos = command
485                                .args
486                                .iter()
487                                .position(|arg| arg == "--")
488                                .unwrap_or(command.args.len());
489
490                            // For release builds, add full optimization flags
491                            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                    // Remove flag if present
507                    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                            // Append mode - keep existing, might need special handling
513                            // For now, just replace
514                            command.args[opt_idx + 1] = val.clone();
515                        } else {
516                            // Replace the value
517                            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                        // Add new option
525                        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                            // Append mode - merge values
539                            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                            // Replace the value
546                            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                        // Add new option
554                        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    /// Generate run commands
568    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    /// Generate test commands
605    fn generate_test_commands(
606        context: &FileExecutionContext,
607        cursor: Option<Position>,
608    ) -> RazResult<Vec<Command>> {
609        let mut commands = Vec::new();
610
611        // If cursor is on a specific test, prioritize that test
612        if let Some(cursor_pos) = cursor {
613            if let Some(test_entry) = Self::find_test_at_cursor(&context.entry_points, cursor_pos) {
614                // Generate specific test commands for the test at cursor
615                commands.extend(Self::generate_specific_test_commands(context, test_entry)?);
616                // Boost priority for cursor-specific commands
617                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        // General test commands
626        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    /// Generate benchmark commands
658    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            _ => {} // Cargo scripts don't typically have benchmarks
680        }
681
682        Ok(commands)
683    }
684
685    /// Generate doc-test commands
686    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                // Only generate doc test commands if the file actually has doc test entry points
706                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            _ => {} // Cargo scripts don't typically have doc tests
715        }
716
717        Ok(commands)
718    }
719
720    /// Generate build script specific commands
721    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                // Primary: Trigger build script via cargo build
728                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                // Alternative: Run build script directly (only if it has a main function)
745                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                // Handle standalone build.rs files
774                commands.extend(Self::generate_standalone_build_script_commands(file_path)?);
775            }
776            _ => {} // Build scripts in cargo scripts don't make sense
777        }
778
779        Ok(commands)
780    }
781
782    // Cargo Workspace Commands
783
784    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                // Find the package this file belongs to
795                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                    // Module files should use --lib for library tests
852                    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    // Cargo Package Commands
928
929    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                // Build scripts are now handled separately in generate_build_script_commands
959                // to ensure they always generate commands regardless of can_run capability
960            }
961            FileRole::Standalone => {
962                // Standalone files should not be handled here - they will be handled
963                // as single files through the detection logic
964            }
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                // Module files should use --lib for library tests
999                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    // Cargo Script Commands
1066
1067    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    // Single File Commands
1130
1131    /// Generate commands for standalone build.rs files
1132    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        // Check if this is in a cargo project directory
1139        if parent_dir.join("Cargo.toml").exists() {
1140            // In a cargo project - trigger via cargo build
1141            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        // Always offer direct execution option
1159        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            // Optimized version
1223            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        // Library/Test/Module single files don't run directly
1247
1248        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    // Framework-specific commands
1356
1357    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    // Helper methods for creating commands
1419
1420    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        // Add --bin flag for binary targets
1512        args.extend(vec!["--bin".to_string(), binary_name.to_string()]);
1513
1514        // Add test filter after --
1515        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        // Add function name filter and show output
1624        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    // Helper methods
1685
1686    fn find_package_for_file(
1687        members: &[crate::file_detection::WorkspaceMember],
1688        _root: &Path,
1689    ) -> Option<String> {
1690        // In a real implementation, we'd determine which workspace member
1691        // contains the current file. For now, return the first member.
1692        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; // Convert to 1-based
1697
1698        // Check if cursor is on or near main function
1699        for ep in entry_points {
1700            if ep.entry_type == EntryPointType::Main {
1701                // Check if cursor is on the main function line or within 2 lines
1702                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    /// Find a doctest entry that belongs to a function the cursor is currently on
1712    /// Uses tree-sitter AST for accurate detection when available
1713    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            // Try tree-sitter approach first for more accurate detection
1720            if let Some(_context) = entry_points.first().and({
1721                // We need access to the source code to use tree-sitter
1722                // For now, we'll use the file path from the entry point context
1723                None::<()> // TODO: Get source content here
1724            }) {
1725                // Tree-sitter implementation would go here
1726                // For now, fall back to regex approach
1727            }
1728        }
1729
1730        // Fallback to the previous line-based approach (which is flawed but still better than nothing)
1731        let cursor_line = cursor.line + 1; // Convert to 1-based
1732
1733        // First check: if cursor is inside a test module, don't look for doctests
1734        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; // Don't associate doctests when cursor is in test modules
1742        }
1743
1744        // Also check for #[cfg(test)] modules by looking for test functions nearby
1745        let near_test_function = entry_points.iter().any(|ep| {
1746            ep.entry_type == EntryPointType::Test && ep.line.abs_diff(cursor_line) <= 10 // Within 10 lines of a test function
1747        });
1748
1749        if near_test_function {
1750            return None; // Don't associate doctests when cursor is near test functions
1751        }
1752
1753        // Find the closest preceding doctest
1754        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                // Check if cursor is after this doctest and within reasonable distance
1762                // Reduced distance to be more conservative
1763                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; // Convert to 1-based
1781
1782        // First priority: Check if cursor is within any test function's line range
1783        for ep in entry_points {
1784            match ep.entry_type {
1785                EntryPointType::Test | EntryPointType::DocTest => {
1786                    // Check if cursor is within the test function's line range
1787                    if cursor_line >= ep.line_range.0 && cursor_line <= ep.line_range.1 {
1788                        return Some(ep);
1789                    }
1790                }
1791                _ => {}
1792            }
1793        }
1794
1795        // Second priority: Check if cursor is on a function that has an associated doctest
1796        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        // Second priority: Check if cursor is INSIDE a test module scope
1803        // Find the most specific (deepest) module that contains the cursor
1804        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                // Check if cursor is within the test module scope
1810                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        // Third priority: Find closest test within 5 lines
1825        // But ONLY if we're not inside a test module (to avoid overriding module selection)
1826        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                        // Only consider tests within 5 lines of the cursor
1842                        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                // For doc tests, generate doc test command with function name filter
1866                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, // High priority for specific doc test
1877                            ));
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, // High priority for specific doc test
1887                        ));
1888                    }
1889                    RustProjectType::SingleFile { file_path, .. } => {
1890                        // For single files, use rustdoc directly
1891                        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                // For test modules, run all tests in the module
1919                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                            // Check if this is a binary file
1925                            if let FileRole::AdditionalBinary { binary_name }
1926                            | FileRole::MainBinary { binary_name } = &context.file_role
1927                            {
1928                                // For binaries, use --bin flag and simpler test path
1929                                let test_path = if module_name.contains("::") {
1930                                    // Extract just the module path after the binary module prefix
1931                                    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                                // Use standard module filtering for libraries
1950                                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                        // Check if this is a binary file
1962                        if let FileRole::AdditionalBinary { binary_name }
1963                        | FileRole::MainBinary { binary_name } = &context.file_role
1964                        {
1965                            // For binaries, use --bin flag and simpler test path
1966                            let test_path = if module_name.contains("::") {
1967                                // Extract just the module path after the binary module prefix
1968                                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                            // Use standard module filtering for libraries
1987                            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                // For specific tests, run the exact test
2033                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                            // Check if this is a binary file
2039                            if let FileRole::AdditionalBinary { binary_name }
2040                            | FileRole::MainBinary { binary_name } = &context.file_role
2041                            {
2042                                // For binaries, use --bin flag and strip the binary module prefix from test path
2043                                let test_path = if test_name.starts_with("bin::") {
2044                                    // Remove "bin::binary_name::" prefix
2045                                    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                                // Check if this is an integration test
2062                                if let FileRole::IntegrationTest {
2063                                    test_name: integration_test_name,
2064                                } = &context.file_role
2065                                {
2066                                    // For integration tests, use --test flag with test file name and specific test function
2067                                    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                                    // For unit tests, add --lib flag for clarity
2082                                    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                        // Check if this is a binary file
2100                        if let FileRole::AdditionalBinary { binary_name }
2101                        | FileRole::MainBinary { binary_name } = &context.file_role
2102                        {
2103                            // For binaries, use --bin flag and strip the binary module prefix from test path
2104                            let test_path = if test_name.starts_with("bin::") {
2105                                // Remove "bin::binary_name::" prefix
2106                                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                            // Check if this is an integration test
2121                            if let FileRole::IntegrationTest {
2122                                test_name: integration_test_name,
2123                            } = &context.file_role
2124                            {
2125                                // For integration tests, use --test flag with test file name and specific test function
2126                                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                                // For unit tests, add --lib flag for clarity
2139                                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                        // For single files, we can't run a specific test with rustc,
2154                        // but we can provide a focused test command
2155                        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, // Higher priority for specific test
2179                            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    /// Generate framework-specific commands using lightweight detection
2198    fn generate_advanced_framework_commands(
2199        context: &FileExecutionContext,
2200    ) -> RazResult<Vec<Command>> {
2201        let mut commands = Vec::new();
2202
2203        // Convert FileExecutionContext to ProjectContext for framework detection
2204        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            // Check all framework scores to find the best match
2209            for score in framework_scores.iter() {
2210                let confidence_threshold = match score.framework {
2211                    FrameworkType::YewWeb => 6.0, // Even lower threshold for Yew
2212                    _ => 8.0,                     // Higher threshold for other frameworks
2213                };
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, // Skip unknown frameworks
2235                    }
2236                    break; // Only process the first matching framework
2237                }
2238            }
2239        }
2240
2241        Ok(commands)
2242    }
2243
2244    /// Convert FileExecutionContext to ProjectContext for framework detection
2245    fn convert_to_project_context(context: &FileExecutionContext) -> Option<ProjectContext> {
2246        match &context.project_type {
2247            RustProjectType::CargoWorkspace { root, .. }
2248            | RustProjectType::CargoPackage { root, .. } => {
2249                // Try to analyze the project at the root
2250                // For now, create a minimal context for detection
2251                // This is a simplified conversion - in a full implementation,
2252                // we'd parse Cargo.toml to get dependencies
2253                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, // Simplified
2262                            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    /// Parse dependencies from Cargo.toml content (simplified)
2277    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                    // Extract features if they exist
2286                    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(), // Simplified
2300                        features,
2301                        optional: false,
2302                        dev_dependency: false,
2303                    });
2304                }
2305            }
2306        }
2307
2308        dependencies
2309    }
2310
2311    /// Generate Dioxus-specific commands
2312    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                // Detect platform preference
2322                let platform = Self::detect_dioxus_platform(root, framework);
2323                (root.clone(), platform)
2324            }
2325            _ => return Ok(commands),
2326        };
2327
2328        // Primary Dioxus serve command
2329        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, // Higher than cargo run
2343            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    /// Generate Leptos-specific commands
2353    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        // Primary Leptos watch command
2366        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, // Higher than cargo run
2376            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    /// Generate Yew framework commands
2386    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        // Primary Yew serve command using trunk
2399        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, // Higher than cargo run
2409            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        // Yew serve with auto-open browser
2416        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, // Slightly lower than basic serve
2426            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        // Yew build command
2438        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    /// Generate Tauri framework commands
2458    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        // Determine the correct working directory
2471        // If we're in src-tauri, use current directory
2472        // Otherwise, check if src-tauri exists as subdirectory
2473        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        // Primary Tauri dev command
2482        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, // Higher than cargo run
2492            conditions: Vec::new(),
2493            tags: vec!["tauri".to_string(), "dev".to_string()],
2494            requires_input: false,
2495            estimated_duration: Some(10),
2496        });
2497
2498        // Tauri build command
2499        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        // Tauri build with debug info
2516        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    /// Detect Dioxus platform preference
2544    fn detect_dioxus_platform(root: &Path, _framework: &FrameworkType) -> String {
2545        // 1. Check for environment variable override (highest priority)
2546        if let Ok(platform_override) = std::env::var("RAZ_PLATFORM_OVERRIDE") {
2547            return platform_override;
2548        }
2549
2550        // 2. Check Dioxus.toml
2551        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        // 3. Default to desktop for all Dioxus projects (user can override)
2567        "desktop".to_string()
2568    }
2569
2570    /// Check if a command is a web framework serve/dev command
2571    fn is_web_framework_command(cmd: &Command) -> bool {
2572        // Check for framework-specific serve/dev commands
2573        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        // Check for framework-specific commands
2578        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    /// Helper to intelligently add test arguments with defaults
2589    /// This ensures we don't duplicate flags like --exact or --show-output
2590    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        // Add default flags only if requested
2598        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}