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