1use iced::Task;
4use iced::widget::operation::{focus, select_all};
5
6use super::command::{
7 Command, CompositeCommand, DeleteCharCommand, DeleteForwardCommand,
8 InsertCharCommand, InsertNewlineCommand, ReplaceTextCommand,
9};
10use super::{CURSOR_BLINK_INTERVAL, CodeEditor, ImePreedit, Message};
11
12impl CodeEditor {
13 pub fn update(&mut self, message: &Message) -> Task<Message> {
23 match message {
24 Message::CharacterInput(ch) => {
25 if !self.is_grouping {
27 self.history.begin_group("Typing");
28 self.is_grouping = true;
29 }
30
31 let (line, col) = self.cursor;
32 let mut cmd =
33 InsertCharCommand::new(line, col, *ch, self.cursor);
34 cmd.execute(&mut self.buffer, &mut self.cursor);
35 self.history.push(Box::new(cmd));
36
37 self.reset_cursor_blink();
38 self.refresh_search_matches_if_needed();
39 self.cache.clear();
40 Task::none()
41 }
42 Message::Backspace => {
43 if self.is_grouping {
45 self.history.end_group();
46 self.is_grouping = false;
47 }
48
49 if self.selection_start.is_some()
51 && self.selection_end.is_some()
52 {
53 self.delete_selection();
54 self.reset_cursor_blink();
55 self.refresh_search_matches_if_needed();
56 self.cache.clear();
57 return self.scroll_to_cursor();
58 }
59
60 let (line, col) = self.cursor;
62 let mut cmd = DeleteCharCommand::new(
63 &self.buffer,
64 line,
65 col,
66 self.cursor,
67 );
68 cmd.execute(&mut self.buffer, &mut self.cursor);
69 self.history.push(Box::new(cmd));
70
71 self.reset_cursor_blink();
72 self.refresh_search_matches_if_needed();
73 self.cache.clear();
74 self.scroll_to_cursor()
75 }
76 Message::Delete => {
77 if self.is_grouping {
79 self.history.end_group();
80 self.is_grouping = false;
81 }
82
83 if self.selection_start.is_some()
85 && self.selection_end.is_some()
86 {
87 self.delete_selection();
88 self.reset_cursor_blink();
89 self.refresh_search_matches_if_needed();
90 self.cache.clear();
91 return self.scroll_to_cursor();
92 }
93
94 let (line, col) = self.cursor;
96 let mut cmd = DeleteForwardCommand::new(
97 &self.buffer,
98 line,
99 col,
100 self.cursor,
101 );
102 cmd.execute(&mut self.buffer, &mut self.cursor);
103 self.history.push(Box::new(cmd));
104
105 self.reset_cursor_blink();
106 self.refresh_search_matches_if_needed();
107 self.cache.clear();
108 Task::none()
109 }
110 Message::Enter => {
111 if self.is_grouping {
113 self.history.end_group();
114 self.is_grouping = false;
115 }
116
117 let (line, col) = self.cursor;
118 let mut cmd = InsertNewlineCommand::new(line, col, self.cursor);
119 cmd.execute(&mut self.buffer, &mut self.cursor);
120 self.history.push(Box::new(cmd));
121
122 self.reset_cursor_blink();
123 self.refresh_search_matches_if_needed();
124 self.cache.clear();
125 self.scroll_to_cursor()
126 }
127 Message::Tab => {
128 if !self.is_grouping {
131 self.history.begin_group("Tab");
132 self.is_grouping = true;
133 }
134
135 let (line, col) = self.cursor;
136 for i in 0..4 {
138 let current_col = col + i;
139 let mut cmd = InsertCharCommand::new(
140 line,
141 current_col,
142 ' ',
143 (line, current_col),
144 );
145 cmd.execute(&mut self.buffer, &mut self.cursor);
146 self.history.push(Box::new(cmd));
147 }
148
149 self.reset_cursor_blink();
150 self.cache.clear();
151 Task::none()
152 }
153 Message::ArrowKey(direction, shift_pressed) => {
154 if self.is_grouping {
156 self.history.end_group();
157 self.is_grouping = false;
158 }
159
160 if *shift_pressed {
161 if self.selection_start.is_none() {
163 self.selection_start = Some(self.cursor);
164 }
165 self.move_cursor(*direction);
166 self.selection_end = Some(self.cursor);
167 } else {
168 self.clear_selection();
170 self.move_cursor(*direction);
171 }
172 self.reset_cursor_blink();
173 self.cache.clear();
174 self.scroll_to_cursor()
175 }
176 Message::MouseClick(point) => {
177 super::FOCUSED_EDITOR_ID.store(
179 self.editor_id,
180 std::sync::atomic::Ordering::Relaxed,
181 );
182
183 if self.is_grouping {
185 self.history.end_group();
186 self.is_grouping = false;
187 }
188
189 self.handle_mouse_click(*point);
190 self.reset_cursor_blink();
191 self.clear_selection();
193 self.is_dragging = true;
194 self.selection_start = Some(self.cursor);
195
196 self.has_canvas_focus = true;
198 self.show_cursor = true;
199
200 Task::none()
201 }
202 Message::MouseDrag(point) => {
203 if self.is_dragging {
204 self.handle_mouse_drag(*point);
205 self.cache.clear();
206 }
207 Task::none()
208 }
209 Message::MouseRelease => {
210 self.is_dragging = false;
211 Task::none()
212 }
213 Message::Copy => self.copy_selection(),
214 Message::Paste(text) => {
215 if self.is_grouping {
217 self.history.end_group();
218 self.is_grouping = false;
219 }
220
221 if text.is_empty() {
223 iced::clipboard::read().and_then(|clipboard_text| {
225 Task::done(Message::Paste(clipboard_text))
226 })
227 } else {
228 self.paste_text(text);
230 self.refresh_search_matches_if_needed();
231 self.cache.clear();
232 self.scroll_to_cursor()
233 }
234 }
235 Message::DeleteSelection => {
236 if self.is_grouping {
238 self.history.end_group();
239 self.is_grouping = false;
240 }
241
242 self.delete_selection();
244 self.reset_cursor_blink();
245 self.cache.clear();
246 self.scroll_to_cursor()
247 }
248 Message::Tick => {
249 if self.is_focused()
251 && self.has_canvas_focus
252 && self.last_blink.elapsed() >= CURSOR_BLINK_INTERVAL
253 {
254 self.cursor_visible = !self.cursor_visible;
255 self.last_blink = std::time::Instant::now();
256 self.cache.clear();
257 }
258
259 if !self.has_canvas_focus {
261 self.show_cursor = false;
262 }
263
264 Task::none()
265 }
266 Message::PageUp => {
267 self.page_up();
268 self.reset_cursor_blink();
269 self.scroll_to_cursor()
270 }
271 Message::PageDown => {
272 self.page_down();
273 self.reset_cursor_blink();
274 self.scroll_to_cursor()
275 }
276 Message::Home(shift_pressed) => {
277 if *shift_pressed {
278 if self.selection_start.is_none() {
280 self.selection_start = Some(self.cursor);
281 }
282 self.cursor.1 = 0; self.selection_end = Some(self.cursor);
284 } else {
285 self.clear_selection();
287 self.cursor.1 = 0;
288 }
289 self.reset_cursor_blink();
290 self.cache.clear();
291 Task::none()
292 }
293 Message::End(shift_pressed) => {
294 let line = self.cursor.0;
295 let line_len = self.buffer.line_len(line);
296
297 if *shift_pressed {
298 if self.selection_start.is_none() {
300 self.selection_start = Some(self.cursor);
301 }
302 self.cursor.1 = line_len; self.selection_end = Some(self.cursor);
304 } else {
305 self.clear_selection();
307 self.cursor.1 = line_len;
308 }
309 self.reset_cursor_blink();
310 self.cache.clear();
311 Task::none()
312 }
313 Message::CtrlHome => {
314 self.clear_selection();
316 self.cursor = (0, 0);
317 self.reset_cursor_blink();
318 self.cache.clear();
319 self.scroll_to_cursor()
320 }
321 Message::CtrlEnd => {
322 self.clear_selection();
324 let last_line = self.buffer.line_count().saturating_sub(1);
325 let last_col = self.buffer.line_len(last_line);
326 self.cursor = (last_line, last_col);
327 self.reset_cursor_blink();
328 self.cache.clear();
329 self.scroll_to_cursor()
330 }
331 Message::Scrolled(viewport) => {
332 self.viewport_scroll = viewport.absolute_offset().y;
334 let new_height = viewport.bounds().height;
335 let new_width = viewport.bounds().width;
336 if (self.viewport_height - new_height).abs() > 1.0
339 || (self.viewport_width - new_width).abs() > 1.0
340 {
341 self.cache.clear();
342 }
343 self.viewport_height = new_height;
344 self.viewport_width = new_width;
345 Task::none()
346 }
347 Message::Undo => {
348 if self.is_grouping {
350 self.history.end_group();
351 self.is_grouping = false;
352 }
353
354 if self.history.undo(&mut self.buffer, &mut self.cursor) {
355 self.clear_selection();
356 self.reset_cursor_blink();
357 self.refresh_search_matches_if_needed();
358 self.cache.clear();
359 self.scroll_to_cursor()
360 } else {
361 Task::none()
362 }
363 }
364 Message::Redo => {
365 if self.history.redo(&mut self.buffer, &mut self.cursor) {
366 self.clear_selection();
367 self.reset_cursor_blink();
368 self.refresh_search_matches_if_needed();
369 self.cache.clear();
370 self.scroll_to_cursor()
371 } else {
372 Task::none()
373 }
374 }
375 Message::OpenSearch => {
376 self.search_state.open_search();
377 self.cache.clear();
378
379 Task::batch([
381 focus(self.search_state.search_input_id.clone()),
382 select_all(self.search_state.search_input_id.clone()),
383 ])
384 }
385 Message::OpenSearchReplace => {
386 self.search_state.open_replace();
387 self.cache.clear();
388
389 Task::batch([
391 focus(self.search_state.search_input_id.clone()),
392 select_all(self.search_state.search_input_id.clone()),
393 ])
394 }
395 Message::CloseSearch => {
396 self.search_state.close();
397 self.cache.clear();
398 Task::none()
399 }
400 Message::SearchQueryChanged(query) => {
401 self.search_state.set_query(query.clone(), &self.buffer);
402 self.cache.clear();
403
404 if let Some(match_pos) = self.search_state.current_match() {
406 self.cursor = (match_pos.line, match_pos.col);
407 self.clear_selection();
408 return self.scroll_to_cursor();
409 }
410 Task::none()
411 }
412 Message::ReplaceQueryChanged(replace_text) => {
413 self.search_state.set_replace_with(replace_text.clone());
414 Task::none()
415 }
416 Message::ToggleCaseSensitive => {
417 self.search_state.toggle_case_sensitive(&self.buffer);
418 self.cache.clear();
419
420 if let Some(match_pos) = self.search_state.current_match() {
422 self.cursor = (match_pos.line, match_pos.col);
423 self.clear_selection();
424 return self.scroll_to_cursor();
425 }
426 Task::none()
427 }
428 Message::FindNext => {
429 if !self.search_state.matches.is_empty() {
430 self.search_state.next_match();
431 if let Some(match_pos) = self.search_state.current_match() {
432 self.cursor = (match_pos.line, match_pos.col);
433 self.clear_selection();
434 self.cache.clear();
435 return self.scroll_to_cursor();
436 }
437 }
438 Task::none()
439 }
440 Message::FindPrevious => {
441 if !self.search_state.matches.is_empty() {
442 self.search_state.previous_match();
443 if let Some(match_pos) = self.search_state.current_match() {
444 self.cursor = (match_pos.line, match_pos.col);
445 self.clear_selection();
446 self.cache.clear();
447 return self.scroll_to_cursor();
448 }
449 }
450 Task::none()
451 }
452 Message::ReplaceNext => {
453 if let Some(match_pos) = self.search_state.current_match() {
455 let query_len = self.search_state.query.chars().count();
456 let replace_text = self.search_state.replace_with.clone();
457
458 let mut cmd = ReplaceTextCommand::new(
460 &self.buffer,
461 (match_pos.line, match_pos.col),
462 query_len,
463 replace_text,
464 self.cursor,
465 );
466 cmd.execute(&mut self.buffer, &mut self.cursor);
467 self.history.push(Box::new(cmd));
468
469 self.search_state.update_matches(&self.buffer);
471
472 if !self.search_state.matches.is_empty()
474 && let Some(next_match) =
475 self.search_state.current_match()
476 {
477 self.cursor = (next_match.line, next_match.col);
478 }
479
480 self.clear_selection();
481 self.cache.clear();
482 return self.scroll_to_cursor();
483 }
484 Task::none()
485 }
486 Message::ReplaceAll => {
487 if !self.search_state.matches.is_empty() {
489 let query_len = self.search_state.query.chars().count();
490 let replace_text = self.search_state.replace_with.clone();
491
492 let mut composite =
494 CompositeCommand::new("Replace All".to_string());
495
496 for match_pos in self.search_state.matches.iter().rev() {
498 let cmd = ReplaceTextCommand::new(
499 &self.buffer,
500 (match_pos.line, match_pos.col),
501 query_len,
502 replace_text.clone(),
503 self.cursor,
504 );
505 composite.add(Box::new(cmd));
506 }
507
508 composite.execute(&mut self.buffer, &mut self.cursor);
510 self.history.push(Box::new(composite));
511
512 self.search_state.update_matches(&self.buffer);
514
515 self.clear_selection();
516 self.cache.clear();
517 return self.scroll_to_cursor();
518 }
519 Task::none()
520 }
521 Message::SearchDialogTab => {
522 self.search_state.focus_next_field();
524
525 match self.search_state.focused_field {
527 crate::canvas_editor::search::SearchFocusedField::Search => {
528 focus(self.search_state.search_input_id.clone())
529 }
530 crate::canvas_editor::search::SearchFocusedField::Replace => {
531 focus(self.search_state.replace_input_id.clone())
532 }
533 }
534 }
535 Message::SearchDialogShiftTab => {
536 self.search_state.focus_previous_field();
538
539 match self.search_state.focused_field {
541 crate::canvas_editor::search::SearchFocusedField::Search => {
542 focus(self.search_state.search_input_id.clone())
543 }
544 crate::canvas_editor::search::SearchFocusedField::Replace => {
545 focus(self.search_state.replace_input_id.clone())
546 }
547 }
548 }
549 Message::CanvasFocusGained => {
550 self.has_canvas_focus = true;
551 self.show_cursor = true;
552 self.reset_cursor_blink();
553 self.cache.clear();
554 Task::none()
555 }
556 Message::CanvasFocusLost => {
557 self.has_canvas_focus = false;
558 self.show_cursor = false;
559 self.ime_preedit = None;
560 self.cache.clear();
561 Task::none()
562 }
563 Message::ImeOpened => {
564 self.ime_preedit = None;
571 self.cache.clear();
572 Task::none()
573 }
574 Message::ImePreedit(content, selection) => {
575 if content.is_empty() {
586 self.ime_preedit = None;
587 } else {
588 self.ime_preedit = Some(ImePreedit {
589 content: content.clone(),
590 selection: selection.clone(),
591 });
592 }
593
594 self.cache.clear();
595 Task::none()
596 }
597 Message::ImeCommit(text) => {
598 self.ime_preedit = None;
607
608 if text.is_empty() {
609 self.cache.clear();
610 return Task::none();
611 }
612
613 if !self.is_grouping {
614 self.history.begin_group("Typing");
615 self.is_grouping = true;
616 }
617
618 self.paste_text(text);
619 self.reset_cursor_blink();
620 self.refresh_search_matches_if_needed();
621 self.cache.clear();
622 self.scroll_to_cursor()
623 }
624 Message::ImeClosed => {
625 self.ime_preedit = None;
631 self.cache.clear();
632 Task::none()
633 }
634 }
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641 use crate::canvas_editor::ArrowDirection;
642
643 #[test]
644 fn test_new_canvas_editor() {
645 let editor = CodeEditor::new("line1\nline2", "py");
646 assert_eq!(editor.cursor, (0, 0));
647 }
648
649 #[test]
650 fn test_home_key() {
651 let mut editor = CodeEditor::new("hello world", "py");
652 editor.cursor = (0, 5); let _ = editor.update(&Message::Home(false));
654 assert_eq!(editor.cursor, (0, 0));
655 }
656
657 #[test]
658 fn test_end_key() {
659 let mut editor = CodeEditor::new("hello world", "py");
660 editor.cursor = (0, 0);
661 let _ = editor.update(&Message::End(false));
662 assert_eq!(editor.cursor, (0, 11)); }
664
665 #[test]
666 fn test_arrow_key_with_shift_creates_selection() {
667 let mut editor = CodeEditor::new("hello world", "py");
668 editor.cursor = (0, 0);
669
670 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, true));
672 assert!(editor.selection_start.is_some());
673 assert!(editor.selection_end.is_some());
674 }
675
676 #[test]
677 fn test_arrow_key_without_shift_clears_selection() {
678 let mut editor = CodeEditor::new("hello world", "py");
679 editor.selection_start = Some((0, 0));
680 editor.selection_end = Some((0, 5));
681
682 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, false));
684 assert_eq!(editor.selection_start, None);
685 assert_eq!(editor.selection_end, None);
686 }
687
688 #[test]
689 fn test_typing_with_selection() {
690 let mut editor = CodeEditor::new("hello world", "py");
691 editor.selection_start = Some((0, 0));
692 editor.selection_end = Some((0, 5));
693
694 let _ = editor.update(&Message::CharacterInput('X'));
695 assert_eq!(editor.buffer.line(0), "Xhello world");
698 }
699
700 #[test]
701 fn test_ctrl_home() {
702 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
703 editor.cursor = (2, 5); let _ = editor.update(&Message::CtrlHome);
705 assert_eq!(editor.cursor, (0, 0)); }
707
708 #[test]
709 fn test_ctrl_end() {
710 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
711 editor.cursor = (0, 0); let _ = editor.update(&Message::CtrlEnd);
713 assert_eq!(editor.cursor, (2, 5)); }
715
716 #[test]
717 fn test_ctrl_home_clears_selection() {
718 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
719 editor.cursor = (2, 5);
720 editor.selection_start = Some((0, 0));
721 editor.selection_end = Some((2, 5));
722
723 let _ = editor.update(&Message::CtrlHome);
724 assert_eq!(editor.cursor, (0, 0));
725 assert_eq!(editor.selection_start, None);
726 assert_eq!(editor.selection_end, None);
727 }
728
729 #[test]
730 fn test_ctrl_end_clears_selection() {
731 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
732 editor.cursor = (0, 0);
733 editor.selection_start = Some((0, 0));
734 editor.selection_end = Some((1, 3));
735
736 let _ = editor.update(&Message::CtrlEnd);
737 assert_eq!(editor.cursor, (2, 5));
738 assert_eq!(editor.selection_start, None);
739 assert_eq!(editor.selection_end, None);
740 }
741
742 #[test]
743 fn test_delete_selection_message() {
744 let mut editor = CodeEditor::new("hello world", "py");
745 editor.cursor = (0, 0);
746 editor.selection_start = Some((0, 0));
747 editor.selection_end = Some((0, 5));
748
749 let _ = editor.update(&Message::DeleteSelection);
750 assert_eq!(editor.buffer.line(0), " world");
751 assert_eq!(editor.cursor, (0, 0));
752 assert_eq!(editor.selection_start, None);
753 assert_eq!(editor.selection_end, None);
754 }
755
756 #[test]
757 fn test_delete_selection_multiline() {
758 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
759 editor.cursor = (0, 2);
760 editor.selection_start = Some((0, 2));
761 editor.selection_end = Some((2, 2));
762
763 let _ = editor.update(&Message::DeleteSelection);
764 assert_eq!(editor.buffer.line(0), "line3");
765 assert_eq!(editor.cursor, (0, 2));
766 assert_eq!(editor.selection_start, None);
767 }
768
769 #[test]
770 fn test_delete_selection_no_selection() {
771 let mut editor = CodeEditor::new("hello world", "py");
772 editor.cursor = (0, 5);
773
774 let _ = editor.update(&Message::DeleteSelection);
775 assert_eq!(editor.buffer.line(0), "hello world");
777 assert_eq!(editor.cursor, (0, 5));
778 }
779
780 #[test]
781 #[allow(clippy::unwrap_used)]
782 fn test_ime_preedit_and_commit_chinese() {
783 let mut editor = CodeEditor::new("", "py");
784 let _ = editor.update(&Message::ImeOpened);
786 assert!(editor.ime_preedit.is_none());
787
788 let content = "安全与合规".to_string();
790 let selection = Some(0..3); let _ = editor
792 .update(&Message::ImePreedit(content.clone(), selection.clone()));
793
794 assert!(editor.ime_preedit.is_some());
795 assert_eq!(
796 editor.ime_preedit.as_ref().unwrap().content.clone(),
797 content
798 );
799 assert_eq!(
800 editor.ime_preedit.as_ref().unwrap().selection.clone(),
801 selection
802 );
803
804 let _ = editor.update(&Message::ImeCommit("安全与合规".to_string()));
806 assert!(editor.ime_preedit.is_none());
807 assert_eq!(editor.buffer.line(0), "安全与合规");
808 assert_eq!(editor.cursor, (0, "安全与合规".chars().count()));
809 }
810
811 #[test]
812 fn test_undo_char_insert() {
813 let mut editor = CodeEditor::new("hello", "py");
814 editor.cursor = (0, 5);
815
816 let _ = editor.update(&Message::CharacterInput('!'));
818 assert_eq!(editor.buffer.line(0), "hello!");
819 assert_eq!(editor.cursor, (0, 6));
820
821 editor.history.end_group();
823 let _ = editor.update(&Message::Undo);
824 assert_eq!(editor.buffer.line(0), "hello");
825 assert_eq!(editor.cursor, (0, 5));
826 }
827
828 #[test]
829 fn test_undo_redo_char_insert() {
830 let mut editor = CodeEditor::new("hello", "py");
831 editor.cursor = (0, 5);
832
833 let _ = editor.update(&Message::CharacterInput('!'));
835 editor.history.end_group();
836
837 let _ = editor.update(&Message::Undo);
839 assert_eq!(editor.buffer.line(0), "hello");
840
841 let _ = editor.update(&Message::Redo);
843 assert_eq!(editor.buffer.line(0), "hello!");
844 assert_eq!(editor.cursor, (0, 6));
845 }
846
847 #[test]
848 fn test_undo_backspace() {
849 let mut editor = CodeEditor::new("hello", "py");
850 editor.cursor = (0, 5);
851
852 let _ = editor.update(&Message::Backspace);
854 assert_eq!(editor.buffer.line(0), "hell");
855 assert_eq!(editor.cursor, (0, 4));
856
857 let _ = editor.update(&Message::Undo);
859 assert_eq!(editor.buffer.line(0), "hello");
860 assert_eq!(editor.cursor, (0, 5));
861 }
862
863 #[test]
864 fn test_undo_newline() {
865 let mut editor = CodeEditor::new("hello world", "py");
866 editor.cursor = (0, 5);
867
868 let _ = editor.update(&Message::Enter);
870 assert_eq!(editor.buffer.line(0), "hello");
871 assert_eq!(editor.buffer.line(1), " world");
872 assert_eq!(editor.cursor, (1, 0));
873
874 let _ = editor.update(&Message::Undo);
876 assert_eq!(editor.buffer.line(0), "hello world");
877 assert_eq!(editor.cursor, (0, 5));
878 }
879
880 #[test]
881 fn test_undo_grouped_typing() {
882 let mut editor = CodeEditor::new("hello", "py");
883 editor.cursor = (0, 5);
884
885 let _ = editor.update(&Message::CharacterInput(' '));
887 let _ = editor.update(&Message::CharacterInput('w'));
888 let _ = editor.update(&Message::CharacterInput('o'));
889 let _ = editor.update(&Message::CharacterInput('r'));
890 let _ = editor.update(&Message::CharacterInput('l'));
891 let _ = editor.update(&Message::CharacterInput('d'));
892
893 assert_eq!(editor.buffer.line(0), "hello world");
894
895 editor.history.end_group();
897
898 let _ = editor.update(&Message::Undo);
900 assert_eq!(editor.buffer.line(0), "hello");
901 assert_eq!(editor.cursor, (0, 5));
902 }
903
904 #[test]
905 fn test_navigation_ends_grouping() {
906 let mut editor = CodeEditor::new("hello", "py");
907 editor.cursor = (0, 5);
908
909 let _ = editor.update(&Message::CharacterInput('!'));
911 assert!(editor.is_grouping);
912
913 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Left, false));
915 assert!(!editor.is_grouping);
916
917 let _ = editor.update(&Message::CharacterInput('?'));
919 assert!(editor.is_grouping);
920
921 editor.history.end_group();
922
923 let _ = editor.update(&Message::Undo);
925 assert_eq!(editor.buffer.line(0), "hello!");
926
927 let _ = editor.update(&Message::Undo);
928 assert_eq!(editor.buffer.line(0), "hello");
929 }
930
931 #[test]
932 fn test_multiple_undo_redo() {
933 let mut editor = CodeEditor::new("a", "py");
934 editor.cursor = (0, 1);
935
936 let _ = editor.update(&Message::CharacterInput('b'));
938 editor.history.end_group();
939
940 let _ = editor.update(&Message::CharacterInput('c'));
941 editor.history.end_group();
942
943 let _ = editor.update(&Message::CharacterInput('d'));
944 editor.history.end_group();
945
946 assert_eq!(editor.buffer.line(0), "abcd");
947
948 let _ = editor.update(&Message::Undo);
950 assert_eq!(editor.buffer.line(0), "abc");
951
952 let _ = editor.update(&Message::Undo);
953 assert_eq!(editor.buffer.line(0), "ab");
954
955 let _ = editor.update(&Message::Undo);
956 assert_eq!(editor.buffer.line(0), "a");
957
958 let _ = editor.update(&Message::Redo);
960 assert_eq!(editor.buffer.line(0), "ab");
961
962 let _ = editor.update(&Message::Redo);
963 assert_eq!(editor.buffer.line(0), "abc");
964
965 let _ = editor.update(&Message::Redo);
966 assert_eq!(editor.buffer.line(0), "abcd");
967 }
968
969 #[test]
970 fn test_delete_key_with_selection() {
971 let mut editor = CodeEditor::new("hello world", "py");
972 editor.selection_start = Some((0, 0));
973 editor.selection_end = Some((0, 5));
974 editor.cursor = (0, 5);
975
976 let _ = editor.update(&Message::Delete);
977
978 assert_eq!(editor.buffer.line(0), " world");
979 assert_eq!(editor.cursor, (0, 0));
980 assert_eq!(editor.selection_start, None);
981 assert_eq!(editor.selection_end, None);
982 }
983
984 #[test]
985 fn test_delete_key_without_selection() {
986 let mut editor = CodeEditor::new("hello", "py");
987 editor.cursor = (0, 0);
988
989 let _ = editor.update(&Message::Delete);
990
991 assert_eq!(editor.buffer.line(0), "ello");
993 assert_eq!(editor.cursor, (0, 0));
994 }
995
996 #[test]
997 fn test_backspace_with_selection() {
998 let mut editor = CodeEditor::new("hello world", "py");
999 editor.selection_start = Some((0, 6));
1000 editor.selection_end = Some((0, 11));
1001 editor.cursor = (0, 11);
1002
1003 let _ = editor.update(&Message::Backspace);
1004
1005 assert_eq!(editor.buffer.line(0), "hello ");
1006 assert_eq!(editor.cursor, (0, 6));
1007 assert_eq!(editor.selection_start, None);
1008 assert_eq!(editor.selection_end, None);
1009 }
1010
1011 #[test]
1012 fn test_backspace_without_selection() {
1013 let mut editor = CodeEditor::new("hello", "py");
1014 editor.cursor = (0, 5);
1015
1016 let _ = editor.update(&Message::Backspace);
1017
1018 assert_eq!(editor.buffer.line(0), "hell");
1020 assert_eq!(editor.cursor, (0, 4));
1021 }
1022
1023 #[test]
1024 fn test_delete_multiline_selection() {
1025 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1026 editor.selection_start = Some((0, 2));
1027 editor.selection_end = Some((2, 2));
1028 editor.cursor = (2, 2);
1029
1030 let _ = editor.update(&Message::Delete);
1031
1032 assert_eq!(editor.buffer.line(0), "line3");
1033 assert_eq!(editor.cursor, (0, 2));
1034 assert_eq!(editor.selection_start, None);
1035 }
1036
1037 #[test]
1038 fn test_canvas_focus_gained() {
1039 let mut editor = CodeEditor::new("hello world", "py");
1040 assert!(!editor.has_canvas_focus);
1041 assert!(!editor.show_cursor);
1042
1043 let _ = editor.update(&Message::CanvasFocusGained);
1044
1045 assert!(editor.has_canvas_focus);
1046 assert!(editor.show_cursor);
1047 }
1048
1049 #[test]
1050 fn test_canvas_focus_lost() {
1051 let mut editor = CodeEditor::new("hello world", "py");
1052 editor.has_canvas_focus = true;
1053 editor.show_cursor = true;
1054
1055 let _ = editor.update(&Message::CanvasFocusLost);
1056
1057 assert!(!editor.has_canvas_focus);
1058 assert!(!editor.show_cursor);
1059 }
1060
1061 #[test]
1062 fn test_mouse_click_gains_focus() {
1063 let mut editor = CodeEditor::new("hello world", "py");
1064 editor.has_canvas_focus = false;
1065 editor.show_cursor = false;
1066
1067 let _ =
1068 editor.update(&Message::MouseClick(iced::Point::new(100.0, 10.0)));
1069
1070 assert!(editor.has_canvas_focus);
1071 assert!(editor.show_cursor);
1072 }
1073}