Skip to main content

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 freya_core::events::modifiers::ModifiersExt;
10use keyboard_types::{
11    Key,
12    Modifiers,
13    NamedKey,
14};
15use unicode_segmentation::UnicodeSegmentation;
16
17use crate::editor_history::EditorHistory;
18
19#[derive(PartialEq, Clone, Debug, Copy, Hash)]
20pub enum EditorLine {
21    /// Only one `paragraph` element exists in the whole editor.
22    SingleParagraph,
23    /// There are multiple `paragraph` elements in the editor, one per line.
24    Paragraph(usize),
25}
26
27/// Holds the position of a cursor in a text
28#[derive(Clone, PartialEq, Debug)]
29pub enum TextSelection {
30    Cursor(usize),
31    Range { from: usize, to: usize },
32}
33
34impl TextSelection {
35    /// Create a new [TextSelection::Cursor]
36    pub fn new_cursor(pos: usize) -> Self {
37        Self::Cursor(pos)
38    }
39
40    /// Create a new [TextSelection::Range]
41    pub fn new_range((from, to): (usize, usize)) -> Self {
42        Self::Range { from, to }
43    }
44
45    /// Get the position
46    pub fn pos(&self) -> usize {
47        self.end()
48    }
49
50    /// Set the selection as a cursor
51    pub fn set_as_cursor(&mut self) {
52        *self = Self::Cursor(self.end())
53    }
54
55    /// Set the selection as a range
56    pub fn set_as_range(&mut self) {
57        *self = Self::Range {
58            from: self.start(),
59            to: self.end(),
60        }
61    }
62
63    /// Get the start of the cursor position.
64    pub fn start(&self) -> usize {
65        match self {
66            Self::Cursor(pos) => *pos,
67            Self::Range { from, .. } => *from,
68        }
69    }
70
71    /// Get the end of the cursor position.
72    pub fn end(&self) -> usize {
73        match self {
74            Self::Cursor(pos) => *pos,
75            Self::Range { to, .. } => *to,
76        }
77    }
78
79    /// Move the end position of the cursor.
80    pub fn move_to(&mut self, position: usize) {
81        match self {
82            Self::Cursor(pos) => *pos = position,
83            Self::Range { to, .. } => {
84                *to = position;
85            }
86        }
87    }
88
89    pub fn is_range(&self) -> bool {
90        matches!(self, Self::Range { .. })
91    }
92}
93
94/// A text line from a [TextEditor]
95#[derive(Clone)]
96pub struct Line<'a> {
97    pub text: Cow<'a, str>,
98    pub utf16_len: usize,
99}
100
101impl Line<'_> {
102    /// Get the length of the line
103    pub fn utf16_len(&self) -> usize {
104        self.utf16_len
105    }
106}
107
108impl Display for Line<'_> {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.write_str(&self.text)
111    }
112}
113
114bitflags::bitflags! {
115    /// Events for [TextEditor]
116    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
117    pub struct TextEvent: u8 {
118         /// Cursor position has been moved
119        const CURSOR_CHANGED = 0x01;
120        /// Text has changed
121        const TEXT_CHANGED = 0x02;
122        /// Selected text has changed
123        const SELECTION_CHANGED = 0x04;
124    }
125}
126
127/// Common trait for editable texts
128pub trait TextEditor {
129    type LinesIterator<'a>: Iterator<Item = Line<'a>>
130    where
131        Self: 'a;
132
133    fn set(&mut self, text: &str);
134
135    /// Iterator over all the lines in the text.
136    fn lines(&self) -> Self::LinesIterator<'_>;
137
138    /// Insert a character in the text in the given position.
139    fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
140
141    /// Insert a string in the text in the given position.
142    fn insert(&mut self, text: &str, char_idx: usize) -> usize;
143
144    /// Remove a part of the text.
145    fn remove(&mut self, range: Range<usize>) -> usize;
146
147    /// Get line from the given char
148    fn char_to_line(&self, char_idx: usize) -> usize;
149
150    /// Get the first char from the given line
151    fn line_to_char(&self, line_idx: usize) -> usize;
152
153    fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
154
155    fn char_to_utf16_cu(&self, idx: usize) -> usize;
156
157    /// Get a line from the text
158    fn line(&self, line_idx: usize) -> Option<Line<'_>>;
159
160    /// Total of lines
161    fn len_lines(&self) -> usize;
162
163    /// Total of chars
164    fn len_chars(&self) -> usize;
165
166    /// Total of utf16 code units
167    fn len_utf16_cu(&self) -> usize;
168
169    /// Get a readable text selection
170    fn selection(&self) -> &TextSelection;
171
172    /// Get a mutable reference to text selection
173    fn selection_mut(&mut self) -> &mut TextSelection;
174
175    /// Get the UTF-16 range of the grapheme cluster containing the given position.
176    fn grapheme_cluster_at(&self, pos_utf16: usize) -> Range<usize> {
177        let line_idx = self.char_to_line(self.utf16_cu_to_char(pos_utf16));
178        let line_start = self.char_to_utf16_cu(self.line_to_char(line_idx));
179        let Some(line) = self.line(line_idx) else {
180            return pos_utf16..pos_utf16;
181        };
182
183        let mut cluster = line_start..line_start;
184        for grapheme in line.text.graphemes(true) {
185            cluster = cluster.end..cluster.end + grapheme.encode_utf16().count();
186            if cluster.end > pos_utf16 {
187                return cluster;
188            }
189        }
190        pos_utf16..pos_utf16
191    }
192
193    /// Get the cursor row
194    fn cursor_row(&self) -> usize {
195        let pos = self.cursor_pos();
196        let pos_utf8 = self.utf16_cu_to_char(pos);
197        self.char_to_line(pos_utf8)
198    }
199
200    /// Get the cursor column
201    fn cursor_col(&self) -> usize {
202        let pos = self.cursor_pos();
203        let pos_utf8 = self.utf16_cu_to_char(pos);
204        let line = self.char_to_line(pos_utf8);
205        let line_char_utf8 = self.line_to_char(line);
206        let line_char = self.char_to_utf16_cu(line_char_utf8);
207        pos - line_char
208    }
209
210    /// Last valid cursor position in `row`, before the line ending if it has one.
211    fn line_end_position(&self, row: usize) -> Option<usize> {
212        let line = self.line(row)?;
213        let row_start = self.char_to_utf16_cu(self.line_to_char(row));
214        let row_end = row_start + line.utf16_len();
215        if row + 1 == self.len_lines() {
216            Some(row_end)
217        } else {
218            Some(self.grapheme_cluster_at(row_end - 1).start)
219        }
220    }
221
222    /// Move the cursor to `row`, keeping `col` when possible and snapping to a
223    /// grapheme cluster boundary.
224    fn move_cursor_to_row(&mut self, row: usize, col: usize) {
225        let Some(last_position) = self.line_end_position(row) else {
226            return;
227        };
228        let row_start = self.char_to_utf16_cu(self.line_to_char(row));
229        let col = col.min(last_position - row_start);
230        let pos = self.grapheme_cluster_at(row_start + col).start;
231        self.selection_mut().move_to(pos);
232    }
233
234    /// Move the cursor 1 line down
235    fn cursor_down(&mut self) -> bool {
236        let old_row = self.cursor_row();
237        let old_col = self.cursor_col();
238
239        match old_row.cmp(&(self.len_lines() - 1)) {
240            Ordering::Less => {
241                self.move_cursor_to_row(old_row + 1, old_col);
242
243                true
244            }
245            Ordering::Equal => {
246                let end = self.len_utf16_cu();
247                self.selection_mut().move_to(end);
248
249                true
250            }
251            Ordering::Greater => false,
252        }
253    }
254
255    /// Move the cursor 1 line up
256    fn cursor_up(&mut self) -> bool {
257        let pos = self.cursor_pos();
258        let old_row = self.cursor_row();
259        let old_col = self.cursor_col();
260
261        if pos > 0 {
262            if old_row == 0 {
263                self.selection_mut().move_to(0);
264            } else {
265                self.move_cursor_to_row(old_row - 1, old_col);
266            }
267
268            true
269        } else {
270            false
271        }
272    }
273
274    /// Move the cursor 1 grapheme cluster to the right
275    fn cursor_right(&mut self) -> bool {
276        if self.cursor_pos() < self.len_utf16_cu() {
277            let to = self.grapheme_cluster_at(self.selection().end()).end;
278            self.selection_mut().move_to(to);
279
280            true
281        } else {
282            false
283        }
284    }
285
286    /// Move the cursor 1 grapheme cluster to the left
287    fn cursor_left(&mut self) -> bool {
288        if self.cursor_pos() > 0 {
289            let to = self.grapheme_cluster_at(self.selection().end() - 1).start;
290            self.selection_mut().move_to(to);
291
292            true
293        } else {
294            false
295        }
296    }
297
298    /// Find the end of the next word from the given position, if any.
299    fn next_word_pos(&self, pos: usize) -> Option<usize> {
300        let len = self.len_utf16_cu();
301        if pos >= len {
302            return None;
303        }
304
305        // Walk forward line by line starting at the given position.
306        let start_char = self.utf16_cu_to_char(pos);
307        let initial_line = self.char_to_line(start_char);
308        let initial_offset = start_char - self.line_to_char(initial_line);
309
310        for line_idx in initial_line..self.len_lines() {
311            let Some(line) = self.line(line_idx) else {
312                continue;
313            };
314            let line_char_offset = self.line_to_char(line_idx);
315            let from = if line_idx == initial_line {
316                initial_offset
317            } else {
318                0
319            };
320
321            // Stop at the end of the first non-whitespace segment past the position.
322            let mut char_offset = 0;
323            for word in line.text.split_word_bounds() {
324                char_offset += word.chars().count();
325                if char_offset > from && !word.chars().all(char::is_whitespace) {
326                    return Some(self.char_to_utf16_cu(line_char_offset + char_offset));
327                }
328            }
329        }
330
331        // Trailing whitespace only, snap to text end.
332        Some(len)
333    }
334
335    /// Find the start of the previous word from the given position, if any.
336    fn prev_word_pos(&self, pos: usize) -> Option<usize> {
337        if pos == 0 {
338            return None;
339        }
340
341        // Walk backward line by line starting at the given position.
342        let start_char = self.utf16_cu_to_char(pos);
343        let initial_line = self.char_to_line(start_char);
344        let initial_offset = start_char - self.line_to_char(initial_line);
345
346        for line_idx in (0..=initial_line).rev() {
347            let Some(line) = self.line(line_idx) else {
348                continue;
349            };
350            let line_char_offset = self.line_to_char(line_idx);
351            let to = if line_idx == initial_line {
352                initial_offset
353            } else {
354                line.text.chars().count()
355            };
356
357            // Track the latest non-whitespace segment that starts before the position.
358            let mut char_offset = 0;
359            let mut last_word_start = None;
360            for word in line.text.split_word_bounds() {
361                if char_offset >= to {
362                    break;
363                }
364                if !word.chars().all(char::is_whitespace) {
365                    last_word_start = Some(char_offset);
366                }
367                char_offset += word.chars().count();
368            }
369
370            if let Some(start) = last_word_start {
371                return Some(self.char_to_utf16_cu(line_char_offset + start));
372            }
373        }
374
375        // Leading whitespace only, snap to text start.
376        Some(0)
377    }
378
379    /// Move the cursor to the end of the next word.
380    fn cursor_word_right(&mut self) -> bool {
381        if let Some(new_pos) = self.next_word_pos(self.cursor_pos()) {
382            self.selection_mut().move_to(new_pos);
383            true
384        } else {
385            false
386        }
387    }
388
389    /// Move the cursor to the start of the previous word.
390    fn cursor_word_left(&mut self) -> bool {
391        if let Some(new_pos) = self.prev_word_pos(self.cursor_pos()) {
392            self.selection_mut().move_to(new_pos);
393            true
394        } else {
395            false
396        }
397    }
398
399    /// Get the cursor position
400    fn cursor_pos(&self) -> usize {
401        self.selection().pos()
402    }
403
404    /// Move the cursor position
405    fn move_cursor_to(&mut self, pos: usize) {
406        self.selection_mut().move_to(pos);
407    }
408
409    // Check if has any selection at all
410    fn has_any_selection(&self) -> bool;
411
412    // Return the selected text
413    fn get_selection(&self) -> Option<(usize, usize)>;
414
415    // Return the visible selected text for the given editor line
416    fn get_visible_selection(&self, editor_line: EditorLine) -> Option<(usize, usize)> {
417        let (selected_from, selected_to) = match self.selection() {
418            TextSelection::Cursor(_) => return None,
419            TextSelection::Range { from, to } => (*from, *to),
420        };
421
422        match editor_line {
423            EditorLine::Paragraph(line_index) => {
424                let selected_from_row = self.char_to_line(self.utf16_cu_to_char(selected_from));
425                let selected_to_row = self.char_to_line(self.utf16_cu_to_char(selected_to));
426
427                let editor_row_idx = self.char_to_utf16_cu(self.line_to_char(line_index));
428                let selected_from_row_idx =
429                    self.char_to_utf16_cu(self.line_to_char(selected_from_row));
430                let selected_to_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_to_row));
431
432                let selected_from_col_idx = selected_from - selected_from_row_idx;
433                let selected_to_col_idx = selected_to - selected_to_row_idx;
434
435                // Between starting line and endling line
436                if (line_index > selected_from_row && line_index < selected_to_row)
437                    || (line_index < selected_from_row && line_index > selected_to_row)
438                {
439                    let len = self.line(line_index).unwrap().utf16_len();
440                    return Some((0, len));
441                }
442
443                match selected_from_row.cmp(&selected_to_row) {
444                    // Selection direction is from bottom -> top
445                    Ordering::Greater => {
446                        if selected_from_row == line_index {
447                            // Starting line
448                            Some((0, selected_from_col_idx))
449                        } else if selected_to_row == line_index {
450                            // Ending line
451                            let len = self.line(selected_to_row).unwrap().utf16_len();
452                            Some((selected_to_col_idx, len))
453                        } else {
454                            None
455                        }
456                    }
457                    // Selection direction is from top -> bottom
458                    Ordering::Less => {
459                        if selected_from_row == line_index {
460                            // Starting line
461                            let len = self.line(selected_from_row).unwrap().utf16_len();
462                            Some((selected_from_col_idx, len))
463                        } else if selected_to_row == line_index {
464                            // Ending line
465                            Some((0, selected_to_col_idx))
466                        } else {
467                            None
468                        }
469                    }
470                    Ordering::Equal if selected_from_row == line_index => {
471                        // Starting and endline line are the same
472                        Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
473                    }
474                    _ => None,
475                }
476            }
477            EditorLine::SingleParagraph => Some((selected_from, selected_to)),
478        }
479    }
480
481    // Remove the selection
482    fn clear_selection(&mut self);
483
484    // Select some text
485    fn set_selection(&mut self, selected: (usize, usize));
486
487    // Measure a new text selection
488
489    fn measure_selection(&self, to: usize, line_index: EditorLine) -> TextSelection {
490        let mut selection = self.selection().clone();
491
492        match line_index {
493            EditorLine::Paragraph(line_index) => {
494                let row_char = self.line_to_char(line_index);
495                let pos = self.char_to_utf16_cu(row_char) + to;
496                selection.move_to(pos);
497            }
498            EditorLine::SingleParagraph => {
499                selection.move_to(to);
500            }
501        }
502
503        selection
504    }
505
506    // Process a Keyboard event
507    fn process_key(
508        &mut self,
509        key: &Key,
510        modifiers: &Modifiers,
511        allow_tabs: bool,
512        allow_changes: bool,
513        allow_read_clipboard: bool,
514        allow_write_clipboard: bool,
515    ) -> TextEvent {
516        let mut event = TextEvent::empty();
517
518        let selection = self.get_selection();
519        let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
520        let word_jump = modifiers.contains(Modifiers::ctrl_or_alt());
521
522        match key {
523            Key::Named(NamedKey::Shift) => {}
524            Key::Named(NamedKey::Control) => {}
525            Key::Named(NamedKey::Alt) => {}
526            Key::Named(NamedKey::Escape) => {
527                self.clear_selection();
528            }
529            Key::Named(NamedKey::ArrowDown) => {
530                if modifiers.contains(Modifiers::SHIFT) {
531                    self.selection_mut().set_as_range();
532                } else {
533                    self.selection_mut().set_as_cursor();
534                }
535
536                if !skip_arrows_movement && self.cursor_down() {
537                    event.insert(TextEvent::CURSOR_CHANGED);
538                }
539            }
540            Key::Named(NamedKey::ArrowLeft) => {
541                if modifiers.contains(Modifiers::SHIFT) {
542                    self.selection_mut().set_as_range();
543                } else {
544                    self.selection_mut().set_as_cursor();
545                }
546
547                let moved = !skip_arrows_movement
548                    && if word_jump {
549                        self.cursor_word_left()
550                    } else {
551                        self.cursor_left()
552                    };
553
554                if moved {
555                    event.insert(TextEvent::CURSOR_CHANGED);
556                }
557            }
558            Key::Named(NamedKey::ArrowRight) => {
559                if modifiers.contains(Modifiers::SHIFT) {
560                    self.selection_mut().set_as_range();
561                } else {
562                    self.selection_mut().set_as_cursor();
563                }
564
565                let moved = !skip_arrows_movement
566                    && if word_jump {
567                        self.cursor_word_right()
568                    } else {
569                        self.cursor_right()
570                    };
571
572                if moved {
573                    event.insert(TextEvent::CURSOR_CHANGED);
574                }
575            }
576            Key::Named(NamedKey::ArrowUp) => {
577                if modifiers.contains(Modifiers::SHIFT) {
578                    self.selection_mut().set_as_range();
579                } else {
580                    self.selection_mut().set_as_cursor();
581                }
582
583                if !skip_arrows_movement && self.cursor_up() {
584                    event.insert(TextEvent::CURSOR_CHANGED);
585                }
586            }
587            Key::Named(named_key @ (NamedKey::Home | NamedKey::End)) => {
588                if modifiers.contains(Modifiers::SHIFT) {
589                    self.selection_mut().set_as_range();
590                } else {
591                    self.selection_mut().set_as_cursor();
592                }
593
594                let whole_text = modifiers.contains(Modifiers::ctrl_or_meta());
595                let pos = match (named_key, whole_text) {
596                    (NamedKey::Home, true) => 0,
597                    (NamedKey::Home, false) => {
598                        self.char_to_utf16_cu(self.line_to_char(self.cursor_row()))
599                    }
600                    (_, true) => self.len_utf16_cu(),
601                    (_, false) => self
602                        .line_end_position(self.cursor_row())
603                        .unwrap_or_else(|| self.len_utf16_cu()),
604                };
605
606                if pos != self.cursor_pos() {
607                    self.selection_mut().move_to(pos);
608                    event.insert(TextEvent::CURSOR_CHANGED);
609                }
610            }
611            Key::Named(NamedKey::Backspace) if allow_changes => {
612                let cursor_pos = self.cursor_pos();
613
614                let removal = if let Some((start, end)) = self.get_selection_range() {
615                    Some(start..end)
616                } else if word_jump {
617                    self.prev_word_pos(cursor_pos)
618                        .map(|start| start..cursor_pos)
619                } else if cursor_pos > 0 {
620                    Some(self.grapheme_cluster_at(cursor_pos - 1).start..cursor_pos)
621                } else {
622                    None
623                };
624
625                if let Some(removal) = removal {
626                    let end = removal.end;
627                    let removed_text_len = self.remove(removal);
628                    self.move_cursor_to(end - removed_text_len);
629                    event.insert(TextEvent::TEXT_CHANGED);
630                }
631            }
632            Key::Named(NamedKey::Delete) if allow_changes => {
633                let cursor_pos = self.cursor_pos();
634
635                let removal = if let Some((start, end)) = self.get_selection_range() {
636                    Some(start..end)
637                } else if word_jump {
638                    self.next_word_pos(cursor_pos).map(|end| cursor_pos..end)
639                } else if cursor_pos < self.len_utf16_cu() {
640                    Some(cursor_pos..self.grapheme_cluster_at(cursor_pos).end)
641                } else {
642                    None
643                };
644
645                if let Some(removal) = removal {
646                    let start = removal.start;
647                    self.remove(removal);
648                    self.move_cursor_to(start);
649                    event.insert(TextEvent::TEXT_CHANGED);
650                }
651            }
652            Key::Named(NamedKey::Enter) if allow_changes => {
653                // Breaks the line
654                let cursor_pos = self.cursor_pos();
655                self.insert_char('\n', cursor_pos);
656                self.cursor_right();
657
658                event.insert(TextEvent::TEXT_CHANGED);
659            }
660            Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
661                // Inserts a tab
662                let text = " ".repeat(self.get_indentation().into());
663                let cursor_pos = self.cursor_pos();
664                self.insert(&text, cursor_pos);
665                self.move_cursor_to(cursor_pos + text.chars().count());
666
667                event.insert(TextEvent::TEXT_CHANGED);
668            }
669            Key::Character(character) => {
670                let meta_or_ctrl = modifiers.contains(Modifiers::ctrl_or_meta());
671
672                match character.as_str() {
673                    " " if allow_changes => {
674                        let selection = self.get_selection_range();
675                        if let Some((start, end)) = selection {
676                            self.remove(start..end);
677                            self.move_cursor_to(start);
678                            event.insert(TextEvent::TEXT_CHANGED);
679                        }
680
681                        // Simply adds an space
682                        let cursor_pos = self.cursor_pos();
683                        self.insert_char(' ', cursor_pos);
684                        self.cursor_right();
685
686                        event.insert(TextEvent::TEXT_CHANGED);
687                    }
688
689                    // Select all text
690                    "a" if meta_or_ctrl => {
691                        let len = self.len_utf16_cu();
692                        self.set_selection((0, len));
693                    }
694
695                    // Copy selected text
696                    "c" if meta_or_ctrl && allow_write_clipboard => {
697                        let selected = self.get_selected_text();
698                        if let Some(selected) = selected {
699                            Clipboard::set(selected).ok();
700                        }
701                    }
702
703                    // Cut selected text
704                    "x" if meta_or_ctrl && allow_changes && allow_write_clipboard => {
705                        let selection = self.get_selection_range();
706                        if let Some((start, end)) = selection {
707                            let text = self.get_selected_text().unwrap();
708                            self.remove(start..end);
709                            Clipboard::set(text).ok();
710                            self.move_cursor_to(start);
711                            event.insert(TextEvent::TEXT_CHANGED);
712                        }
713                    }
714
715                    // Paste copied text
716                    "v" if meta_or_ctrl && allow_changes && allow_read_clipboard => {
717                        if let Ok(copied_text) = Clipboard::get() {
718                            let selection = self.get_selection_range();
719                            if let Some((start, end)) = selection {
720                                self.remove(start..end);
721                                self.move_cursor_to(start);
722                            }
723                            let cursor_pos = self.cursor_pos();
724                            self.insert(&copied_text, cursor_pos);
725                            let last_idx = copied_text.encode_utf16().count() + cursor_pos;
726                            self.move_cursor_to(last_idx);
727                            event.insert(TextEvent::TEXT_CHANGED);
728                        }
729                    }
730
731                    // Undo last change
732                    "z" if meta_or_ctrl && allow_changes => {
733                        let undo_result = self.undo();
734
735                        if let Some(selection) = undo_result {
736                            *self.selection_mut() = selection;
737                            event.insert(TextEvent::TEXT_CHANGED);
738                            event.insert(TextEvent::SELECTION_CHANGED);
739                        }
740                    }
741
742                    // Redo last change
743                    "y" if meta_or_ctrl && allow_changes => {
744                        let redo_result = self.redo();
745
746                        if let Some(selection) = redo_result {
747                            *self.selection_mut() = selection;
748                            event.insert(TextEvent::TEXT_CHANGED);
749                            event.insert(TextEvent::SELECTION_CHANGED);
750                        }
751                    }
752
753                    _ if allow_changes => {
754                        // Remove selected text
755                        let selection = self.get_selection_range();
756                        if let Some((start, end)) = selection {
757                            self.remove(start..end);
758                            self.move_cursor_to(start);
759                            event.insert(TextEvent::TEXT_CHANGED);
760                        }
761
762                        if let Ok(ch) = character.parse::<char>() {
763                            // Inserts a character
764                            let cursor_pos = self.cursor_pos();
765                            let inserted_text_len = self.insert_char(ch, cursor_pos);
766                            self.move_cursor_to(cursor_pos + inserted_text_len);
767                            event.insert(TextEvent::TEXT_CHANGED);
768                        } else {
769                            // Inserts a text
770                            let cursor_pos = self.cursor_pos();
771                            let inserted_text_len = self.insert(character, cursor_pos);
772                            self.move_cursor_to(cursor_pos + inserted_text_len);
773                            event.insert(TextEvent::TEXT_CHANGED);
774                        }
775                    }
776                    _ => {}
777                }
778            }
779            _ => {}
780        }
781
782        if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
783        {
784            self.clear_selection();
785        }
786
787        if self.get_selection() != selection {
788            event.insert(TextEvent::SELECTION_CHANGED);
789        }
790
791        event
792    }
793
794    fn get_selected_text(&self) -> Option<String>;
795
796    fn undo(&mut self) -> Option<TextSelection>;
797
798    fn redo(&mut self) -> Option<TextSelection>;
799
800    fn editor_history(&self) -> &EditorHistory;
801
802    fn editor_history_mut(&mut self) -> &mut EditorHistory;
803
804    fn get_selection_range(&self) -> Option<(usize, usize)>;
805
806    fn get_indentation(&self) -> u8;
807
808    fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
809        let pos_char = self.utf16_cu_to_char(pos);
810        let len_chars = self.len_chars();
811
812        if len_chars == 0 {
813            return (pos, pos);
814        }
815
816        // Get the line containing the cursor
817        let line_idx = self.char_to_line(pos_char);
818        let line_char = self.line_to_char(line_idx);
819        let line = self.line(line_idx).unwrap();
820
821        let line_str: std::borrow::Cow<str> = line.text;
822        let pos_in_line = pos_char - line_char;
823
824        // Find word boundaries within the line
825        let mut char_offset = 0;
826        for word in line_str.split_word_bounds() {
827            let word_char_len = word.chars().count();
828            let word_start = char_offset;
829            let word_end = char_offset + word_char_len;
830
831            if pos_in_line >= word_start && pos_in_line < word_end {
832                let start_char = line_char + word_start;
833                let end_char = line_char + word_end;
834                return (
835                    self.char_to_utf16_cu(start_char),
836                    self.char_to_utf16_cu(end_char),
837                );
838            }
839
840            char_offset = word_end;
841        }
842
843        (pos, pos)
844    }
845}