Skip to main content

debugger/setup/
detector.rs

1//! Project type detection and debugger recommendations
2//!
3//! Detects project types from the current directory and recommends appropriate debuggers.
4
5use std::path::Path;
6
7/// Detected project type
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum ProjectType {
10    Rust,
11    Cuda,
12    Go,
13    Python,
14    JavaScript,
15    TypeScript,
16    C,
17    Cpp,
18    CSharp,
19    Java,
20}
21
22/// Detect project types in a directory
23pub fn detect_project_types(dir: &Path) -> Vec<ProjectType> {
24    let mut types = Vec::new();
25
26    // Rust
27    if dir.join("Cargo.toml").exists() {
28        types.push(ProjectType::Rust);
29    }
30
31    // CUDA detection must precede C/C++ (.cu files are valid C++ but require CUDA-GDB)
32    if has_extension_in_dir(dir, "cu") {
33        types.push(ProjectType::Cuda);
34    }
35
36    // Go
37    if dir.join("go.mod").exists() || dir.join("go.sum").exists() {
38        types.push(ProjectType::Go);
39    }
40
41    // Python
42    if dir.join("pyproject.toml").exists()
43        || dir.join("setup.py").exists()
44        || dir.join("requirements.txt").exists()
45        || dir.join("Pipfile").exists()
46    {
47        types.push(ProjectType::Python);
48    }
49
50    // JavaScript / TypeScript
51    if dir.join("package.json").exists() {
52        // Check for TypeScript
53        if dir.join("tsconfig.json").exists() {
54            types.push(ProjectType::TypeScript);
55        } else {
56            types.push(ProjectType::JavaScript);
57        }
58    }
59
60    // C / C++
61    if dir.join("CMakeLists.txt").exists()
62        || dir.join("Makefile").exists()
63        || dir.join("configure").exists()
64        || dir.join("meson.build").exists()
65    {
66        // Try to detect if it's C or C++
67        if has_cpp_files(dir) {
68            types.push(ProjectType::Cpp);
69        } else if has_c_files(dir) {
70            types.push(ProjectType::C);
71        } else {
72            // Default to C++ for CMake/Makefile projects
73            types.push(ProjectType::Cpp);
74        }
75    }
76
77    // C#
78    if has_extension_in_dir(dir, "csproj") || has_extension_in_dir(dir, "sln") {
79        types.push(ProjectType::CSharp);
80    }
81
82    // Java
83    if dir.join("pom.xml").exists()
84        || dir.join("build.gradle").exists()
85        || dir.join("build.gradle.kts").exists()
86    {
87        types.push(ProjectType::Java);
88    }
89
90    types
91}
92
93/// Get recommended debuggers for a project type
94pub fn debuggers_for_project(project: &ProjectType) -> Vec<&'static str> {
95    match project {
96        ProjectType::Rust => vec!["codelldb", "lldb"],
97        ProjectType::Cuda => vec!["cuda-gdb"],
98        ProjectType::Go => vec!["go"],
99        ProjectType::Python => vec!["python"],
100        ProjectType::JavaScript | ProjectType::TypeScript => vec![], // js-debug not yet implemented
101        ProjectType::C | ProjectType::Cpp => vec!["lldb", "codelldb"],
102        ProjectType::CSharp => vec![], // netcoredbg not yet implemented
103        ProjectType::Java => vec![],   // java-debug not yet implemented
104    }
105}
106
107/// Check if directory contains C++ files
108fn has_cpp_files(dir: &Path) -> bool {
109    has_extension_in_dir(dir, "cpp")
110        || has_extension_in_dir(dir, "cc")
111        || has_extension_in_dir(dir, "cxx")
112        || has_extension_in_dir(dir, "hpp")
113        || has_extension_in_dir(dir, "hxx")
114}
115
116/// Check if directory contains C files
117fn has_c_files(dir: &Path) -> bool {
118    has_extension_in_dir(dir, "c") || has_extension_in_dir(dir, "h")
119}
120
121/// Check if directory contains files with a specific extension
122fn has_extension_in_dir(dir: &Path, ext: &str) -> bool {
123    if let Ok(entries) = std::fs::read_dir(dir) {
124        for entry in entries {
125            // Explicitly handle Result - skip entries that can't be read
126            if let Ok(entry) = entry {
127                let path = entry.path();
128                if path.extension().map(|e| e == ext).unwrap_or(false) {
129                    return true;
130                }
131            }
132        }
133    }
134    false
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use tempfile::tempdir;
141
142    #[test]
143    fn test_detect_rust_project() {
144        let dir = tempdir().unwrap();
145        std::fs::write(dir.path().join("Cargo.toml"), "[package]").unwrap();
146        let types = detect_project_types(dir.path());
147        assert!(types.contains(&ProjectType::Rust));
148    }
149
150    #[test]
151    fn test_detect_python_project() {
152        let dir = tempdir().unwrap();
153        std::fs::write(dir.path().join("requirements.txt"), "requests").unwrap();
154        let types = detect_project_types(dir.path());
155        assert!(types.contains(&ProjectType::Python));
156    }
157
158    #[test]
159    fn test_debuggers_for_rust() {
160        let debuggers = debuggers_for_project(&ProjectType::Rust);
161        assert!(debuggers.contains(&"codelldb"));
162    }
163}