#![allow(dead_code)]
use crate::position_memory::NotePosition;
const MAX_HISTORY_SIZE: usize = 100;
#[derive(Debug, Clone)]
pub struct HistoryEntry {
pub note_name: String,
pub position: NotePosition,
}
impl HistoryEntry {
pub fn new(note_name: String, position: NotePosition) -> Self {
HistoryEntry {
note_name,
position,
}
}
}
#[derive(Debug)]
pub struct History {
entries: Vec<HistoryEntry>,
current_index: Option<usize>,
}
impl History {
pub fn new() -> Self {
History {
entries: Vec::new(),
current_index: None,
}
}
pub fn push(&mut self, note_name: String, position: NotePosition) {
if let Some(idx) = self.current_index {
self.entries.truncate(idx + 1);
}
self.entries.push(HistoryEntry::new(note_name, position));
if self.entries.len() > MAX_HISTORY_SIZE {
self.entries.remove(0);
}
self.current_index = Some(self.entries.len() - 1);
}
pub fn rename_note(&mut self, old: &str, new: &str) {
for entry in &mut self.entries {
if entry.note_name == old {
entry.note_name = new.to_string();
}
}
}
pub fn remove_note(&mut self, note: &str) {
let old_current = self.current_index.unwrap_or(0);
let mut new_current: Option<usize> = None;
let mut kept: Vec<HistoryEntry> = Vec::with_capacity(self.entries.len());
for (i, entry) in std::mem::take(&mut self.entries).into_iter().enumerate() {
if entry.note_name == note {
continue;
}
kept.push(entry);
if i <= old_current {
new_current = Some(kept.len() - 1);
}
}
self.current_index = (!kept.is_empty()).then(|| new_current.unwrap_or(0));
self.entries = kept;
}
pub fn update_position(&mut self, position: NotePosition) {
if let Some(idx) = self.current_index
&& let Some(entry) = self.entries.get_mut(idx)
{
entry.position = position;
}
}
pub fn can_go_back(&self) -> bool {
if let Some(idx) = self.current_index {
idx > 0
} else {
false
}
}
pub fn can_go_forward(&self) -> bool {
if let Some(idx) = self.current_index {
idx < self.entries.len() - 1
} else {
false
}
}
pub fn go_back(&mut self) -> Option<&HistoryEntry> {
if let Some(idx) = self.current_index
&& idx > 0
{
self.current_index = Some(idx - 1);
return self.entries.get(idx - 1);
}
None
}
pub fn go_forward(&mut self) -> Option<&HistoryEntry> {
if let Some(idx) = self.current_index
&& idx < self.entries.len() - 1
{
self.current_index = Some(idx + 1);
return self.entries.get(idx + 1);
}
None
}
pub fn current(&self) -> Option<&HistoryEntry> {
if let Some(idx) = self.current_index {
self.entries.get(idx)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rutle::tree_path::DocumentPosition;
fn scroll(n: i32) -> NotePosition {
NotePosition {
scroll: n,
cursor: None,
}
}
#[test]
fn test_push_and_navigate() {
let mut history = History::new();
history.push("note1".to_string(), scroll(0));
history.push("note2".to_string(), scroll(10));
history.push("note3".to_string(), scroll(20));
assert_eq!(history.current().unwrap().note_name, "note3");
assert!(history.can_go_back());
assert!(!history.can_go_forward());
history.go_back();
assert_eq!(history.current().unwrap().note_name, "note2");
assert_eq!(history.current().unwrap().position.scroll, 10);
assert!(history.can_go_back());
assert!(history.can_go_forward());
history.go_forward();
assert_eq!(history.current().unwrap().note_name, "note3");
}
#[test]
fn test_push_clears_forward_history() {
let mut history = History::new();
history.push("note1".to_string(), scroll(0));
history.push("note2".to_string(), scroll(0));
history.push("note3".to_string(), scroll(0));
history.go_back();
history.go_back();
assert_eq!(history.current().unwrap().note_name, "note1");
history.push("note4".to_string(), scroll(0));
assert_eq!(history.current().unwrap().note_name, "note4");
assert!(!history.can_go_forward());
}
#[test]
fn test_max_size() {
let mut history = History::new();
for i in 0..150 {
history.push(format!("note{}", i), scroll(i));
}
assert_eq!(history.entries.len(), MAX_HISTORY_SIZE);
assert_eq!(history.current().unwrap().note_name, "note149");
}
#[test]
fn test_rename_note_updates_all_matching_entries() {
let mut history = History::new();
history.push("untitled_x".to_string(), scroll(0));
history.push("other".to_string(), scroll(0));
history.push("untitled_x".to_string(), scroll(0));
history.rename_note("untitled_x", "real-name");
assert_eq!(history.current().unwrap().note_name, "real-name");
history.go_back();
assert_eq!(history.current().unwrap().note_name, "other");
history.go_back();
assert_eq!(history.current().unwrap().note_name, "real-name");
}
#[test]
fn test_remove_note_drops_entries_and_tracks_current() {
let mut history = History::new();
history.push("a".to_string(), scroll(0));
history.push("b".to_string(), scroll(0));
history.push("c".to_string(), scroll(0));
history.remove_note("b");
assert_eq!(history.current().unwrap().note_name, "c");
history.go_back();
assert_eq!(history.current().unwrap().note_name, "a");
assert!(!history.can_go_back());
}
#[test]
fn test_remove_note_when_current_is_removed() {
let mut history = History::new();
history.push("a".to_string(), scroll(0));
history.push("b".to_string(), scroll(0));
history.push("c".to_string(), scroll(0));
history.go_back();
history.remove_note("b");
assert_eq!(history.current().unwrap().note_name, "a");
history.go_forward();
assert_eq!(history.current().unwrap().note_name, "c");
}
#[test]
fn test_remove_note_all_entries_leaves_empty() {
let mut history = History::new();
history.push("only".to_string(), scroll(0));
history.remove_note("only");
assert!(history.current().is_none());
assert!(!history.can_go_back());
assert!(!history.can_go_forward());
}
#[test]
fn test_update_position() {
let mut history = History::new();
history.push("note1".to_string(), scroll(0));
assert_eq!(history.current().unwrap().position.scroll, 0);
let updated = NotePosition {
scroll: 42,
cursor: Some(DocumentPosition::new(1, 3)),
};
history.update_position(updated.clone());
assert_eq!(history.current().unwrap().position, updated);
}
}