1use std::{
2 borrow::Cow,
3 cmp::Ordering,
4 fmt::Display,
5 ops::Range,
6};
7
8use freya_clipboard::clipboard::Clipboard;
9use keyboard_types::{
10 Key,
11 Modifiers,
12 NamedKey,
13};
14use unicode_segmentation::UnicodeSegmentation;
15
16use crate::editor_history::EditorHistory;
17
18#[derive(PartialEq, Clone, Debug, Copy, Hash)]
19pub enum EditorLine {
20 SingleParagraph,
22 Paragraph(usize),
24}
25
26#[derive(Clone, PartialEq, Debug)]
28pub enum TextSelection {
29 Cursor(usize),
30 Range { from: usize, to: usize },
31}
32
33impl TextSelection {
34 pub fn new_cursor(pos: usize) -> Self {
36 Self::Cursor(pos)
37 }
38
39 pub fn new_range((from, to): (usize, usize)) -> Self {
41 Self::Range { from, to }
42 }
43
44 pub fn pos(&self) -> usize {
46 self.end()
47 }
48
49 pub fn set_as_cursor(&mut self) {
51 *self = Self::Cursor(self.end())
52 }
53
54 pub fn set_as_range(&mut self) {
56 *self = Self::Range {
57 from: self.start(),
58 to: self.end(),
59 }
60 }
61
62 pub fn start(&self) -> usize {
64 match self {
65 Self::Cursor(pos) => *pos,
66 Self::Range { from, .. } => *from,
67 }
68 }
69
70 pub fn end(&self) -> usize {
72 match self {
73 Self::Cursor(pos) => *pos,
74 Self::Range { to, .. } => *to,
75 }
76 }
77
78 pub fn move_to(&mut self, position: usize) {
80 match self {
81 Self::Cursor(pos) => *pos = position,
82 Self::Range { to, .. } => {
83 *to = position;
84 }
85 }
86 }
87
88 pub fn is_range(&self) -> bool {
89 matches!(self, Self::Range { .. })
90 }
91}
92
93#[derive(Clone)]
95pub struct Line<'a> {
96 pub text: Cow<'a, str>,
97 pub utf16_len: usize,
98}
99
100impl Line<'_> {
101 pub fn utf16_len(&self) -> usize {
103 self.utf16_len
104 }
105}
106
107impl Display for Line<'_> {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.write_str(&self.text)
110 }
111}
112
113bitflags::bitflags! {
114 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
116 pub struct TextEvent: u8 {
117 const CURSOR_CHANGED = 0x01;
119 const TEXT_CHANGED = 0x02;
121 const SELECTION_CHANGED = 0x04;
123 }
124}
125
126pub trait TextEditor {
128 type LinesIterator<'a>: Iterator<Item = Line<'a>>
129 where
130 Self: 'a;
131
132 fn set(&mut self, text: &str);
133
134 fn lines(&self) -> Self::LinesIterator<'_>;
136
137 fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
139
140 fn insert(&mut self, text: &str, char_idx: usize) -> usize;
142
143 fn remove(&mut self, range: Range<usize>) -> usize;
145
146 fn char_to_line(&self, char_idx: usize) -> usize;
148
149 fn line_to_char(&self, line_idx: usize) -> usize;
151
152 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
153
154 fn char_to_utf16_cu(&self, idx: usize) -> usize;
155
156 fn line(&self, line_idx: usize) -> Option<Line<'_>>;
158
159 fn len_lines(&self) -> usize;
161
162 fn len_chars(&self) -> usize;
164
165 fn len_utf16_cu(&self) -> usize;
167
168 fn selection(&self) -> &TextSelection;
170
171 fn selection_mut(&mut self) -> &mut TextSelection;
173
174 fn cursor_row(&self) -> usize {
176 let pos = self.cursor_pos();
177 let pos_utf8 = self.utf16_cu_to_char(pos);
178 self.char_to_line(pos_utf8)
179 }
180
181 fn cursor_col(&self) -> usize {
183 let pos = self.cursor_pos();
184 let pos_utf8 = self.utf16_cu_to_char(pos);
185 let line = self.char_to_line(pos_utf8);
186 let line_char_utf8 = self.line_to_char(line);
187 let line_char = self.char_to_utf16_cu(line_char_utf8);
188 pos - line_char
189 }
190
191 fn cursor_down(&mut self) -> bool {
193 let old_row = self.cursor_row();
194 let old_col = self.cursor_col();
195
196 match old_row.cmp(&(self.len_lines() - 1)) {
197 Ordering::Less => {
198 let new_row = old_row + 1;
200 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
201 let new_row_len = self.line(new_row).unwrap().utf16_len();
202 let new_col = old_col.min(new_row_len.saturating_sub(1));
203 self.selection_mut().move_to(new_row_char + new_col);
204
205 true
206 }
207 Ordering::Equal => {
208 let end = self.len_utf16_cu();
209 self.selection_mut().move_to(end);
211
212 true
213 }
214 Ordering::Greater => {
215 false
218 }
219 }
220 }
221
222 fn cursor_up(&mut self) -> bool {
224 let pos = self.cursor_pos();
225 let old_row = self.cursor_row();
226 let old_col = self.cursor_col();
227
228 if pos > 0 {
229 if old_row == 0 {
231 self.selection_mut().move_to(0);
232 } else {
233 let new_row = old_row - 1;
234 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
235 let new_row_len = self.line(new_row).unwrap().utf16_len();
236 let new_col = old_col.min(new_row_len.saturating_sub(1));
237 self.selection_mut().move_to(new_row_char + new_col);
238 }
239
240 true
241 } else {
242 false
243 }
244 }
245
246 fn cursor_right(&mut self) -> bool {
248 if self.cursor_pos() < self.len_utf16_cu() {
249 let to = self.selection().end() + 1;
250 self.selection_mut().move_to(to);
251
252 true
253 } else {
254 false
255 }
256 }
257
258 fn cursor_left(&mut self) -> bool {
260 if self.cursor_pos() > 0 {
261 let to = self.selection().end() - 1;
262 self.selection_mut().move_to(to);
263
264 true
265 } else {
266 false
267 }
268 }
269
270 fn cursor_word_right(&mut self) -> bool {
272 let pos = self.cursor_pos();
273 let len = self.len_utf16_cu();
274 if pos >= len {
275 return false;
276 }
277
278 let start_char = self.utf16_cu_to_char(pos);
280 let initial_line = self.char_to_line(start_char);
281 let initial_offset = start_char - self.line_to_char(initial_line);
282
283 for line_idx in initial_line..self.len_lines() {
284 let Some(line) = self.line(line_idx) else {
285 continue;
286 };
287 let line_char_offset = self.line_to_char(line_idx);
288 let from = if line_idx == initial_line {
289 initial_offset
290 } else {
291 0
292 };
293
294 let mut char_offset = 0;
296 for word in line.text.split_word_bounds() {
297 char_offset += word.chars().count();
298 if char_offset > from && !word.chars().all(char::is_whitespace) {
299 let new_pos = self.char_to_utf16_cu(line_char_offset + char_offset);
300 self.selection_mut().move_to(new_pos);
301 return true;
302 }
303 }
304 }
305
306 self.selection_mut().move_to(len);
308 true
309 }
310
311 fn cursor_word_left(&mut self) -> bool {
313 let pos = self.cursor_pos();
314 if pos == 0 {
315 return false;
316 }
317
318 let start_char = self.utf16_cu_to_char(pos);
320 let initial_line = self.char_to_line(start_char);
321 let initial_offset = start_char - self.line_to_char(initial_line);
322
323 for line_idx in (0..=initial_line).rev() {
324 let Some(line) = self.line(line_idx) else {
325 continue;
326 };
327 let line_char_offset = self.line_to_char(line_idx);
328 let to = if line_idx == initial_line {
329 initial_offset
330 } else {
331 line.text.chars().count()
332 };
333
334 let mut char_offset = 0;
336 let mut last_word_start = None;
337 for word in line.text.split_word_bounds() {
338 if char_offset >= to {
339 break;
340 }
341 if !word.chars().all(char::is_whitespace) {
342 last_word_start = Some(char_offset);
343 }
344 char_offset += word.chars().count();
345 }
346
347 if let Some(start) = last_word_start {
349 let new_pos = self.char_to_utf16_cu(line_char_offset + start);
350 self.selection_mut().move_to(new_pos);
351 return true;
352 }
353 }
354
355 self.selection_mut().move_to(0);
357 true
358 }
359
360 fn cursor_pos(&self) -> usize {
362 self.selection().pos()
363 }
364
365 fn move_cursor_to(&mut self, pos: usize) {
367 self.selection_mut().move_to(pos);
368 }
369
370 fn has_any_selection(&self) -> bool;
372
373 fn get_selection(&self) -> Option<(usize, usize)>;
375
376 fn get_visible_selection(&self, editor_line: EditorLine) -> Option<(usize, usize)> {
378 let (selected_from, selected_to) = match self.selection() {
379 TextSelection::Cursor(_) => return None,
380 TextSelection::Range { from, to } => (*from, *to),
381 };
382
383 match editor_line {
384 EditorLine::Paragraph(line_index) => {
385 let selected_from_row = self.char_to_line(self.utf16_cu_to_char(selected_from));
386 let selected_to_row = self.char_to_line(self.utf16_cu_to_char(selected_to));
387
388 let editor_row_idx = self.char_to_utf16_cu(self.line_to_char(line_index));
389 let selected_from_row_idx =
390 self.char_to_utf16_cu(self.line_to_char(selected_from_row));
391 let selected_to_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_to_row));
392
393 let selected_from_col_idx = selected_from - selected_from_row_idx;
394 let selected_to_col_idx = selected_to - selected_to_row_idx;
395
396 if (line_index > selected_from_row && line_index < selected_to_row)
398 || (line_index < selected_from_row && line_index > selected_to_row)
399 {
400 let len = self.line(line_index).unwrap().utf16_len();
401 return Some((0, len));
402 }
403
404 match selected_from_row.cmp(&selected_to_row) {
405 Ordering::Greater => {
407 if selected_from_row == line_index {
408 Some((0, selected_from_col_idx))
410 } else if selected_to_row == line_index {
411 let len = self.line(selected_to_row).unwrap().utf16_len();
413 Some((selected_to_col_idx, len))
414 } else {
415 None
416 }
417 }
418 Ordering::Less => {
420 if selected_from_row == line_index {
421 let len = self.line(selected_from_row).unwrap().utf16_len();
423 Some((selected_from_col_idx, len))
424 } else if selected_to_row == line_index {
425 Some((0, selected_to_col_idx))
427 } else {
428 None
429 }
430 }
431 Ordering::Equal if selected_from_row == line_index => {
432 Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
434 }
435 _ => None,
436 }
437 }
438 EditorLine::SingleParagraph => Some((selected_from, selected_to)),
439 }
440 }
441
442 fn clear_selection(&mut self);
444
445 fn set_selection(&mut self, selected: (usize, usize));
447
448 fn measure_selection(&self, to: usize, line_index: EditorLine) -> TextSelection {
451 let mut selection = self.selection().clone();
452
453 match line_index {
454 EditorLine::Paragraph(line_index) => {
455 let row_char = self.line_to_char(line_index);
456 let pos = self.char_to_utf16_cu(row_char) + to;
457 selection.move_to(pos);
458 }
459 EditorLine::SingleParagraph => {
460 selection.move_to(to);
461 }
462 }
463
464 selection
465 }
466
467 fn process_key(
469 &mut self,
470 key: &Key,
471 modifiers: &Modifiers,
472 allow_tabs: bool,
473 allow_changes: bool,
474 allow_clipboard: bool,
475 ) -> TextEvent {
476 let mut event = TextEvent::empty();
477
478 let selection = self.get_selection();
479 let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
480
481 match key {
482 Key::Named(NamedKey::Shift) => {}
483 Key::Named(NamedKey::Control) => {}
484 Key::Named(NamedKey::Alt) => {}
485 Key::Named(NamedKey::Escape) => {
486 self.clear_selection();
487 }
488 Key::Named(NamedKey::ArrowDown) => {
489 if modifiers.contains(Modifiers::SHIFT) {
490 self.selection_mut().set_as_range();
491 } else {
492 self.selection_mut().set_as_cursor();
493 }
494
495 if !skip_arrows_movement && self.cursor_down() {
496 event.insert(TextEvent::CURSOR_CHANGED);
497 }
498 }
499 Key::Named(NamedKey::ArrowLeft) => {
500 if modifiers.contains(Modifiers::SHIFT) {
501 self.selection_mut().set_as_range();
502 } else {
503 self.selection_mut().set_as_cursor();
504 }
505
506 let word_jump = if cfg!(target_os = "macos") {
507 modifiers.contains(Modifiers::ALT)
508 } else {
509 modifiers.contains(Modifiers::CONTROL)
510 };
511
512 let moved = !skip_arrows_movement
513 && if word_jump {
514 self.cursor_word_left()
515 } else {
516 self.cursor_left()
517 };
518
519 if moved {
520 event.insert(TextEvent::CURSOR_CHANGED);
521 }
522 }
523 Key::Named(NamedKey::ArrowRight) => {
524 if modifiers.contains(Modifiers::SHIFT) {
525 self.selection_mut().set_as_range();
526 } else {
527 self.selection_mut().set_as_cursor();
528 }
529
530 let word_jump = if cfg!(target_os = "macos") {
531 modifiers.contains(Modifiers::ALT)
532 } else {
533 modifiers.contains(Modifiers::CONTROL)
534 };
535
536 let moved = !skip_arrows_movement
537 && if word_jump {
538 self.cursor_word_right()
539 } else {
540 self.cursor_right()
541 };
542
543 if moved {
544 event.insert(TextEvent::CURSOR_CHANGED);
545 }
546 }
547 Key::Named(NamedKey::ArrowUp) => {
548 if modifiers.contains(Modifiers::SHIFT) {
549 self.selection_mut().set_as_range();
550 } else {
551 self.selection_mut().set_as_cursor();
552 }
553
554 if !skip_arrows_movement && self.cursor_up() {
555 event.insert(TextEvent::CURSOR_CHANGED);
556 }
557 }
558 Key::Named(NamedKey::Backspace) if allow_changes => {
559 let cursor_pos = self.cursor_pos();
560 let selection = self.get_selection_range();
561
562 if let Some((start, end)) = selection {
563 self.remove(start..end);
564 self.move_cursor_to(start);
565 event.insert(TextEvent::TEXT_CHANGED);
566 } else if cursor_pos > 0 {
567 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
569 self.move_cursor_to(cursor_pos - removed_text_len);
570 event.insert(TextEvent::TEXT_CHANGED);
571 }
572 }
573 Key::Named(NamedKey::Delete) 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 < self.len_utf16_cu() {
582 self.remove(cursor_pos..cursor_pos + 1);
584 event.insert(TextEvent::TEXT_CHANGED);
585 }
586 }
587 Key::Named(NamedKey::Enter) if allow_changes => {
588 let cursor_pos = self.cursor_pos();
590 self.insert_char('\n', cursor_pos);
591 self.cursor_right();
592
593 event.insert(TextEvent::TEXT_CHANGED);
594 }
595 Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
596 let text = " ".repeat(self.get_indentation().into());
598 let cursor_pos = self.cursor_pos();
599 self.insert(&text, cursor_pos);
600 self.move_cursor_to(cursor_pos + text.chars().count());
601
602 event.insert(TextEvent::TEXT_CHANGED);
603 }
604 Key::Character(character) => {
605 let meta_or_ctrl = if cfg!(target_os = "macos") {
606 modifiers.meta()
607 } else {
608 modifiers.ctrl()
609 };
610
611 match character.as_str() {
612 " " if allow_changes => {
613 let selection = self.get_selection_range();
614 if let Some((start, end)) = selection {
615 self.remove(start..end);
616 self.move_cursor_to(start);
617 event.insert(TextEvent::TEXT_CHANGED);
618 }
619
620 let cursor_pos = self.cursor_pos();
622 self.insert_char(' ', cursor_pos);
623 self.cursor_right();
624
625 event.insert(TextEvent::TEXT_CHANGED);
626 }
627
628 "a" if meta_or_ctrl => {
630 let len = self.len_utf16_cu();
631 self.set_selection((0, len));
632 }
633
634 "c" if meta_or_ctrl && allow_clipboard => {
636 let selected = self.get_selected_text();
637 if let Some(selected) = selected {
638 Clipboard::set(selected).ok();
639 }
640 }
641
642 "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
644 let selection = self.get_selection_range();
645 if let Some((start, end)) = selection {
646 let text = self.get_selected_text().unwrap();
647 self.remove(start..end);
648 Clipboard::set(text).ok();
649 self.move_cursor_to(start);
650 event.insert(TextEvent::TEXT_CHANGED);
651 }
652 }
653
654 "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
656 if let Ok(copied_text) = Clipboard::get() {
657 let selection = self.get_selection_range();
658 if let Some((start, end)) = selection {
659 self.remove(start..end);
660 self.move_cursor_to(start);
661 }
662 let cursor_pos = self.cursor_pos();
663 self.insert(&copied_text, cursor_pos);
664 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
665 self.move_cursor_to(last_idx);
666 event.insert(TextEvent::TEXT_CHANGED);
667 }
668 }
669
670 "z" if meta_or_ctrl && allow_changes => {
672 let undo_result = self.undo();
673
674 if let Some(selection) = undo_result {
675 *self.selection_mut() = selection;
676 event.insert(TextEvent::TEXT_CHANGED);
677 event.insert(TextEvent::SELECTION_CHANGED);
678 }
679 }
680
681 "y" if meta_or_ctrl && allow_changes => {
683 let redo_result = self.redo();
684
685 if let Some(selection) = redo_result {
686 *self.selection_mut() = selection;
687 event.insert(TextEvent::TEXT_CHANGED);
688 event.insert(TextEvent::SELECTION_CHANGED);
689 }
690 }
691
692 _ if allow_changes => {
693 let selection = self.get_selection_range();
695 if let Some((start, end)) = selection {
696 self.remove(start..end);
697 self.move_cursor_to(start);
698 event.insert(TextEvent::TEXT_CHANGED);
699 }
700
701 if let Ok(ch) = character.parse::<char>() {
702 let cursor_pos = self.cursor_pos();
704 let inserted_text_len = self.insert_char(ch, cursor_pos);
705 self.move_cursor_to(cursor_pos + inserted_text_len);
706 event.insert(TextEvent::TEXT_CHANGED);
707 } else {
708 let cursor_pos = self.cursor_pos();
710 let inserted_text_len = self.insert(character, cursor_pos);
711 self.move_cursor_to(cursor_pos + inserted_text_len);
712 event.insert(TextEvent::TEXT_CHANGED);
713 }
714 }
715 _ => {}
716 }
717 }
718 _ => {}
719 }
720
721 if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
722 {
723 self.clear_selection();
724 }
725
726 if self.get_selection() != selection {
727 event.insert(TextEvent::SELECTION_CHANGED);
728 }
729
730 event
731 }
732
733 fn get_selected_text(&self) -> Option<String>;
734
735 fn undo(&mut self) -> Option<TextSelection>;
736
737 fn redo(&mut self) -> Option<TextSelection>;
738
739 fn editor_history(&mut self) -> &mut EditorHistory;
740
741 fn get_selection_range(&self) -> Option<(usize, usize)>;
742
743 fn get_indentation(&self) -> u8;
744
745 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
746 let pos_char = self.utf16_cu_to_char(pos);
747 let len_chars = self.len_chars();
748
749 if len_chars == 0 {
750 return (pos, pos);
751 }
752
753 let line_idx = self.char_to_line(pos_char);
755 let line_char = self.line_to_char(line_idx);
756 let line = self.line(line_idx).unwrap();
757
758 let line_str: std::borrow::Cow<str> = line.text;
759 let pos_in_line = pos_char - line_char;
760
761 let mut char_offset = 0;
763 for word in line_str.split_word_bounds() {
764 let word_char_len = word.chars().count();
765 let word_start = char_offset;
766 let word_end = char_offset + word_char_len;
767
768 if pos_in_line >= word_start && pos_in_line < word_end {
769 let start_char = line_char + word_start;
770 let end_char = line_char + word_end;
771 return (
772 self.char_to_utf16_cu(start_char),
773 self.char_to_utf16_cu(end_char),
774 );
775 }
776
777 char_offset = word_end;
778 }
779
780 (pos, pos)
781 }
782}