text_editing/cursor.rs
1use super::TextLine;
2
3#[derive(Copy, Clone, PartialEq, Eq)]
4enum CharKind {
5 Whitespace,
6 Alphanumeric,
7 Other,
8}
9
10impl CharKind {
11 fn of(c: char) -> Self {
12 use CharKind::*;
13 if c.is_whitespace() {
14 Whitespace
15 } else if c.is_alphanumeric() {
16 Alphanumeric
17 } else {
18 Other
19 }
20 }
21}
22
23/// Represents the direction of movement for the text cursor.
24#[derive(Copy, Clone, PartialEq, Eq)]
25pub enum Direction {
26 /// Moves the text cursor forward.
27 Forward,
28 /// Moves the text cursor backward.
29 Backward,
30}
31
32impl TextLine {
33 /// Returns the appropriate movement function based on parameters `movement` and `skip`.
34 ///
35 /// # Arguments
36 /// * `movement` - The direction of movement (`Forward` or `Backward`).
37 /// * `skip` - Whether to skip over whitespace characters.
38 ///
39 /// # Returns
40 /// A function pointer to the appropriate movement function.
41 ///
42 /// # Examples
43 /// ```
44 /// use text_editing::{TextLine, Direction};
45 ///
46 /// let line = TextLine::from_string("Hello, world!".into());
47 /// let mut text_cursor = 7;
48 /// let movement_fn = TextLine::cursor_movement(Direction::Forward, false);
49 /// movement_fn(&line, &mut text_cursor);
50 /// assert_eq!(text_cursor, 8);
51 /// ```
52 pub fn cursor_movement(movement: Direction, skip: bool) -> fn(&Self, &mut usize) -> bool {
53 use Direction::*;
54 match (movement, skip) {
55 (Backward, false) => Self::backward,
56 (Backward, true) => Self::skip_backward,
57 (Forward, false) => Self::forward,
58 (Forward, true) => Self::skip_forward,
59 }
60 }
61
62 /// Moves the text cursor forward by one.
63 ///
64 /// # Arguments
65 /// * `text_cursor` - A mutable reference to the current text cursor position.
66 ///
67 /// # Returns
68 /// `true` if the cursor was successfully moved forward, `false` otherwise.
69 ///
70 /// # Examples
71 /// ```
72 /// use text_editing::TextLine;
73 ///
74 /// let line = TextLine::from_string("Hello, world!".into());
75 /// let mut text_cursor = 6;
76 /// assert!(line.forward(&mut text_cursor));
77 /// assert_eq!(text_cursor, 7);
78 /// ```
79 pub fn forward(&self, text_cursor: &mut usize) -> bool {
80 if *text_cursor < self.len() {
81 *text_cursor += 1;
82 true
83 } else {
84 false
85 }
86 }
87
88 /// Moves the text cursor backward by one.
89 ///
90 /// # Arguments
91 /// * `text_cursor` - A mutable reference to the current text cursor position.
92 ///
93 /// # Returns
94 /// `true` if the cursor was successfully moved backward, `false` otherwise.
95 ///
96 /// # Examples
97 /// ```
98 /// use text_editing::TextLine;
99 ///
100 /// let line = TextLine::from_string("Hello, world!".into());
101 /// let mut text_cursor = 7;
102 /// assert!(line.backward(&mut text_cursor));
103 /// assert_eq!(text_cursor, 6);
104 /// ```
105 pub fn backward(&self, text_cursor: &mut usize) -> bool {
106 if *text_cursor > 0 {
107 *text_cursor -= 1;
108 true
109 } else {
110 false
111 }
112 }
113
114 /// Moves the text cursor forward until the end of the current word.
115 ///
116 /// # Arguments
117 /// * `text_cursor` - A mutable reference to the current text cursor position.
118 ///
119 /// # Returns
120 /// `true` if the cursor was successfully moved to the end of the word, `false` otherwise.
121 ///
122 /// # Examples
123 /// ```
124 /// use text_editing::TextLine;
125 ///
126 /// let line = TextLine::from_string("Hello, world!".into());
127 /// let mut text_cursor = 6;
128 /// assert!(line.skip_forward(&mut text_cursor));
129 /// assert_eq!(text_cursor, 12);
130 /// ```
131 pub fn skip_forward(&self, text_cursor: &mut usize) -> bool {
132 let len = self.len();
133
134 let start_kind = loop {
135 if *text_cursor == len {
136 return false;
137 }
138
139 match CharKind::of(self.char_at(*text_cursor)) {
140 CharKind::Whitespace => (),
141 kind => break kind,
142 }
143
144 *text_cursor += 1;
145 };
146
147 loop {
148 *text_cursor += 1;
149
150 if *text_cursor == len {
151 return true;
152 }
153
154 if CharKind::of(self.char_at(*text_cursor)) != start_kind {
155 return true;
156 }
157 }
158 }
159
160 /// Moves the text cursor back until the start of the current word.
161 ///
162 /// # Arguments
163 /// * `text_cursor` - A mutable reference to the current text cursor position.
164 ///
165 /// # Returns
166 /// `true` if the cursor was successfully moved to the start of the word, `false` otherwise.
167 ///
168 /// # Examples
169 /// ```
170 /// use text_editing::TextLine;
171 ///
172 /// let line = TextLine::from_string("Hello, world!".into());
173 /// let mut text_cursor = 10;
174 /// assert!(line.skip_backward(&mut text_cursor));
175 /// assert_eq!(text_cursor, 7);
176 /// ```
177 pub fn skip_backward(&self, text_cursor: &mut usize) -> bool {
178 if *text_cursor == 0 {
179 return false;
180 }
181
182 let start_kind = loop {
183 *text_cursor -= 1;
184
185 if *text_cursor == 0 {
186 return false;
187 }
188
189 match CharKind::of(self.char_at(*text_cursor)) {
190 CharKind::Whitespace => (),
191 kind => break kind,
192 }
193 };
194
195 loop {
196 *text_cursor -= 1;
197
198 if CharKind::of(self.char_at(*text_cursor)) != start_kind {
199 *text_cursor += 1;
200 return true;
201 }
202
203 if *text_cursor == 0 {
204 return true;
205 }
206 }
207 }
208}