pub struct History<T: Clone> {
pub past: Vec<T>,
pub present: T,
pub future: Vec<T>,
pub latest_unfiltered: T,
}
impl<T: Clone> History<T> {
pub fn new(
past: Vec<T>,
present: T,
future: Vec<T>,
) -> Self {
let latest_unfiltered = present.clone();
History { past, present, future, latest_unfiltered }
}
}
use crate::config::HistoryConfig;
pub struct HistoryManager<T: Clone> {
config: HistoryConfig,
history: History<T>,
}
impl<T: Clone> HistoryManager<T> {
pub fn new(
initial_state: T,
history_limit: Option<usize>,
) -> Self {
let mut config = HistoryConfig::default();
if let Some(limit) = history_limit {
config.max_entries = limit;
}
Self::with_config(initial_state, config)
}
pub fn with_config(
initial_state: T,
config: HistoryConfig,
) -> Self {
HistoryManager {
config,
history: History::new(Vec::new(), initial_state, Vec::new()),
}
}
pub fn get_present(&self) -> T {
self.history.present.clone()
}
pub fn get_history(&self) -> &History<T> {
&self.history
}
pub fn get_history_length(&self) -> usize {
self.history.past.len() + self.history.future.len() + 1
}
pub fn get_config(&self) -> &HistoryConfig {
&self.config
}
pub fn update_config(
&mut self,
config: HistoryConfig,
) {
self.config = config;
}
pub fn insert(
&mut self,
state: T,
) {
let past = &self.history.past;
let length = past.len() + 1;
let past_sliced = if length >= self.config.max_entries {
if past.is_empty() { Vec::new() } else { past[1..].to_vec() }
} else {
past.clone()
};
let mut new_past = past_sliced;
new_past.push(self.history.latest_unfiltered.clone());
self.history = History::new(new_past, state.clone(), Vec::new());
self.history.latest_unfiltered = state;
}
pub fn jump_to_future(
&mut self,
index: usize,
) {
if index >= self.history.future.len() {
return;
}
let mut new_past = self.history.past.clone();
new_past.push(self.history.latest_unfiltered.clone());
if index > 0 {
new_past.extend_from_slice(&self.history.future[..index]);
}
let new_present = self.history.future[index].clone();
let new_future = if index + 1 < self.history.future.len() {
self.history.future[index + 1..].to_vec()
} else {
Vec::new()
};
self.history = History::new(new_past, new_present, new_future);
}
pub fn jump_to_past(
&mut self,
index: usize,
) {
if index >= self.history.past.len() {
return;
}
let new_past = if index > 0 {
self.history.past[..index].to_vec()
} else {
Vec::new()
};
let mut new_future = Vec::new();
if index + 1 < self.history.past.len() {
new_future.extend_from_slice(&self.history.past[index + 1..]);
}
new_future.push(self.history.latest_unfiltered.clone());
new_future.extend_from_slice(&self.history.future);
let new_present = self.history.past[index].clone();
self.history = History::new(new_past, new_present, new_future);
}
pub fn jump(
&mut self,
n: isize,
) {
match n.cmp(&0) {
std::cmp::Ordering::Less => {
let past_len = self.history.past.len() as isize;
let target = past_len + n;
if target >= 0 && (target as usize) < self.history.past.len() {
self.jump_to_past(target as usize);
}
},
std::cmp::Ordering::Equal => {
},
std::cmp::Ordering::Greater => {
let future_index = (n - 1) as usize;
if future_index < self.history.future.len() {
self.jump_to_future(future_index);
}
},
}
}
pub fn clear_history(&mut self) {
let present = self.history.present.clone();
self.history = History::new(Vec::new(), present, Vec::new());
}
pub fn get_past_state(
&self,
index: usize,
) -> Option<&T> {
self.history.past.get(index)
}
pub fn get_future_state(
&self,
index: usize,
) -> Option<&T> {
self.history.future.get(index)
}
pub fn can_undo(&self) -> bool {
!self.history.past.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.history.future.is_empty()
}
pub fn past_count(&self) -> usize {
self.history.past.len()
}
pub fn future_count(&self) -> usize {
self.history.future.len()
}
pub fn validate_integrity(&self) -> bool {
if self.config.max_entries == 0 {
return false;
}
let total_length =
self.history.past.len() + 1 + self.history.future.len();
if total_length > self.config.max_entries {
return false;
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_insert_with_limit() {
let mut manager = HistoryManager::with_config(
"initial".to_string(),
HistoryConfig { max_entries: 3, ..Default::default() },
);
manager.insert("state1".to_string());
manager.insert("state2".to_string());
manager.insert("state3".to_string());
assert_eq!(manager.past_count(), 2); assert_eq!(manager.get_present(), "state3");
assert!(manager.validate_integrity());
}
#[test]
fn test_jump_boundary_checks() {
let mut manager = HistoryManager::new("initial".to_string(), Some(10));
manager.insert("state1".to_string());
manager.insert("state2".to_string());
manager.jump_to_past(0); assert_eq!(manager.get_present(), "initial");
manager.jump_to_past(100); assert_eq!(manager.get_present(), "initial");
manager.jump_to_future(0); assert_eq!(manager.get_present(), "state1");
manager.jump_to_future(100); assert_eq!(manager.get_present(), "state1");
}
#[test]
fn test_safe_access_methods() {
let mut manager = HistoryManager::new("initial".to_string(), Some(10));
manager.insert("state1".to_string());
manager.insert("state2".to_string());
assert_eq!(manager.get_past_state(0), Some(&"initial".to_string()));
assert_eq!(manager.get_past_state(100), None);
manager.jump_to_past(0);
assert_eq!(manager.get_future_state(0), Some(&"state1".to_string()));
assert_eq!(manager.get_future_state(100), None);
}
#[test]
fn test_can_undo_redo() {
let mut manager = HistoryManager::new("initial".to_string(), Some(10));
assert!(!manager.can_undo());
assert!(!manager.can_redo());
manager.insert("state1".to_string());
assert!(manager.can_undo());
assert!(!manager.can_redo());
manager.jump_to_past(0);
assert!(!manager.can_undo());
assert!(manager.can_redo());
}
#[test]
fn test_jump_with_bounds() {
let mut manager = HistoryManager::new("initial".to_string(), Some(10));
manager.insert("state1".to_string());
manager.insert("state2".to_string());
manager.jump(-10); assert_eq!(manager.get_present(), "state2");
manager.jump(-1); assert_eq!(manager.get_present(), "state1");
manager.jump(10); assert_eq!(manager.get_present(), "state1");
manager.jump(1); assert_eq!(manager.get_present(), "state2");
}
}