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_read_clipboard: bool,
476 allow_write_clipboard: bool,
477 ) -> TextEvent {
478 let mut event = TextEvent::empty();
479
480 let selection = self.get_selection();
481 let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
482
483 match key {
484 Key::Named(NamedKey::Shift) => {}
485 Key::Named(NamedKey::Control) => {}
486 Key::Named(NamedKey::Alt) => {}
487 Key::Named(NamedKey::Escape) => {
488 self.clear_selection();
489 }
490 Key::Named(NamedKey::ArrowDown) => {
491 if modifiers.contains(Modifiers::SHIFT) {
492 self.selection_mut().set_as_range();
493 } else {
494 self.selection_mut().set_as_cursor();
495 }
496
497 if !skip_arrows_movement && self.cursor_down() {
498 event.insert(TextEvent::CURSOR_CHANGED);
499 }
500 }
501 Key::Named(NamedKey::ArrowLeft) => {
502 if modifiers.contains(Modifiers::SHIFT) {
503 self.selection_mut().set_as_range();
504 } else {
505 self.selection_mut().set_as_cursor();
506 }
507
508 let word_jump = if cfg!(target_os = "macos") {
509 modifiers.contains(Modifiers::ALT)
510 } else {
511 modifiers.contains(Modifiers::CONTROL)
512 };
513
514 let moved = !skip_arrows_movement
515 && if word_jump {
516 self.cursor_word_left()
517 } else {
518 self.cursor_left()
519 };
520
521 if moved {
522 event.insert(TextEvent::CURSOR_CHANGED);
523 }
524 }
525 Key::Named(NamedKey::ArrowRight) => {
526 if modifiers.contains(Modifiers::SHIFT) {
527 self.selection_mut().set_as_range();
528 } else {
529 self.selection_mut().set_as_cursor();
530 }
531
532 let word_jump = if cfg!(target_os = "macos") {
533 modifiers.contains(Modifiers::ALT)
534 } else {
535 modifiers.contains(Modifiers::CONTROL)
536 };
537
538 let moved = !skip_arrows_movement
539 && if word_jump {
540 self.cursor_word_right()
541 } else {
542 self.cursor_right()
543 };
544
545 if moved {
546 event.insert(TextEvent::CURSOR_CHANGED);
547 }
548 }
549 Key::Named(NamedKey::ArrowUp) => {
550 if modifiers.contains(Modifiers::SHIFT) {
551 self.selection_mut().set_as_range();
552 } else {
553 self.selection_mut().set_as_cursor();
554 }
555
556 if !skip_arrows_movement && self.cursor_up() {
557 event.insert(TextEvent::CURSOR_CHANGED);
558 }
559 }
560 Key::Named(NamedKey::Backspace) if allow_changes => {
561 let cursor_pos = self.cursor_pos();
562 let selection = self.get_selection_range();
563
564 if let Some((start, end)) = selection {
565 self.remove(start..end);
566 self.move_cursor_to(start);
567 event.insert(TextEvent::TEXT_CHANGED);
568 } else if cursor_pos > 0 {
569 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
571 self.move_cursor_to(cursor_pos - removed_text_len);
572 event.insert(TextEvent::TEXT_CHANGED);
573 }
574 }
575 Key::Named(NamedKey::Delete) if allow_changes => {
576 let cursor_pos = self.cursor_pos();
577 let selection = self.get_selection_range();
578
579 if let Some((start, end)) = selection {
580 self.remove(start..end);
581 self.move_cursor_to(start);
582 event.insert(TextEvent::TEXT_CHANGED);
583 } else if cursor_pos < self.len_utf16_cu() {
584 self.remove(cursor_pos..cursor_pos + 1);
586 event.insert(TextEvent::TEXT_CHANGED);
587 }
588 }
589 Key::Named(NamedKey::Enter) if allow_changes => {
590 let cursor_pos = self.cursor_pos();
592 self.insert_char('\n', cursor_pos);
593 self.cursor_right();
594
595 event.insert(TextEvent::TEXT_CHANGED);
596 }
597 Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
598 let text = " ".repeat(self.get_indentation().into());
600 let cursor_pos = self.cursor_pos();
601 self.insert(&text, cursor_pos);
602 self.move_cursor_to(cursor_pos + text.chars().count());
603
604 event.insert(TextEvent::TEXT_CHANGED);
605 }
606 Key::Character(character) => {
607 let meta_or_ctrl = modifiers.contains(Modifiers::ctrl_or_meta());
608
609 match character.as_str() {
610 " " if allow_changes => {
611 let selection = self.get_selection_range();
612 if let Some((start, end)) = selection {
613 self.remove(start..end);
614 self.move_cursor_to(start);
615 event.insert(TextEvent::TEXT_CHANGED);
616 }
617
618 let cursor_pos = self.cursor_pos();
620 self.insert_char(' ', cursor_pos);
621 self.cursor_right();
622
623 event.insert(TextEvent::TEXT_CHANGED);
624 }
625
626 "a" if meta_or_ctrl => {
628 let len = self.len_utf16_cu();
629 self.set_selection((0, len));
630 }
631
632 "c" if meta_or_ctrl && allow_write_clipboard => {
634 let selected = self.get_selected_text();
635 if let Some(selected) = selected {
636 Clipboard::set(selected).ok();
637 }
638 }
639
640 "x" if meta_or_ctrl && allow_changes && allow_write_clipboard => {
642 let selection = self.get_selection_range();
643 if let Some((start, end)) = selection {
644 let text = self.get_selected_text().unwrap();
645 self.remove(start..end);
646 Clipboard::set(text).ok();
647 self.move_cursor_to(start);
648 event.insert(TextEvent::TEXT_CHANGED);
649 }
650 }
651
652 "v" if meta_or_ctrl && allow_changes && allow_read_clipboard => {
654 if let Ok(copied_text) = Clipboard::get() {
655 let selection = self.get_selection_range();
656 if let Some((start, end)) = selection {
657 self.remove(start..end);
658 self.move_cursor_to(start);
659 }
660 let cursor_pos = self.cursor_pos();
661 self.insert(&copied_text, cursor_pos);
662 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
663 self.move_cursor_to(last_idx);
664 event.insert(TextEvent::TEXT_CHANGED);
665 }
666 }
667
668 "z" if meta_or_ctrl && allow_changes => {
670 let undo_result = self.undo();
671
672 if let Some(selection) = undo_result {
673 *self.selection_mut() = selection;
674 event.insert(TextEvent::TEXT_CHANGED);
675 event.insert(TextEvent::SELECTION_CHANGED);
676 }
677 }
678
679 "y" if meta_or_ctrl && allow_changes => {
681 let redo_result = self.redo();
682
683 if let Some(selection) = redo_result {
684 *self.selection_mut() = selection;
685 event.insert(TextEvent::TEXT_CHANGED);
686 event.insert(TextEvent::SELECTION_CHANGED);
687 }
688 }
689
690 _ if allow_changes => {
691 let selection = self.get_selection_range();
693 if let Some((start, end)) = selection {
694 self.remove(start..end);
695 self.move_cursor_to(start);
696 event.insert(TextEvent::TEXT_CHANGED);
697 }
698
699 if let Ok(ch) = character.parse::<char>() {
700 let cursor_pos = self.cursor_pos();
702 let inserted_text_len = self.insert_char(ch, cursor_pos);
703 self.move_cursor_to(cursor_pos + inserted_text_len);
704 event.insert(TextEvent::TEXT_CHANGED);
705 } else {
706 let cursor_pos = self.cursor_pos();
708 let inserted_text_len = self.insert(character, cursor_pos);
709 self.move_cursor_to(cursor_pos + inserted_text_len);
710 event.insert(TextEvent::TEXT_CHANGED);
711 }
712 }
713 _ => {}
714 }
715 }
716 _ => {}
717 }
718
719 if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
720 {
721 self.clear_selection();
722 }
723
724 if self.get_selection() != selection {
725 event.insert(TextEvent::SELECTION_CHANGED);
726 }
727
728 event
729 }
730
731 fn get_selected_text(&self) -> Option<String>;
732
733 fn undo(&mut self) -> Option<TextSelection>;
734
735 fn redo(&mut self) -> Option<TextSelection>;
736
737 fn editor_history(&mut self) -> &mut EditorHistory;
738
739 fn get_selection_range(&self) -> Option<(usize, usize)>;
740
741 fn get_indentation(&self) -> u8;
742
743 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
744 let pos_char = self.utf16_cu_to_char(pos);
745 let len_chars = self.len_chars();
746
747 if len_chars == 0 {
748 return (pos, pos);
749 }
750
751 let line_idx = self.char_to_line(pos_char);
753 let line_char = self.line_to_char(line_idx);
754 let line = self.line(line_idx).unwrap();
755
756 let line_str: std::borrow::Cow<str> = line.text;
757 let pos_in_line = pos_char - line_char;
758
759 let mut char_offset = 0;
761 for word in line_str.split_word_bounds() {
762 let word_char_len = word.chars().count();
763 let word_start = char_offset;
764 let word_end = char_offset + word_char_len;
765
766 if pos_in_line >= word_start && pos_in_line < word_end {
767 let start_char = line_char + word_start;
768 let end_char = line_char + word_end;
769 return (
770 self.char_to_utf16_cu(start_char),
771 self.char_to_utf16_cu(end_char),
772 );
773 }
774
775 char_offset = word_end;
776 }
777
778 (pos, pos)
779 }
780}