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 SingleParagraph,
23 Paragraph(usize),
25}
26
27#[derive(Clone, PartialEq, Debug)]
29pub enum TextSelection {
30 Cursor(usize),
31 Range { from: usize, to: usize },
32}
33
34impl TextSelection {
35 pub fn new_cursor(pos: usize) -> Self {
37 Self::Cursor(pos)
38 }
39
40 pub fn new_range((from, to): (usize, usize)) -> Self {
42 Self::Range { from, to }
43 }
44
45 pub fn pos(&self) -> usize {
47 self.end()
48 }
49
50 pub fn set_as_cursor(&mut self) {
52 *self = Self::Cursor(self.end())
53 }
54
55 pub fn set_as_range(&mut self) {
57 *self = Self::Range {
58 from: self.start(),
59 to: self.end(),
60 }
61 }
62
63 pub fn start(&self) -> usize {
65 match self {
66 Self::Cursor(pos) => *pos,
67 Self::Range { from, .. } => *from,
68 }
69 }
70
71 pub fn end(&self) -> usize {
73 match self {
74 Self::Cursor(pos) => *pos,
75 Self::Range { to, .. } => *to,
76 }
77 }
78
79 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#[derive(Clone)]
96pub struct Line<'a> {
97 pub text: Cow<'a, str>,
98 pub utf16_len: usize,
99}
100
101impl Line<'_> {
102 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 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
117 pub struct TextEvent: u8 {
118 const CURSOR_CHANGED = 0x01;
120 const TEXT_CHANGED = 0x02;
122 const SELECTION_CHANGED = 0x04;
124 }
125}
126
127pub 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 fn lines(&self) -> Self::LinesIterator<'_>;
137
138 fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
140
141 fn insert(&mut self, text: &str, char_idx: usize) -> usize;
143
144 fn remove(&mut self, range: Range<usize>) -> usize;
146
147 fn char_to_line(&self, char_idx: usize) -> usize;
149
150 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 fn line(&self, line_idx: usize) -> Option<Line<'_>>;
159
160 fn len_lines(&self) -> usize;
162
163 fn len_chars(&self) -> usize;
165
166 fn len_utf16_cu(&self) -> usize;
168
169 fn selection(&self) -> &TextSelection;
171
172 fn selection_mut(&mut self) -> &mut TextSelection;
174
175 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 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 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 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 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 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 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 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 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 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 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 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 self.selection_mut().move_to(len);
336 true
337 }
338
339 fn cursor_word_left(&mut self) -> bool {
341 let pos = self.cursor_pos();
342 if pos == 0 {
343 return false;
344 }
345
346 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 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 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 self.selection_mut().move_to(0);
385 true
386 }
387
388 fn cursor_pos(&self) -> usize {
390 self.selection().pos()
391 }
392
393 fn move_cursor_to(&mut self, pos: usize) {
395 self.selection_mut().move_to(pos);
396 }
397
398 fn has_any_selection(&self) -> bool;
400
401 fn get_selection(&self) -> Option<(usize, usize)>;
403
404 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 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 Ordering::Greater => {
435 if selected_from_row == line_index {
436 Some((0, selected_from_col_idx))
438 } else if selected_to_row == line_index {
439 let len = self.line(selected_to_row).unwrap().utf16_len();
441 Some((selected_to_col_idx, len))
442 } else {
443 None
444 }
445 }
446 Ordering::Less => {
448 if selected_from_row == line_index {
449 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 Some((0, selected_to_col_idx))
455 } else {
456 None
457 }
458 }
459 Ordering::Equal if selected_from_row == line_index => {
460 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 fn clear_selection(&mut self);
472
473 fn set_selection(&mut self, selected: (usize, usize));
475
476 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 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 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 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 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 "a" if meta_or_ctrl => {
679 let len = self.len_utf16_cu();
680 self.set_selection((0, len));
681 }
682
683 "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 "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 "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 "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 "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 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 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 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 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 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}