datui_lib/widgets/
text_input_common.rs1use color_eyre::Result;
2use std::fs;
3use std::io::{BufRead, BufReader, Write};
4
5use crate::cache::CacheManager;
6
7pub fn load_history_impl(cache: &CacheManager, history_id: &str) -> Result<Vec<String>> {
10 let history_file = cache.cache_file(&format!("{}_history.txt", history_id));
11
12 if !history_file.exists() {
13 return Ok(Vec::new());
14 }
15
16 let file = fs::File::open(&history_file)?;
17 let reader = BufReader::new(file);
18 let mut history = Vec::new();
19
20 for line in reader.lines() {
21 let line = line?;
22 if !line.trim().is_empty() {
23 history.push(line);
24 }
25 }
26
27 Ok(history)
28}
29
30pub fn save_history_impl(
32 cache: &CacheManager,
33 history_id: &str,
34 history: &[String],
35 limit: usize,
36) -> Result<()> {
37 cache.ensure_cache_dir()?;
38 let history_file = cache.cache_file(&format!("{}_history.txt", history_id));
39
40 let mut file = fs::File::create(&history_file)?;
41
42 let start = history.len().saturating_sub(limit);
44 for entry in history.iter().skip(start) {
45 writeln!(file, "{}", entry)?;
46 }
47
48 Ok(())
49}
50
51pub fn add_to_history(history: &mut Vec<String>, entry: String) {
54 if let Some(last) = history.last() {
56 if last == &entry {
57 return; }
59 }
60 history.push(entry);
61}
62
63pub fn char_to_byte_pos(text: &str, char_pos: usize) -> usize {
65 text.chars().take(char_pos).map(|c| c.len_utf8()).sum()
66}
67
68pub fn byte_to_char_pos(text: &str, byte_pos: usize) -> usize {
70 let mut char_pos = 0;
71 let mut byte_count = 0;
72
73 for ch in text.chars() {
74 if byte_count >= byte_pos {
75 break;
76 }
77 byte_count += ch.len_utf8();
78 char_pos += 1;
79 }
80
81 char_pos
82}
83
84pub fn char_at(text: &str, char_pos: usize) -> Option<char> {
86 text.chars().nth(char_pos)
87}
88
89pub fn char_byte_range(text: &str, char_pos: usize) -> Option<(usize, usize)> {
91 let mut byte_start = 0;
92
93 for (char_count, ch) in text.chars().enumerate() {
94 if char_count == char_pos {
95 return Some((byte_start, byte_start + ch.len_utf8()));
96 }
97 byte_start += ch.len_utf8();
98 }
99
100 None
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_char_to_byte_pos() {
109 assert_eq!(char_to_byte_pos("hello", 0), 0);
110 assert_eq!(char_to_byte_pos("hello", 5), 5);
111 assert_eq!(char_to_byte_pos("café", 3), 3); assert_eq!(char_to_byte_pos("café", 4), 5);
113 assert_eq!(char_to_byte_pos("🚀", 0), 0);
114 assert_eq!(char_to_byte_pos("🚀", 1), 4); }
116
117 #[test]
118 fn test_byte_to_char_pos() {
119 assert_eq!(byte_to_char_pos("hello", 0), 0);
120 assert_eq!(byte_to_char_pos("hello", 5), 5);
121 assert_eq!(byte_to_char_pos("café", 3), 3);
122 assert_eq!(byte_to_char_pos("café", 5), 4);
123 assert_eq!(byte_to_char_pos("🚀", 0), 0);
124 assert_eq!(byte_to_char_pos("🚀", 4), 1);
125 }
126
127 #[test]
128 fn test_char_at() {
129 assert_eq!(char_at("hello", 0), Some('h'));
130 assert_eq!(char_at("hello", 4), Some('o'));
131 assert_eq!(char_at("café", 3), Some('é'));
132 assert_eq!(char_at("🚀", 0), Some('🚀'));
133 assert_eq!(char_at("hello", 10), None);
134 }
135
136 #[test]
137 fn test_char_byte_range() {
138 assert_eq!(char_byte_range("hello", 0), Some((0, 1)));
139 assert_eq!(char_byte_range("hello", 4), Some((4, 5)));
140 assert_eq!(char_byte_range("café", 3), Some((3, 5))); assert_eq!(char_byte_range("🚀", 0), Some((0, 4))); assert_eq!(char_byte_range("hello", 10), None);
143 }
144
145 #[test]
146 fn test_add_to_history() {
147 let mut history = Vec::new();
148
149 add_to_history(&mut history, "query1".to_string());
151 assert_eq!(history.len(), 1);
152
153 add_to_history(&mut history, "query2".to_string());
155 assert_eq!(history.len(), 2);
156
157 add_to_history(&mut history, "query2".to_string());
159 assert_eq!(history.len(), 2);
160
161 add_to_history(&mut history, "query1".to_string());
163 assert_eq!(history.len(), 3);
164 assert_eq!(history[0], "query1");
165 assert_eq!(history[1], "query2");
166 assert_eq!(history[2], "query1");
167 }
168}