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