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