use crate::id::OpId;
use crate::list::List;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CursorSide {
Before,
After,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Cursor {
Start,
End,
Anchored {
char_id: OpId,
side: CursorSide,
},
}
impl Cursor {
pub fn at<T: Clone>(list: &List<T>, pos: usize) -> Self {
let len = list.len();
if list.is_empty() && pos == 0 {
return Cursor::Start;
}
if pos >= len {
return Cursor::End;
}
let char_id = list.id_at(pos).expect("pos < len");
Cursor::Anchored {
char_id,
side: CursorSide::Before,
}
}
pub fn after<T: Clone>(list: &List<T>, pos: usize) -> Self {
let len = list.len();
if pos + 1 >= len {
return Cursor::End;
}
let char_id = list.id_at(pos).expect("pos < len");
Cursor::Anchored {
char_id,
side: CursorSide::After,
}
}
pub fn resolve<T: Clone>(self, list: &List<T>) -> usize {
match self {
Cursor::Start => 0,
Cursor::End => list.len(),
Cursor::Anchored { char_id, side } => {
if let Some(pos) = list.position_of(char_id) {
return match side {
CursorSide::Before => pos,
CursorSide::After => pos + 1,
};
}
list.phantom_position_of(char_id).unwrap_or(0)
}
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Selection {
pub anchor: Cursor,
pub head: Cursor,
}
impl Selection {
pub fn over<T: Clone>(list: &List<T>, range: std::ops::Range<usize>) -> Self {
Self {
anchor: Cursor::at(list, range.start),
head: if range.end == 0 {
Cursor::Start
} else {
Cursor::after(list, range.end - 1)
},
}
}
pub fn resolve<T: Clone>(self, list: &List<T>) -> std::ops::Range<usize> {
let a = self.anchor.resolve(list);
let h = self.head.resolve(list);
if a <= h {
a..h
} else {
h..a
}
}
pub fn is_empty<T: Clone>(self, list: &List<T>) -> bool {
self.anchor.resolve(list) == self.head.resolve(list)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build(s: &str) -> List<char> {
let mut list = List::<char>::new(1);
for (i, c) in s.chars().enumerate() {
list.insert(i, c);
}
list
}
#[test]
fn cursor_at_start_and_end() {
let mut list = List::<char>::new(1);
assert_eq!(Cursor::at(&list, 0), Cursor::Start);
list.insert(0, 'a');
list.insert(1, 'b');
assert_eq!(Cursor::at(&list, 2), Cursor::End);
}
#[test]
fn cursor_follows_inserts_before() {
let mut list = build("hello");
let c = Cursor::at(&list, 3); assert_eq!(c.resolve(&list), 3);
list.insert(0, 'X');
assert_eq!(c.resolve(&list), 4);
list.insert(0, 'Y');
assert_eq!(c.resolve(&list), 5);
}
#[test]
fn cursor_does_not_move_for_inserts_after() {
let mut list = build("hello");
let c = Cursor::at(&list, 2); list.insert(5, '!'); assert_eq!(c.resolve(&list), 2);
}
#[test]
fn cursor_resolves_after_deletion_of_anchor() {
let mut list = build("hello");
let c = Cursor::at(&list, 2); list.delete(2); assert_eq!(c.resolve(&list), 2);
}
#[test]
fn cursor_after_specific_char() {
let mut list = build("abcd");
let c = Cursor::after(&list, 1); assert_eq!(c.resolve(&list), 2);
list.insert(0, 'X');
assert_eq!(c.resolve(&list), 3);
let pos = c.resolve(&list);
list.insert(pos, 'M');
assert_eq!(list.to_vec(), vec!['X', 'a', 'b', 'M', 'c', 'd']);
assert_eq!(c.resolve(&list), 3);
}
#[test]
fn selection_over_range() {
let mut list = build("hello world");
let sel = Selection::over(&list, 6..11); assert_eq!(sel.resolve(&list), 6..11);
list.insert(0, 'X');
assert_eq!(sel.resolve(&list), 7..12);
}
#[test]
fn selection_emptiness() {
let list = build("abc");
let sel = Selection::over(&list, 1..1);
assert!(sel.is_empty(&list));
}
#[cfg(feature = "serde")]
#[test]
fn cursor_round_trips_via_json() {
let list = build("hello");
let c = Cursor::at(&list, 3);
let json = serde_json::to_string(&c).unwrap();
let restored: Cursor = serde_json::from_str(&json).unwrap();
assert_eq!(c, restored);
assert_eq!(restored.resolve(&list), 3);
}
}