freya_hooks/
text_editor.rs1use std::{
2 borrow::Cow,
3 cmp::Ordering,
4 fmt::Display,
5 ops::Range,
6};
7
8use dioxus_clipboard::prelude::UseClipboard;
9use freya_elements::events::keyboard::{
10 Code,
11 Key,
12 Modifiers,
13};
14
15use crate::EditorHistory;
16
17#[derive(Clone, Default, PartialEq, Debug)]
19pub struct TextCursor(usize);
20
21impl TextCursor {
22 pub fn new(pos: usize) -> Self {
24 Self(pos)
25 }
26
27 pub fn pos(&self) -> usize {
29 self.0
30 }
31
32 pub fn set(&mut self, pos: usize) {
34 self.0 = pos;
35 }
36
37 pub fn write(&mut self) -> &mut usize {
39 &mut self.0
40 }
41}
42
43#[derive(Clone)]
45pub struct Line<'a> {
46 pub text: Cow<'a, str>,
47 pub utf16_len: usize,
48}
49
50impl Line<'_> {
51 pub fn utf16_len(&self) -> usize {
53 self.utf16_len
54 }
55}
56
57impl Display for Line<'_> {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.write_str(&self.text)
60 }
61}
62
63bitflags::bitflags! {
64 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
66 pub struct TextEvent: u8 {
67 const CURSOR_CHANGED = 0x01;
69 const TEXT_CHANGED = 0x02;
71 const SELECTION_CHANGED = 0x04;
73 }
74}
75
76pub trait TextEditor {
78 type LinesIterator<'a>: Iterator<Item = Line<'a>>
79 where
80 Self: 'a;
81
82 fn set(&mut self, text: &str);
83
84 fn lines(&self) -> Self::LinesIterator<'_>;
86
87 fn insert_char(&mut self, char: char, char_idx: usize) -> usize;
89
90 fn insert(&mut self, text: &str, char_idx: usize) -> usize;
92
93 fn remove(&mut self, range: Range<usize>) -> usize;
95
96 fn char_to_line(&self, char_idx: usize) -> usize;
98
99 fn line_to_char(&self, line_idx: usize) -> usize;
101
102 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;
103
104 fn char_to_utf16_cu(&self, idx: usize) -> usize;
105
106 fn line(&self, line_idx: usize) -> Option<Line<'_>>;
108
109 fn len_lines(&self) -> usize;
111
112 fn len_chars(&self) -> usize;
114
115 fn len_utf16_cu(&self) -> usize;
117
118 fn cursor(&self) -> &TextCursor;
120
121 fn cursor_mut(&mut self) -> &mut TextCursor;
123
124 fn cursor_row(&self) -> usize {
126 let pos = self.cursor_pos();
127 let pos_utf8 = self.utf16_cu_to_char(pos);
128 self.char_to_line(pos_utf8)
129 }
130
131 fn cursor_col(&self) -> usize {
133 let pos = self.cursor_pos();
134 let pos_utf8 = self.utf16_cu_to_char(pos);
135 let line = self.char_to_line(pos_utf8);
136 let line_char_utf8 = self.line_to_char(line);
137 let line_char = self.char_to_utf16_cu(line_char_utf8);
138 pos - line_char
139 }
140
141 fn cursor_row_and_col(&self) -> (usize, usize) {
143 (self.cursor_row(), self.cursor_col())
144 }
145
146 fn cursor_down(&mut self) -> bool {
148 let old_row = self.cursor_row();
149 let old_col = self.cursor_col();
150
151 match old_row.cmp(&(self.len_lines() - 1)) {
152 Ordering::Less => {
153 let new_row = old_row + 1;
155 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
156 let new_row_len = self.line(new_row).unwrap().utf16_len();
157 let new_col = old_col.min(new_row_len.saturating_sub(1));
158 self.cursor_mut().set(new_row_char + new_col);
159
160 true
161 }
162 Ordering::Equal => {
163 let end = self.len_utf16_cu();
164 self.cursor_mut().set(end);
166
167 true
168 }
169 Ordering::Greater => {
170 false
173 }
174 }
175 }
176
177 fn cursor_up(&mut self) -> bool {
179 let pos = self.cursor_pos();
180 let old_row = self.cursor_row();
181 let old_col = self.cursor_col();
182
183 if pos > 0 {
184 if old_row == 0 {
186 self.cursor_mut().set(0);
187 } else {
188 let new_row = old_row - 1;
189 let new_row_char = self.char_to_utf16_cu(self.line_to_char(new_row));
190 let new_row_len = self.line(new_row).unwrap().utf16_len();
191 let new_col = old_col.min(new_row_len.saturating_sub(1));
192 self.cursor_mut().set(new_row_char + new_col);
193 }
194
195 true
196 } else {
197 false
198 }
199 }
200
201 fn cursor_right(&mut self) -> bool {
203 if self.cursor_pos() < self.len_utf16_cu() {
204 *self.cursor_mut().write() += 1;
205
206 true
207 } else {
208 false
209 }
210 }
211
212 fn cursor_left(&mut self) -> bool {
214 if self.cursor_pos() > 0 {
215 *self.cursor_mut().write() -= 1;
216
217 true
218 } else {
219 false
220 }
221 }
222
223 fn cursor_pos(&self) -> usize {
225 self.cursor().pos()
226 }
227
228 fn set_cursor_pos(&mut self, pos: usize) {
230 self.cursor_mut().set(pos);
231 }
232
233 fn has_any_selection(&self) -> bool;
235
236 fn get_selection(&self) -> Option<(usize, usize)>;
238
239 fn get_visible_selection(&self, editor_id: usize) -> Option<(usize, usize)>;
241
242 fn clear_selection(&mut self);
244
245 fn set_selection(&mut self, selected: (usize, usize));
247
248 fn measure_new_selection(&self, from: usize, to: usize, editor_id: usize) -> (usize, usize);
250
251 fn measure_new_cursor(&self, to: usize, editor_id: usize) -> TextCursor;
253
254 fn expand_selection_to_cursor(&mut self);
256
257 fn get_clipboard(&mut self) -> &mut UseClipboard;
258
259 fn process_key(
261 &mut self,
262 key: &Key,
263 code: &Code,
264 modifiers: &Modifiers,
265 allow_tabs: bool,
266 allow_changes: bool,
267 allow_clipboard: bool,
268 ) -> TextEvent {
269 let mut event = if self.has_any_selection() {
270 TextEvent::SELECTION_CHANGED
271 } else {
272 TextEvent::empty()
273 };
274
275 match key {
276 Key::Shift => {
277 event.remove(TextEvent::SELECTION_CHANGED);
278 }
279 Key::Control => {
280 event.remove(TextEvent::SELECTION_CHANGED);
281 }
282 Key::Alt => {
283 event.remove(TextEvent::SELECTION_CHANGED);
284 }
285 Key::Escape => {
286 event.insert(TextEvent::SELECTION_CHANGED);
287 }
288 Key::ArrowDown => {
289 if modifiers.contains(Modifiers::SHIFT) {
290 event.remove(TextEvent::SELECTION_CHANGED);
291 self.expand_selection_to_cursor();
292 }
293
294 if self.cursor_down() {
295 event.insert(TextEvent::CURSOR_CHANGED);
296 }
297
298 if modifiers.contains(Modifiers::SHIFT) {
299 self.expand_selection_to_cursor();
300 }
301 }
302 Key::ArrowLeft => {
303 if modifiers.contains(Modifiers::SHIFT) {
304 event.remove(TextEvent::SELECTION_CHANGED);
305 self.expand_selection_to_cursor();
306 }
307
308 if self.cursor_left() {
309 event.insert(TextEvent::CURSOR_CHANGED);
310 }
311
312 if modifiers.contains(Modifiers::SHIFT) {
313 self.expand_selection_to_cursor();
314 }
315 }
316 Key::ArrowRight => {
317 if modifiers.contains(Modifiers::SHIFT) {
318 event.remove(TextEvent::SELECTION_CHANGED);
319 self.expand_selection_to_cursor();
320 }
321
322 if self.cursor_right() {
323 event.insert(TextEvent::CURSOR_CHANGED);
324 }
325
326 if modifiers.contains(Modifiers::SHIFT) {
327 self.expand_selection_to_cursor();
328 }
329 }
330 Key::ArrowUp => {
331 if modifiers.contains(Modifiers::SHIFT) {
332 event.remove(TextEvent::SELECTION_CHANGED);
333 self.expand_selection_to_cursor();
334 }
335
336 if self.cursor_up() {
337 event.insert(TextEvent::CURSOR_CHANGED);
338 }
339
340 if modifiers.contains(Modifiers::SHIFT) {
341 self.expand_selection_to_cursor();
342 }
343 }
344 Key::Backspace if allow_changes => {
345 let cursor_pos = self.cursor_pos();
346 let selection = self.get_selection_range();
347
348 if let Some((start, end)) = selection {
349 self.remove(start..end);
350 self.set_cursor_pos(start);
351 event.insert(TextEvent::TEXT_CHANGED);
352 } else if cursor_pos > 0 {
353 let removed_text_len = self.remove(cursor_pos - 1..cursor_pos);
355 self.set_cursor_pos(cursor_pos - removed_text_len);
356 event.insert(TextEvent::TEXT_CHANGED);
357 }
358 }
359 Key::Delete if allow_changes => {
360 let cursor_pos = self.cursor_pos();
361 let selection = self.get_selection_range();
362
363 if let Some((start, end)) = selection {
364 self.remove(start..end);
365 self.set_cursor_pos(start);
366 event.insert(TextEvent::TEXT_CHANGED);
367 } else if cursor_pos < self.len_utf16_cu() {
368 self.remove(cursor_pos..cursor_pos + 1);
370 event.insert(TextEvent::TEXT_CHANGED);
371 }
372 }
373 Key::Enter if allow_changes => {
374 let cursor_pos = self.cursor_pos();
376 self.insert_char('\n', cursor_pos);
377 self.cursor_right();
378
379 event.insert(TextEvent::TEXT_CHANGED);
380 }
381 Key::Tab if allow_tabs && allow_changes => {
382 let text = " ".repeat(self.get_identation().into());
384 let cursor_pos = self.cursor_pos();
385 self.insert(&text, cursor_pos);
386 self.set_cursor_pos(cursor_pos + text.chars().count());
387
388 event.insert(TextEvent::TEXT_CHANGED);
389 }
390 Key::Character(character) => {
391 let meta_or_ctrl = if cfg!(target_os = "macos") {
392 modifiers.meta()
393 } else {
394 modifiers.ctrl()
395 };
396
397 match code {
398 Code::Delete if allow_changes => {}
399 Code::Space if allow_changes => {
400 let cursor_pos = self.cursor_pos();
402 self.insert_char(' ', cursor_pos);
403 self.cursor_right();
404
405 event.insert(TextEvent::TEXT_CHANGED);
406 }
407
408 Code::KeyA if meta_or_ctrl => {
410 let len = self.len_utf16_cu();
411 self.set_selection((0, len));
412 event.remove(TextEvent::SELECTION_CHANGED);
413 }
414
415 Code::KeyC if meta_or_ctrl && allow_clipboard => {
417 let selected = self.get_selected_text();
418 if let Some(selected) = selected {
419 self.get_clipboard().set(selected).ok();
420 }
421 event.remove(TextEvent::SELECTION_CHANGED);
422 }
423
424 Code::KeyX if meta_or_ctrl && allow_changes && allow_clipboard => {
426 let selection = self.get_selection_range();
427 if let Some((start, end)) = selection {
428 let text = self.get_selected_text().unwrap();
429 self.remove(start..end);
430 self.get_clipboard().set(text).ok();
431 self.set_cursor_pos(start);
432 event.insert(TextEvent::TEXT_CHANGED);
433 }
434 }
435
436 Code::KeyV if meta_or_ctrl && allow_changes && allow_clipboard => {
438 let copied_text = self.get_clipboard().get();
439 if let Ok(copied_text) = copied_text {
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 Code::KeyZ 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 Code::KeyY 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
483 event.insert(TextEvent::TEXT_CHANGED);
484 } else {
485 let cursor_pos = self.cursor_pos();
487 let inserted_text_len = self.insert(character, cursor_pos);
488 self.set_cursor_pos(cursor_pos + inserted_text_len);
489
490 event.insert(TextEvent::TEXT_CHANGED);
491 }
492 }
493 _ => {}
494 }
495 }
496 _ => {}
497 }
498
499 if event.contains(TextEvent::SELECTION_CHANGED) {
500 self.clear_selection();
501 }
502
503 event
504 }
505
506 fn get_selected_text(&self) -> Option<String>;
507
508 fn undo(&mut self) -> Option<usize>;
509
510 fn redo(&mut self) -> Option<usize>;
511
512 fn editor_history(&mut self) -> &mut EditorHistory;
513
514 fn get_selection_range(&self) -> Option<(usize, usize)>;
515
516 fn get_identation(&self) -> u8;
517}