1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3
4#[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 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, "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 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 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 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 pub fn supports_lsp(&self) -> bool {
117 self.lsp_server().is_some()
118 }
119
120 pub fn search_boost(&self) -> f64 {
122 match self {
123 Language::Rust => 1.2, Language::TypeScript => 1.1, Language::Python => 1.1, Language::JavaScript => 1.0, Language::Go => 1.05, Language::Java => 1.0, _ => 0.9, }
131 }
132
133 pub fn identifier_patterns(&self) -> Vec<&'static str> {
135 match self {
136 Language::Rust => vec![
137 r"fn\s+(\w+)", r"struct\s+(\w+)", r"enum\s+(\w+)", r"trait\s+(\w+)", r"impl\s+(?:\w+\s+for\s+)?(\w+)", r"mod\s+(\w+)", r"use\s+(?:.+::)?(\w+)", ],
145 Language::TypeScript | Language::JavaScript => vec![
146 r"function\s+(\w+)", r"class\s+(\w+)", r"interface\s+(\w+)", r"type\s+(\w+)", r"const\s+(\w+)", r"let\s+(\w+)", r"var\s+(\w+)", r"export\s+(?:function|class|interface|type|const|let|var)\s+(\w+)", ],
155 Language::Python => vec![
156 r"def\s+(\w+)", r"class\s+(\w+)", r"import\s+(\w+)", r"from\s+\w+\s+import\s+(\w+)", r"(\w+)\s*=\s*", ],
162 Language::Go => vec![
163 r"func\s+(\w+)", r"type\s+(\w+)", r"var\s+(\w+)", r"const\s+(\w+)", r"package\s+(\w+)", r#"import\s+(?:\w+\s+)?"(?:.+/)?(\w+)""#, ],
170 Language::Java => vec![
171 r"public\s+(?:static\s+)?(?:class|interface|enum)\s+(\w+)", r"public\s+(?:static\s+)?(?:\w+\s+)*(\w+)\s*\(", r"private\s+(?:static\s+)?(?:\w+\s+)*(\w+)\s*\(", r"(?:public|private|protected)\s+(?:static\s+)?(\w+)\s+(\w+);", ],
176 _ => vec![], }
178 }
179
180 pub fn should_index(&self) -> bool {
182 !matches!(self, Language::Unknown)
183 }
184}
185
186#[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
196pub struct LanguageDetector {
198 extension_map: HashMap<String, Language>,
200}
201
202impl LanguageDetector {
203 pub fn new() -> Self {
205 let mut extension_map = HashMap::new();
206
207 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 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 pub fn detect_from_content(&self, content: &str) -> Language {
249 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 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 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}