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};
13
14use crate::editor_history::EditorHistory;
15
16#[derive(Clone, Default, PartialEq, Debug)]
18pub struct TextCursor(usize);
19
20impl TextCursor {
21 pub fn new(pos: usize) -> Self {
23 Self(pos)
24 }
25
26 pub fn pos(&self) -> usize {
28 self.0
29 }
30
31 pub fn set(&mut self, pos: usize) {
33 self.0 = pos;
34 }
35
36 pub fn write(&mut self) -> &mut usize {
38 &mut self.0
39 }
40}
41
42#[derive(Clone)]
44pub struct Line<'a> {
45 pub text: Cow<'a, str>,
46 pub utf16_len: usize,
47}
48
49impl Line<'_> {
50 pub fn utf16_len(&self) -> usize {
52 self.utf16_len
53 }
54}
55
56impl Display for Line<'_> {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.write_str(&self.text)
59 }
60}
61
62bitflags::bitflags! {
63 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
65 pub struct TextEvent: u8 {
66 const CURSOR_CHANGED = 0x01;
68 const TEXT_CHANGED = 0x02;
70 const SELECTION_CHANGED = 0x04;
72 }
73}
74
75pub trait TextEditor {
77 type LinesIterator<'a>: Iterator<Item = Line<'a>>
78 where
79 Self: 'a;
80
81 fn set(&mut self, text: &str);
82
83 fn lines(&self) -> Self::LinesIterator<'_>;
85
86 fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
88
89 fn insert(&mut self, text: &str, char_idx: usize) -> usize;
91
92 fn remove(&mut self, range: Range<usize>) -> usize;
94
95 fn char_to_line(&self, char_idx: usize) -> usize;
97
98 fn line_to_char(&self, line_idx: usize) -> usize;
100
101 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
102
103 fn char_to_utf16_cu(&self, idx: usize) -> usize;
104
105 fn line(&self, line_idx: usize) -> Option<Line<'_>>;
107
108 fn len_lines(&self) -> usize;
110
111 fn len_chars(&self) -> usize;
113
114 fn len_utf16_cu(&self) -> usize;
116
117 fn cursor(&self) -> &TextCursor;
119
120 fn cursor_mut(&mut self) -> &mut TextCursor;
122
123 fn cursor_row(&self) -> usize {
125 let pos = self.cursor_pos();
126 let pos_utf8 = self.utf16_cu_to_char(pos);
127 self.char_to_line(pos_utf8)
128 }
129
130 fn cursor_col(&self) -> usize {
132 let pos = self.cursor_pos();
133 let pos_utf8 = self.utf16_cu_to_char(pos);
134 let line = self.char_to_line(pos_utf8);
135 let line_char_utf8 = self.line_to_char(line);
136 let line_char = self.char_to_utf16_cu(line_char_utf8);
137 pos - line_char
138 }
139
140 fn cursor_row_and_col(&self) -> (usize, usize) {
142 (self.cursor_row(), self.cursor_col())
143 }
144
145 fn cursor_down(&mut self) -> bool {
147 let old_row = self.cursor_row();
148 let old_col = self.cursor_col();
149
150 match old_row.cmp(&(self.len_lines() - 1)) {
151 Ordering::Less => {
152 let new_row = old_row + 1;
154 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
155 let new_row_len = self.line(new_row).unwrap().utf16_len();
156 let new_col = old_col.min(new_row_len.saturating_sub(1));
157 self.cursor_mut().set(new_row_char + new_col);
158
159 true
160 }
161 Ordering::Equal => {
162 let end = self.len_utf16_cu();
163 self.cursor_mut().set(end);
165
166 true
167 }
168 Ordering::Greater => {
169 false
172 }
173 }
174 }
175
176 fn cursor_up(&mut self) -> bool {
178 let pos = self.cursor_pos();
179 let old_row = self.cursor_row();
180 let old_col = self.cursor_col();
181
182 if pos > 0 {
183 if old_row == 0 {
185 self.cursor_mut().set(0);
186 } else {
187 let new_row = old_row - 1;
188 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
189 let new_row_len = self.line(new_row).unwrap().utf16_len();
190 let new_col = old_col.min(new_row_len.saturating_sub(1));
191 self.cursor_mut().set(new_row_char + new_col);
192 }
193
194 true
195 } else {
196 false
197 }
198 }
199
200 fn cursor_right(&mut self) -> bool {
202 if self.cursor_pos() < self.len_utf16_cu() {
203 *self.cursor_mut().write() += 1;
204
205 true
206 } else {
207 false
208 }
209 }
210
211 fn cursor_left(&mut self) -> bool {
213 if self.cursor_pos() > 0 {
214 *self.cursor_mut().write() -= 1;
215
216 true
217 } else {
218 false
219 }
220 }
221
222 fn cursor_pos(&self) -> usize {
224 self.cursor().pos()
225 }
226
227 fn set_cursor_pos(&mut self, pos: usize) {
229 self.cursor_mut().set(pos);
230 }
231
232 fn has_any_selection(&self) -> bool;
234
235 fn get_selection(&self) -> Option<(usize, usize)>;
237
238 fn get_visible_selection(&self, editor_id: usize) -> Option<(usize, usize)>;
240
241 fn clear_selection(&mut self);
243
244 fn set_selection(&mut self, selected: (usize, usize));
246
247 fn measure_new_selection(&self, from: usize, to: usize, editor_id: usize) -> (usize, usize);
249
250 fn measure_new_cursor(&self, to: usize, editor_id: usize) -> TextCursor;
252
253 fn expand_selection_to_cursor(&mut self);
255
256 fn process_key(
258 &mut self,
259 key: &Key,
260 modifiers: &Modifiers,
261 allow_tabs: bool,
262 allow_changes: bool,
263 allow_clipboard: bool,
264 ) -> TextEvent {
265 let mut event = TextEvent::empty();
266
267 let selection = self.get_selection();
268
269 match key {
270 Key::Shift => {}
271 Key::Control => {}
272 Key::Alt => {}
273 Key::Escape => {
274 self.clear_selection();
275 }
276 Key::ArrowDown => {
277 if modifiers.contains(Modifiers::SHIFT) {
278 self.expand_selection_to_cursor();
279 } else {
280 self.clear_selection();
281 }
282
283 if self.cursor_down() {
284 event.insert(TextEvent::CURSOR_CHANGED);
285 }
286
287 if modifiers.contains(Modifiers::SHIFT) {
288 self.expand_selection_to_cursor();
289 }
290 }
291 Key::ArrowLeft => {
292 if modifiers.contains(Modifiers::SHIFT) {
293 self.expand_selection_to_cursor();
294 } else {
295 self.clear_selection();
296 }
297
298 if self.cursor_left() {
299 event.insert(TextEvent::CURSOR_CHANGED);
300 }
301
302 if modifiers.contains(Modifiers::SHIFT) {
303 self.expand_selection_to_cursor();
304 }
305 }
306 Key::ArrowRight => {
307 if modifiers.contains(Modifiers::SHIFT) {
308 self.expand_selection_to_cursor();
309 } else {
310 self.clear_selection();
311 }
312
313 if self.cursor_right() {
314 event.insert(TextEvent::CURSOR_CHANGED);
315 }
316
317 if modifiers.contains(Modifiers::SHIFT) {
318 self.expand_selection_to_cursor();
319 }
320 }
321 Key::ArrowUp => {
322 if modifiers.contains(Modifiers::SHIFT) {
323 self.expand_selection_to_cursor();
324 } else {
325 self.clear_selection();
326 }
327
328 if self.cursor_up() {
329 event.insert(TextEvent::CURSOR_CHANGED);
330 }
331
332 if modifiers.contains(Modifiers::SHIFT) {
333 self.expand_selection_to_cursor();
334 }
335 }
336 Key::Backspace if allow_changes => {
337 let cursor_pos = self.cursor_pos();
338 let selection = self.get_selection_range();
339
340 if let Some((start, end)) = selection {
341 self.remove(start..end);
342 self.set_cursor_pos(start);
343 event.insert(TextEvent::TEXT_CHANGED);
344 } else if cursor_pos > 0 {
345 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
347 self.set_cursor_pos(cursor_pos - removed_text_len);
348 event.insert(TextEvent::TEXT_CHANGED);
349 }
350 }
351 Key::Delete if allow_changes => {
352 let cursor_pos = self.cursor_pos();
353 let selection = self.get_selection_range();
354
355 if let Some((start, end)) = selection {
356 self.remove(start..end);
357 self.set_cursor_pos(start);
358 event.insert(TextEvent::TEXT_CHANGED);
359 } else if cursor_pos < self.len_utf16_cu() {
360 self.remove(cursor_pos..cursor_pos + 1);
362 event.insert(TextEvent::TEXT_CHANGED);
363 }
364 }
365 Key::Enter if allow_changes => {
366 let cursor_pos = self.cursor_pos();
368 self.insert_char('\n', cursor_pos);
369 self.cursor_right();
370
371 event.insert(TextEvent::TEXT_CHANGED);
372 }
373 Key::Tab if allow_tabs && allow_changes => {
374 let text = " ".repeat(self.get_identation().into());
376 let cursor_pos = self.cursor_pos();
377 self.insert(&text, cursor_pos);
378 self.set_cursor_pos(cursor_pos + text.chars().count());
379
380 event.insert(TextEvent::TEXT_CHANGED);
381 }
382 Key::Character(character) => {
383 let meta_or_ctrl = if cfg!(target_os = "macos") {
384 modifiers.meta()
385 } else {
386 modifiers.ctrl()
387 };
388
389 match character.as_str() {
390 " " if allow_changes => {
391 let selection = self.get_selection_range();
392 if let Some((start, end)) = selection {
393 self.remove(start..end);
394 self.set_cursor_pos(start);
395 event.insert(TextEvent::TEXT_CHANGED);
396 }
397
398 let cursor_pos = self.cursor_pos();
400 self.insert_char(' ', cursor_pos);
401 self.cursor_right();
402
403 event.insert(TextEvent::TEXT_CHANGED);
404 }
405
406 "a" if meta_or_ctrl => {
408 let len = self.len_utf16_cu();
409 self.set_selection((0, len));
410 }
411
412 "c" if meta_or_ctrl && allow_clipboard => {
414 let selected = self.get_selected_text();
415 if let Some(selected) = selected {
416 Clipboard::set(selected).ok();
417 }
418 }
419
420 "x" if meta_or_ctrl && allow_changes && allow_clipboard => {
422 let selection = self.get_selection_range();
423 if let Some((start, end)) = selection {
424 let text = self.get_selected_text().unwrap();
425 self.remove(start..end);
426 Clipboard::set(text).ok();
427 self.set_cursor_pos(start);
428 event.insert(TextEvent::TEXT_CHANGED);
429 }
430 }
431
432 "v" if meta_or_ctrl && allow_changes && allow_clipboard => {
434 if let Ok(copied_text) = Clipboard::get() {
435 let selection = self.get_selection_range();
436 if let Some((start, end)) = selection {
437 self.remove(start..end);
438 self.set_cursor_pos(start);
439 }
440 let cursor_pos = self.cursor_pos();
441 self.insert(&copied_text, cursor_pos);
442 let last_idx = copied_text.encode_utf16().count() + cursor_pos;
443 self.set_cursor_pos(last_idx);
444 event.insert(TextEvent::TEXT_CHANGED);
445 }
446 }
447
448 "z" if meta_or_ctrl && allow_changes => {
450 let undo_result = self.undo();
451
452 if let Some(idx) = undo_result {
453 self.set_cursor_pos(idx);
454 event.insert(TextEvent::TEXT_CHANGED);
455 }
456 }
457
458 "y" if meta_or_ctrl && allow_changes => {
460 let redo_result = self.redo();
461
462 if let Some(idx) = redo_result {
463 self.set_cursor_pos(idx);
464 event.insert(TextEvent::TEXT_CHANGED);
465 }
466 }
467
468 _ if allow_changes => {
469 let selection = self.get_selection_range();
471 if let Some((start, end)) = selection {
472 self.remove(start..end);
473 self.set_cursor_pos(start);
474 event.insert(TextEvent::TEXT_CHANGED);
475 }
476
477 if let Ok(ch) = character.parse::<char>() {
478 let cursor_pos = self.cursor_pos();
480 let inserted_text_len = self.insert_char(ch, cursor_pos);
481 self.set_cursor_pos(cursor_pos + inserted_text_len);
482 event.insert(TextEvent::TEXT_CHANGED);
483 } else {
484 let cursor_pos = self.cursor_pos();
486 let inserted_text_len = self.insert(character, cursor_pos);
487 self.set_cursor_pos(cursor_pos + inserted_text_len);
488 event.insert(TextEvent::TEXT_CHANGED);
489 }
490 }
491 _ => {}
492 }
493 }
494 _ => {}
495 }
496
497 if event.contains(TextEvent::TEXT_CHANGED) {
498 self.clear_selection();
499 }
500
501 if self.get_selection() != selection {
502 event.insert(TextEvent::SELECTION_CHANGED);
503 }
504
505 event
506 }
507
508 fn get_selected_text(&self) -> Option<String>;
509
510 fn undo(&mut self) -> Option<usize>;
511
512 fn redo(&mut self) -> Option<usize>;
513
514 fn editor_history(&mut self) -> &mut EditorHistory;
515
516 fn get_selection_range(&self) -> Option<(usize, usize)>;
517
518 fn get_identation(&self) -> u8;
519}