history.rs
1 use heapless::{String, Vec}; 2 3 /// Configuration for command history 4 #[derive(Clone, Copy)] 5 pub struct HistoryConfig { 6 /// Maximum number of history entries 7 pub max_entries: usize, 8 /// Whether to deduplicate consecutive identical commands 9 pub deduplicate: bool, 10 } 11 12 impl Default for HistoryConfig { 13 fn default() -> Self { 14 Self { 15 max_entries: 10, 16 deduplicate: true, 17 } 18 } 19 } 20 21 /// Command history manager 22 pub struct History<const BUF_SIZE: usize> { 23 entries: Vec<String<BUF_SIZE>, 16>, 24 config: HistoryConfig, 25 current_index: Option<usize>, 26 } 27 28 impl<const BUF_SIZE: usize> History<BUF_SIZE> { 29 /// Create a new history manager 30 pub fn new(config: HistoryConfig) -> Self { 31 Self { 32 entries: Vec::new(), 33 config, 34 current_index: None, 35 } 36 } 37 38 /// Add a command to history 39 pub fn add(&mut self, command: &str) -> Result<(), ()> { 40 // Skip empty commands 41 if command.trim().is_empty() { 42 return Ok(()); 43 } 44 45 // Check for deduplication 46 if self.config.deduplicate { 47 if let Some(last) = self.entries.last() { 48 if last.as_str() == command { 49 return Ok(()); 50 } 51 } 52 } 53 54 let entry = String::try_from(command).map_err(|_| ())?; 55 56 // If at capacity, remove oldest 57 if self.entries.len() >= self.config.max_entries { 58 self.entries.remove(0); 59 } 60 61 self.entries.push(entry).map_err(|_| ())?; 62 self.current_index = None; 63 Ok(()) 64 } 65 66 /// Get the previous command in history 67 pub fn previous(&mut self) -> Option<&str> { 68 if self.entries.is_empty() { 69 return None; 70 } 71 72 let new_index = match self.current_index { 73 None => self.entries.len() - 1, 74 Some(0) => return Some(&self.entries[0]), 75 Some(i) => i - 1, 76 }; 77 78 self.current_index = Some(new_index); 79 Some(&self.entries[new_index]) 80 } 81 82 /// Get the next command in history 83 pub fn next(&mut self) -> Option<&str> { 84 match self.current_index { 85 None => None, 86 Some(i) if i >= self.entries.len() - 1 => { 87 self.current_index = None; 88 None 89 } 90 Some(i) => { 91 self.current_index = Some(i + 1); 92 Some(&self.entries[i + 1]) 93 } 94 } 95 } 96 97 /// Reset the history navigation position 98 pub fn reset_position(&mut self) { 99 self.current_index = None; 100 } 101 102 /// Get the number of entries in history 103 pub fn len(&self) -> usize { 104 self.entries.len() 105 } 106 107 /// Check if history is empty 108 pub fn is_empty(&self) -> bool { 109 self.entries.is_empty() 110 } 111 112 /// Clear all history 113 pub fn clear(&mut self) { 114 self.entries.clear(); 115 self.current_index = None; 116 } 117 118 /// Get an iterator over history entries (oldest to newest) 119 pub fn iter(&self) -> impl Iterator<Item = &str> { 120 self.entries.iter().map(|s| s.as_str()) 121 } 122 123 /// Get an iterator over history entries in reverse (newest to oldest) 124 pub fn iter_rev(&self) -> impl Iterator<Item = &str> { 125 self.entries.iter().rev().map(|s| s.as_str()) 126 } 127 } 128 129 #[cfg(test)] 130 mod tests { 131 use super::*; 132 133 #[test] 134 fn test_history_add() { 135 let mut history = History::<64>::new(HistoryConfig::default()); 136 history.add("command1").unwrap(); 137 history.add("command2").unwrap(); 138 assert_eq!(history.len(), 2); 139 } 140 141 #[test] 142 fn test_history_deduplicate() { 143 let mut history = History::<64>::new(HistoryConfig { 144 deduplicate: true, 145 ..Default::default() 146 }); 147 history.add("command1").unwrap(); 148 history.add("command1").unwrap(); 149 assert_eq!(history.len(), 1); 150 } 151 152 #[test] 153 fn test_history_navigation() { 154 let mut history = History::<64>::new(HistoryConfig::default()); 155 history.add("cmd1").unwrap(); 156 history.add("cmd2").unwrap(); 157 history.add("cmd3").unwrap(); 158 159 assert_eq!(history.previous(), Some("cmd3")); 160 assert_eq!(history.previous(), Some("cmd2")); 161 assert_eq!(history.next(), Some("cmd3")); 162 assert_eq!(history.next(), None); 163 } 164 }