bubbletea_widgets/textarea/
memoization.rs1use std::collections::{HashMap, VecDeque};
13use std::hash::Hash;
14use std::marker::PhantomData;
15use unicode_width::UnicodeWidthChar;
16
17pub trait Hasher {
20 fn hash_key(&self) -> String;
22}
23
24#[derive(Debug, Clone)]
26struct CacheEntry<T> {
27 value: T,
28 access_order: usize,
29}
30
31#[derive(Debug)]
34pub struct MemoCache<H, T>
35where
36 H: Hasher + Clone,
37 T: Clone,
38{
39 capacity: usize,
40 cache: HashMap<String, CacheEntry<T>>,
41 access_counter: usize,
42 eviction_queue: VecDeque<String>,
43 _phantom: PhantomData<H>,
44}
45
46impl<H, T> MemoCache<H, T>
47where
48 H: Hasher + Clone,
49 T: Clone,
50{
51 pub fn new(capacity: usize) -> Self {
53 Self {
54 capacity,
55 cache: HashMap::new(),
56 access_counter: 0,
57 eviction_queue: VecDeque::new(),
58 _phantom: PhantomData,
59 }
60 }
61
62 pub fn capacity(&self) -> usize {
64 self.capacity
65 }
66
67 pub fn size(&self) -> usize {
69 self.cache.len()
70 }
71
72 pub fn get(&mut self, hashable: &H) -> Option<T> {
74 let key = hashable.hash_key();
75
76 if let Some(entry) = self.cache.get_mut(&key) {
77 self.access_counter += 1;
79 entry.access_order = self.access_counter;
80
81 if let Some(pos) = self.eviction_queue.iter().position(|x| x == &key) {
83 self.eviction_queue.remove(pos);
84 }
85 self.eviction_queue.push_front(key);
86
87 Some(entry.value.clone())
88 } else {
89 None
90 }
91 }
92
93 pub fn set(&mut self, hashable: &H, value: T) {
95 let key = hashable.hash_key();
96
97 if self.cache.contains_key(&key) {
99 self.access_counter += 1;
100 self.cache.insert(
101 key.clone(),
102 CacheEntry {
103 value,
104 access_order: self.access_counter,
105 },
106 );
107
108 if let Some(pos) = self.eviction_queue.iter().position(|x| x == &key) {
110 self.eviction_queue.remove(pos);
111 }
112 self.eviction_queue.push_front(key);
113 return;
114 }
115
116 if self.cache.len() >= self.capacity {
118 self.evict_lru();
119 }
120
121 self.access_counter += 1;
123 self.cache.insert(
124 key.clone(),
125 CacheEntry {
126 value,
127 access_order: self.access_counter,
128 },
129 );
130 self.eviction_queue.push_front(key);
131 }
132
133 fn evict_lru(&mut self) {
135 if let Some(lru_key) = self.eviction_queue.pop_back() {
136 self.cache.remove(&lru_key);
137 }
138 }
139
140 pub fn clear(&mut self) {
142 self.cache.clear();
143 self.eviction_queue.clear();
144 self.access_counter = 0;
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Hash)]
150pub struct Line {
151 pub runes: Vec<char>,
153 pub width: usize,
155}
156
157impl Hasher for Line {
158 fn hash_key(&self) -> String {
159 use std::collections::hash_map::DefaultHasher;
160 use std::hash::{Hash, Hasher as StdHasher};
161
162 let mut hasher = DefaultHasher::new();
163 self.hash(&mut hasher);
164 format!("{:x}", hasher.finish())
165 }
166}
167
168#[derive(Debug)]
170pub struct MemoizedWrap {
171 cache: HashMap<String, Vec<Vec<char>>>,
172}
173
174impl MemoizedWrap {
175 pub fn new() -> Self {
177 Self::with_capacity(10000) }
179
180 pub fn with_capacity(_capacity: usize) -> Self {
182 Self {
183 cache: HashMap::new(),
184 }
185 }
186
187 pub fn wrap(&mut self, runes: &[char], width: usize) -> Vec<Vec<char>> {
189 let line = Line {
190 runes: runes.to_vec(),
191 width,
192 };
193
194 let key = line.hash_key();
195 if let Some(cached) = self.cache.get(&key) {
196 return cached.clone();
197 }
198
199 let wrapped = self.do_wrap(runes, width);
200 self.cache.insert(key, wrapped.clone());
201 wrapped
202 }
203
204 fn do_wrap(&self, runes: &[char], width: usize) -> Vec<Vec<char>> {
206 let mut lines = vec![Vec::new()];
207 let mut word = Vec::new();
208 let mut row = 0;
209 let mut spaces = 0;
210
211 for &r in runes {
213 if r.is_whitespace() {
214 spaces += 1;
215 } else {
216 word.push(r);
217 }
218
219 if spaces > 0 {
220 let current_line_width = self.line_width(&lines[row]);
221 let word_width = self.line_width(&word);
222
223 if current_line_width + word_width + spaces > width {
224 row += 1;
225 lines.push(Vec::new());
226 lines[row].extend_from_slice(&word);
227 lines[row].extend(std::iter::repeat_n(' ', spaces));
228 spaces = 0;
229 word.clear();
230 } else {
231 lines[row].extend_from_slice(&word);
232 lines[row].extend(std::iter::repeat_n(' ', spaces));
233 spaces = 0;
234 word.clear();
235 }
236 } else {
237 let last_char_width = word
239 .last()
240 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
241 .unwrap_or(0);
242 let word_width = self.line_width(&word);
243
244 if word_width + last_char_width > width {
245 if !lines[row].is_empty() {
247 row += 1;
248 lines.push(Vec::new());
249 }
250 lines[row].extend_from_slice(&word);
251 word.clear();
252 }
253 }
254 }
255
256 let current_line_width = self.line_width(&lines[row]);
258 let word_width = self.line_width(&word);
259
260 if current_line_width + word_width + spaces >= width {
261 lines.push(Vec::new());
262 lines[row + 1].extend_from_slice(&word);
263 spaces += 1;
265 lines[row + 1].extend(std::iter::repeat_n(' ', spaces));
266 } else {
267 lines[row].extend_from_slice(&word);
268 spaces += 1;
269 lines[row].extend(std::iter::repeat_n(' ', spaces));
270 }
271
272 lines
273 }
274
275 fn line_width(&self, line: &[char]) -> usize {
277 line.iter()
278 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
279 .sum()
280 }
281
282 pub fn clear_cache(&mut self) {
284 self.cache.clear();
285 }
286
287 pub fn capacity(&self) -> usize {
289 10000 }
291
292 pub fn size(&self) -> usize {
294 self.cache.len()
295 }
296}
297
298impl Default for MemoizedWrap {
299 fn default() -> Self {
300 Self::new()
301 }
302}
303
304impl Clone for MemoizedWrap {
305 fn clone(&self) -> Self {
306 Self::with_capacity(self.capacity())
308 }
309}