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 cursor_row(&self) -> usize {
177 let pos = self.cursor_pos();
178 let pos_utf8 = self.utf16_cu_to_char(pos);
179 self.char_to_line(pos_utf8)
180 }
181
182 fn cursor_col(&self) -> usize {
184 let pos = self.cursor_pos();
185 let pos_utf8 = self.utf16_cu_to_char(pos);
186 let line = self.char_to_line(pos_utf8);
187 let line_char_utf8 = self.line_to_char(line);
188 let line_char = self.char_to_utf16_cu(line_char_utf8);
189 pos - line_char
190 }
191
192 fn cursor_down(&mut self) -> bool {
194 let old_row = self.cursor_row();
195 let old_col = self.cursor_col();
196
197 match old_row.cmp(&(self.len_lines() - 1)) {
198 Ordering::Less => {
199 let new_row = old_row + 1;
201 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
202 let new_row_len = self.line(new_row).unwrap().utf16_len();
203 let new_col = old_col.min(new_row_len.saturating_sub(1));
204 self.selection_mut().move_to(new_row_char + new_col);
205
206 true
207 }
208 Ordering::Equal => {
209 let end = self.len_utf16_cu();
210 self.selection_mut().move_to(end);
212
213 true
214 }
215 Ordering::Greater => {
216 false
219 }
220 }
221 }
222
223 fn cursor_up(&mut self) -> bool {
225 let pos = self.cursor_pos();
226 let old_row = self.cursor_row();
227 let old_col = self.cursor_col();
228
229 if pos > 0 {
230 if old_row == 0 {
232 self.selection_mut().move_to(0);
233 } else {
234 let new_row = old_row - 1;
235 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
236 let new_row_len = self.line(new_row).unwrap().utf16_len();
237 let new_col = old_col.min(new_row_len.saturating_sub(1));
238 self.selection_mut().move_to(new_row_char + new_col);
239 }
240
241 true
242 } else {
243 false
244 }
245 }
246
247 fn cursor_right(&mut self) -> bool {
249 if self.cursor_pos() < self.len_utf16_cu() {
250 let to = self.selection().end() + 1;
251 self.selection_mut().move_to(to);
252
253 true
254 } else {
255 false
256 }
257 }
258
259 fn cursor_left(&mut self) -> bool {
261 if self.cursor_pos() > 0 {
262 let to = self.selection().end() - 1;
263 self.selection_mut().move_to(to);
264
265 true
266 } else {
267 false
268 }
269 }
270
271 fn cursor_word_right(&mut self) -> bool {
273 let pos = self.cursor_pos();
274 let len = self.len_utf16_cu();
275 if pos >= len {
276 return false;
277 }
278
279 let start_char = self.utf16_cu_to_char(pos);
281 let initial_line = self.char_to_line(start_char);
282 let initial_offset = start_char - self.line_to_char(initial_line);
283
284 for line_idx in initial_line..self.len_lines() {
285 let Some(line) = self.line(line_idx) else {
286 continue;
287 };
288 let line_char_offset = self.line_to_char(line_idx);
289 let from = if line_idx == initial_line {
290 initial_offset
291 } else {
292 0
293 };
294
295 let mut char_offset = 0;
297 for word in line.text.split_word_bounds() {
298 char_offset += word.chars().count();
299 if char_offset > from && !word.chars().all(char::is_whitespace) {
300 let new_pos = self.char_to_utf16_cu(line_char_offset + char_offset);
301 self.selection_mut().move_to(new_pos);
302 return true;
303 }
304 }
305 }
306
307 self.selection_mut().move_to(len);
309 true
310 }
311
312 fn cursor_word_left(&mut self) -> bool {
314 let pos = self.cursor_pos();
315 if pos == 0 {
316 return false;
317 }
318
319 let start_char = self.utf16_cu_to_char(pos);
321 let initial_line = self.char_to_line(start_char);
322 let initial_offset = start_char - self.line_to_char(initial_line);
323
324 for line_idx in (0..=initial_line).rev() {
325 let Some(line) = self.line(line_idx) else {
326 continue;
327 };
328 let line_char_offset = self.line_to_char(line_idx);
329 let to = if line_idx == initial_line {
330 initial_offset
331 } else {
332 line.text.chars().count()
333 };
334
335 let mut char_offset = 0;
337 let mut last_word_start = None;
338 for word in line.text.split_word_bounds() {
339 if char_offset >= to {
340 break;
341 }
342 if !word.chars().all(char::is_whitespace) {
343 last_word_start = Some(char_offset);
344 }
345 char_offset += word.chars().count();
346 }
347
348 if let Some(start) = last_word_start {
350 let new_pos = self.char_to_utf16_cu(line_char_offset + start);
351 self.selection_mut().move_to(new_pos);
352 return true;
353 }
354 }
355
356 self.selection_mut().move_to(0);
358 true
359 }
360
361 fn cursor_pos(&self) -> usize {
363 self.selection().pos()
364 }
365
366 fn move_cursor_to(&mut self, pos: usize) {
368 self.selection_mut().move_to(pos);
369 }
370
371 fn has_any_selection(&self) -> bool;
373
374 fn get_selection(&self) -> Option<(usize, usize)>;
376
377 fn get_visible_selection(&self, editor_line: EditorLine) -> Option<(usize, usize)> {
379 let (selected_from, selected_to) = match self.selection() {
380 TextSelection::Cursor(_) => return None,
381 TextSelection::Range { from, to } => (*from, *to),
382 };
383
384 match editor_line {
385 EditorLine::Paragraph(line_index) => {
386 let selected_from_row = self.char_to_line(self.utf16_cu_to_char(selected_from));
387 let selected_to_row = self.char_to_line(self.utf16_cu_to_char(selected_to));
388
389 let editor_row_idx = self.char_to_utf16_cu(self.line_to_char(line_index));
390 let selected_from_row_idx =
391 self.char_to_utf16_cu(self.line_to_char(selected_from_row));
392 let selected_to_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_to_row));
393
394 let selected_from_col_idx = selected_from - selected_from_row_idx;
395 let selected_to_col_idx = selected_to - selected_to_row_idx;
396
397 if (line_index > selected_from_row && line_index < selected_to_row)
399 || (line_index < selected_from_row && line_index > selected_to_row)
400 {
401 let len = self.line(line_index).unwrap().utf16_len();
402 return Some((0, len));
403 }
404
405 match selected_from_row.cmp(&selected_to_row) {
406 Ordering::Greater => {
408 if selected_from_row == line_index {
409 Some((0, selected_from_col_idx))
411 } else if selected_to_row == line_index {
412 let len = self.line(selected_to_row).unwrap().utf16_len();
414 Some((selected_to_col_idx, len))
415 } else {
416 None
417 }
418 }
419 Ordering::Less => {
421 if selected_from_row == line_index {
422 let len = self.line(selected_from_row).unwrap().utf16_len();
424 Some((selected_from_col_idx, len))
425 } else if selected_to_row == line_index {
426 Some((0, selected_to_col_idx))
428 } else {
429 None
430 }
431 }
432 Ordering::Equal if selected_from_row == line_index => {
433 Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
435 }
436 _ => None,
437 }
438 }
439 EditorLine::SingleParagraph => Some((selected_from, selected_to)),
440 }
441 }
442
443 fn clear_selection(&mut self);
445
446 fn set_selection(&mut self, selected: (usize, usize));
448
449 fn measure_selection(&self, to: usize, line_index: EditorLine) -> TextSelection {
452 let mut selection = self.selection().clone();
453
454 match line_index {
455 EditorLine::Paragraph(line_index) => {
456 let row_char = self.line_to_char(line_index);
457 let pos = self.char_to_utf16_cu(row_char) + to;
458 selection.move_to(pos);
459 }
460 EditorLine::SingleParagraph => {
461 selection.move_to(to);
462 }
463 }
464
465 selection
466 }
467
468 fn process_key(
470 &mut self,
471 key: &Key,
472 modifiers: &Modifiers,
473 allow_tabs: bool,
474 allow_changes: bool,
475 allow_clipboard: bool,
476 ) -> TextEvent {
477 let mut event = TextEvent::empty();
478
479 let selection = self.get_selection();
480 let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
481
482 match key {
483 Key::Named(NamedKey::Shift) => {}
484 Key::Named(NamedKey::Control) => {}
485 Key::Named(NamedKey::Alt) => {}
486 Key::Named(NamedKey::Escape) => {
487 self.clear_selection();
488 }
489 Key::Named(NamedKey::ArrowDown) => {
490 if modifiers.contains(Modifiers::SHIFT) {
491 self.selection_mut().set_as_range();
492 } else {
493 self.selection_mut().set_as_cursor();
494 }
495
496 if !skip_arrows_movement && self.cursor_down() {
497 event.insert(TextEvent::CURSOR_CHANGED);
498 }
499 }
500 Key::Named(NamedKey::ArrowLeft) => {
501 if modifiers.contains(Modifiers::SHIFT) {
502 self.selection_mut().set_as_range();
503 } else {
504 self.selection_mut().set_as_cursor();
505 }
506
507 let word_jump = if cfg!(target_os = "macos") {
508 modifiers.contains(Modifiers::ALT)
509 } else {
510 modifiers.contains(Modifiers::CONTROL)
511 };
512
513 let moved = !skip_arrows_movement
514 && if word_jump {
515 self.cursor_word_left()
516 } else {
517 self.cursor_left()
518 };
519
520 if moved {
521 event.insert(TextEvent::CURSOR_CHANGED);
522 }
523 }
524 Key::Named(NamedKey::ArrowRight) => {
525 if modifiers.contains(Modifiers::SHIFT) {
526 self.selection_mut().set_as_range();
527 } else {
528 self.selection_mut().set_as_cursor();
529 }
530
531 let word_jump = if cfg!(target_os = "macos") {
532 modifiers.contains(Modifiers::ALT)
533 } else {
534 modifiers.contains(Modifiers::CONTROL)
535 };
536
537 let moved = !skip_arrows_movement
538 && if word_jump {
539 self.cursor_word_right()
540 } else {
541 self.cursor_right()
542 };
543
544 if moved {
545 event.insert(TextEvent::CURSOR_CHANGED);
546 }
547 }
548 Key::Named(NamedKey::ArrowUp) => {
549 if modifiers.contains(Modifiers::SHIFT) {
550 self.selection_mut().set_as_range();
551 } else {
552 self.selection_mut().set_as_cursor();
553 }
554
555 if !skip_arrows_movement && self.cursor_up() {
556 event.insert(TextEvent::CURSOR_CHANGED);
557 }
558 }
559 Key::Named(NamedKey::Backspace) if allow_changes => {
560 let cursor_pos = self.cursor_pos();
561 let selection = self.get_selection_range();
562
563 if let Some((start, end)) = selection {
564 self.remove(start..end);
565 self.move_cursor_to(start);
566 event.insert(TextEvent::TEXT_CHANGED);
567 } else if cursor_pos > 0 {
568 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
570 self.move_cursor_to(cursor_pos - removed_text_len);
571 event.insert(TextEvent::TEXT_CHANGED);
572 }
573 }
574 Key::Named(NamedKey::Delete) if allow_changes => {
575 let cursor_pos = self.cursor_pos();
576 let selection = self.get_selection_range();
577
578 if let Some((start, end)) = selection {
579 self.remove(start..end);
580 self.move_cursor_to(start);
581 event.insert(TextEvent::TEXT_CHANGED);
582 } else if cursor_pos < self.len_utf16_cu() {
583 self.remove(cursor_pos..cursor_pos + 1);
585 event.insert(TextEvent::TEXT_CHANGED);
586 }
587 }
588 Key::Named(NamedKey::Enter) if allow_changes => {
589 let cursor_pos = self.cursor_pos();
591 self.insert_char('\n', cursor_pos);
592 self.cursor_right();
593
594 event.insert(TextEvent::TEXT_CHANGED);
595 }
596 Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
597 let text = " ".repeat(self.get_indentation().into());
599 let cursor_pos = self.cursor_pos();
600 self.insert(&text, cursor_pos);
601 self.move_cursor_to(cursor_pos + text.chars().count());
602
603 event.insert(TextEvent::TEXT_CHANGED);
604 }
605 Key::Character(character) => {
606 let meta_or_ctrl = modifiers.ctrl_or_meta();
607
608 match character.as_str() {
609 " " if allow_changes => {
610 let selection = self.get_selection_range();
611 if let Some((start, end)) = selection {
612 self.remove(start..end);
613 self.move_cursor_to(start);
614 event.insert(TextEvent::TEXT_CHANGED);
615 }
616
617 let cursor_pos = self.cursor_pos();
619 self.insert_char(' ', cursor_pos);
620 self.cursor_right();
621
622 event.insert(TextEvent::TEXT_CHANGED);
623 }
624
625 "a" if meta_or_ctrl => {
627 let len = self.len_utf16_cu();
628 self.set_selection((0, len));
629 }
630
631 "c" if meta_or_ctrl && allow_clipboard => {
633 let selected = self.get_selected_text();
634 if let Some(selected) = selected {
635 Clipboard::set(selected).ok();
636 }
637 }
638
639 "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
641 let selection = self.get_selection_range();
642 if let Some((start, end)) = selection {
643 let text = self.get_selected_text().unwrap();
644 self.remove(start..end);
645 Clipboard::set(text).ok();
646 self.move_cursor_to(start);
647 event.insert(TextEvent::TEXT_CHANGED);
648 }
649 }
650
651 "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
653 if let Ok(copied_text) = Clipboard::get() {
654 let selection = self.get_selection_range();
655 if let Some((start, end)) = selection {
656 self.remove(start..end);
657 self.move_cursor_to(start);
658 }
659 let cursor_pos = self.cursor_pos();
660 self.insert(&copied_text, cursor_pos);
661 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
662 self.move_cursor_to(last_idx);
663 event.insert(TextEvent::TEXT_CHANGED);
664 }
665 }
666
667 "z" if meta_or_ctrl && allow_changes => {
669 let undo_result = self.undo();
670
671 if let Some(selection) = undo_result {
672 *self.selection_mut() = selection;
673 event.insert(TextEvent::TEXT_CHANGED);
674 event.insert(TextEvent::SELECTION_CHANGED);
675 }
676 }
677
678 "y" if meta_or_ctrl && allow_changes => {
680 let redo_result = self.redo();
681
682 if let Some(selection) = redo_result {
683 *self.selection_mut() = selection;
684 event.insert(TextEvent::TEXT_CHANGED);
685 event.insert(TextEvent::SELECTION_CHANGED);
686 }
687 }
688
689 _ if allow_changes => {
690 let selection = self.get_selection_range();
692 if let Some((start, end)) = selection {
693 self.remove(start..end);
694 self.move_cursor_to(start);
695 event.insert(TextEvent::TEXT_CHANGED);
696 }
697
698 if let Ok(ch) = character.parse::<char>() {
699 let cursor_pos = self.cursor_pos();
701 let inserted_text_len = self.insert_char(ch, cursor_pos);
702 self.move_cursor_to(cursor_pos + inserted_text_len);
703 event.insert(TextEvent::TEXT_CHANGED);
704 } else {
705 let cursor_pos = self.cursor_pos();
707 let inserted_text_len = self.insert(character, cursor_pos);
708 self.move_cursor_to(cursor_pos + inserted_text_len);
709 event.insert(TextEvent::TEXT_CHANGED);
710 }
711 }
712 _ => {}
713 }
714 }
715 _ => {}
716 }
717
718 if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
719 {
720 self.clear_selection();
721 }
722
723 if self.get_selection() != selection {
724 event.insert(TextEvent::SELECTION_CHANGED);
725 }
726
727 event
728 }
729
730 fn get_selected_text(&self) -> Option<String>;
731
732 fn undo(&mut self) -> Option<TextSelection>;
733
734 fn redo(&mut self) -> Option<TextSelection>;
735
736 fn editor_history(&mut self) -> &mut EditorHistory;
737
738 fn get_selection_range(&self) -> Option<(usize, usize)>;
739
740 fn get_indentation(&self) -> u8;
741
742 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
743 let pos_char = self.utf16_cu_to_char(pos);
744 let len_chars = self.len_chars();
745
746 if len_chars == 0 {
747 return (pos, pos);
748 }
749
750 let line_idx = self.char_to_line(pos_char);
752 let line_char = self.line_to_char(line_idx);
753 let line = self.line(line_idx).unwrap();
754
755 let line_str: std::borrow::Cow<str> = line.text;
756 let pos_in_line = pos_char - line_char;
757
758 let mut char_offset = 0;
760 for word in line_str.split_word_bounds() {
761 let word_char_len = word.chars().count();
762 let word_start = char_offset;
763 let word_end = char_offset + word_char_len;
764
765 if pos_in_line >= word_start && pos_in_line < word_end {
766 let start_char = line_char + word_start;
767 let end_char = line_char + word_end;
768 return (
769 self.char_to_utf16_cu(start_char),
770 self.char_to_utf16_cu(end_char),
771 );
772 }
773
774 char_offset = word_end;
775 }
776
777 (pos, pos)
778 }
779}