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}