freya_edit/
text_editor.rs

1use std::{
2    borrow::Cow,
3    cmp::Ordering,
4    fmt::Display,
5    ops::Range,
6};
7
8use freya_clipboard::clipboard::Clipboard;
9use keyboard_types::{
10    Key,
11    Modifiers,
12};
13
14use crate::editor_history::EditorHistory;
15
16/// Holds the position of a cursor in a text
17#[derive(Clone, Default, PartialEq, Debug)]
18pub struct TextCursor(usize);
19
20impl TextCursor {
21    /// Construct a new [TextCursor]
22    pub fn new(pos: usize) -> Self {
23        Self(pos)
24    }
25
26    /// Get the position
27    pub fn pos(&self) -> usize {
28        self.0
29    }
30
31    /// Set the position
32    pub fn set(&mut self, pos: usize) {
33        self.0 = pos;
34    }
35
36    /// Write the position
37    pub fn write(&mut self) -> &mut usize {
38        &mut self.0
39    }
40}
41
42/// A text line from a [TextEditor]
43#[derive(Clone)]
44pub struct Line<'a> {
45    pub text: Cow<'a, str>,
46    pub utf16_len: usize,
47}
48
49impl Line<'_> {
50    /// Get the length of the line
51    pub fn utf16_len(&self) -> usize {
52        self.utf16_len
53    }
54}
55
56impl Display for Line<'_> {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.write_str(&self.text)
59    }
60}
61
62bitflags::bitflags! {
63    /// Events for [TextEditor]
64    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
65    pub struct TextEvent: u8 {
66         /// Cursor position has been moved
67        const CURSOR_CHANGED = 0x01;
68        /// Text has changed
69        const TEXT_CHANGED = 0x02;
70        /// Selected text has changed
71        const SELECTION_CHANGED = 0x04;
72    }
73}
74
75/// Common trait for editable texts
76pub trait TextEditor {
77    type LinesIterator<'a>: Iterator<Item = Line<'a>>
78    where
79        Self: 'a;
80
81    fn set(&mut self, text: &str);
82
83    /// Iterator over all the lines in the text.
84    fn lines(&self) -> Self::LinesIterator<'_>;
85
86    /// Insert a character in the text in the given position.
87    fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
88
89    /// Insert a string in the text in the given position.
90    fn insert(&mut self, text: &str, char_idx: usize) -> usize;
91
92    /// Remove a part of the text.
93    fn remove(&mut self, range: Range<usize>) -> usize;
94
95    /// Get line from the given char
96    fn char_to_line(&self, char_idx: usize) -> usize;
97
98    /// Get the first char from the given line
99    fn line_to_char(&self, line_idx: usize) -> usize;
100
101    fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
102
103    fn char_to_utf16_cu(&self, idx: usize) -> usize;
104
105    /// Get a line from the text
106    fn line(&self, line_idx: usize) -> Option<Line<'_>>;
107
108    /// Total of lines
109    fn len_lines(&self) -> usize;
110
111    /// Total of chars
112    fn len_chars(&self) -> usize;
113
114    /// Total of utf16 code units
115    fn len_utf16_cu(&self) -> usize;
116
117    /// Get a readable cursor
118    fn cursor(&self) -> &TextCursor;
119
120    /// Get a mutable cursor
121    fn cursor_mut(&mut self) -> &mut TextCursor;
122
123    /// Get the cursor row
124    fn cursor_row(&self) -> usize {
125        let pos = self.cursor_pos();
126        let pos_utf8 = self.utf16_cu_to_char(pos);
127        self.char_to_line(pos_utf8)
128    }
129
130    /// Get the cursor column
131    fn cursor_col(&self) -> usize {
132        let pos = self.cursor_pos();
133        let pos_utf8 = self.utf16_cu_to_char(pos);
134        let line = self.char_to_line(pos_utf8);
135        let line_char_utf8 = self.line_to_char(line);
136        let line_char = self.char_to_utf16_cu(line_char_utf8);
137        pos - line_char
138    }
139
140    /// Get the cursor row and col
141    fn cursor_row_and_col(&self) -> (usize, usize) {
142        (self.cursor_row(), self.cursor_col())
143    }
144
145    /// Move the cursor 1 line down
146    fn cursor_down(&mut self) -> bool {
147        let old_row = self.cursor_row();
148        let old_col = self.cursor_col();
149
150        match old_row.cmp(&(self.len_lines() - 1)) {
151            Ordering::Less => {
152                // One line below
153                let new_row = old_row + 1;
154                let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
155                let new_row_len = self.line(new_row).unwrap().utf16_len();
156                let new_col = old_col.min(new_row_len.saturating_sub(1));
157                self.cursor_mut().set(new_row_char + new_col);
158
159                true
160            }
161            Ordering::Equal => {
162                let end = self.len_utf16_cu();
163                // Reached max
164                self.cursor_mut().set(end);
165
166                true
167            }
168            Ordering::Greater => {
169                // Can't go further
170
171                false
172            }
173        }
174    }
175
176    /// Move the cursor 1 line up
177    fn cursor_up(&mut self) -> bool {
178        let pos = self.cursor_pos();
179        let old_row = self.cursor_row();
180        let old_col = self.cursor_col();
181
182        if pos > 0 {
183            // Reached max
184            if old_row == 0 {
185                self.cursor_mut().set(0);
186            } else {
187                let new_row = old_row - 1;
188                let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
189                let new_row_len = self.line(new_row).unwrap().utf16_len();
190                let new_col = old_col.min(new_row_len.saturating_sub(1));
191                self.cursor_mut().set(new_row_char + new_col);
192            }
193
194            true
195        } else {
196            false
197        }
198    }
199
200    /// Move the cursor 1 char to the right
201    fn cursor_right(&mut self) -> bool {
202        if self.cursor_pos() < self.len_utf16_cu() {
203            *self.cursor_mut().write() += 1;
204
205            true
206        } else {
207            false
208        }
209    }
210
211    /// Move the cursor 1 char to the left
212    fn cursor_left(&mut self) -> bool {
213        if self.cursor_pos() > 0 {
214            *self.cursor_mut().write() -= 1;
215
216            true
217        } else {
218            false
219        }
220    }
221
222    /// Get the cursor position
223    fn cursor_pos(&self) -> usize {
224        self.cursor().pos()
225    }
226
227    /// Set the cursor position
228    fn set_cursor_pos(&mut self, pos: usize) {
229        self.cursor_mut().set(pos);
230    }
231
232    // Check if has any selection at all
233    fn has_any_selection(&self) -> bool;
234
235    // Return the selected text
236    fn get_selection(&self) -> Option<(usize, usize)>;
237
238    // Return the visible selected text from a given editor Id
239    fn get_visible_selection(&self, editor_id: usize) -> Option<(usize, usize)>;
240
241    // Remove the selection
242    fn clear_selection(&mut self);
243
244    // Select some text
245    fn set_selection(&mut self, selected: (usize, usize));
246
247    // Measure a new text selection
248    fn measure_new_selection(&self, from: usize, to: usize, editor_id: usize) -> (usize, usize);
249
250    // Measure a new cursor
251    fn measure_new_cursor(&self, to: usize, editor_id: usize) -> TextCursor;
252
253    // Update the selection with a new cursor
254    fn expand_selection_to_cursor(&mut self);
255
256    // Process a Keyboard event
257    fn process_key(
258        &mut self,
259        key: &Key,
260        modifiers: &Modifiers,
261        allow_tabs: bool,
262        allow_changes: bool,
263        allow_clipboard: bool,
264    ) -> TextEvent {
265        let mut event = TextEvent::empty();
266
267        let selection = self.get_selection();
268
269        match key {
270            Key::Shift => {}
271            Key::Control => {}
272            Key::Alt => {}
273            Key::Escape => {
274                self.clear_selection();
275            }
276            Key::ArrowDown => {
277                if modifiers.contains(Modifiers::SHIFT) {
278                    self.expand_selection_to_cursor();
279                } else {
280                    self.clear_selection();
281                }
282
283                if self.cursor_down() {
284                    event.insert(TextEvent::CURSOR_CHANGED);
285                }
286
287                if modifiers.contains(Modifiers::SHIFT) {
288                    self.expand_selection_to_cursor();
289                }
290            }
291            Key::ArrowLeft => {
292                if modifiers.contains(Modifiers::SHIFT) {
293                    self.expand_selection_to_cursor();
294                } else {
295                    self.clear_selection();
296                }
297
298                if self.cursor_left() {
299                    event.insert(TextEvent::CURSOR_CHANGED);
300                }
301
302                if modifiers.contains(Modifiers::SHIFT) {
303                    self.expand_selection_to_cursor();
304                }
305            }
306            Key::ArrowRight => {
307                if modifiers.contains(Modifiers::SHIFT) {
308                    self.expand_selection_to_cursor();
309                } else {
310                    self.clear_selection();
311                }
312
313                if self.cursor_right() {
314                    event.insert(TextEvent::CURSOR_CHANGED);
315                }
316
317                if modifiers.contains(Modifiers::SHIFT) {
318                    self.expand_selection_to_cursor();
319                }
320            }
321            Key::ArrowUp => {
322                if modifiers.contains(Modifiers::SHIFT) {
323                    self.expand_selection_to_cursor();
324                } else {
325                    self.clear_selection();
326                }
327
328                if self.cursor_up() {
329                    event.insert(TextEvent::CURSOR_CHANGED);
330                }
331
332                if modifiers.contains(Modifiers::SHIFT) {
333                    self.expand_selection_to_cursor();
334                }
335            }
336            Key::Backspace if allow_changes => {
337                let cursor_pos = self.cursor_pos();
338                let selection = self.get_selection_range();
339
340                if let Some((start, end)) = selection {
341                    self.remove(start..end);
342                    self.set_cursor_pos(start);
343                    event.insert(TextEvent::TEXT_CHANGED);
344                } else if cursor_pos > 0 {
345                    // Remove the character to the left if there is any
346                    let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
347                    self.set_cursor_pos(cursor_pos - removed_text_len);
348                    event.insert(TextEvent::TEXT_CHANGED);
349                }
350            }
351            Key::Delete if allow_changes => {
352                let cursor_pos = self.cursor_pos();
353                let selection = self.get_selection_range();
354
355                if let Some((start, end)) = selection {
356                    self.remove(start..end);
357                    self.set_cursor_pos(start);
358                    event.insert(TextEvent::TEXT_CHANGED);
359                } else if cursor_pos < self.len_utf16_cu() {
360                    // Remove the character to the right if there is any
361                    self.remove(cursor_pos..cursor_pos + 1);
362                    event.insert(TextEvent::TEXT_CHANGED);
363                }
364            }
365            Key::Enter if allow_changes => {
366                // Breaks the line
367                let cursor_pos = self.cursor_pos();
368                self.insert_char('\n', cursor_pos);
369                self.cursor_right();
370
371                event.insert(TextEvent::TEXT_CHANGED);
372            }
373            Key::Tab if allow_tabs && allow_changes => {
374                // Inserts a tab
375                let text = " ".repeat(self.get_identation().into());
376                let cursor_pos = self.cursor_pos();
377                self.insert(&text, cursor_pos);
378                self.set_cursor_pos(cursor_pos + text.chars().count());
379
380                event.insert(TextEvent::TEXT_CHANGED);
381            }
382            Key::Character(character) => {
383                let meta_or_ctrl = if cfg!(target_os = "macos") {
384                    modifiers.meta()
385                } else {
386                    modifiers.ctrl()
387                };
388
389                match character.as_str() {
390                    " " if allow_changes => {
391                        let selection = self.get_selection_range();
392                        if let Some((start, end)) = selection {
393                            self.remove(start..end);
394                            self.set_cursor_pos(start);
395                            event.insert(TextEvent::TEXT_CHANGED);
396                        }
397
398                        // Simply adds an space
399                        let cursor_pos = self.cursor_pos();
400                        self.insert_char(' ', cursor_pos);
401                        self.cursor_right();
402
403                        event.insert(TextEvent::TEXT_CHANGED);
404                    }
405
406                    // Select all text
407                    "a" if meta_or_ctrl => {
408                        let len = self.len_utf16_cu();
409                        self.set_selection((0, len));
410                    }
411
412                    // Copy selected text
413                    "c" if meta_or_ctrl && allow_clipboard => {
414                        let selected = self.get_selected_text();
415                        if let Some(selected) = selected {
416                            Clipboard::set(selected).ok();
417                        }
418                    }
419
420                    // Cut selected text
421                    "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
422                        let selection = self.get_selection_range();
423                        if let Some((start, end)) = selection {
424                            let text = self.get_selected_text().unwrap();
425                            self.remove(start..end);
426                            Clipboard::set(text).ok();
427                            self.set_cursor_pos(start);
428                            event.insert(TextEvent::TEXT_CHANGED);
429                        }
430                    }
431
432                    // Paste copied text
433                    "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
434                        if let Ok(copied_text) = Clipboard::get() {
435                            let selection = self.get_selection_range();
436                            if let Some((start, end)) = selection {
437                                self.remove(start..end);
438                                self.set_cursor_pos(start);
439                            }
440                            let cursor_pos = self.cursor_pos();
441                            self.insert(&copied_text, cursor_pos);
442                            let last_idx = copied_text.encode_utf16().count() + cursor_pos;
443                            self.set_cursor_pos(last_idx);
444                            event.insert(TextEvent::TEXT_CHANGED);
445                        }
446                    }
447
448                    // Undo last change
449                    "z" if meta_or_ctrl && allow_changes => {
450                        let undo_result = self.undo();
451
452                        if let Some(idx) = undo_result {
453                            self.set_cursor_pos(idx);
454                            event.insert(TextEvent::TEXT_CHANGED);
455                        }
456                    }
457
458                    // Redo last change
459                    "y" if meta_or_ctrl && allow_changes => {
460                        let redo_result = self.redo();
461
462                        if let Some(idx) = redo_result {
463                            self.set_cursor_pos(idx);
464                            event.insert(TextEvent::TEXT_CHANGED);
465                        }
466                    }
467
468                    _ if allow_changes => {
469                        // Remove selected text
470                        let selection = self.get_selection_range();
471                        if let Some((start, end)) = selection {
472                            self.remove(start..end);
473                            self.set_cursor_pos(start);
474                            event.insert(TextEvent::TEXT_CHANGED);
475                        }
476
477                        if let Ok(ch) = character.parse::<char>() {
478                            // Inserts a character
479                            let cursor_pos = self.cursor_pos();
480                            let inserted_text_len = self.insert_char(ch, cursor_pos);
481                            self.set_cursor_pos(cursor_pos + inserted_text_len);
482                            event.insert(TextEvent::TEXT_CHANGED);
483                        } else {
484                            // Inserts a text
485                            let cursor_pos = self.cursor_pos();
486                            let inserted_text_len = self.insert(character, cursor_pos);
487                            self.set_cursor_pos(cursor_pos + inserted_text_len);
488                            event.insert(TextEvent::TEXT_CHANGED);
489                        }
490                    }
491                    _ => {}
492                }
493            }
494            _ => {}
495        }
496
497        if event.contains(TextEvent::TEXT_CHANGED) {
498            self.clear_selection();
499        }
500
501        if self.get_selection() != selection {
502            event.insert(TextEvent::SELECTION_CHANGED);
503        }
504
505        event
506    }
507
508    fn get_selected_text(&self) -> Option<String>;
509
510    fn undo(&mut self) -> Option<usize>;
511
512    fn redo(&mut self) -> Option<usize>;
513
514    fn editor_history(&mut self) -> &mut EditorHistory;
515
516    fn get_selection_range(&self) -> Option<(usize, usize)>;
517
518    fn get_identation(&self) -> u8;
519}