Skip to main content

lens_core/
lang.rs

1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3
4/// Language detection and classification
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub enum Language {
7    Rust,
8    TypeScript,
9    JavaScript,
10    Python,
11    Go,
12    Java,
13    CSharp,
14    Cpp,
15    C,
16    Ruby,
17    Php,
18    Swift,
19    Kotlin,
20    Scala,
21    Clojure,
22    Haskell,
23    Unknown,
24}
25
26impl Language {
27    /// Detect language from file extension
28    pub fn from_extension(extension: &str) -> Self {
29        match extension.to_lowercase().as_str() {
30            "rs" => Language::Rust,
31            "ts" => Language::TypeScript,
32            "tsx" => Language::TypeScript,
33            "js" => Language::JavaScript,
34            "jsx" => Language::JavaScript,
35            "mjs" => Language::JavaScript,
36            "py" => Language::Python,
37            "pyi" => Language::Python,
38            "go" => Language::Go,
39            "java" => Language::Java,
40            "cs" => Language::CSharp,
41            "cpp" | "cxx" | "cc" => Language::Cpp,
42            "c" => Language::C,
43            "h" | "hpp" => Language::Cpp, // Header files default to C++
44            "rb" => Language::Ruby,
45            "php" => Language::Php,
46            "swift" => Language::Swift,
47            "kt" | "kts" => Language::Kotlin,
48            "scala" | "sc" => Language::Scala,
49            "clj" | "cljs" | "cljc" => Language::Clojure,
50            "hs" => Language::Haskell,
51            _ => Language::Unknown,
52        }
53    }
54
55    /// Get file extensions for this language
56    pub fn extensions(&self) -> Vec<&'static str> {
57        match self {
58            Language::Rust => vec!["rs"],
59            Language::TypeScript => vec!["ts", "tsx"],
60            Language::JavaScript => vec!["js", "jsx", "mjs"],
61            Language::Python => vec!["py", "pyi"],
62            Language::Go => vec!["go"],
63            Language::Java => vec!["java"],
64            Language::CSharp => vec!["cs"],
65            Language::Cpp => vec!["cpp", "cxx", "cc", "hpp", "h"],
66            Language::C => vec!["c", "h"],
67            Language::Ruby => vec!["rb"],
68            Language::Php => vec!["php"],
69            Language::Swift => vec!["swift"],
70            Language::Kotlin => vec!["kt", "kts"],
71            Language::Scala => vec!["scala", "sc"],
72            Language::Clojure => vec!["clj", "cljs", "cljc"],
73            Language::Haskell => vec!["hs"],
74            Language::Unknown => vec![],
75        }
76    }
77
78    /// Get LSP server name for this language
79    pub fn lsp_server(&self) -> Option<&'static str> {
80        match self {
81            Language::TypeScript | Language::JavaScript => Some("tsserver"),
82            Language::Python => Some("pylsp"),
83            Language::Rust => Some("rust-analyzer"),
84            Language::Go => Some("gopls"),
85            Language::Java => Some("jdtls"),
86            Language::CSharp => Some("omnisharp"),
87            Language::Cpp | Language::C => Some("clangd"),
88            _ => None,
89        }
90    }
91
92    /// Get language display name
93    pub fn display_name(&self) -> &'static str {
94        match self {
95            Language::Rust => "Rust",
96            Language::TypeScript => "TypeScript",
97            Language::JavaScript => "JavaScript",
98            Language::Python => "Python",
99            Language::Go => "Go",
100            Language::Java => "Java",
101            Language::CSharp => "C#",
102            Language::Cpp => "C++",
103            Language::C => "C",
104            Language::Ruby => "Ruby",
105            Language::Php => "PHP",
106            Language::Swift => "Swift",
107            Language::Kotlin => "Kotlin",
108            Language::Scala => "Scala",
109            Language::Clojure => "Clojure",
110            Language::Haskell => "Haskell",
111            Language::Unknown => "Unknown",
112        }
113    }
114
115    /// Check if language supports LSP integration
116    pub fn supports_lsp(&self) -> bool {
117        self.lsp_server().is_some()
118    }
119
120    /// Get default search boost for this language
121    pub fn search_boost(&self) -> f64 {
122        match self {
123            Language::Rust => 1.2,        // Boost Rust files
124            Language::TypeScript => 1.1,  // Boost TypeScript files
125            Language::Python => 1.1,      // Boost Python files
126            Language::JavaScript => 1.0,  // Neutral
127            Language::Go => 1.05,         // Slight boost
128            Language::Java => 1.0,        // Neutral
129            _ => 0.9,                     // Slight penalty for less common languages
130        }
131    }
132
133    /// Get common identifier patterns for this language
134    pub fn identifier_patterns(&self) -> Vec<&'static str> {
135        match self {
136            Language::Rust => vec![
137                r"fn\s+(\w+)",           // Functions
138                r"struct\s+(\w+)",       // Structs
139                r"enum\s+(\w+)",         // Enums
140                r"trait\s+(\w+)",        // Traits
141                r"impl\s+(?:\w+\s+for\s+)?(\w+)", // Implementations
142                r"mod\s+(\w+)",          // Modules
143                r"use\s+(?:.+::)?(\w+)", // Imports
144            ],
145            Language::TypeScript | Language::JavaScript => vec![
146                r"function\s+(\w+)",     // Functions
147                r"class\s+(\w+)",        // Classes
148                r"interface\s+(\w+)",    // Interfaces (TS)
149                r"type\s+(\w+)",         // Type aliases (TS)
150                r"const\s+(\w+)",        // Constants
151                r"let\s+(\w+)",          // Variables
152                r"var\s+(\w+)",          // Variables
153                r"export\s+(?:function|class|interface|type|const|let|var)\s+(\w+)", // Exports
154            ],
155            Language::Python => vec![
156                r"def\s+(\w+)",          // Functions
157                r"class\s+(\w+)",        // Classes
158                r"import\s+(\w+)",       // Imports
159                r"from\s+\w+\s+import\s+(\w+)", // From imports
160                r"(\w+)\s*=\s*", // Assignments (variables)
161            ],
162            Language::Go => vec![
163                r"func\s+(\w+)",         // Functions
164                r"type\s+(\w+)",         // Types
165                r"var\s+(\w+)",          // Variables
166                r"const\s+(\w+)",        // Constants
167                r"package\s+(\w+)",      // Package
168                r#"import\s+(?:\w+\s+)?"(?:.+/)?(\w+)""#, // Imports
169            ],
170            Language::Java => vec![
171                r"public\s+(?:static\s+)?(?:class|interface|enum)\s+(\w+)", // Classes/Interfaces/Enums
172                r"public\s+(?:static\s+)?(?:\w+\s+)*(\w+)\s*\(", // Methods
173                r"private\s+(?:static\s+)?(?:\w+\s+)*(\w+)\s*\(", // Private methods
174                r"(?:public|private|protected)\s+(?:static\s+)?(\w+)\s+(\w+);", // Fields
175            ],
176            _ => vec![], // Default: no patterns
177        }
178    }
179
180    /// Check if file should be indexed based on language
181    pub fn should_index(&self) -> bool {
182        !matches!(self, Language::Unknown)
183    }
184}
185
186/// Language statistics for corpus analysis
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct LanguageStats {
189    pub language: Language,
190    pub file_count: usize,
191    pub total_lines: usize,
192    pub total_bytes: u64,
193    pub average_file_size: f64,
194}
195
196/// Language detector that can analyze file content
197pub struct LanguageDetector {
198    // Extension-based detection patterns
199    extension_map: HashMap<String, Language>,
200}
201
202impl LanguageDetector {
203    /// Create a new language detector
204    pub fn new() -> Self {
205        let mut extension_map = HashMap::new();
206        
207        // Build comprehensive extension mapping
208        for lang in [
209            Language::Rust,
210            Language::TypeScript,
211            Language::JavaScript,
212            Language::Python,
213            Language::Go,
214            Language::Java,
215            Language::CSharp,
216            Language::Cpp,
217            Language::C,
218            Language::Ruby,
219            Language::Php,
220            Language::Swift,
221            Language::Kotlin,
222            Language::Scala,
223            Language::Clojure,
224            Language::Haskell,
225        ] {
226            for ext in lang.extensions() {
227                extension_map.insert(ext.to_string(), lang.clone());
228            }
229        }
230
231        Self { extension_map }
232    }
233
234    /// Detect language from file path
235    pub fn detect_from_path(&self, path: &std::path::Path) -> Language {
236        if let Some(extension) = path.extension() {
237            if let Some(ext_str) = extension.to_str() {
238                return self.extension_map
239                    .get(&ext_str.to_lowercase())
240                    .cloned()
241                    .unwrap_or(Language::Unknown);
242            }
243        }
244        Language::Unknown
245    }
246
247    /// Detect language from file content (fallback method)
248    pub fn detect_from_content(&self, content: &str) -> Language {
249        // Simple heuristic-based detection for common patterns
250        if content.contains("fn main()") || content.contains("use std::") {
251            Language::Rust
252        } else if content.contains("interface ") || content.contains(": string") {
253            Language::TypeScript
254        } else if content.contains("def ") || content.contains("import ") || content.contains("print(") {
255            Language::Python
256        } else if content.contains("func main()") || content.contains("package main") {
257            Language::Go
258        } else if content.contains("public class") || content.contains("import java.") {
259            Language::Java
260        } else {
261            Language::Unknown
262        }
263    }
264
265    /// Get all supported languages
266    pub fn supported_languages(&self) -> Vec<Language> {
267        let mut languages: Vec<_> = self.extension_map.values().cloned().collect();
268        languages.sort_by_key(|lang| lang.display_name());
269        languages.dedup();
270        languages
271    }
272
273    /// Check if extension is supported
274    pub fn is_supported_extension(&self, extension: &str) -> bool {
275        self.extension_map.contains_key(&extension.to_lowercase())
276    }
277}
278
279impl Default for LanguageDetector {
280    fn default() -> Self {
281        Self::new()
282    }
283}
284
285impl std::fmt::Display for Language {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        write!(f, "{}", self.display_name())
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use std::path::Path;
295
296    #[test]
297    fn test_language_from_extension() {
298        assert_eq!(Language::from_extension("rs"), Language::Rust);
299        assert_eq!(Language::from_extension("ts"), Language::TypeScript);
300        assert_eq!(Language::from_extension("py"), Language::Python);
301        assert_eq!(Language::from_extension("go"), Language::Go);
302        assert_eq!(Language::from_extension("unknown"), Language::Unknown);
303    }
304
305    #[test]
306    fn test_language_detector() {
307        let detector = LanguageDetector::new();
308        
309        assert_eq!(detector.detect_from_path(Path::new("main.rs")), Language::Rust);
310        assert_eq!(detector.detect_from_path(Path::new("app.ts")), Language::TypeScript);
311        assert_eq!(detector.detect_from_path(Path::new("script.py")), Language::Python);
312        assert_eq!(detector.detect_from_path(Path::new("unknown.xyz")), Language::Unknown);
313    }
314
315    #[test]
316    fn test_lsp_support() {
317        assert!(Language::Rust.supports_lsp());
318        assert!(Language::TypeScript.supports_lsp());
319        assert!(Language::Python.supports_lsp());
320        assert!(Language::Go.supports_lsp());
321        assert!(!Language::Unknown.supports_lsp());
322    }
323
324    #[test]
325    fn test_content_detection() {
326        let detector = LanguageDetector::new();
327        
328        let rust_content = "fn main() { println!(\"Hello\"); }";
329        assert_eq!(detector.detect_from_content(rust_content), Language::Rust);
330        
331        let ts_content = "interface User { name: string; }";
332        assert_eq!(detector.detect_from_content(ts_content), Language::TypeScript);
333        
334        let python_content = "def hello():\n    print('Hello')";
335        assert_eq!(detector.detect_from_content(python_content), Language::Python);
336    }
337
338    #[test]
339    fn test_search_boost() {
340        assert_eq!(Language::Rust.search_boost(), 1.2);
341        assert_eq!(Language::TypeScript.search_boost(), 1.1);
342        assert_eq!(Language::JavaScript.search_boost(), 1.0);
343        assert!(Language::Unknown.search_boost() < 1.0);
344    }
345
346    #[test]
347    fn test_supported_languages() {
348        let detector = LanguageDetector::new();
349        let languages = detector.supported_languages();
350        
351        assert!(!languages.is_empty());
352        assert!(languages.contains(&Language::Rust));
353        assert!(languages.contains(&Language::TypeScript));
354        assert!(!languages.contains(&Language::Unknown));
355    }
356}