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