glossa_codegen/
highlight.rs

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
14/// `[`(map_name & map_suffix, Highlight resource & syntax name)`]`
15pub 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, //
49  Serialize,
50  Deserialize,
51)]
52#[getset(get = "pub with_prefix", set_with = "pub")]
53pub struct DerivedMapKey {
54  /// map_name
55  base_name: KString,
56  /// map_suffix
57  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>;
76// type SmallKeys<'k> = Vec<&'k KString>;
77
78/// -> `HashMap<base_name, Vec<suffix>>`
79pub(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    /*
250    v = {
251        "unread": [ "_toml" ],
252        "hi": [ "_md_one_dark", "_md" ],
253    }
254    */
255    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      // .with_theme_name(ayu_dark())
273      ;
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]
313  // #[test]
314  // fn test_build_highlight_toml() -> AnyResult<()> {
315  //   let hmap = new_highlight_map();
316  //   let s = toml::to_string_pretty(&hmap)?;
317  //   println!("{s}");
318
319  //   Ok(())
320  // }
321
322  #[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    // generator.output_phf(MapType::Highlight)?;
337
338    Ok(())
339  }
340}