use crate::model::event::BufferId;
#[derive(Clone, Debug, PartialEq)]
pub struct PositionEntry {
pub buffer_id: BufferId,
pub position: usize,
pub anchor: Option<usize>,
}
impl PositionEntry {
pub fn new(buffer_id: BufferId, position: usize, anchor: Option<usize>) -> Self {
Self {
buffer_id,
position,
anchor,
}
}
}
#[derive(Clone, Debug)]
struct PendingMovement {
start_entry: PositionEntry,
}
const LARGE_JUMP_THRESHOLD: usize = 50;
pub struct PositionHistory {
entries: Vec<PositionEntry>,
current_index: Option<usize>,
max_entries: usize,
pending_movement: Option<PendingMovement>,
}
impl PositionHistory {
pub fn new() -> Self {
Self::with_capacity(100)
}
pub fn with_capacity(max_entries: usize) -> Self {
Self {
entries: Vec::new(),
current_index: None,
max_entries,
pending_movement: None,
}
}
pub fn record_movement(&mut self, buffer_id: BufferId, position: usize, anchor: Option<usize>) {
let entry = PositionEntry::new(buffer_id, position, anchor);
if let Some(pending) = &mut self.pending_movement {
if pending.start_entry.buffer_id == buffer_id {
let distance = position.abs_diff(pending.start_entry.position);
if distance <= LARGE_JUMP_THRESHOLD {
return;
}
}
self.commit_pending_movement();
}
self.pending_movement = Some(PendingMovement { start_entry: entry });
}
pub fn commit_pending_movement(&mut self) {
if let Some(pending) = self.pending_movement.take() {
self.push(pending.start_entry);
}
}
pub fn push(&mut self, entry: PositionEntry) {
if let Some(current_idx) = self.current_index {
self.entries.truncate(current_idx + 1);
}
if let Some(current_idx) = self.current_index {
if current_idx < self.entries.len() && self.entries[current_idx] == entry {
return;
}
}
self.entries.push(entry);
if self.entries.len() > self.max_entries {
self.entries.remove(0);
}
self.current_index = Some(self.entries.len() - 1);
}
pub fn back(&mut self) -> Option<&PositionEntry> {
self.commit_pending_movement();
if self.entries.is_empty() {
return None;
}
match self.current_index {
None => None,
Some(0) => None, Some(idx) => {
self.current_index = Some(idx - 1);
Some(&self.entries[idx - 1])
}
}
}
pub fn forward(&mut self) -> Option<&PositionEntry> {
if self.entries.is_empty() {
return None;
}
match self.current_index {
None => None,
Some(idx) if idx >= self.entries.len() - 1 => None, Some(idx) => {
self.current_index = Some(idx + 1);
Some(&self.entries[idx + 1])
}
}
}
pub fn can_go_back(&self) -> bool {
match self.current_index {
Some(idx) => idx > 0,
None => false,
}
}
pub fn can_go_forward(&self) -> bool {
match self.current_index {
Some(idx) => idx < self.entries.len() - 1,
None => false,
}
}
pub fn current(&self) -> Option<&PositionEntry> {
self.current_index.and_then(|idx| self.entries.get(idx))
}
pub fn clear(&mut self) {
self.entries.clear();
self.current_index = None;
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn current_index(&self) -> Option<usize> {
self.current_index
}
}
impl Default for PositionHistory {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_entry(buffer_id: usize, position: usize) -> PositionEntry {
PositionEntry::new(BufferId(buffer_id), position, None)
}
#[test]
fn test_new_history_is_empty() {
let history = PositionHistory::new();
assert!(history.is_empty());
assert_eq!(history.len(), 0);
assert!(!history.can_go_back());
assert!(!history.can_go_forward());
}
#[test]
fn test_push_single_entry() {
let mut history = PositionHistory::new();
let entry = make_entry(1, 10);
history.push(entry.clone());
assert_eq!(history.len(), 1);
assert_eq!(history.current(), Some(&entry));
assert!(!history.can_go_back());
assert!(!history.can_go_forward());
}
#[test]
fn test_push_multiple_entries() {
let mut history = PositionHistory::new();
let entry1 = make_entry(1, 10);
let entry2 = make_entry(1, 20);
let entry3 = make_entry(2, 5);
history.push(entry1.clone());
history.push(entry2.clone());
history.push(entry3.clone());
assert_eq!(history.len(), 3);
assert_eq!(history.current(), Some(&entry3));
assert!(history.can_go_back());
assert!(!history.can_go_forward());
}
#[test]
fn test_back_navigation() {
let mut history = PositionHistory::new();
let entry1 = make_entry(1, 10);
let entry2 = make_entry(1, 20);
let entry3 = make_entry(2, 5);
history.push(entry1.clone());
history.push(entry2.clone());
history.push(entry3.clone());
let back1 = history.back();
assert_eq!(back1, Some(&entry2));
assert_eq!(history.current(), Some(&entry2));
assert!(history.can_go_back());
assert!(history.can_go_forward());
let back2 = history.back();
assert_eq!(back2, Some(&entry1));
assert_eq!(history.current(), Some(&entry1));
assert!(!history.can_go_back());
assert!(history.can_go_forward());
let back3 = history.back();
assert_eq!(back3, None);
assert_eq!(history.current(), Some(&entry1));
}
#[test]
fn test_forward_navigation() {
let mut history = PositionHistory::new();
let entry1 = make_entry(1, 10);
let entry2 = make_entry(1, 20);
let entry3 = make_entry(2, 5);
history.push(entry1.clone());
history.push(entry2.clone());
history.push(entry3.clone());
history.back();
history.back();
assert_eq!(history.current(), Some(&entry1));
let fwd1 = history.forward();
assert_eq!(fwd1, Some(&entry2));
assert_eq!(history.current(), Some(&entry2));
let fwd2 = history.forward();
assert_eq!(fwd2, Some(&entry3));
assert_eq!(history.current(), Some(&entry3));
let fwd3 = history.forward();
assert_eq!(fwd3, None);
assert_eq!(history.current(), Some(&entry3));
}
#[test]
fn test_push_truncates_forward_history() {
let mut history = PositionHistory::new();
let entry1 = make_entry(1, 10);
let entry2 = make_entry(1, 20);
let entry3 = make_entry(2, 5);
let entry4 = make_entry(2, 15);
history.push(entry1.clone());
history.push(entry2.clone());
history.push(entry3.clone());
history.back();
history.back();
assert_eq!(history.current(), Some(&entry1));
history.push(entry4.clone());
assert_eq!(history.len(), 2);
assert_eq!(history.current(), Some(&entry4));
assert!(history.can_go_back());
assert!(!history.can_go_forward());
let back = history.back();
assert_eq!(back, Some(&entry1));
}
#[test]
fn test_duplicate_consecutive_entries_not_added() {
let mut history = PositionHistory::new();
let entry1 = make_entry(1, 10);
history.push(entry1.clone());
history.push(entry1.clone());
history.push(entry1.clone());
assert_eq!(history.len(), 1);
}
#[test]
fn test_max_entries_limit() {
let mut history = PositionHistory::with_capacity(3);
for i in 0..5 {
history.push(make_entry(1, i * 10));
}
assert_eq!(history.len(), 3);
assert_eq!(history.current(), Some(&make_entry(1, 40)));
history.back();
assert_eq!(history.current(), Some(&make_entry(1, 30)));
history.back();
assert_eq!(history.current(), Some(&make_entry(1, 20)));
}
#[test]
fn test_clear() {
let mut history = PositionHistory::new();
history.push(make_entry(1, 10));
history.push(make_entry(1, 20));
assert_eq!(history.len(), 2);
history.clear();
assert!(history.is_empty());
assert_eq!(history.len(), 0);
assert_eq!(history.current(), None);
assert!(!history.can_go_back());
assert!(!history.can_go_forward());
}
}