semantic_code_edit_mcp/languages/
mod.rs

1pub mod javascript;
2pub mod json;
3pub mod plain;
4pub mod python;
5pub mod rust;
6pub mod toml;
7pub mod traits;
8pub mod tsx;
9pub mod typescript;
10
11use anyhow::Result;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use std::{
15    collections::HashMap,
16    fmt::{self, Display, Formatter},
17    path::Path,
18};
19use tree_sitter::{Language, Parser, Query};
20
21use crate::languages::traits::LanguageEditor;
22
23/// Registry to manage all supported languages
24#[derive(Debug)]
25pub struct LanguageRegistry {
26    languages: HashMap<LanguageName, LanguageCommon>,
27    extensions: HashMap<&'static str, LanguageName>,
28}
29
30#[derive(fieldwork::Fieldwork)]
31#[fieldwork(get)]
32pub struct LanguageCommon {
33    #[fieldwork(get(copy))]
34    name: LanguageName,
35    file_extensions: &'static [&'static str],
36    #[fieldwork(rename = tree_sitter_language)]
37    language: Language,
38    editor: Box<dyn LanguageEditor>,
39    validation_query: Option<Query>,
40}
41
42impl fmt::Debug for LanguageCommon {
43    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
44        f.debug_struct("LanguageCommon")
45            .field("name", &self.name)
46            .field("file_extensions", &self.file_extensions)
47            .field("language", &self.language)
48            .field("validation_query", &self.validation_query)
49            .finish()
50    }
51}
52impl Display for LanguageCommon {
53    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
54        f.write_str(self.name.as_str())
55    }
56}
57
58impl LanguageCommon {
59    pub fn tree_sitter_parser(&self) -> Result<Parser> {
60        let mut parser = Parser::new();
61        parser.set_language(self.tree_sitter_language())?;
62        Ok(parser)
63    }
64}
65
66#[derive(
67    Serialize, Deserialize, Debug, JsonSchema, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy,
68)]
69#[serde(rename_all = "snake_case")]
70pub enum LanguageName {
71    Rust,
72    Json,
73    Toml,
74    Javascript,
75    Typescript,
76    Tsx,
77    Python,
78    #[serde(other)]
79    Other,
80}
81impl LanguageName {
82    fn as_str(&self) -> &str {
83        match self {
84            LanguageName::Rust => "rust",
85            LanguageName::Json => "json",
86            LanguageName::Toml => "toml",
87            LanguageName::Javascript => "javascript",
88            LanguageName::Typescript => "typescript",
89            LanguageName::Tsx => "tsx",
90            LanguageName::Python => "python",
91            LanguageName::Other => "other",
92        }
93    }
94}
95
96impl Display for LanguageName {
97    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98        f.write_str(self.as_str())
99    }
100}
101
102impl LanguageRegistry {
103    pub fn new() -> Result<Self> {
104        let mut registry = Self {
105            languages: HashMap::new(),
106            extensions: HashMap::new(),
107        };
108
109        registry.register_language(json::language()?);
110        registry.register_language(rust::language()?);
111        registry.register_language(toml::language()?);
112        registry.register_language(typescript::language()?);
113        registry.register_language(tsx::language()?);
114        registry.register_language(javascript::language()?);
115        registry.register_language(python::language()?);
116        registry.register_language(plain::language()?);
117
118        Ok(registry)
119    }
120
121    pub fn register_language(&mut self, language: LanguageCommon) {
122        let name = language.name();
123        for extension in language.file_extensions() {
124            self.extensions.insert(extension, name);
125        }
126        self.languages.insert(name, language);
127    }
128
129    pub fn get_language(&self, name: LanguageName) -> &LanguageCommon {
130        self.languages.get(&name).unwrap()
131    }
132
133    pub fn get_language_with_hint(
134        &self,
135        file_path: &Path,
136        language_hint: Option<LanguageName>,
137    ) -> Result<&LanguageCommon> {
138        let language_name = language_hint
139            .or_else(|| self.detect_language_from_path(file_path))
140            .unwrap_or(LanguageName::Other);
141        Ok(self.get_language(language_name))
142    }
143
144    pub fn detect_language_from_path(&self, file_path: &Path) -> Option<LanguageName> {
145        let extension = file_path.extension()?.to_str()?;
146        self.extensions.get(extension).copied()
147    }
148}