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 SingleParagraph,
30 Paragraph(usize),
32}
33
34#[derive(Clone, PartialEq, Debug)]
36pub enum TextSelection {
37 Cursor(usize),
38 Range { from: usize, to: usize },
39}
40
41impl TextSelection {
42 pub fn new_cursor(pos: usize) -> Self {
44 Self::Cursor(pos)
45 }
46
47 pub fn new_range((from, to): (usize, usize)) -> Self {
49 Self::Range { from, to }
50 }
51
52 pub fn pos(&self) -> usize {
54 self.end()
55 }
56
57 pub fn set_as_cursor(&mut self) {
59 *self = Self::Cursor(self.end())
60 }
61
62 pub fn set_as_range(&mut self) {
64 *self = Self::Range {
65 from: self.start(),
66 to: self.end(),
67 }
68 }
69
70 pub fn start(&self) -> usize {
72 match self {
73 Self::Cursor(pos) => *pos,
74 Self::Range { from, .. } => *from,
75 }
76 }
77
78 pub fn end(&self) -> usize {
80 match self {
81 Self::Cursor(pos) => *pos,
82 Self::Range { to, .. } => *to,
83 }
84 }
85
86 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#[derive(Clone)]
103pub struct Line<'a> {
104 pub text: Cow<'a, str>,
105 pub utf16_len: usize,
106}
107
108impl Line<'_> {
109 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 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
124 pub struct TextEvent: u8 {
125 const CURSOR_CHANGED = 0x01;
127 const TEXT_CHANGED = 0x02;
129 const SELECTION_CHANGED = 0x04;
131 }
132}
133
134pub 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 fn lines(&self) -> Self::LinesIterator<'_>;
144
145 fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
147
148 fn insert(&mut self, text: &str, char_idx: usize) -> usize;
150
151 fn remove(&mut self, range: Range<usize>) -> usize;
153
154 fn char_to_line(&self, char_idx: usize) -> usize;
156
157 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 fn line(&self, line_idx: usize) -> Option<Line<'_>>;
166
167 fn text(&self) -> Cow<'_, str>;
168
169 fn len_lines(&self) -> usize;
171
172 fn len_chars(&self) -> usize;
174
175 fn len_utf16_cu(&self) -> usize;
177
178 fn selection(&self) -> &TextSelection;
180
181 fn selection_mut(&mut self) -> &mut TextSelection;
183
184 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 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 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 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 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 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 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 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 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 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 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 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 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 Some(len)
393 }
394
395 fn prev_word_pos(&self, pos: usize) -> Option<usize> {
397 if pos == 0 {
398 return None;
399 }
400
401 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 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 Some(0)
437 }
438
439 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 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 fn cursor_pos(&self) -> usize {
461 self.selection().pos()
462 }
463
464 fn move_cursor_to(&mut self, pos: usize) {
466 self.selection_mut().move_to(pos);
467 }
468
469 fn has_any_selection(&self) -> bool;
471
472 fn get_selection(&self) -> Option<(usize, usize)>;
474
475 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 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 Ordering::Greater => {
506 if selected_from_row == line_index {
507 Some((0, selected_from_col_idx))
509 } else if selected_to_row == line_index {
510 let len = self.line(selected_to_row).unwrap().utf16_len();
512 Some((selected_to_col_idx, len))
513 } else {
514 None
515 }
516 }
517 Ordering::Less => {
519 if selected_from_row == line_index {
520 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 Some((0, selected_to_col_idx))
526 } else {
527 None
528 }
529 }
530 Ordering::Equal if selected_from_row == line_index => {
531 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 fn clear_selection(&mut self);
543
544 fn set_selection(&mut self, selected: (usize, usize));
546
547 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 #[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 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 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 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 "a" if meta_or_ctrl => {
754 let len = self.len_utf16_cu();
755 self.set_selection((0, len));
756 }
757
758 "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 "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 "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 "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 "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 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 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 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 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 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}