vtcode_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 fn new() -> Self {
23 Self {
24 git_cache: LrMap::new(),
25 ls_colors_cache: LrMap::new(),
26 }
27 }
28
29 pub 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 = anstyle_git::parse(input)
36 .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 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)
49 .ok_or_else(|| anyhow::anyhow!("Failed to parse LS_COLORS '{}'", input))?;
50
51 self.ls_colors_cache.insert(input.to_string(), result);
52 Ok(result)
53 }
54
55 pub fn parse_flexible(&self, input: &str) -> Result<AnsiStyle> {
57 match self.parse_git_style(input) {
58 Ok(style) => Ok(style),
59 Err(_) => self
60 .parse_ls_colors(input)
61 .with_context(|| format!("Could not parse style string: '{}'", input)),
62 }
63 }
64
65 pub fn clear_cache(&self) {
67 self.git_cache.clear();
68 self.ls_colors_cache.clear();
69 }
70
71 pub fn cache_stats(&self) -> (usize, usize) {
73 (self.git_cache.len(), self.ls_colors_cache.len())
74 }
75}
76
77impl Default for CachedStyleParser {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_parse_git_style() {
89 let parser = CachedStyleParser::new();
90 let result = parser.parse_git_style("bold red").unwrap();
91
92 assert!(result.get_effects().contains(anstyle::Effects::BOLD));
93 }
94
95 #[test]
96 fn test_parse_ls_colors() {
97 let parser = CachedStyleParser::new();
98 let result = parser.parse_ls_colors("34").unwrap(); assert!(result.get_fg_color().is_some());
101 }
102
103 #[test]
104 fn test_parse_flexible_git_first() {
105 let parser = CachedStyleParser::new();
106 let result = parser.parse_flexible("bold green").unwrap();
107
108 assert!(result.get_effects().contains(anstyle::Effects::BOLD));
109 }
110
111 #[test]
112 fn test_parse_flexible_ls_fallback() {
113 let parser = CachedStyleParser::new();
114 let result = parser.parse_flexible("01;34").unwrap(); assert!(result.get_effects().contains(anstyle::Effects::BOLD));
117 }
118
119 #[test]
120 fn test_caching_behavior() {
121 let parser = CachedStyleParser::new();
122
123 let _result1 = parser.parse_git_style("red").unwrap();
125 let _result2 = parser.parse_git_style("red").unwrap();
126
127 let (git_count, _) = parser.cache_stats();
128 assert_eq!(git_count, 1); }
130
131 #[test]
132 fn test_cache_clear() {
133 let parser = CachedStyleParser::new();
134 let _result = parser.parse_git_style("blue").unwrap();
135
136 assert_eq!(parser.cache_stats().0, 1); parser.clear_cache();
139
140 assert_eq!(parser.cache_stats().0, 0); }
142
143 #[test]
144 fn test_multiple_cache_entries() {
145 let parser = CachedStyleParser::new();
146 let _result1 = parser.parse_git_style("bold red").unwrap();
147 let _result2 = parser.parse_git_style("italic green").unwrap();
148 let _result3 = parser.parse_ls_colors("34").unwrap();
149
150 let (git_count, ls_count) = parser.cache_stats();
151 assert_eq!(git_count, 2); assert_eq!(ls_count, 1); }
154}