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};
10
11use crate::{Generator, MiniStr, resources::L10nMapEntry, to_kstr};
12
13/// `[`(map_name & map_suffix, Highlight resource & syntax name)`]`
14pub 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  /// map_name
43  base_name: KString,
44  /// map_suffix
45  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>;
64// type SmallKeys<'k> = Vec<&'k KString>;
65
66/// -> `HashMap<base_name, Vec<suffix>>`
67pub(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    /*
238    v = {
239        "unread": [ "_toml" ],
240        "hi": [ "_md_one_dark", "_md" ],
241    }
242    */
243    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      // .with_theme_name(ayu_dark())
261      ;
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    // generator.output_phf(MapType::Highlight)?;
315
316    Ok(())
317  }
318}