edtui_papier/actions/
motion.rs

1use serde::{Deserialize, Serialize};
2
3use super::Execute;
4use crate::{
5    debug::log_to_file,
6    helper::{max_col, max_row, set_selection, skip_whitespace, skip_whitespace_rev},
7    EditorMode, EditorState,
8};
9
10#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
11pub struct MoveForward(pub usize);
12
13impl Execute for MoveForward {
14    fn execute(&mut self, state: &mut EditorState) {
15        for _ in 0..self.0 {
16            if state.cursor.col >= max_col(&state.lines, &state.cursor, state.mode) {
17                break;
18            }
19            state.cursor.col += 1;
20        }
21        if state.mode == EditorMode::Visual {
22            set_selection(&mut state.selection, state.cursor);
23        }
24    }
25}
26
27#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
28pub struct MoveBackward(pub usize);
29
30impl Execute for MoveBackward {
31    fn execute(&mut self, state: &mut EditorState) {
32        for _ in 0..self.0 {
33            if state.cursor.col == 0 {
34                break;
35            }
36            let max_col = max_col(&state.lines, &state.cursor, state.mode);
37            if state.cursor.col > max_col {
38                state.cursor.col = max_col;
39            }
40            state.cursor.col = state.cursor.col.saturating_sub(1);
41        }
42        if state.mode == EditorMode::Visual {
43            set_selection(&mut state.selection, state.cursor);
44        }
45    }
46}
47
48#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
49pub struct MoveUp(pub usize);
50
51impl Execute for MoveUp {
52    fn execute(&mut self, state: &mut EditorState) {
53        for _ in 0..self.0 {
54            if state.cursor.row == 0 {
55                break;
56            }
57            state.cursor.row = state.cursor.row.saturating_sub(1);
58        }
59        if state.mode == EditorMode::Visual {
60            set_selection(&mut state.selection, state.cursor);
61        }
62    }
63}
64
65#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
66pub struct MoveDown(pub usize);
67
68impl Execute for MoveDown {
69    fn execute(&mut self, state: &mut EditorState) {
70        for _ in 0..self.0 {
71            if state.cursor.row >= max_row(state) {
72                break;
73            }
74            state.cursor.row += 1;
75        }
76        if state.mode == EditorMode::Visual {
77            set_selection(&mut state.selection, state.cursor);
78        }
79    }
80}
81
82/// Move one word forward. Breaks on the first character that is not of
83/// the same class as the initial character or breaks on line ending.
84/// Furthermore, after the first break, whitespaces are skipped.
85#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
86pub struct MoveWordForwardStart(pub usize);
87
88impl Execute for MoveWordForwardStart {
89    fn execute(&mut self, state: &mut EditorState) {
90        fn move_word_right(state: &mut EditorState) {
91            let lines = &state.lines;
92            let mut index = state.cursor;
93            let first_char = state.lines.get(index);
94            let mut iter = state.lines.iter().from(index);
95            iter.next();
96            for (val, i) in iter {
97                index = i;
98                // Break loop if it reaches the end of the line
99                if state.cursor.col >= max_col(&state.lines, &state.cursor, state.mode) {
100                    break;
101                }
102                // Break loop if characters don't belong to the same class
103                if !is_same_word_class(val, first_char) {
104                    break;
105                }
106            }
107            // Skip whitespaces moving to the right.
108            skip_whitespace(lines, &mut index);
109
110            state.cursor = index;
111        }
112
113        if state.lines.is_empty() {
114            return;
115        }
116
117        for _ in 0..self.0 {
118            move_word_right(state);
119        }
120
121        if state.mode == EditorMode::Visual {
122            set_selection(&mut state.selection, state.cursor);
123        }
124    }
125}
126
127/// Move at the end of the word. Breaks on the first character that is not of
128/// the same class as the initial character or breaks on line ending.
129/// Furthermore, after the first break, whitespaces are skipped.
130#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
131pub struct MoveWordForwardEnd(pub usize);
132
133impl Execute for MoveWordForwardEnd {
134    fn execute(&mut self, state: &mut EditorState) {
135        fn move_word_right(state: &mut EditorState) {
136            let lines = &state.lines;
137            let mut index = state.cursor;
138            let first_char = state.lines.get(index);
139            let mut iter = state.lines.iter().from(index);
140            iter.next();
141            log_to_file(format!("index: {:?}", index));
142            skip_whitespace(lines, &mut index);
143            log_to_file(format!("index_after: {:?}", index));
144            for (val, i) in iter {
145                // Break loop if it reaches the end of the line
146                if state.cursor.col >= max_col(&state.lines, &state.cursor, state.mode) {
147                    break;
148                }
149                // Break loop if characters don't belong to the same class
150                if !is_same_word_class(val, first_char) {
151                    break;
152                }
153                index = i;
154            }
155
156            state.cursor = index;
157        }
158
159        if state.lines.is_empty() {
160            return;
161        }
162
163        for _ in 0..self.0 {
164            move_word_right(state);
165        }
166
167        if state.mode == EditorMode::Visual {
168            set_selection(&mut state.selection, state.cursor);
169        }
170    }
171}
172
173/// Move one word forward. Breaks on the first character that is not of
174/// the same class as the initial character or breaks on line starts.
175/// Skips whitespaces if necessary.
176#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
177pub struct MoveWordBackward(pub usize);
178
179impl Execute for MoveWordBackward {
180    fn execute(&mut self, state: &mut EditorState) {
181        fn move_word_left(state: &mut EditorState) {
182            let lines = &state.lines;
183            let mut index = state.cursor;
184            if index.row == 0 && index.col == 0 {
185                return;
186            }
187
188            if index.col == 0 {
189                index.row = index.row.saturating_sub(1);
190                if let Some(len) = lines.len_col(index.row) {
191                    state.cursor.col = len.saturating_sub(1);
192                }
193                state.cursor.row = index.row;
194                return;
195            }
196
197            index.col = index.col.saturating_sub(1);
198            skip_whitespace_rev(lines, &mut index);
199            let first_char = lines.get(index);
200            for (val, i) in lines.iter().from(index).rev() {
201                // Break loop if it reaches the start of the line
202                if i.col == 0 {
203                    index = i;
204                    break;
205                }
206                // Break loop if characters don't belong to the same class
207                if !is_same_word_class(val, first_char) {
208                    break;
209                }
210                index = i;
211            }
212            state.cursor = index;
213        }
214
215        if state.lines.is_empty() {
216            return;
217        }
218
219        let max_col = max_col(&state.lines, &state.cursor, state.mode);
220        if state.cursor.col > max_col {
221            state.cursor.col = max_col;
222        }
223
224        for _ in 0..self.0 {
225            move_word_left(state);
226        }
227
228        if state.mode == EditorMode::Visual {
229            set_selection(&mut state.selection, state.cursor);
230        }
231    }
232}
233
234/// Whether two characters are considered of the same class.
235fn is_same_word_class(a: Option<&char>, b: Option<&char>) -> bool {
236    match (a, b) {
237        (Some(a), Some(b)) => {
238            a.is_ascii_alphanumeric() && b.is_ascii_alphanumeric()
239                || (a.is_ascii_punctuation() && b.is_ascii_punctuation())
240                || (a.is_ascii_whitespace() && b.is_ascii_whitespace())
241        },
242        _ => false,
243    }
244}
245
246// Move the cursor to the start of the line.
247#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
248pub struct MoveToStart();
249
250impl Execute for MoveToStart {
251    fn execute(&mut self, state: &mut EditorState) {
252        state.cursor.col = 0;
253
254        if state.mode == EditorMode::Visual {
255            set_selection(&mut state.selection, state.cursor);
256        }
257    }
258}
259
260// move to the first non-whitespace character in the line.
261#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
262pub struct MoveToFirst();
263
264impl Execute for MoveToFirst {
265    fn execute(&mut self, state: &mut EditorState) {
266        state.cursor.col = 0;
267
268        if state.mode == EditorMode::Visual {
269            set_selection(&mut state.selection, state.cursor);
270        }
271    }
272}
273
274// Move the cursor to the end of the line.
275#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
276pub struct MoveToEnd();
277
278impl Execute for MoveToEnd {
279    fn execute(&mut self, state: &mut EditorState) {
280        state.cursor.col = max_col(&state.lines, &state.cursor, state.mode);
281
282        if state.mode == EditorMode::Visual {
283            set_selection(&mut state.selection, state.cursor);
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::{Index2, Lines};
292    fn test_state() -> EditorState {
293        EditorState::new(Lines::from("Hello World!\n\n123."), "txt")
294    }
295
296    #[test]
297    fn test_move_forward() {
298        let mut state = test_state();
299
300        MoveForward(1).execute(&mut state);
301        assert_eq!(state.cursor, Index2::new(0, 1));
302
303        MoveForward(10).execute(&mut state);
304        assert_eq!(state.cursor, Index2::new(0, 11));
305
306        MoveForward(1).execute(&mut state);
307        assert_eq!(state.cursor, Index2::new(0, 11));
308    }
309
310    #[test]
311    fn test_move_backward() {
312        let mut state = test_state();
313        state.cursor = Index2::new(0, 11);
314
315        MoveBackward(1).execute(&mut state);
316        assert_eq!(state.cursor, Index2::new(0, 10));
317
318        MoveBackward(10).execute(&mut state);
319        assert_eq!(state.cursor, Index2::new(0, 0));
320
321        MoveBackward(1).execute(&mut state);
322        assert_eq!(state.cursor, Index2::new(0, 0));
323    }
324
325    #[test]
326    fn test_move_down() {
327        let mut state = test_state();
328        state.cursor = Index2::new(0, 6);
329
330        MoveDown(1).execute(&mut state);
331        assert_eq!(state.cursor, Index2::new(1, 6));
332
333        MoveDown(1).execute(&mut state);
334        assert_eq!(state.cursor, Index2::new(2, 6));
335
336        MoveDown(1).execute(&mut state);
337        assert_eq!(state.cursor, Index2::new(2, 6));
338    }
339
340    #[test]
341    fn test_move_up() {
342        let mut state = test_state();
343        state.cursor = Index2::new(2, 2);
344
345        MoveUp(1).execute(&mut state);
346        assert_eq!(state.cursor, Index2::new(1, 2));
347
348        MoveUp(1).execute(&mut state);
349        assert_eq!(state.cursor, Index2::new(0, 2));
350
351        MoveUp(1).execute(&mut state);
352        assert_eq!(state.cursor, Index2::new(0, 2));
353    }
354
355    #[test]
356    fn test_move_word_forward() {
357        let mut state = test_state();
358
359        MoveWordForwardStart(1).execute(&mut state);
360        assert_eq!(state.cursor, Index2::new(0, 6));
361
362        MoveWordForwardStart(1).execute(&mut state);
363        assert_eq!(state.cursor, Index2::new(0, 11));
364
365        MoveWordForwardStart(1).execute(&mut state);
366        assert_eq!(state.cursor, Index2::new(1, 0));
367
368        MoveWordForwardStart(1).execute(&mut state);
369        assert_eq!(state.cursor, Index2::new(2, 0));
370
371        MoveWordForwardStart(1).execute(&mut state);
372        assert_eq!(state.cursor, Index2::new(2, 3));
373    }
374
375    #[test]
376    fn test_move_word_backward() {
377        let mut state = test_state();
378        state.cursor = Index2::new(2, 3);
379
380        MoveWordBackward(1).execute(&mut state);
381        assert_eq!(state.cursor, Index2::new(2, 0));
382
383        MoveWordBackward(1).execute(&mut state);
384        assert_eq!(state.cursor, Index2::new(1, 0));
385
386        MoveWordBackward(1).execute(&mut state);
387        assert_eq!(state.cursor, Index2::new(0, 11));
388
389        MoveWordBackward(1).execute(&mut state);
390        assert_eq!(state.cursor, Index2::new(0, 6));
391
392        MoveWordBackward(1).execute(&mut state);
393        assert_eq!(state.cursor, Index2::new(0, 0));
394
395        MoveWordBackward(1).execute(&mut state);
396        assert_eq!(state.cursor, Index2::new(0, 0));
397    }
398
399    #[test]
400    fn test_move_to_start() {
401        let mut state = test_state();
402        state.cursor = Index2::new(0, 2);
403
404        MoveToStart().execute(&mut state);
405        assert_eq!(state.cursor, Index2::new(0, 0));
406    }
407
408    #[test]
409    fn test_move_to_end() {
410        let mut state = test_state();
411        state.cursor = Index2::new(0, 2);
412
413        MoveToEnd().execute(&mut state);
414        assert_eq!(state.cursor, Index2::new(0, 11));
415    }
416}