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