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
289 fn clear_selection(&mut self);
291
292 fn set_selection(&mut self, selected: (usize, usize));
294
295 fn measure_selection(&self, to: usize, line_index: EditorLine) -> TextSelection {
298 let mut selection = self.selection().clone();
299
300 match line_index {
301 EditorLine::Paragraph(line_index) => {
302 let row_char = self.line_to_char(line_index);
303 let pos = self.char_to_utf16_cu(row_char) + to;
304 selection.move_to(pos);
305 }
306 EditorLine::SingleParagraph => {
307 selection.move_to(to);
308 }
309 }
310
311 selection
312 }
313
314 fn process_key(
316 &mut self,
317 key: &Key,
318 modifiers: &Modifiers,
319 allow_tabs: bool,
320 allow_changes: bool,
321 allow_clipboard: bool,
322 ) -> TextEvent {
323 let mut event = TextEvent::empty();
324
325 let selection = self.get_selection();
326 let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
327
328 match key {
329 Key::Named(NamedKey::Shift) => {}
330 Key::Named(NamedKey::Control) => {}
331 Key::Named(NamedKey::Alt) => {}
332 Key::Named(NamedKey::Escape) => {
333 self.clear_selection();
334 }
335 Key::Named(NamedKey::ArrowDown) => {
336 if modifiers.contains(Modifiers::SHIFT) {
337 self.selection_mut().set_as_range();
338 } else {
339 self.selection_mut().set_as_cursor();
340 }
341
342 if !skip_arrows_movement && self.cursor_down() {
343 event.insert(TextEvent::CURSOR_CHANGED);
344 }
345 }
346 Key::Named(NamedKey::ArrowLeft) => {
347 if modifiers.contains(Modifiers::SHIFT) {
348 self.selection_mut().set_as_range();
349 } else {
350 self.selection_mut().set_as_cursor();
351 }
352
353 if !skip_arrows_movement && self.cursor_left() {
354 event.insert(TextEvent::CURSOR_CHANGED);
355 }
356 }
357 Key::Named(NamedKey::ArrowRight) => {
358 if modifiers.contains(Modifiers::SHIFT) {
359 self.selection_mut().set_as_range();
360 } else {
361 self.selection_mut().set_as_cursor();
362 }
363
364 if !skip_arrows_movement && self.cursor_right() {
365 event.insert(TextEvent::CURSOR_CHANGED);
366 }
367 }
368 Key::Named(NamedKey::ArrowUp) => {
369 if modifiers.contains(Modifiers::SHIFT) {
370 self.selection_mut().set_as_range();
371 } else {
372 self.selection_mut().set_as_cursor();
373 }
374
375 if !skip_arrows_movement && self.cursor_up() {
376 event.insert(TextEvent::CURSOR_CHANGED);
377 }
378 }
379 Key::Named(NamedKey::Backspace) if allow_changes => {
380 let cursor_pos = self.cursor_pos();
381 let selection = self.get_selection_range();
382
383 if let Some((start, end)) = selection {
384 self.remove(start..end);
385 self.move_cursor_to(start);
386 event.insert(TextEvent::TEXT_CHANGED);
387 } else if cursor_pos > 0 {
388 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
390 self.move_cursor_to(cursor_pos - removed_text_len);
391 event.insert(TextEvent::TEXT_CHANGED);
392 }
393 }
394 Key::Named(NamedKey::Delete) if allow_changes => {
395 let cursor_pos = self.cursor_pos();
396 let selection = self.get_selection_range();
397
398 if let Some((start, end)) = selection {
399 self.remove(start..end);
400 self.move_cursor_to(start);
401 event.insert(TextEvent::TEXT_CHANGED);
402 } else if cursor_pos < self.len_utf16_cu() {
403 self.remove(cursor_pos..cursor_pos + 1);
405 event.insert(TextEvent::TEXT_CHANGED);
406 }
407 }
408 Key::Named(NamedKey::Enter) if allow_changes => {
409 let cursor_pos = self.cursor_pos();
411 self.insert_char('\n', cursor_pos);
412 self.cursor_right();
413
414 event.insert(TextEvent::TEXT_CHANGED);
415 }
416 Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
417 let text = " ".repeat(self.get_indentation().into());
419 let cursor_pos = self.cursor_pos();
420 self.insert(&text, cursor_pos);
421 self.move_cursor_to(cursor_pos + text.chars().count());
422
423 event.insert(TextEvent::TEXT_CHANGED);
424 }
425 Key::Character(character) => {
426 let meta_or_ctrl = if cfg!(target_os = "macos") {
427 modifiers.meta()
428 } else {
429 modifiers.ctrl()
430 };
431
432 match character.as_str() {
433 " " if allow_changes => {
434 let selection = self.get_selection_range();
435 if let Some((start, end)) = selection {
436 self.remove(start..end);
437 self.move_cursor_to(start);
438 event.insert(TextEvent::TEXT_CHANGED);
439 }
440
441 let cursor_pos = self.cursor_pos();
443 self.insert_char(' ', cursor_pos);
444 self.cursor_right();
445
446 event.insert(TextEvent::TEXT_CHANGED);
447 }
448
449 "a" if meta_or_ctrl => {
451 let len = self.len_utf16_cu();
452 self.set_selection((0, len));
453 }
454
455 "c" if meta_or_ctrl && allow_clipboard => {
457 let selected = self.get_selected_text();
458 if let Some(selected) = selected {
459 Clipboard::set(selected).ok();
460 }
461 }
462
463 "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
465 let selection = self.get_selection_range();
466 if let Some((start, end)) = selection {
467 let text = self.get_selected_text().unwrap();
468 self.remove(start..end);
469 Clipboard::set(text).ok();
470 self.move_cursor_to(start);
471 event.insert(TextEvent::TEXT_CHANGED);
472 }
473 }
474
475 "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
477 if let Ok(copied_text) = Clipboard::get() {
478 let selection = self.get_selection_range();
479 if let Some((start, end)) = selection {
480 self.remove(start..end);
481 self.move_cursor_to(start);
482 }
483 let cursor_pos = self.cursor_pos();
484 self.insert(&copied_text, cursor_pos);
485 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
486 self.move_cursor_to(last_idx);
487 event.insert(TextEvent::TEXT_CHANGED);
488 }
489 }
490
491 "z" if meta_or_ctrl && allow_changes => {
493 let undo_result = self.undo();
494
495 if let Some(selection) = undo_result {
496 *self.selection_mut() = selection;
497 event.insert(TextEvent::TEXT_CHANGED);
498 event.insert(TextEvent::SELECTION_CHANGED);
499 }
500 }
501
502 "y" if meta_or_ctrl && allow_changes => {
504 let redo_result = self.redo();
505
506 if let Some(selection) = redo_result {
507 *self.selection_mut() = selection;
508 event.insert(TextEvent::TEXT_CHANGED);
509 event.insert(TextEvent::SELECTION_CHANGED);
510 }
511 }
512
513 _ if allow_changes => {
514 let selection = self.get_selection_range();
516 if let Some((start, end)) = selection {
517 self.remove(start..end);
518 self.move_cursor_to(start);
519 event.insert(TextEvent::TEXT_CHANGED);
520 }
521
522 if let Ok(ch) = character.parse::<char>() {
523 let cursor_pos = self.cursor_pos();
525 let inserted_text_len = self.insert_char(ch, cursor_pos);
526 self.move_cursor_to(cursor_pos + inserted_text_len);
527 event.insert(TextEvent::TEXT_CHANGED);
528 } else {
529 let cursor_pos = self.cursor_pos();
531 let inserted_text_len = self.insert(character, cursor_pos);
532 self.move_cursor_to(cursor_pos + inserted_text_len);
533 event.insert(TextEvent::TEXT_CHANGED);
534 }
535 }
536 _ => {}
537 }
538 }
539 _ => {}
540 }
541
542 if event.contains(TextEvent::TEXT_CHANGED) && !event.contains(TextEvent::SELECTION_CHANGED)
543 {
544 self.clear_selection();
545 }
546
547 if self.get_selection() != selection {
548 event.insert(TextEvent::SELECTION_CHANGED);
549 }
550
551 event
552 }
553
554 fn get_selected_text(&self) -> Option<String>;
555
556 fn undo(&mut self) -> Option<TextSelection>;
557
558 fn redo(&mut self) -> Option<TextSelection>;
559
560 fn editor_history(&mut self) -> &mut EditorHistory;
561
562 fn get_selection_range(&self) -> Option<(usize, usize)>;
563
564 fn get_indentation(&self) -> u8;
565
566 fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
567 let pos_char = self.utf16_cu_to_char(pos);
568 let len_chars = self.len_chars();
569
570 if len_chars == 0 {
571 return (pos, pos);
572 }
573
574 let line_idx = self.char_to_line(pos_char);
576 let line_char = self.line_to_char(line_idx);
577 let line = self.line(line_idx).unwrap();
578
579 let line_str: std::borrow::Cow<str> = line.text;
580 let pos_in_line = pos_char - line_char;
581
582 let mut char_offset = 0;
584 for word in line_str.split_word_bounds() {
585 let word_char_len = word.chars().count();
586 let word_start = char_offset;
587 let word_end = char_offset + word_char_len;
588
589 if pos_in_line >= word_start && pos_in_line < word_end {
590 let start_char = line_char + word_start;
591 let end_char = line_char + word_end;
592 return (
593 self.char_to_utf16_cu(start_char),
594 self.char_to_utf16_cu(end_char),
595 );
596 }
597
598 char_offset = word_end;
599 }
600
601 (pos, pos)
602 }
603}