floem_editor_core/
movement.rs

1#[derive(Clone, Debug)]
2pub enum LinePosition {
3    First,
4    Last,
5    Line(usize),
6}
7
8#[derive(Clone, Debug)]
9pub enum Movement {
10    Left,
11    Right,
12    Up,
13    Down,
14    DocumentStart,
15    DocumentEnd,
16    FirstNonBlank,
17    StartOfLine,
18    EndOfLine,
19    Line(LinePosition),
20    Offset(usize),
21    WordEndForward,
22    WordForward,
23    WordBackward,
24    NextUnmatched(char),
25    PreviousUnmatched(char),
26    MatchPairs,
27    ParagraphForward,
28    ParagraphBackward,
29}
30
31impl PartialEq for Movement {
32    fn eq(&self, other: &Self) -> bool {
33        std::mem::discriminant(self) == std::mem::discriminant(other)
34    }
35}
36
37impl Movement {
38    pub fn is_vertical(&self) -> bool {
39        matches!(
40            self,
41            Movement::Up
42                | Movement::Down
43                | Movement::Line(_)
44                | Movement::DocumentStart
45                | Movement::DocumentEnd
46                | Movement::ParagraphForward
47                | Movement::ParagraphBackward
48        )
49    }
50
51    pub fn is_inclusive(&self) -> bool {
52        matches!(self, Movement::WordEndForward)
53    }
54
55    pub fn is_jump(&self) -> bool {
56        matches!(
57            self,
58            Movement::Line(_)
59                | Movement::Offset(_)
60                | Movement::DocumentStart
61                | Movement::DocumentEnd
62                | Movement::ParagraphForward
63                | Movement::ParagraphBackward
64        )
65    }
66
67    pub fn update_index(&self, index: usize, len: usize, count: usize, wrapping: bool) -> usize {
68        if len == 0 {
69            return 0;
70        }
71        let last = len - 1;
72        match self {
73            // Select the next entry/line
74            Movement::Down if wrapping => (index + count) % len,
75            Movement::Down => (index + count).min(last),
76
77            // Selects the previous entry/line
78            Movement::Up if wrapping => (index + (len.saturating_sub(count))) % len,
79            Movement::Up => index.saturating_sub(count),
80
81            Movement::Line(position) => match position {
82                // Selects the nth line
83                LinePosition::Line(n) => (*n).min(last),
84                LinePosition::First => 0,
85                LinePosition::Last => last,
86            },
87
88            Movement::ParagraphForward => 0,
89            Movement::ParagraphBackward => 0,
90            _ => index,
91        }
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use crate::movement::Movement;
98
99    #[test]
100    fn test_wrapping() {
101        // Move by 1 position
102        // List length of 1
103        assert_eq!(0, Movement::Up.update_index(0, 1, 1, true));
104        assert_eq!(0, Movement::Down.update_index(0, 1, 1, true));
105
106        // List length of 5
107        assert_eq!(4, Movement::Up.update_index(0, 5, 1, true));
108        assert_eq!(1, Movement::Down.update_index(0, 5, 1, true));
109
110        // Move by 2 positions
111        // List length of 1
112        assert_eq!(0, Movement::Up.update_index(0, 1, 2, true));
113        assert_eq!(0, Movement::Down.update_index(0, 1, 2, true));
114
115        // List length of 5
116        assert_eq!(3, Movement::Up.update_index(0, 5, 2, true));
117        assert_eq!(2, Movement::Down.update_index(0, 5, 2, true));
118    }
119
120    #[test]
121    fn test_non_wrapping() {
122        // Move by 1 position
123        // List length of 1
124        assert_eq!(0, Movement::Up.update_index(0, 1, 1, false));
125        assert_eq!(0, Movement::Down.update_index(0, 1, 1, false));
126
127        // List length of 5
128        assert_eq!(0, Movement::Up.update_index(0, 5, 1, false));
129        assert_eq!(1, Movement::Down.update_index(0, 5, 1, false));
130
131        // Move by 2 positions
132        // List length of 1
133        assert_eq!(0, Movement::Up.update_index(0, 1, 2, false));
134        assert_eq!(0, Movement::Down.update_index(0, 1, 2, false));
135
136        // List length of 5
137        assert_eq!(0, Movement::Up.update_index(0, 5, 2, false));
138        assert_eq!(2, Movement::Down.update_index(0, 5, 2, false));
139    }
140}