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_pos(&self) -> usize {
272 self.selection().pos()
273 }
274
275 fn move_cursor_to(&mut self, pos: usize) {
277 self.selection_mut().move_to(pos);
278 }
279
280 fn has_any_selection(&self) -> bool;
282
283 fn get_selection(&self) -> Option<(usize, usize)>;
285
286 fn get_visible_selection(&self, editor_line: EditorLine) -> Option<(usize, usize)> {
288 let (selected_from, selected_to) = match self.selection() {
289 TextSelection::Cursor(_) => return None,
290 TextSelection::Range { from, to } => (*from, *to),
291 };
292
293 match editor_line {
294 EditorLine::Paragraph(line_index) => {
295 let selected_from_row = self.char_to_line(self.utf16_cu_to_char(selected_from));
296 let selected_to_row = self.char_to_line(self.utf16_cu_to_char(selected_to));
297
298 let editor_row_idx = self.char_to_utf16_cu(self.line_to_char(line_index));
299 let selected_from_row_idx =
300 self.char_to_utf16_cu(self.line_to_char(selected_from_row));
301 let selected_to_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_to_row));
302
303 let selected_from_col_idx = selected_from - selected_from_row_idx;
304 let selected_to_col_idx = selected_to - selected_to_row_idx;
305
306 if (line_index > selected_from_row && line_index < selected_to_row)
308 || (line_index < selected_from_row && line_index > selected_to_row)
309 {
310 let len = self.line(line_index).unwrap().utf16_len();
311 return Some((0, len));
312 }
313
314 match selected_from_row.cmp(&selected_to_row) {
315 Ordering::Greater => {
317 if selected_from_row == line_index {
318 Some((0, selected_from_col_idx))
320 } else if selected_to_row == line_index {
321 let len = self.line(selected_to_row).unwrap().utf16_len();
323 Some((selected_to_col_idx, len))
324 } else {
325 None
326 }
327 }
328 Ordering::Less => {
330 if selected_from_row == line_index {
331 let len = self.line(selected_from_row).unwrap().utf16_len();
333 Some((selected_from_col_idx, len))
334 } else if selected_to_row == line_index {
335 Some((0, selected_to_col_idx))
337 } else {
338 None
339 }
340 }
341 Ordering::Equal if selected_from_row == line_index => {
342 Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
344 }
345 _ => None,
346 }
347 }
348 EditorLine::SingleParagraph => Some((selected_from, selected_to)),
349 }
350 }
351
352 fn clear_selection(&mut self);
354
355 fn set_selection(&mut self, selected: (usize, usize));
357
358 fn measure_selection(&self, to: usize, line_index: EditorLine) -> TextSelection {
361 let mut selection = self.selection().clone();
362
363 match line_index {
364 EditorLine::Paragraph(line_index) => {
365 let row_char = self.line_to_char(line_index);
366 let pos = self.char_to_utf16_cu(row_char) + to;
367 selection.move_to(pos);
368 }
369 EditorLine::SingleParagraph => {
370 selection.move_to(to);
371 }
372 }
373
374 selection
375 }
376
377 fn process_key(
379 &mut self,
380 key: &Key,
381 modifiers: &Modifiers,
382 allow_tabs: bool,
383 allow_changes: bool,
384 allow_clipboard: bool,
385 ) -> TextEvent {
386 let mut event = TextEvent::empty();
387
388 let selection = self.get_selection();
389 let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
390
391 match key {
392 Key::Named(NamedKey::Shift) => {}
393 Key::Named(NamedKey::Control) => {}
394 Key::Named(NamedKey::Alt) => {}
395 Key::Named(NamedKey::Escape) => {
396 self.clear_selection();
397 }
398 Key::Named(NamedKey::ArrowDown) => {
399 if modifiers.contains(Modifiers::SHIFT) {
400 self.selection_mut().set_as_range();
401 } else {
402 self.selection_mut().set_as_cursor();
403 }
404
405 if !skip_arrows_movement && self.cursor_down() {
406 event.insert(TextEvent::CURSOR_CHANGED);
407 }
408 }
409 Key::Named(NamedKey::ArrowLeft) => {
410 if modifiers.contains(Modifiers::SHIFT) {
411 self.selection_mut().set_as_range();
412 } else {
413 self.selection_mut().set_as_cursor();
414 }
415
416 if !skip_arrows_movement && self.cursor_left() {
417 event.insert(TextEvent::CURSOR_CHANGED);
418 }
419 }
420 Key::Named(NamedKey::ArrowRight) => {
421 if modifiers.contains(Modifiers::SHIFT) {
422 self.selection_mut().set_as_range();
423 } else {
424 self.selection_mut().set_as_cursor();
425 }
426
427 if !skip_arrows_movement && self.cursor_right() {
428 event.insert(TextEvent::CURSOR_CHANGED);
429 }
430 }
431 Key::Named(NamedKey::ArrowUp) => {
432 if modifiers.contains(Modifiers::SHIFT) {
433 self.selection_mut().set_as_range();
434 } else {
435 self.selection_mut().set_as_cursor();
436 }
437
438 if !skip_arrows_movement && self.cursor_up() {
439 event.insert(TextEvent::CURSOR_CHANGED);
440 }
441 }
442 Key::Named(NamedKey::Backspace) if allow_changes => {
443 let cursor_pos = self.cursor_pos();
444 let selection = self.get_selection_range();
445
446 if let Some((start, end)) = selection {
447 self.remove(start..end);
448 self.move_cursor_to(start);
449 event.insert(TextEvent::TEXT_CHANGED);
450 } else if cursor_pos > 0 {
451 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
453 self.move_cursor_to(cursor_pos - removed_text_len);
454 event.insert(TextEvent::TEXT_CHANGED);
455 }
456 }
457 Key::Named(NamedKey::Delete) if allow_changes => {
458 let cursor_pos = self.cursor_pos();
459 let selection = self.get_selection_range();
460
461 if let Some((start, end)) = selection {
462 self.remove(start..end);
463 self.move_cursor_to(start);
464 event.insert(TextEvent::TEXT_CHANGED);
465 } else if cursor_pos < self.len_utf16_cu() {
466 self.remove(cursor_pos..cursor_pos + 1);
468 event.insert(TextEvent::TEXT_CHANGED);
469 }
470 }
471 Key::Named(NamedKey::Enter) if allow_changes => {
472 let cursor_pos = self.cursor_pos();
474 self.insert_char('\n', cursor_pos);
475 self.cursor_right();
476
477 event.insert(TextEvent::TEXT_CHANGED);
478 }
479 Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
480 let text = " ".repeat(self.get_indentation().into());
482 let cursor_pos = self.cursor_pos();
483 self.insert(&text, cursor_pos);
484 self.move_cursor_to(cursor_pos + text.chars().count());
485
486 event.insert(TextEvent::TEXT_CHANGED);
487 }
488 Key::Character(character) => {
489 let meta_or_ctrl = if cfg!(target_os = "macos") {
490 modifiers.meta()
491 } else {
492 modifiers.ctrl()
493 };
494
495 match character.as_str() {
496 " " if allow_changes => {
497 let selection = self.get_selection_range();
498 if let Some((start, end)) = selection {
499 self.remove(start..end);
500 self.move_cursor_to(start);
501 event.insert(TextEvent::TEXT_CHANGED);
502 }
503
504 let cursor_pos = self.cursor_pos();
506 self.insert_char(' ', cursor_pos);
507 self.cursor_right();
508
509 event.insert(TextEvent::TEXT_CHANGED);
510 }
511
512 "a" if meta_or_ctrl => {
514 let len = self.len_utf16_cu();
515 self.set_selection((0, len));
516 }
517
518 "c" if meta_or_ctrl && allow_clipboard => {
520 let selected = self.get_selected_text();
521 if let Some(selected) = selected {
522 Clipboard::set(selected).ok();
523 }
524 }
525
526 "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
528 let selection = self.get_selection_range();
529 if let Some((start, end)) = selection {
530 let text = self.get_selected_text().unwrap();
531 self.remove(start..end);
532 Clipboard::set(text).ok();
533 self.move_cursor_to(start);
534 event.insert(TextEvent::TEXT_CHANGED);
535 }
536 }
537
538 "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
540 if let Ok(copied_text) = Clipboard::get() {
541 let selection = self.get_selection_range();
542 if let Some((start, end)) = selection {
543 self.remove(start..end);
544 self.move_cursor_to(start);
545 }
546 let cursor_pos = self.cursor_pos();
547 self.insert(&copied_text, cursor_pos);
548 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
549 self.move_cursor_to(last_idx);
550 event.insert(TextEvent::TEXT_CHANGED);
551 }
552 }
553
554 "z" if meta_or_ctrl && allow_changes => {
556 let undo_result = self.undo();
557
558 if let Some(selection) = undo_result {
559 *self.selection_mut() = selection;
560 event.insert(TextEvent::TEXT_CHANGED);
561 event.insert(TextEvent::SELECTION_CHANGED);
562 }
563 }
564
565 "y" if meta_or_ctrl && allow_changes => {
567 let redo_result = self.redo();
568
569 if let Some(selection) = redo_result {
570 *self.selection_mut() = selection;
571 event.insert(TextEvent::TEXT_CHANGED);
572 event.insert(TextEvent::SELECTION_CHANGED);
573 }
574 }
575
576 _ if allow_changes => {
577 let selection = self.get_selection_range();
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 }
584
585 if let Ok(ch) = character.parse::<char>() {
586 let cursor_pos = self.cursor_pos();
588 let inserted_text_len = self.insert_char(ch, cursor_pos);
589 self.move_cursor_to(cursor_pos + inserted_text_len);
590 event.insert(TextEvent::TEXT_CHANGED);
591 } else {
592 let cursor_pos = self.cursor_pos();
594 let inserted_text_len = self.insert(character, cursor_pos);
595 self.move_cursor_to(cursor_pos + inserted_text_len);
596 event.insert(TextEvent::TEXT_CHANGED);
597 }
598 }
599 _ => {}
600 }
601 }
602 _ => {}
603 }
604
605 if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
606 {
607 self.clear_selection();
608 }
609
610 if self.get_selection() != selection {
611 event.insert(TextEvent::SELECTION_CHANGED);
612 }
613
614 event
615 }
616
617 fn get_selected_text(&self) -> Option<String>;
618
619 fn undo(&mut self) -> Option<TextSelection>;
620
621 fn redo(&mut self) -> Option<TextSelection>;
622
623 fn editor_history(&mut self) -> &mut EditorHistory;
624
625 fn get_selection_range(&self) -> Option<(usize, usize)>;
626
627 fn get_indentation(&self) -> u8;
628
629 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
630 let pos_char = self.utf16_cu_to_char(pos);
631 let len_chars = self.len_chars();
632
633 if len_chars == 0 {
634 return (pos, pos);
635 }
636
637 let line_idx = self.char_to_line(pos_char);
639 let line_char = self.line_to_char(line_idx);
640 let line = self.line(line_idx).unwrap();
641
642 let line_str: std::borrow::Cow<str> = line.text;
643 let pos_in_line = pos_char - line_char;
644
645 let mut char_offset = 0;
647 for word in line_str.split_word_bounds() {
648 let word_char_len = word.chars().count();
649 let word_start = char_offset;
650 let word_end = char_offset + word_char_len;
651
652 if pos_in_line >= word_start && pos_in_line < word_end {
653 let start_char = line_char + word_start;
654 let end_char = line_char + word_end;
655 return (
656 self.char_to_utf16_cu(start_char),
657 self.char_to_utf16_cu(end_char),
658 );
659 }
660
661 char_offset = word_end;
662 }
663
664 (pos, pos)
665 }
666}