freya_edit/
text_editor.rs1use 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};
14
15use crate::editor_history::EditorHistory;
16
17pub enum EditorLine {
18 SingleParagraph,
20 Paragraph(usize),
22}
23
24#[derive(Clone, PartialEq, Debug)]
26pub enum TextSelection {
27 Cursor(usize),
28 Range { from: usize, to: usize },
29}
30
31impl TextSelection {
32 pub fn new_cursor(pos: usize) -> Self {
34 Self::Cursor(pos)
35 }
36
37 pub fn new_range((from, to): (usize, usize)) -> Self {
39 Self::Range { from, to }
40 }
41
42 pub fn pos(&self) -> usize {
44 self.end()
45 }
46
47 pub fn set_as_cursor(&mut self) {
49 *self = Self::Cursor(self.end())
50 }
51
52 pub fn set_as_range(&mut self) {
54 *self = Self::Range {
55 from: self.start(),
56 to: self.end(),
57 }
58 }
59
60 pub fn start(&self) -> usize {
62 match self {
63 Self::Cursor(pos) => *pos,
64 Self::Range { from, .. } => *from,
65 }
66 }
67
68 pub fn end(&self) -> usize {
70 match self {
71 Self::Cursor(pos) => *pos,
72 Self::Range { to, .. } => *to,
73 }
74 }
75
76 pub fn move_to(&mut self, position: usize) {
78 match self {
79 Self::Cursor(pos) => *pos = position,
80 Self::Range { to, .. } => {
81 *to = position;
82 }
83 }
84 }
85
86 pub fn is_range(&self) -> bool {
87 matches!(self, Self::Range { .. })
88 }
89}
90
91#[derive(Clone)]
93pub struct Line<'a> {
94 pub text: Cow<'a, str>,
95 pub utf16_len: usize,
96}
97
98impl Line<'_> {
99 pub fn utf16_len(&self) -> usize {
101 self.utf16_len
102 }
103}
104
105impl Display for Line<'_> {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.write_str(&self.text)
108 }
109}
110
111bitflags::bitflags! {
112 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
114 pub struct TextEvent: u8 {
115 const CURSOR_CHANGED = 0x01;
117 const TEXT_CHANGED = 0x02;
119 const SELECTION_CHANGED = 0x04;
121 }
122}
123
124pub trait TextEditor {
126 type LinesIterator<'a>: Iterator<Item = Line<'a>>
127 where
128 Self: 'a;
129
130 fn set(&mut self, text: &str);
131
132 fn lines(&self) -> Self::LinesIterator<'_>;
134
135 fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
137
138 fn insert(&mut self, text: &str, char_idx: usize) -> usize;
140
141 fn remove(&mut self, range: Range<usize>) -> usize;
143
144 fn char_to_line(&self, char_idx: usize) -> usize;
146
147 fn line_to_char(&self, line_idx: usize) -> usize;
149
150 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
151
152 fn char_to_utf16_cu(&self, idx: usize) -> usize;
153
154 fn line(&self, line_idx: usize) -> Option<Line<'_>>;
156
157 fn len_lines(&self) -> usize;
159
160 fn len_chars(&self) -> usize;
162
163 fn len_utf16_cu(&self) -> usize;
165
166 fn selection(&self) -> &TextSelection;
168
169 fn selection_mut(&mut self) -> &mut TextSelection;
171
172 fn cursor_row(&self) -> usize {
174 let pos = self.cursor_pos();
175 let pos_utf8 = self.utf16_cu_to_char(pos);
176 self.char_to_line(pos_utf8)
177 }
178
179 fn cursor_col(&self) -> usize {
181 let pos = self.cursor_pos();
182 let pos_utf8 = self.utf16_cu_to_char(pos);
183 let line = self.char_to_line(pos_utf8);
184 let line_char_utf8 = self.line_to_char(line);
185 let line_char = self.char_to_utf16_cu(line_char_utf8);
186 pos - line_char
187 }
188
189 fn cursor_down(&mut self) -> bool {
191 let old_row = self.cursor_row();
192 let old_col = self.cursor_col();
193
194 match old_row.cmp(&(self.len_lines() - 1)) {
195 Ordering::Less => {
196 let new_row = old_row + 1;
198 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
199 let new_row_len = self.line(new_row).unwrap().utf16_len();
200 let new_col = old_col.min(new_row_len.saturating_sub(1));
201 self.selection_mut().move_to(new_row_char + new_col);
202
203 true
204 }
205 Ordering::Equal => {
206 let end = self.len_utf16_cu();
207 self.selection_mut().move_to(end);
209
210 true
211 }
212 Ordering::Greater => {
213 false
216 }
217 }
218 }
219
220 fn cursor_up(&mut self) -> bool {
222 let pos = self.cursor_pos();
223 let old_row = self.cursor_row();
224 let old_col = self.cursor_col();
225
226 if pos > 0 {
227 if old_row == 0 {
229 self.selection_mut().move_to(0);
230 } else {
231 let new_row = old_row - 1;
232 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
233 let new_row_len = self.line(new_row).unwrap().utf16_len();
234 let new_col = old_col.min(new_row_len.saturating_sub(1));
235 self.selection_mut().move_to(new_row_char + new_col);
236 }
237
238 true
239 } else {
240 false
241 }
242 }
243
244 fn cursor_right(&mut self) -> bool {
246 if self.cursor_pos() < self.len_utf16_cu() {
247 let to = self.selection().end() + 1;
248 self.selection_mut().move_to(to);
249
250 true
251 } else {
252 false
253 }
254 }
255
256 fn cursor_left(&mut self) -> bool {
258 if self.cursor_pos() > 0 {
259 let to = self.selection().end() - 1;
260 self.selection_mut().move_to(to);
261
262 true
263 } else {
264 false
265 }
266 }
267
268 fn cursor_pos(&self) -> usize {
270 self.selection().pos()
271 }
272
273 fn move_cursor_to(&mut self, pos: usize) {
275 self.selection_mut().move_to(pos);
276 }
277
278 fn has_any_selection(&self) -> bool;
280
281 fn get_selection(&self) -> Option<(usize, usize)>;
283
284 fn get_visible_selection(&self, editor_line: EditorLine) -> Option<(usize, usize)>;
286
287 fn clear_selection(&mut self);
289
290 fn set_selection(&mut self, selected: (usize, usize));
292
293 fn measure_selection(&self, to: usize, editor_line: EditorLine) -> TextSelection;
295
296 fn process_key(
298 &mut self,
299 key: &Key,
300 modifiers: &Modifiers,
301 allow_tabs: bool,
302 allow_changes: bool,
303 allow_clipboard: bool,
304 ) -> TextEvent {
305 let mut event = TextEvent::empty();
306
307 let selection = self.get_selection();
308 let skip_arrows_movement = !modifiers.contains(Modifiers::SHIFT) && selection.is_some();
309
310 match key {
311 Key::Named(NamedKey::Shift) => {}
312 Key::Named(NamedKey::Control) => {}
313 Key::Named(NamedKey::Alt) => {}
314 Key::Named(NamedKey::Escape) => {
315 self.clear_selection();
316 }
317 Key::Named(NamedKey::ArrowDown) => {
318 if modifiers.contains(Modifiers::SHIFT) {
319 self.selection_mut().set_as_range();
320 } else {
321 self.selection_mut().set_as_cursor();
322 }
323
324 if !skip_arrows_movement && self.cursor_down() {
325 event.insert(TextEvent::CURSOR_CHANGED);
326 }
327 }
328 Key::Named(NamedKey::ArrowLeft) => {
329 if modifiers.contains(Modifiers::SHIFT) {
330 self.selection_mut().set_as_range();
331 } else {
332 self.selection_mut().set_as_cursor();
333 }
334
335 if !skip_arrows_movement && self.cursor_left() {
336 event.insert(TextEvent::CURSOR_CHANGED);
337 }
338 }
339 Key::Named(NamedKey::ArrowRight) => {
340 if modifiers.contains(Modifiers::SHIFT) {
341 self.selection_mut().set_as_range();
342 } else {
343 self.selection_mut().set_as_cursor();
344 }
345
346 if !skip_arrows_movement && self.cursor_right() {
347 event.insert(TextEvent::CURSOR_CHANGED);
348 }
349 }
350 Key::Named(NamedKey::ArrowUp) => {
351 if modifiers.contains(Modifiers::SHIFT) {
352 self.selection_mut().set_as_range();
353 } else {
354 self.selection_mut().set_as_cursor();
355 }
356
357 if !skip_arrows_movement && self.cursor_up() {
358 event.insert(TextEvent::CURSOR_CHANGED);
359 }
360 }
361 Key::Named(NamedKey::Backspace) if allow_changes => {
362 let cursor_pos = self.cursor_pos();
363 let selection = self.get_selection_range();
364
365 if let Some((start, end)) = selection {
366 self.remove(start..end);
367 self.move_cursor_to(start);
368 event.insert(TextEvent::TEXT_CHANGED);
369 } else if cursor_pos > 0 {
370 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
372 self.move_cursor_to(cursor_pos - removed_text_len);
373 event.insert(TextEvent::TEXT_CHANGED);
374 }
375 }
376 Key::Named(NamedKey::Delete) if allow_changes => {
377 let cursor_pos = self.cursor_pos();
378 let selection = self.get_selection_range();
379
380 if let Some((start, end)) = selection {
381 self.remove(start..end);
382 self.move_cursor_to(start);
383 event.insert(TextEvent::TEXT_CHANGED);
384 } else if cursor_pos < self.len_utf16_cu() {
385 self.remove(cursor_pos..cursor_pos + 1);
387 event.insert(TextEvent::TEXT_CHANGED);
388 }
389 }
390 Key::Named(NamedKey::Enter) if allow_changes => {
391 let cursor_pos = self.cursor_pos();
393 self.insert_char('\n', cursor_pos);
394 self.cursor_right();
395
396 event.insert(TextEvent::TEXT_CHANGED);
397 }
398 Key::Named(NamedKey::Tab) if allow_tabs && allow_changes => {
399 let text = " ".repeat(self.get_identation().into());
401 let cursor_pos = self.cursor_pos();
402 self.insert(&text, cursor_pos);
403 self.move_cursor_to(cursor_pos + text.chars().count());
404
405 event.insert(TextEvent::TEXT_CHANGED);
406 }
407 Key::Character(character) => {
408 let meta_or_ctrl = if cfg!(target_os = "macos") {
409 modifiers.meta()
410 } else {
411 modifiers.ctrl()
412 };
413
414 match character.as_str() {
415 " " if allow_changes => {
416 let selection = self.get_selection_range();
417 if let Some((start, end)) = selection {
418 self.remove(start..end);
419 self.move_cursor_to(start);
420 event.insert(TextEvent::TEXT_CHANGED);
421 }
422
423 let cursor_pos = self.cursor_pos();
425 self.insert_char(' ', cursor_pos);
426 self.cursor_right();
427
428 event.insert(TextEvent::TEXT_CHANGED);
429 }
430
431 "a" if meta_or_ctrl => {
433 let len = self.len_utf16_cu();
434 self.set_selection((0, len));
435 }
436
437 "c" if meta_or_ctrl && allow_clipboard => {
439 let selected = self.get_selected_text();
440 if let Some(selected) = selected {
441 Clipboard::set(selected).ok();
442 }
443 }
444
445 "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
447 let selection = self.get_selection_range();
448 if let Some((start, end)) = selection {
449 let text = self.get_selected_text().unwrap();
450 self.remove(start..end);
451 Clipboard::set(text).ok();
452 self.move_cursor_to(start);
453 event.insert(TextEvent::TEXT_CHANGED);
454 }
455 }
456
457 "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
459 if let Ok(copied_text) = Clipboard::get() {
460 let selection = self.get_selection_range();
461 if let Some((start, end)) = selection {
462 self.remove(start..end);
463 self.move_cursor_to(start);
464 }
465 let cursor_pos = self.cursor_pos();
466 self.insert(&copied_text, cursor_pos);
467 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
468 self.move_cursor_to(last_idx);
469 event.insert(TextEvent::TEXT_CHANGED);
470 }
471 }
472
473 "z" if meta_or_ctrl && allow_changes => {
475 let undo_result = self.undo();
476
477 if let Some(idx) = undo_result {
478 self.move_cursor_to(idx);
479 event.insert(TextEvent::TEXT_CHANGED);
480 }
481 }
482
483 "y" if meta_or_ctrl && allow_changes => {
485 let redo_result = self.redo();
486
487 if let Some(idx) = redo_result {
488 self.move_cursor_to(idx);
489 event.insert(TextEvent::TEXT_CHANGED);
490 }
491 }
492
493 _ if allow_changes => {
494 let selection = self.get_selection_range();
496 if let Some((start, end)) = selection {
497 self.remove(start..end);
498 self.move_cursor_to(start);
499 event.insert(TextEvent::TEXT_CHANGED);
500 }
501
502 if let Ok(ch) = character.parse::<char>() {
503 let cursor_pos = self.cursor_pos();
505 let inserted_text_len = self.insert_char(ch, cursor_pos);
506 self.move_cursor_to(cursor_pos + inserted_text_len);
507 event.insert(TextEvent::TEXT_CHANGED);
508 } else {
509 let cursor_pos = self.cursor_pos();
511 let inserted_text_len = self.insert(character, cursor_pos);
512 self.move_cursor_to(cursor_pos + inserted_text_len);
513 event.insert(TextEvent::TEXT_CHANGED);
514 }
515 }
516 _ => {}
517 }
518 }
519 _ => {}
520 }
521
522 if event.contains(TextEvent::TEXT_CHANGED) {
523 self.clear_selection();
524 }
525
526 if self.get_selection() != selection {
527 event.insert(TextEvent::SELECTION_CHANGED);
528 }
529
530 event
531 }
532
533 fn get_selected_text(&self) -> Option<String>;
534
535 fn undo(&mut self) -> Option<usize>;
536
537 fn redo(&mut self) -> Option<usize>;
538
539 fn editor_history(&mut self) -> &mut EditorHistory;
540
541 fn get_selection_range(&self) -> Option<(usize, usize)>;
542
543 fn get_identation(&self) -> u8;
544
545 fn find_word_boundaries(&self, pos: usize) -> (usize, usize);
546}