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 Movement::Down if wrapping => (index + count) % len,
75 Movement::Down => (index + count).min(last),
76
77 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 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 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 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 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 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 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 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 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 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}