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