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