nut_shell/shell/
history.rs1#![cfg_attr(not(feature = "history"), allow(unused_variables))]
6
7#[cfg(not(feature = "history"))]
8use core::marker::PhantomData;
9
10#[derive(Debug)]
12pub struct CommandHistory<const N: usize, const INPUT_SIZE: usize> {
13 #[cfg(feature = "history")]
14 buffer: heapless::Vec<heapless::String<INPUT_SIZE>, N>,
15
16 #[cfg(feature = "history")]
17 position: Option<usize>,
18
19 #[cfg(not(feature = "history"))]
20 _phantom: PhantomData<[u8; INPUT_SIZE]>,
21}
22
23impl<const N: usize, const INPUT_SIZE: usize> CommandHistory<N, INPUT_SIZE> {
24 #[cfg(feature = "history")]
26 pub fn new() -> Self {
27 Self {
28 buffer: heapless::Vec::new(),
29 position: None,
30 }
31 }
32
33 #[cfg(not(feature = "history"))]
35 pub fn new() -> Self {
36 Self {
37 _phantom: PhantomData,
38 }
39 }
40
41 #[cfg(feature = "history")]
43 pub fn add(&mut self, cmd: &str) {
44 if cmd.is_empty() {
46 return;
47 }
48
49 self.position = None;
51
52 if let Some(last) = self.buffer.last()
54 && last.as_str() == cmd
55 {
56 return;
57 }
58
59 let mut entry = heapless::String::new();
60 if entry.push_str(cmd).is_ok() {
61 if self.buffer.is_full() {
63 self.buffer.remove(0);
64 }
65 let _ = self.buffer.push(entry);
66 }
67 }
68
69 #[cfg(not(feature = "history"))]
71 pub fn add(&mut self, _cmd: &str) {
72 }
74
75 #[cfg(feature = "history")]
77 pub fn previous_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
78 if self.buffer.is_empty() {
79 return None;
80 }
81
82 let pos = match self.position {
83 None => self.buffer.len() - 1,
84 Some(0) => 0, Some(p) => p - 1,
86 };
87
88 self.position = Some(pos);
89 self.buffer.get(pos).cloned()
90 }
91
92 #[cfg(not(feature = "history"))]
94 pub fn previous_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
95 None
96 }
97
98 #[cfg(feature = "history")]
100 pub fn next_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
101 match self.position {
102 None => None, Some(p) if p >= self.buffer.len() - 1 => {
104 self.position = None;
106 Some(heapless::String::new()) }
108 Some(p) => {
109 let pos = p + 1;
110 self.position = Some(pos);
111 self.buffer.get(pos).cloned()
112 }
113 }
114 }
115
116 #[cfg(not(feature = "history"))]
118 pub fn next_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
119 None
120 }
121
122 #[cfg(feature = "history")]
124 pub fn reset_position(&mut self) {
125 self.position = None;
126 }
127
128 #[cfg(not(feature = "history"))]
130 pub fn reset_position(&mut self) {
131 }
133}
134
135impl<const N: usize, const INPUT_SIZE: usize> Default for CommandHistory<N, INPUT_SIZE> {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 #[cfg(feature = "history")]
147 fn test_add_and_navigate() {
148 let mut history = CommandHistory::<5, 128>::new();
149
150 history.add("cmd1");
151 history.add("cmd2");
152 history.add("cmd3");
153
154 assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
156 assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
157 assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
158
159 assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
161
162 assert_eq!(history.next_command().unwrap().as_str(), "cmd2");
164 assert_eq!(history.next_command().unwrap().as_str(), "cmd3");
165
166 assert_eq!(history.next_command().unwrap().as_str(), "");
168 }
169
170 #[test]
171 #[cfg(not(feature = "history"))]
172 fn test_stub_behavior() {
173 let mut history = CommandHistory::<5, 128>::new();
174
175 history.add("cmd1");
176 assert!(history.previous_command().is_none());
177 assert!(history.next_command().is_none());
178 }
179
180 #[test]
181 #[cfg(feature = "history")]
182 fn test_ring_buffer_behavior() {
183 let mut history = CommandHistory::<3, 128>::new();
184
185 history.add("cmd1");
187 history.add("cmd2");
188 history.add("cmd3");
189
190 history.add("cmd4");
192
193 assert_eq!(history.previous_command().unwrap().as_str(), "cmd4");
195 assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
196 assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
197
198 assert_eq!(history.previous_command().unwrap().as_str(), "cmd2"); }
201
202 #[test]
203 #[cfg(feature = "history")]
204 fn test_empty_commands_ignored() {
205 let mut history = CommandHistory::<5, 128>::new();
206
207 history.add("");
208 history.add("cmd1");
209 history.add("");
210 history.add("cmd2");
211
212 assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
214 assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
215 assert_eq!(history.previous_command().unwrap().as_str(), "cmd1"); }
217
218 #[test]
219 #[cfg(feature = "history")]
220 fn test_duplicate_commands_ignored() {
221 let mut history = CommandHistory::<5, 128>::new();
222
223 history.add("cmd1");
224 history.add("cmd1"); history.add("cmd2");
226 history.add("cmd2"); history.add("cmd1"); assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
231 assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
232 assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
233 }
234
235 #[test]
236 #[cfg(feature = "history")]
237 fn test_navigation_without_adding() {
238 let mut history = CommandHistory::<5, 128>::new();
239
240 assert!(history.previous_command().is_none());
242 assert!(history.next_command().is_none());
243 }
244
245 #[test]
246 #[cfg(feature = "history")]
247 fn test_reset_position() {
248 let mut history = CommandHistory::<5, 128>::new();
249
250 history.add("cmd1");
251 history.add("cmd2");
252 history.add("cmd3");
253
254 history.previous_command();
256 history.previous_command();
257
258 history.reset_position();
260
261 assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
263 }
264
265 #[test]
266 #[cfg(feature = "history")]
267 fn test_position_resets_on_add() {
268 let mut history = CommandHistory::<5, 128>::new();
269
270 history.add("cmd1");
271 history.add("cmd2");
272
273 history.previous_command();
275
276 history.add("cmd3");
278
279 assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
281 }
282
283 #[test]
284 #[cfg(feature = "history")]
285 fn test_position_resets_on_duplicate_add() {
286 let mut history = CommandHistory::<5, 128>::new();
287
288 history.add("cmd_a");
289 history.add("cmd_b");
290
291 assert_eq!(history.previous_command().unwrap().as_str(), "cmd_b");
293
294 history.add("cmd_b");
296
297 assert_eq!(history.previous_command().unwrap().as_str(), "cmd_b");
299 }
300
301 #[test]
302 #[cfg(feature = "history")]
303 fn test_default() {
304 let history = CommandHistory::<5, 128>::default();
305 let mut history2 = history;
306 assert!(history2.previous_command().is_none());
307 }
308
309 #[test]
310 #[cfg(not(feature = "history"))]
311 fn test_stub_reset_position() {
312 let mut history = CommandHistory::<5, 128>::new();
313 history.reset_position(); }
315}