1use std::{collections::HashMap, sync::Arc};
18
19use syntastica::{Processor, render, renderer::HtmlRenderer};
20use syntastica_core::theme::ResolvedTheme;
21use syntastica_parsers::{Lang, LanguageSetImpl};
22
23use super::{
24 error::{SyntaxError, SyntaxResult},
25 types::{SyntaxConfig, SyntaxHighlighter, SyntaxManager},
26};
27
28pub struct SyntasticaHighlighter {
30 language_set: Arc<LanguageSetImpl>,
31 themes: HashMap<String, ResolvedTheme>,
32 default_theme: ResolvedTheme,
33}
34
35impl SyntasticaHighlighter {
36 pub fn new() -> SyntaxResult<Self> {
38 let language_set = Arc::new(LanguageSetImpl::new());
39
40 let mut themes = HashMap::new();
41
42 for theme_name in syntastica_themes::THEMES {
44 if let Some(theme) = syntastica_themes::from_str(theme_name) {
45 themes.insert(theme_name.to_string(), theme);
46 }
47 }
48
49 let default_theme = syntastica_themes::one::dark();
50
51 Ok(Self {
52 language_set,
53 themes,
54 default_theme,
55 })
56 }
57
58 pub fn add_theme(&mut self, name: String, theme: ResolvedTheme) {
60 self.themes.insert(name, theme);
61 }
62
63 pub fn set_default_theme(&mut self, theme: ResolvedTheme) {
65 self.default_theme = theme;
66 }
67
68 fn parse_language(&self, language: &str) -> Option<Lang> {
70 match language.to_lowercase().as_str() {
71 "rust" | "rs" => Some(Lang::Rust),
72 "python" | "py" => Some(Lang::Python),
73 "javascript" | "js" => Some(Lang::Javascript),
74 "typescript" | "ts" => Some(Lang::Typescript),
75 "nix" => Some(Lang::Nix),
76 "bash" | "sh" | "shell" => Some(Lang::Bash),
77 "c" => Some(Lang::C),
78 "cpp" | "c++" | "cxx" => Some(Lang::Cpp),
79 "go" => Some(Lang::Go),
80 "java" => Some(Lang::Java),
81 "json" => Some(Lang::Json),
82 "yaml" | "yml" => Some(Lang::Yaml),
83 "html" => Some(Lang::Html),
84 "css" => Some(Lang::Css),
85 "markdown" | "md" => Some(Lang::Markdown),
86 "sql" => Some(Lang::Sql),
87 "lua" => Some(Lang::Lua),
88 "ruby" | "rb" => Some(Lang::Ruby),
89 "php" => Some(Lang::Php),
90 "haskell" | "hs" => Some(Lang::Haskell),
91 "ocaml" | "ml" => Some(Lang::Ocaml),
92 "scala" => Some(Lang::Scala),
93 "swift" => Some(Lang::Swift),
94 "makefile" | "make" => Some(Lang::Make),
95 "cmake" => Some(Lang::Cmake),
96 "text" | "txt" | "plain" => None, _ => None,
98 }
99 }
100
101 fn get_theme(&self, theme_name: Option<&str>) -> &ResolvedTheme {
103 theme_name
104 .and_then(|name| self.themes.get(name))
105 .unwrap_or(&self.default_theme)
106 }
107}
108
109impl Default for SyntasticaHighlighter {
110 fn default() -> Self {
111 Self::new().expect("Failed to create Syntastica highlighter")
112 }
113}
114
115impl SyntaxHighlighter for SyntasticaHighlighter {
116 fn name(&self) -> &'static str {
117 "Syntastica"
118 }
119
120 fn supported_languages(&self) -> Vec<String> {
121 vec![
122 "rust",
123 "rs",
124 "python",
125 "py",
126 "javascript",
127 "js",
128 "typescript",
129 "ts",
130 "nix",
131 "bash",
132 "sh",
133 "shell",
134 "c",
135 "cpp",
136 "c++",
137 "cxx",
138 "go",
139 "java",
140 "json",
141 "yaml",
142 "yml",
143 "html",
144 "css",
145 "markdown",
146 "md",
147 "sql",
148 "lua",
149 "ruby",
150 "rb",
151 "php",
152 "haskell",
153 "hs",
154 "ocaml",
155 "ml",
156 "scala",
157 "swift",
158 "makefile",
159 "make",
160 "cmake",
161 "text",
162 "txt",
163 "plain",
164 ]
165 .into_iter()
166 .map(String::from)
167 .collect()
168 }
169
170 fn available_themes(&self) -> Vec<String> {
171 let mut themes: Vec<String> = self.themes.keys().cloned().collect();
172 themes.sort();
173 themes
174 }
175
176 fn highlight(&self, code: &str, language: &str, theme: Option<&str>) -> SyntaxResult<String> {
177 let lang = self
178 .parse_language(language)
179 .ok_or_else(|| SyntaxError::UnsupportedLanguage(language.to_string()))?;
180
181 let theme = self.get_theme(theme);
182
183 let mut processor = Processor::new(self.language_set.as_ref());
185
186 let highlights = processor
188 .process(code, lang)
189 .map_err(|e| SyntaxError::HighlightingFailed(e.to_string()))?;
190
191 let mut renderer = HtmlRenderer::new();
193 let html = render(&highlights, &mut renderer, theme.clone());
194
195 Ok(html)
196 }
197
198 fn language_from_extension(&self, extension: &str) -> Option<String> {
199 match extension.to_lowercase().as_str() {
200 "rs" => Some("rust".to_string()),
201 "py" | "pyw" => Some("python".to_string()),
202 "js" | "mjs" => Some("javascript".to_string()),
203 "ts" => Some("typescript".to_string()),
204 "nix" => Some("nix".to_string()),
205 "sh" | "bash" | "zsh" | "fish" => Some("bash".to_string()),
206 "c" | "h" => Some("c".to_string()),
207 "cpp" | "cxx" | "cc" | "hpp" | "hxx" | "hh" => Some("cpp".to_string()),
208 "go" => Some("go".to_string()),
209 "java" => Some("java".to_string()),
210 "json" => Some("json".to_string()),
211 "yaml" | "yml" => Some("yaml".to_string()),
212 "html" | "htm" => Some("html".to_string()),
213 "css" => Some("css".to_string()),
214 "md" | "markdown" => Some("markdown".to_string()),
215 "sql" => Some("sql".to_string()),
216 "lua" => Some("lua".to_string()),
217 "rb" => Some("ruby".to_string()),
218 "php" => Some("php".to_string()),
219 "hs" => Some("haskell".to_string()),
220 "ml" | "mli" => Some("ocaml".to_string()),
221 "scala" => Some("scala".to_string()),
222 "swift" => Some("swift".to_string()),
223 "txt" => Some("text".to_string()),
224 _ => None,
225 }
226 }
227}
228
229pub fn create_syntastica_manager() -> SyntaxResult<SyntaxManager> {
234 let highlighter = Box::new(SyntasticaHighlighter::new()?);
235 let mut config = SyntaxConfig::default();
236 config.default_theme = Some("one-dark".to_string());
237 Ok(SyntaxManager::new(highlighter, config))
238}