vtcode_ui/tui/utils/
cached_style_parser.rs1use anstyle::Style as AnsiStyle;
11use anyhow::{Context, Result};
12use vtcode_commons::lr_map::LrMap;
13
14pub struct CachedStyleParser {
16 git_cache: LrMap<String, AnsiStyle>,
17 ls_colors_cache: LrMap<String, AnsiStyle>,
18}
19
20impl CachedStyleParser {
21 pub(crate) fn new() -> Self {
23 Self {
24 git_cache: LrMap::new(),
25 ls_colors_cache: LrMap::new(),
26 }
27 }
28
29 pub(crate) fn parse_git_style(&self, input: &str) -> Result<AnsiStyle> {
31 if let Some(cached) = self.git_cache.get(input) {
32 return Ok(cached);
33 }
34
35 let result =
36 anstyle_git::parse(input).map_err(|e| anyhow::anyhow!("Failed to parse Git style '{input}': {e:?}"))?;
37
38 self.git_cache.insert(input.to_string(), result);
39 Ok(result)
40 }
41
42 pub(crate) fn parse_ls_colors(&self, input: &str) -> Result<AnsiStyle> {
44 if let Some(cached) = self.ls_colors_cache.get(input) {
45 return Ok(cached);
46 }
47
48 let result = anstyle_ls::parse(input).ok_or_else(|| anyhow::anyhow!("Failed to parse LS_COLORS '{input}'"))?;
49
50 self.ls_colors_cache.insert(input.to_string(), result);
51 Ok(result)
52 }
53
54 pub(crate) fn parse_flexible(&self, input: &str) -> Result<AnsiStyle> {
56 match self.parse_git_style(input) {
57 Ok(style) => Ok(style),
58 Err(_) => self
59 .parse_ls_colors(input)
60 .with_context(|| format!("Could not parse style string: '{input}'")),
61 }
62 }
63
64 fn clear_cache(&self) {
66 self.git_cache.clear();
67 self.ls_colors_cache.clear();
68 }
69
70 fn cache_stats(&self) -> (usize, usize) {
72 (self.git_cache.len(), self.ls_colors_cache.len())
73 }
74}
75
76impl Default for CachedStyleParser {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_parse_git_style() {
88 let parser = CachedStyleParser::new();
89 let result = parser.parse_git_style("bold red").unwrap();
90
91 assert!(result.get_effects().contains(anstyle::Effects::BOLD));
92 }
93
94 #[test]
95 fn test_parse_ls_colors() {
96 let parser = CachedStyleParser::new();
97 let result = parser.parse_ls_colors("34").unwrap(); assert!(result.get_fg_color().is_some());
100 }
101
102 #[test]
103 fn test_parse_flexible_git_first() {
104 let parser = CachedStyleParser::new();
105 let result = parser.parse_flexible("bold green").unwrap();
106
107 assert!(result.get_effects().contains(anstyle::Effects::BOLD));
108 }
109
110 #[test]
111 fn test_parse_flexible_ls_fallback() {
112 let parser = CachedStyleParser::new();
113 let result = parser.parse_flexible("01;34").unwrap(); assert!(result.get_effects().contains(anstyle::Effects::BOLD));
116 }
117
118 #[test]
119 fn test_caching_behavior() {
120 let parser = CachedStyleParser::new();
121
122 let _result1 = parser.parse_git_style("red").unwrap();
124 let _result2 = parser.parse_git_style("red").unwrap();
125
126 let (git_count, _) = parser.cache_stats();
127 assert_eq!(git_count, 1); }
129
130 #[test]
131 fn test_cache_clear() {
132 let parser = CachedStyleParser::new();
133 let _result = parser.parse_git_style("blue").unwrap();
134
135 assert_eq!(parser.cache_stats().0, 1); parser.clear_cache();
138
139 assert_eq!(parser.cache_stats().0, 0); }
141
142 #[test]
143 fn test_multiple_cache_entries() {
144 let parser = CachedStyleParser::new();
145 let _result1 = parser.parse_git_style("bold red").unwrap();
146 let _result2 = parser.parse_git_style("italic green").unwrap();
147 let _result3 = parser.parse_ls_colors("34").unwrap();
148
149 let (git_count, ls_count) = parser.cache_stats();
150 assert_eq!(git_count, 2); assert_eq!(ls_count, 1); }
153}