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    /// Move the cursor to the end of the next word.
299    fn cursor_word_right(&mut self) -> bool {
300        let pos = self.cursor_pos();
301        let len = self.len_utf16_cu();
302        if pos >= len {
303            return false;
304        }
305
306        // Walk forward line by line starting at the cursor.
307        let start_char = self.utf16_cu_to_char(pos);
308        let initial_line = self.char_to_line(start_char);
309        let initial_offset = start_char - self.line_to_char(initial_line);
310
311        for line_idx in initial_line..self.len_lines() {
312            let Some(line) = self.line(line_idx) else {
313                continue;
314            };
315            let line_char_offset = self.line_to_char(line_idx);
316            let from = if line_idx == initial_line {
317                initial_offset
318            } else {
319                0
320            };
321
322            // Stop at the end of the first non-whitespace segment past the cursor.
323            let mut char_offset = 0;
324            for word in line.text.split_word_bounds() {
325                char_offset += word.chars().count();
326                if char_offset > from && !word.chars().all(char::is_whitespace) {
327                    let new_pos = self.char_to_utf16_cu(line_char_offset + char_offset);
328                    self.selection_mut().move_to(new_pos);
329                    return true;
330                }
331            }
332        }
333
334        // Trailing whitespace only, snap to text end.
335        self.selection_mut().move_to(len);
336        true
337    }
338
339    /// Move the cursor to the start of the previous word.
340    fn cursor_word_left(&mut self) -> bool {
341        let pos = self.cursor_pos();
342        if pos == 0 {
343            return false;
344        }
345
346        // Walk backward line by line starting at the cursor.
347        let start_char = self.utf16_cu_to_char(pos);
348        let initial_line = self.char_to_line(start_char);
349        let initial_offset = start_char - self.line_to_char(initial_line);
350
351        for line_idx in (0..=initial_line).rev() {
352            let Some(line) = self.line(line_idx) else {
353                continue;
354            };
355            let line_char_offset = self.line_to_char(line_idx);
356            let to = if line_idx == initial_line {
357                initial_offset
358            } else {
359                line.text.chars().count()
360            };
361
362            // Track the latest non-whitespace segment that starts before the cursor.
363            let mut char_offset = 0;
364            let mut last_word_start = None;
365            for word in line.text.split_word_bounds() {
366                if char_offset >= to {
367                    break;
368                }
369                if !word.chars().all(char::is_whitespace) {
370                    last_word_start = Some(char_offset);
371                }
372                char_offset += word.chars().count();
373            }
374
375            // Found one on this line, jump to its start.
376            if let Some(start) = last_word_start {
377                let new_pos = self.char_to_utf16_cu(line_char_offset + start);
378                self.selection_mut().move_to(new_pos);
379                return true;
380            }
381        }
382
383        // Leading whitespace only, snap to text start.
384        self.selection_mut().move_to(0);
385        true
386    }
387
388    /// Get the cursor position
389    fn cursor_pos(&self) -> usize {
390        self.selection().pos()
391    }
392
393    /// Move the cursor position
394    fn move_cursor_to(&mut self, pos: usize) {
395        self.selection_mut().move_to(pos);
396    }
397
398    // Check if has any selection at all
399    fn has_any_selection(&self) -> bool;
400
401    // Return the selected text
402    fn get_selection(&self) -> Option<(usize, usize)>;
403
404    // Return the visible selected text for the given editor line
405    fn get_visible_selection(&self, editor_line: EditorLine) -> Option<(usize, usize)> {
406        let (selected_from, selected_to) = match self.selection() {
407            TextSelection::Cursor(_) => return None,
408            TextSelection::Range { from, to } => (*from, *to),
409        };
410
411        match editor_line {
412            EditorLine::Paragraph(line_index) => {
413                let selected_from_row = self.char_to_line(self.utf16_cu_to_char(selected_from));
414                let selected_to_row = self.char_to_line(self.utf16_cu_to_char(selected_to));
415
416                let editor_row_idx = self.char_to_utf16_cu(self.line_to_char(line_index));
417                let selected_from_row_idx =
418                    self.char_to_utf16_cu(self.line_to_char(selected_from_row));
419                let selected_to_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_to_row));
420
421                let selected_from_col_idx = selected_from - selected_from_row_idx;
422                let selected_to_col_idx = selected_to - selected_to_row_idx;
423
424                // Between starting line and endling line
425                if (line_index > selected_from_row && line_index < selected_to_row)
426                    || (line_index < selected_from_row && line_index > selected_to_row)
427                {
428                    let len = self.line(line_index).unwrap().utf16_len();
429                    return Some((0, len));
430                }
431
432                match selected_from_row.cmp(&selected_to_row) {
433                    // Selection direction is from bottom -> top
434                    Ordering::Greater => {
435                        if selected_from_row == line_index {
436                            // Starting line
437                            Some((0, selected_from_col_idx))
438                        } else if selected_to_row == line_index {
439                            // Ending line
440                            let len = self.line(selected_to_row).unwrap().utf16_len();
441                            Some((selected_to_col_idx, len))
442                        } else {
443                            None
444                        }
445                    }
446                    // Selection direction is from top -> bottom
447                    Ordering::Less => {
448                        if selected_from_row == line_index {
449                            // Starting line
450                            let len = self.line(selected_from_row).unwrap().utf16_len();
451                            Some((selected_from_col_idx, len))
452                        } else if selected_to_row == line_index {
453                            // Ending line
454                            Some((0, selected_to_col_idx))
455                        } else {
456                            None
457                        }
458                    }
459                    Ordering::Equal if selected_from_row == line_index => {
460                        // Starting and endline line are the same
461                        Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
462                    }
463                    _ => None,
464                }
465            }
466            EditorLine::SingleParagraph => Some((selected_from, selected_to)),
467        }
468    }
469
470    // Remove the selection
471    fn clear_selection(&mut self);
472
473    // Select some text
474    fn set_selection(&mut self, selected: (usize, usize));
475
476    // Measure a new text selection
477
478    fn measure_selection(&self, to: usize, line_index: EditorLine) -> TextSelection {
479        let mut selection = self.selection().clone();
480
481        match line_index {
482            EditorLine::Paragraph(line_index) => {
483                let row_char = self.line_to_char(line_index);
484                let pos = self.char_to_utf16_cu(row_char) + to;
485                selection.move_to(pos);
486            }
487            EditorLine::SingleParagraph => {
488                selection.move_to(to);
489            }
490        }
491
492        selection
493    }
494
495    // Process a Keyboard event
496    fn process_key(
497        &mut self,
498        key: &Key,
499        modifiers: &Modifiers,
500        allow_tabs: bool,
501        allow_changes: bool,
502        allow_read_clipboard: bool,
503        allow_write_clipboard: bool,
504    ) -> TextEvent {
505        let mut event = TextEvent::empty();
506
507        let selection = self.get_selection();
508        let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
509
510        match key {
511            Key::Named(NamedKey::Shift) => {}
512            Key::Named(NamedKey::Control) => {}
513            Key::Named(NamedKey::Alt) => {}
514            Key::Named(NamedKey::Escape) => {
515                self.clear_selection();
516            }
517            Key::Named(NamedKey::ArrowDown) => {
518                if modifiers.contains(Modifiers::SHIFT) {
519                    self.selection_mut().set_as_range();
520                } else {
521                    self.selection_mut().set_as_cursor();
522                }
523
524                if !skip_arrows_movement && self.cursor_down() {
525                    event.insert(TextEvent::CURSOR_CHANGED);
526                }
527            }
528            Key::Named(NamedKey::ArrowLeft) => {
529                if modifiers.contains(Modifiers::SHIFT) {
530                    self.selection_mut().set_as_range();
531                } else {
532                    self.selection_mut().set_as_cursor();
533                }
534
535                let word_jump = if cfg!(target_os = "macos") {
536                    modifiers.contains(Modifiers::ALT)
537                } else {
538                    modifiers.contains(Modifiers::CONTROL)
539                };
540
541                let moved = !skip_arrows_movement
542                    && if word_jump {
543                        self.cursor_word_left()
544                    } else {
545                        self.cursor_left()
546                    };
547
548                if moved {
549                    event.insert(TextEvent::CURSOR_CHANGED);
550                }
551            }
552            Key::Named(NamedKey::ArrowRight) => {
553                if modifiers.contains(Modifiers::SHIFT) {
554                    self.selection_mut().set_as_range();
555                } else {
556                    self.selection_mut().set_as_cursor();
557                }
558
559                let word_jump = if cfg!(target_os = "macos") {
560                    modifiers.contains(Modifiers::ALT)
561                } else {
562                    modifiers.contains(Modifiers::CONTROL)
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                let selection = self.get_selection_range();
614
615                if let Some((start, end)) = selection {
616                    self.remove(start..end);
617                    self.move_cursor_to(start);
618                    event.insert(TextEvent::TEXT_CHANGED);
619                } else if cursor_pos > 0 {
620                    let remove_from = self.grapheme_cluster_at(cursor_pos - 1).start;
621                    self.remove(remove_from..cursor_pos);
622                    self.move_cursor_to(remove_from);
623                    event.insert(TextEvent::TEXT_CHANGED);
624                }
625            }
626            Key::Named(NamedKey::Delete) if allow_changes => {
627                let cursor_pos = self.cursor_pos();
628                let selection = self.get_selection_range();
629
630                if let Some((start, end)) = selection {
631                    self.remove(start..end);
632                    self.move_cursor_to(start);
633                    event.insert(TextEvent::TEXT_CHANGED);
634                } else if cursor_pos < self.len_utf16_cu() {
635                    let remove_to = self.grapheme_cluster_at(cursor_pos).end;
636                    self.remove(cursor_pos..remove_to);
637                    event.insert(TextEvent::TEXT_CHANGED);
638                }
639            }
640            Key::Named(NamedKey::Enter) if allow_changes => {
641                // Breaks the line
642                let cursor_pos = self.cursor_pos();
643                self.insert_char('\n', cursor_pos);
644                self.cursor_right();
645
646                event.insert(TextEvent::TEXT_CHANGED);
647            }
648            Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
649                // Inserts a tab
650                let text = " ".repeat(self.get_indentation().into());
651                let cursor_pos = self.cursor_pos();
652                self.insert(&text, cursor_pos);
653                self.move_cursor_to(cursor_pos + text.chars().count());
654
655                event.insert(TextEvent::TEXT_CHANGED);
656            }
657            Key::Character(character) => {
658                let meta_or_ctrl = modifiers.contains(Modifiers::ctrl_or_meta());
659
660                match character.as_str() {
661                    " " if allow_changes => {
662                        let selection = self.get_selection_range();
663                        if let Some((start, end)) = selection {
664                            self.remove(start..end);
665                            self.move_cursor_to(start);
666                            event.insert(TextEvent::TEXT_CHANGED);
667                        }
668
669                        // Simply adds an space
670                        let cursor_pos = self.cursor_pos();
671                        self.insert_char(' ', cursor_pos);
672                        self.cursor_right();
673
674                        event.insert(TextEvent::TEXT_CHANGED);
675                    }
676
677                    // Select all text
678                    "a" if meta_or_ctrl => {
679                        let len = self.len_utf16_cu();
680                        self.set_selection((0, len));
681                    }
682
683                    // Copy selected text
684                    "c" if meta_or_ctrl && allow_write_clipboard => {
685                        let selected = self.get_selected_text();
686                        if let Some(selected) = selected {
687                            Clipboard::set(selected).ok();
688                        }
689                    }
690
691                    // Cut selected text
692                    "x" if meta_or_ctrl && allow_changes && allow_write_clipboard => {
693                        let selection = self.get_selection_range();
694                        if let Some((start, end)) = selection {
695                            let text = self.get_selected_text().unwrap();
696                            self.remove(start..end);
697                            Clipboard::set(text).ok();
698                            self.move_cursor_to(start);
699                            event.insert(TextEvent::TEXT_CHANGED);
700                        }
701                    }
702
703                    // Paste copied text
704                    "v" if meta_or_ctrl && allow_changes && allow_read_clipboard => {
705                        if let Ok(copied_text) = Clipboard::get() {
706                            let selection = self.get_selection_range();
707                            if let Some((start, end)) = selection {
708                                self.remove(start..end);
709                                self.move_cursor_to(start);
710                            }
711                            let cursor_pos = self.cursor_pos();
712                            self.insert(&copied_text, cursor_pos);
713                            let last_idx = copied_text.encode_utf16().count() + cursor_pos;
714                            self.move_cursor_to(last_idx);
715                            event.insert(TextEvent::TEXT_CHANGED);
716                        }
717                    }
718
719                    // Undo last change
720                    "z" if meta_or_ctrl && allow_changes => {
721                        let undo_result = self.undo();
722
723                        if let Some(selection) = undo_result {
724                            *self.selection_mut() = selection;
725                            event.insert(TextEvent::TEXT_CHANGED);
726                            event.insert(TextEvent::SELECTION_CHANGED);
727                        }
728                    }
729
730                    // Redo last change
731                    "y" if meta_or_ctrl && allow_changes => {
732                        let redo_result = self.redo();
733
734                        if let Some(selection) = redo_result {
735                            *self.selection_mut() = selection;
736                            event.insert(TextEvent::TEXT_CHANGED);
737                            event.insert(TextEvent::SELECTION_CHANGED);
738                        }
739                    }
740
741                    _ if allow_changes => {
742                        // Remove selected text
743                        let selection = self.get_selection_range();
744                        if let Some((start, end)) = selection {
745                            self.remove(start..end);
746                            self.move_cursor_to(start);
747                            event.insert(TextEvent::TEXT_CHANGED);
748                        }
749
750                        if let Ok(ch) = character.parse::<char>() {
751                            // Inserts a character
752                            let cursor_pos = self.cursor_pos();
753                            let inserted_text_len = self.insert_char(ch, cursor_pos);
754                            self.move_cursor_to(cursor_pos + inserted_text_len);
755                            event.insert(TextEvent::TEXT_CHANGED);
756                        } else {
757                            // Inserts a text
758                            let cursor_pos = self.cursor_pos();
759                            let inserted_text_len = self.insert(character, cursor_pos);
760                            self.move_cursor_to(cursor_pos + inserted_text_len);
761                            event.insert(TextEvent::TEXT_CHANGED);
762                        }
763                    }
764                    _ => {}
765                }
766            }
767            _ => {}
768        }
769
770        if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
771        {
772            self.clear_selection();
773        }
774
775        if self.get_selection() != selection {
776            event.insert(TextEvent::SELECTION_CHANGED);
777        }
778
779        event
780    }
781
782    fn get_selected_text(&self) -> Option<String>;
783
784    fn undo(&mut self) -> Option<TextSelection>;
785
786    fn redo(&mut self) -> Option<TextSelection>;
787
788    fn editor_history(&mut self) -> &mut EditorHistory;
789
790    fn get_selection_range(&self) -> Option<(usize, usize)>;
791
792    fn get_indentation(&self) -> u8;
793
794    fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
795        let pos_char = self.utf16_cu_to_char(pos);
796        let len_chars = self.len_chars();
797
798        if len_chars == 0 {
799            return (pos, pos);
800        }
801
802        // Get the line containing the cursor
803        let line_idx = self.char_to_line(pos_char);
804        let line_char = self.line_to_char(line_idx);
805        let line = self.line(line_idx).unwrap();
806
807        let line_str: std::borrow::Cow<str> = line.text;
808        let pos_in_line = pos_char - line_char;
809
810        // Find word boundaries within the line
811        let mut char_offset = 0;
812        for word in line_str.split_word_bounds() {
813            let word_char_len = word.chars().count();
814            let word_start = char_offset;
815            let word_end = char_offset + word_char_len;
816
817            if pos_in_line >= word_start && pos_in_line < word_end {
818                let start_char = line_char + word_start;
819                let end_char = line_char + word_end;
820                return (
821                    self.char_to_utf16_cu(start_char),
822                    self.char_to_utf16_cu(end_char),
823                );
824            }
825
826            char_offset = word_end;
827        }
828
829        (pos, pos)
830    }
831}