1use getset::{Getters, WithSetters};
2use glossa_shared::{
3 ToCompactString,
4 tap::Pipe,
5 type_aliases::ahash::{HashMap, HashMapExt},
6};
7pub use hlight::HighlightResource;
8pub use kstring::KString;
9use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
10
11use crate::{Generator, MiniStr, resources::L10nMapEntry, to_kstr};
12
13pub type HighlightCfgMap<'h> = HashMap<DerivedMapKey, SyntaxHighlightConfig<'h>>;
15
16type KMap = HashMap<KString, MiniStr>;
17type HighlightedMaps = HashMap<KString, KMap>;
18
19#[derive(Getters, WithSetters, Debug, Clone)]
20#[getset(get = "pub with_prefix", set_with = "pub")]
21pub struct SyntaxHighlightConfig<'r> {
22 resource: HighlightResource<'r>,
23 syntax_name: MiniStr,
24 true_color: bool,
25}
26
27impl Default for SyntaxHighlightConfig<'_> {
28 fn default() -> Self {
29 Self {
30 resource: Default::default(),
31 syntax_name: Default::default(),
32 true_color: true,
33 }
34 }
35}
36
37#[derive(
38 Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Getters, WithSetters, Default,
39)]
40#[getset(get = "pub with_prefix", set_with = "pub")]
41pub struct DerivedMapKey {
42 base_name: KString,
44 suffix: KString,
46}
47
48impl core::fmt::Display for DerivedMapKey {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 let Self { base_name, suffix } = self;
51 write!(f, "{base_name}{suffix}")
52 }
53}
54
55impl DerivedMapKey {
56 fn format(&self) -> KString {
57 self
58 .to_compact_string()
59 .pipe(to_kstr)
60 }
61}
62
63type SmallKeys<'k> = smallvec::SmallVec<&'k KString, 4>;
64pub(crate) fn collect_derived_keys_to_map<'a>(
68 map: &'a HighlightCfgMap,
69) -> HashMap<&'a KString, SmallKeys<'a>> {
70 map
71 .keys()
72 .map(|k| (k.get_base_name(), k.get_suffix()))
73 .fold(HashMap::with_capacity(32), |mut grouped, (base, suffix)| {
74 grouped
75 .entry(base)
76 .or_default()
77 .push(suffix);
78 grouped
79 })
80}
81
82impl<'h> Generator<'h> {
83 pub(crate) fn collect_highlight_maps(
84 &'h self,
85 ) -> Option<Box<[(KString, HighlightedMaps)]>> {
86 let highlight_cfg_map = self.get_highlight().as_ref()?;
87 let highlight_groups = collect_derived_keys_to_map(highlight_cfg_map);
88
89 self
90 .get_l10n_res_map()
91 .par_iter()
92 .map(|(lang_id, entries)| {
93 let map = entries
94 .par_iter()
95 .filter_map(process_single_entry)
96 .filter_map(|(base_map_name, map)| {
97 highlight_groups
98 .get(&base_map_name)?
99 .par_iter()
100 .filter_map(move |suffix| {
101 generate_derived_map(highlight_cfg_map, &base_map_name, suffix)
102 })
103 .map(|(key, config)| {
104 let new_map = generate_highlight_map(map, config);
105 (key.format(), new_map)
106 })
107 .pipe(Some)
108 })
109 .flatten()
110 .collect::<HashMap<_, _>>();
111 (lang_id, map)
112 })
113 .filter_map(|(id, map)| (!map.is_empty()).then_some((id.clone(), map)))
114 .collect::<Box<_>>()
115 .into()
116 }
117}
118
119fn generate_highlight_map<'a>(
120 map: &'a KMap,
121 config: &'a SyntaxHighlightConfig<'a>,
122) -> KMap {
123 let highlight_mapper = |(k, v)| config.apply_highlighting(k, v);
124 map
125 .par_iter()
126 .filter_map(highlight_mapper)
127 .collect()
128}
129
130fn generate_derived_map<'h>(
131 highlight_cfg_map: &'h HighlightCfgMap<'h>,
132 map_name: &KString,
133 suffix: &KString,
134) -> Option<(DerivedMapKey, &'h SyntaxHighlightConfig<'h>)> {
135 let key = DerivedMapKey::default()
136 .with_base_name(map_name.clone())
137 .with_suffix(suffix.clone());
138
139 highlight_cfg_map
140 .get(&key)
141 .map(|config| (key, config))
142}
143
144fn process_single_entry(entry: &L10nMapEntry) -> Option<(KString, &KMap)> {
145 let map_name = entry.map_name_to_kstring();
146
147 entry
148 .get_data()
149 .as_ref()
150 .map(|data| (map_name, data))
151}
152
153impl<'a> SyntaxHighlightConfig<'a> {
154 fn apply_highlighting(
155 &'a self,
156 k: &KString,
157 v: &'a MiniStr,
158 ) -> Option<(KString, MiniStr)> {
159 let mut buffer = Vec::with_capacity(256);
160 hlight::Highlighter::default()
161 .with_resource(Some(&self.resource))
162 .with_syntax_name(&self.syntax_name)
163 .with_true_color(self.true_color)
164 .with_content(v.as_str())
165 .with_writer(Some(&mut buffer))
166 .run()
167 .ok()
168 .map(|_| (k.clone(), MiniStr::from_utf8_lossy(&buffer)))
169 }
170}
171
172#[cfg(test)]
173pub(crate) mod dbg_shared {
174 use super::*;
175
176 pub(crate) fn new_highlight_map<'a>() -> HighlightCfgMap<'a> {
177 let mut hmap = HighlightCfgMap::default();
178 hmap.insert(
179 DerivedMapKey::default()
180 .with_base_name("md".into())
181 .with_suffix("_md".into()),
182 SyntaxHighlightConfig::default()
183 .with_syntax_name("md".into())
184 .with_true_color(false),
185 );
186 hmap.insert(
187 DerivedMapKey::default()
188 .with_base_name("md".into())
189 .with_suffix("_md_ayu_dark".into()),
190 SyntaxHighlightConfig::default()
191 .with_resource(
192 HighlightResource::default()
193 .with_theme_name("ayu-light".into())
194 .with_background(false),
195 )
196 .with_syntax_name("md".into()),
197 );
198 hmap.insert(
199 DerivedMapKey::default()
200 .with_base_name("t".into())
201 .with_suffix("_toml".into()),
202 SyntaxHighlightConfig::default().with_syntax_name("toml".into()),
203 );
204 hmap
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::io;
211
212 use testutils::dbg;
213
214 use super::*;
215 use crate::{
216 AnyResult,
217 generator::{MapType, dbg_generator::en_generator},
218 highlight::dbg_shared::new_highlight_map,
219 };
220
221 #[ignore]
222 #[test]
223 fn test_derived_map_key() {
224 let map_key = DerivedMapKey::default()
225 .with_base_name("island".into())
226 .with_suffix("_md".into());
227 let mut map = HashMap::new();
228 map.insert(map_key, "apple");
229 dbg!(map);
230 }
231
232 #[ignore]
233 #[test]
234 fn test_collect_map_keys() {
235 let map = new_highlight_map();
236 let v = collect_derived_keys_to_map(&map);
237 dbg!(v);
244 }
245
246 #[ignore]
247 #[test]
248 fn test_toml_highlight() -> io::Result<()> {
249 use hlight::{HighlightResource, Highlighter};
250
251 let s: &str = r#"
252[main]
253enabled = false
254"😎" = "🍥"
255float = nan
256"#;
257
258 let res = HighlightResource::default()
259 .with_background(false)
260 ;
262
263 let mut writer = Vec::with_capacity(64);
264
265 Highlighter::default()
266 .with_syntax_name("toml")
267 .with_content(s)
268 .with_resource(Some(&res))
269 .with_writer(Some(&mut writer))
270 .run()?;
271
272 let highlighted_text = String::from_utf8_lossy(&writer);
273
274 println!("{highlighted_text}");
275
276 Ok(())
277 }
278
279 #[ignore]
280 #[test]
281 fn test_highlight() -> io::Result<()> {
282 let hmap = new_highlight_map();
283 let generator = en_generator().with_highlight(hmap);
284
285 let maps = generator
286 .collect_highlight_maps()
287 .unwrap();
288
289 for (map_name, map) in maps
290 .iter()
291 .flat_map(|(_, map)| map.iter())
292 {
293 println!("{map_name} ");
294 println!("{map:?}")
295 }
296
297 Ok(())
298 }
299
300 #[ignore]
301 #[test]
302 fn test_build_maps() -> AnyResult<()> {
303 let hmap = new_highlight_map();
304 let generator = en_generator().with_highlight(hmap);
305
306 if let Some(boxed) = generator.get_or_init_highlight_maps() {
307 for (idx, (lang, map)) in boxed.iter().enumerate() {
308 println!("---- {idx}");
309 std::dbg!(lang, map);
310 }
311 }
312
313 generator.output_match_fn(MapType::Highlight)?;
314 Ok(())
317 }
318}