#![cfg_attr(not(feature = "history"), allow(unused_variables))]
#[cfg(not(feature = "history"))]
use core::marker::PhantomData;
#[derive(Debug)]
pub struct CommandHistory<const N: usize, const INPUT_SIZE: usize> {
#[cfg(feature = "history")]
buffer: heapless::Vec<heapless::String<INPUT_SIZE>, N>,
#[cfg(feature = "history")]
position: Option<usize>,
#[cfg(not(feature = "history"))]
_phantom: PhantomData<[u8; INPUT_SIZE]>,
}
impl<const N: usize, const INPUT_SIZE: usize> CommandHistory<N, INPUT_SIZE> {
#[cfg(feature = "history")]
pub fn new() -> Self {
Self {
buffer: heapless::Vec::new(),
position: None,
}
}
#[cfg(not(feature = "history"))]
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
#[cfg(feature = "history")]
pub fn add(&mut self, cmd: &str) {
if cmd.is_empty() {
return;
}
self.position = None;
if let Some(last) = self.buffer.last()
&& last.as_str() == cmd
{
return;
}
let mut entry = heapless::String::new();
if entry.push_str(cmd).is_ok() {
if self.buffer.is_full() {
self.buffer.remove(0);
}
let _ = self.buffer.push(entry);
}
}
#[cfg(not(feature = "history"))]
pub fn add(&mut self, _cmd: &str) {
}
#[cfg(feature = "history")]
pub fn previous_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
if self.buffer.is_empty() {
return None;
}
let pos = match self.position {
None => self.buffer.len() - 1,
Some(0) => 0, Some(p) => p - 1,
};
self.position = Some(pos);
self.buffer.get(pos).cloned()
}
#[cfg(not(feature = "history"))]
pub fn previous_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
None
}
#[cfg(feature = "history")]
pub fn next_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
match self.position {
None => None, Some(p) if p >= self.buffer.len() - 1 => {
self.position = None;
Some(heapless::String::new()) }
Some(p) => {
let pos = p + 1;
self.position = Some(pos);
self.buffer.get(pos).cloned()
}
}
}
#[cfg(not(feature = "history"))]
pub fn next_command(&mut self) -> Option<heapless::String<INPUT_SIZE>> {
None
}
#[cfg(feature = "history")]
pub fn reset_position(&mut self) {
self.position = None;
}
#[cfg(not(feature = "history"))]
pub fn reset_position(&mut self) {
}
}
impl<const N: usize, const INPUT_SIZE: usize> Default for CommandHistory<N, INPUT_SIZE> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "history")]
fn test_add_and_navigate() {
let mut history = CommandHistory::<5, 128>::new();
history.add("cmd1");
history.add("cmd2");
history.add("cmd3");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
assert_eq!(history.next_command().unwrap().as_str(), "cmd2");
assert_eq!(history.next_command().unwrap().as_str(), "cmd3");
assert_eq!(history.next_command().unwrap().as_str(), "");
}
#[test]
#[cfg(not(feature = "history"))]
fn test_stub_behavior() {
let mut history = CommandHistory::<5, 128>::new();
history.add("cmd1");
assert!(history.previous_command().is_none());
assert!(history.next_command().is_none());
}
#[test]
#[cfg(feature = "history")]
fn test_ring_buffer_behavior() {
let mut history = CommandHistory::<3, 128>::new();
history.add("cmd1");
history.add("cmd2");
history.add("cmd3");
history.add("cmd4");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd4");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd2"); }
#[test]
#[cfg(feature = "history")]
fn test_empty_commands_ignored() {
let mut history = CommandHistory::<5, 128>::new();
history.add("");
history.add("cmd1");
history.add("");
history.add("cmd2");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd1"); }
#[test]
#[cfg(feature = "history")]
fn test_duplicate_commands_ignored() {
let mut history = CommandHistory::<5, 128>::new();
history.add("cmd1");
history.add("cmd1"); history.add("cmd2");
history.add("cmd2"); history.add("cmd1");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd2");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd1");
}
#[test]
#[cfg(feature = "history")]
fn test_navigation_without_adding() {
let mut history = CommandHistory::<5, 128>::new();
assert!(history.previous_command().is_none());
assert!(history.next_command().is_none());
}
#[test]
#[cfg(feature = "history")]
fn test_reset_position() {
let mut history = CommandHistory::<5, 128>::new();
history.add("cmd1");
history.add("cmd2");
history.add("cmd3");
history.previous_command();
history.previous_command();
history.reset_position();
assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
}
#[test]
#[cfg(feature = "history")]
fn test_position_resets_on_add() {
let mut history = CommandHistory::<5, 128>::new();
history.add("cmd1");
history.add("cmd2");
history.previous_command();
history.add("cmd3");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd3");
}
#[test]
#[cfg(feature = "history")]
fn test_position_resets_on_duplicate_add() {
let mut history = CommandHistory::<5, 128>::new();
history.add("cmd_a");
history.add("cmd_b");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd_b");
history.add("cmd_b");
assert_eq!(history.previous_command().unwrap().as_str(), "cmd_b");
}
#[test]
#[cfg(feature = "history")]
fn test_default() {
let history = CommandHistory::<5, 128>::default();
let mut history2 = history;
assert!(history2.previous_command().is_none());
}
#[test]
#[cfg(not(feature = "history"))]
fn test_stub_reset_position() {
let mut history = CommandHistory::<5, 128>::new();
history.reset_position(); }
}