mermaid_cli/tui/state/
input.rs1use std::collections::VecDeque;
6
7const INPUT_HISTORY_CAP: usize = 100;
11
12pub struct InputBuffer {
18 pub content: String,
20 pub cursor_position: usize,
22 pub history: VecDeque<String>,
24 pub history_index: Option<usize>,
26 pub history_buffer: String,
28}
29
30impl InputBuffer {
31 pub fn new() -> Self {
33 Self {
34 content: String::new(),
35 cursor_position: 0,
36 history: VecDeque::new(),
37 history_index: None,
38 history_buffer: String::new(),
39 }
40 }
41
42 pub fn load_history(&mut self, history: VecDeque<String>) {
44 self.history = history;
45 }
46
47 pub fn add_to_history(&mut self, input: String) {
54 if input.trim().is_empty() {
55 return;
56 }
57 if let Some(last) = self.history.back()
58 && last == &input
59 {
60 return;
61 }
62 if self.history.len() >= INPUT_HISTORY_CAP {
63 self.history.pop_front();
64 }
65 self.history.push_back(input);
66 }
67
68 pub fn clear(&mut self) {
70 self.content.clear();
71 self.cursor_position = 0;
72 }
73
74 pub fn is_empty(&self) -> bool {
76 self.content.is_empty()
77 }
78
79 pub fn get(&self) -> &str {
81 &self.content
82 }
83
84 pub fn set(&mut self, content: impl Into<String>) {
86 self.content = content.into();
87 self.cursor_position = self.content.len();
88 }
89
90 pub fn insert(&mut self, c: char) {
92 self.content.insert(self.cursor_position, c);
93 self.cursor_position += c.len_utf8();
94 }
95
96 pub fn insert_str(&mut self, s: &str) {
98 self.content.insert_str(self.cursor_position, s);
99 self.cursor_position += s.len();
100 }
101
102 pub fn backspace(&mut self) -> bool {
104 if self.cursor_position > 0 {
105 let prev_boundary = self.content[..self.cursor_position]
107 .char_indices()
108 .next_back()
109 .map(|(idx, _)| idx)
110 .unwrap_or(0);
111 self.content.remove(prev_boundary);
112 self.cursor_position = prev_boundary;
113 true
114 } else {
115 false
116 }
117 }
118
119 pub fn delete(&mut self) -> bool {
121 if self.cursor_position < self.content.len() {
122 self.content.remove(self.cursor_position);
123 true
124 } else {
125 false
126 }
127 }
128
129 pub fn move_left(&mut self) {
131 if self.cursor_position > 0 {
132 self.cursor_position = self.content[..self.cursor_position]
134 .char_indices()
135 .next_back()
136 .map(|(idx, _)| idx)
137 .unwrap_or(0);
138 }
139 }
140
141 pub fn move_right(&mut self) {
143 if self.cursor_position < self.content.len() {
144 self.cursor_position = self.content[self.cursor_position..]
146 .char_indices()
147 .nth(1)
148 .map(|(idx, _)| self.cursor_position + idx)
149 .unwrap_or(self.content.len());
150 }
151 }
152
153 pub fn move_home(&mut self) {
155 self.cursor_position = 0;
156 }
157
158 pub fn move_end(&mut self) {
160 self.cursor_position = self.content.len();
161 }
162}
163
164impl Default for InputBuffer {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn add_to_history_skips_empty_and_whitespace() {
176 let mut buf = InputBuffer::new();
177 buf.add_to_history(String::new());
178 buf.add_to_history(" ".into());
179 buf.add_to_history("\t\n".into());
180 assert!(buf.history.is_empty());
181 }
182
183 #[test]
184 fn add_to_history_dedups_consecutive() {
185 let mut buf = InputBuffer::new();
186 buf.add_to_history("hello".into());
187 buf.add_to_history("hello".into());
188 buf.add_to_history("world".into());
189 buf.add_to_history("hello".into()); assert_eq!(
191 buf.history.iter().cloned().collect::<Vec<_>>(),
192 vec!["hello", "world", "hello"]
193 );
194 }
195
196 #[test]
197 fn add_to_history_caps_at_limit() {
198 let mut buf = InputBuffer::new();
199 for i in 0..(INPUT_HISTORY_CAP + 25) {
200 buf.add_to_history(format!("msg{}", i));
201 }
202 assert_eq!(buf.history.len(), INPUT_HISTORY_CAP);
203 assert_eq!(buf.history.front().unwrap(), "msg25");
205 assert_eq!(
206 buf.history.back().unwrap(),
207 &format!("msg{}", INPUT_HISTORY_CAP + 24)
208 );
209 }
210}