freya_hooks/
text_editor.rs

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