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> {
22 match message {
23 Message::CharacterInput(ch) => {
24 if !self.is_grouping {
26 self.history.begin_group("Typing");
27 self.is_grouping = true;
28 }
29
30 let (line, col) = self.cursor;
31 let mut cmd =
32 InsertCharCommand::new(line, col, *ch, self.cursor);
33 cmd.execute(&mut self.buffer, &mut self.cursor);
34 self.history.push(Box::new(cmd));
35
36 self.reset_cursor_blink();
37 self.refresh_search_matches_if_needed();
38 self.cache.clear();
39 Task::none()
40 }
41 Message::Backspace => {
42 if self.is_grouping {
44 self.history.end_group();
45 self.is_grouping = false;
46 }
47
48 if self.selection_start.is_some()
50 && self.selection_end.is_some()
51 {
52 self.delete_selection();
53 self.reset_cursor_blink();
54 self.refresh_search_matches_if_needed();
55 self.cache.clear();
56 return self.scroll_to_cursor();
57 }
58
59 let (line, col) = self.cursor;
61 let mut cmd = DeleteCharCommand::new(
62 &self.buffer,
63 line,
64 col,
65 self.cursor,
66 );
67 cmd.execute(&mut self.buffer, &mut self.cursor);
68 self.history.push(Box::new(cmd));
69
70 self.reset_cursor_blink();
71 self.refresh_search_matches_if_needed();
72 self.cache.clear();
73 self.scroll_to_cursor()
74 }
75 Message::Delete => {
76 if self.is_grouping {
78 self.history.end_group();
79 self.is_grouping = false;
80 }
81
82 if self.selection_start.is_some()
84 && self.selection_end.is_some()
85 {
86 self.delete_selection();
87 self.reset_cursor_blink();
88 self.refresh_search_matches_if_needed();
89 self.cache.clear();
90 return self.scroll_to_cursor();
91 }
92
93 let (line, col) = self.cursor;
95 let mut cmd = DeleteForwardCommand::new(
96 &self.buffer,
97 line,
98 col,
99 self.cursor,
100 );
101 cmd.execute(&mut self.buffer, &mut self.cursor);
102 self.history.push(Box::new(cmd));
103
104 self.reset_cursor_blink();
105 self.refresh_search_matches_if_needed();
106 self.cache.clear();
107 Task::none()
108 }
109 Message::Enter => {
110 if self.is_grouping {
112 self.history.end_group();
113 self.is_grouping = false;
114 }
115
116 let (line, col) = self.cursor;
117 let mut cmd = InsertNewlineCommand::new(line, col, self.cursor);
118 cmd.execute(&mut self.buffer, &mut self.cursor);
119 self.history.push(Box::new(cmd));
120
121 self.reset_cursor_blink();
122 self.refresh_search_matches_if_needed();
123 self.cache.clear();
124 self.scroll_to_cursor()
125 }
126 Message::Tab => {
127 if !self.is_grouping {
130 self.history.begin_group("Tab");
131 self.is_grouping = true;
132 }
133
134 let (line, col) = self.cursor;
135 for i in 0..4 {
137 let current_col = col + i;
138 let mut cmd = InsertCharCommand::new(
139 line,
140 current_col,
141 ' ',
142 (line, current_col),
143 );
144 cmd.execute(&mut self.buffer, &mut self.cursor);
145 self.history.push(Box::new(cmd));
146 }
147
148 self.reset_cursor_blink();
149 self.cache.clear();
150 Task::none()
151 }
152 Message::ArrowKey(direction, shift_pressed) => {
153 if self.is_grouping {
155 self.history.end_group();
156 self.is_grouping = false;
157 }
158
159 if *shift_pressed {
160 if self.selection_start.is_none() {
162 self.selection_start = Some(self.cursor);
163 }
164 self.move_cursor(*direction);
165 self.selection_end = Some(self.cursor);
166 } else {
167 self.clear_selection();
169 self.move_cursor(*direction);
170 }
171 self.reset_cursor_blink();
172 self.cache.clear();
173 self.scroll_to_cursor()
174 }
175 Message::MouseClick(point) => {
176 super::FOCUSED_EDITOR_ID.store(
178 self.editor_id,
179 std::sync::atomic::Ordering::Relaxed,
180 );
181
182 if self.is_grouping {
184 self.history.end_group();
185 self.is_grouping = false;
186 }
187
188 self.handle_mouse_click(*point);
189 self.reset_cursor_blink();
190 self.clear_selection();
192 self.is_dragging = true;
193 self.selection_start = Some(self.cursor);
194
195 self.has_canvas_focus = true;
197 self.show_cursor = true;
198
199 Task::none()
200 }
201 Message::MouseDrag(point) => {
202 if self.is_dragging {
203 self.handle_mouse_drag(*point);
204 self.cache.clear();
205 }
206 Task::none()
207 }
208 Message::MouseRelease => {
209 self.is_dragging = false;
210 Task::none()
211 }
212 Message::Copy => self.copy_selection(),
213 Message::Paste(text) => {
214 if self.is_grouping {
216 self.history.end_group();
217 self.is_grouping = false;
218 }
219
220 if text.is_empty() {
222 iced::clipboard::read().and_then(|clipboard_text| {
224 Task::done(Message::Paste(clipboard_text))
225 })
226 } else {
227 self.paste_text(text);
229 self.refresh_search_matches_if_needed();
230 self.cache.clear();
231 self.scroll_to_cursor()
232 }
233 }
234 Message::DeleteSelection => {
235 if self.is_grouping {
237 self.history.end_group();
238 self.is_grouping = false;
239 }
240
241 self.delete_selection();
243 self.reset_cursor_blink();
244 self.cache.clear();
245 self.scroll_to_cursor()
246 }
247 Message::Tick => {
248 if self.is_focused()
250 && self.has_canvas_focus
251 && self.last_blink.elapsed() >= CURSOR_BLINK_INTERVAL
252 {
253 self.cursor_visible = !self.cursor_visible;
254 self.last_blink = std::time::Instant::now();
255 self.cache.clear();
256 }
257
258 if !self.has_canvas_focus {
260 self.show_cursor = false;
261 }
262
263 Task::none()
264 }
265 Message::PageUp => {
266 self.page_up();
267 self.reset_cursor_blink();
268 self.scroll_to_cursor()
269 }
270 Message::PageDown => {
271 self.page_down();
272 self.reset_cursor_blink();
273 self.scroll_to_cursor()
274 }
275 Message::Home(shift_pressed) => {
276 if *shift_pressed {
277 if self.selection_start.is_none() {
279 self.selection_start = Some(self.cursor);
280 }
281 self.cursor.1 = 0; self.selection_end = Some(self.cursor);
283 } else {
284 self.clear_selection();
286 self.cursor.1 = 0;
287 }
288 self.reset_cursor_blink();
289 self.cache.clear();
290 Task::none()
291 }
292 Message::End(shift_pressed) => {
293 let line = self.cursor.0;
294 let line_len = self.buffer.line_len(line);
295
296 if *shift_pressed {
297 if self.selection_start.is_none() {
299 self.selection_start = Some(self.cursor);
300 }
301 self.cursor.1 = line_len; self.selection_end = Some(self.cursor);
303 } else {
304 self.clear_selection();
306 self.cursor.1 = line_len;
307 }
308 self.reset_cursor_blink();
309 self.cache.clear();
310 Task::none()
311 }
312 Message::CtrlHome => {
313 self.clear_selection();
315 self.cursor = (0, 0);
316 self.reset_cursor_blink();
317 self.cache.clear();
318 self.scroll_to_cursor()
319 }
320 Message::CtrlEnd => {
321 self.clear_selection();
323 let last_line = self.buffer.line_count().saturating_sub(1);
324 let last_col = self.buffer.line_len(last_line);
325 self.cursor = (last_line, last_col);
326 self.reset_cursor_blink();
327 self.cache.clear();
328 self.scroll_to_cursor()
329 }
330 Message::Scrolled(viewport) => {
331 let new_scroll = viewport.absolute_offset().y;
340 let new_height = viewport.bounds().height;
341 let new_width = viewport.bounds().width;
342 let scroll_changed =
343 (self.viewport_scroll - new_scroll).abs() > 0.1;
344 let visible_lines_count =
345 (new_height / self.line_height).ceil() as usize + 2;
346 let first_visible_line =
347 (new_scroll / self.line_height).floor() as usize;
348 let last_visible_line =
349 first_visible_line + visible_lines_count;
350 let margin = visible_lines_count
351 * crate::canvas_editor::CACHE_WINDOW_MARGIN_MULTIPLIER;
352 let window_start = first_visible_line.saturating_sub(margin);
353 let window_end = last_visible_line + margin;
354 let need_rewindow = if self.cache_window_end_line
358 > self.cache_window_start_line
359 {
360 let lower_boundary_trigger = self.cache_window_start_line
361 > 0
362 && first_visible_line
363 < self
364 .cache_window_start_line
365 .saturating_add(visible_lines_count / 2);
366 let upper_boundary_trigger = last_visible_line
367 > self
368 .cache_window_end_line
369 .saturating_sub(visible_lines_count / 2);
370 lower_boundary_trigger || upper_boundary_trigger
371 } else {
372 true
373 };
374 if (self.viewport_height - new_height).abs() > 1.0
377 || (self.viewport_width - new_width).abs() > 1.0
378 || (scroll_changed && need_rewindow)
379 {
380 self.cache_window_start_line = window_start;
381 self.cache_window_end_line = window_end;
382 self.last_first_visible_line = first_visible_line;
383 self.cache.clear();
384 }
385 self.viewport_scroll = new_scroll;
386 self.viewport_height = new_height;
387 self.viewport_width = new_width;
388 Task::none()
389 }
390 Message::Undo => {
391 if self.is_grouping {
393 self.history.end_group();
394 self.is_grouping = false;
395 }
396
397 if self.history.undo(&mut self.buffer, &mut self.cursor) {
398 self.clear_selection();
399 self.reset_cursor_blink();
400 self.refresh_search_matches_if_needed();
401 self.cache.clear();
402 self.scroll_to_cursor()
403 } else {
404 Task::none()
405 }
406 }
407 Message::Redo => {
408 if self.history.redo(&mut self.buffer, &mut self.cursor) {
409 self.clear_selection();
410 self.reset_cursor_blink();
411 self.refresh_search_matches_if_needed();
412 self.cache.clear();
413 self.scroll_to_cursor()
414 } else {
415 Task::none()
416 }
417 }
418 Message::OpenSearch => {
419 self.search_state.open_search();
420 self.cache.clear();
421
422 Task::batch([
424 focus(self.search_state.search_input_id.clone()),
425 select_all(self.search_state.search_input_id.clone()),
426 ])
427 }
428 Message::OpenSearchReplace => {
429 self.search_state.open_replace();
430 self.cache.clear();
431
432 Task::batch([
434 focus(self.search_state.search_input_id.clone()),
435 select_all(self.search_state.search_input_id.clone()),
436 ])
437 }
438 Message::CloseSearch => {
439 self.search_state.close();
440 self.cache.clear();
441 Task::none()
442 }
443 Message::SearchQueryChanged(query) => {
444 self.search_state.set_query(query.clone(), &self.buffer);
445 self.cache.clear();
446
447 if let Some(match_pos) = self.search_state.current_match() {
449 self.cursor = (match_pos.line, match_pos.col);
450 self.clear_selection();
451 return self.scroll_to_cursor();
452 }
453 Task::none()
454 }
455 Message::ReplaceQueryChanged(replace_text) => {
456 self.search_state.set_replace_with(replace_text.clone());
457 Task::none()
458 }
459 Message::ToggleCaseSensitive => {
460 self.search_state.toggle_case_sensitive(&self.buffer);
461 self.cache.clear();
462
463 if let Some(match_pos) = self.search_state.current_match() {
465 self.cursor = (match_pos.line, match_pos.col);
466 self.clear_selection();
467 return self.scroll_to_cursor();
468 }
469 Task::none()
470 }
471 Message::FindNext => {
472 if !self.search_state.matches.is_empty() {
473 self.search_state.next_match();
474 if let Some(match_pos) = self.search_state.current_match() {
475 self.cursor = (match_pos.line, match_pos.col);
476 self.clear_selection();
477 self.cache.clear();
478 return self.scroll_to_cursor();
479 }
480 }
481 Task::none()
482 }
483 Message::FindPrevious => {
484 if !self.search_state.matches.is_empty() {
485 self.search_state.previous_match();
486 if let Some(match_pos) = self.search_state.current_match() {
487 self.cursor = (match_pos.line, match_pos.col);
488 self.clear_selection();
489 self.cache.clear();
490 return self.scroll_to_cursor();
491 }
492 }
493 Task::none()
494 }
495 Message::ReplaceNext => {
496 if let Some(match_pos) = self.search_state.current_match() {
498 let query_len = self.search_state.query.chars().count();
499 let replace_text = self.search_state.replace_with.clone();
500
501 let mut cmd = ReplaceTextCommand::new(
503 &self.buffer,
504 (match_pos.line, match_pos.col),
505 query_len,
506 replace_text,
507 self.cursor,
508 );
509 cmd.execute(&mut self.buffer, &mut self.cursor);
510 self.history.push(Box::new(cmd));
511
512 self.search_state.update_matches(&self.buffer);
514
515 if !self.search_state.matches.is_empty()
517 && let Some(next_match) =
518 self.search_state.current_match()
519 {
520 self.cursor = (next_match.line, next_match.col);
521 }
522
523 self.clear_selection();
524 self.cache.clear();
525 return self.scroll_to_cursor();
526 }
527 Task::none()
528 }
529 Message::ReplaceAll => {
530 let all_matches = super::search::find_matches(
532 &self.buffer,
533 &self.search_state.query,
534 self.search_state.case_sensitive,
535 None, );
537
538 if !all_matches.is_empty() {
539 let query_len = self.search_state.query.chars().count();
540 let replace_text = self.search_state.replace_with.clone();
541
542 let mut composite =
544 CompositeCommand::new("Replace All".to_string());
545
546 for match_pos in all_matches.iter().rev() {
548 let cmd = ReplaceTextCommand::new(
549 &self.buffer,
550 (match_pos.line, match_pos.col),
551 query_len,
552 replace_text.clone(),
553 self.cursor,
554 );
555 composite.add(Box::new(cmd));
556 }
557
558 composite.execute(&mut self.buffer, &mut self.cursor);
560 self.history.push(Box::new(composite));
561
562 self.search_state.update_matches(&self.buffer);
564 self.clear_selection();
565 self.cache.clear();
566 self.scroll_to_cursor()
567 } else {
568 Task::none()
569 }
570 }
571 Message::SearchDialogTab => {
572 self.search_state.focus_next_field();
574
575 match self.search_state.focused_field {
577 crate::canvas_editor::search::SearchFocusedField::Search => {
578 focus(self.search_state.search_input_id.clone())
579 }
580 crate::canvas_editor::search::SearchFocusedField::Replace => {
581 focus(self.search_state.replace_input_id.clone())
582 }
583 }
584 }
585 Message::SearchDialogShiftTab => {
586 self.search_state.focus_previous_field();
588
589 match self.search_state.focused_field {
591 crate::canvas_editor::search::SearchFocusedField::Search => {
592 focus(self.search_state.search_input_id.clone())
593 }
594 crate::canvas_editor::search::SearchFocusedField::Replace => {
595 focus(self.search_state.replace_input_id.clone())
596 }
597 }
598 }
599 Message::CanvasFocusGained => {
600 self.has_canvas_focus = true;
601 self.show_cursor = true;
602 self.reset_cursor_blink();
603 self.cache.clear();
604 Task::none()
605 }
606 Message::CanvasFocusLost => {
607 self.has_canvas_focus = false;
608 self.show_cursor = false;
609 self.ime_preedit = None;
610 self.cache.clear();
611 Task::none()
612 }
613 Message::ImeOpened => {
614 self.ime_preedit = None;
621 self.cache.clear();
622 Task::none()
623 }
624 Message::ImePreedit(content, selection) => {
625 if content.is_empty() {
636 self.ime_preedit = None;
637 } else {
638 self.ime_preedit = Some(ImePreedit {
639 content: content.clone(),
640 selection: selection.clone(),
641 });
642 }
643
644 self.cache.clear();
645 Task::none()
646 }
647 Message::ImeCommit(text) => {
648 self.ime_preedit = None;
657
658 if text.is_empty() {
659 self.cache.clear();
660 return Task::none();
661 }
662
663 if !self.is_grouping {
664 self.history.begin_group("Typing");
665 self.is_grouping = true;
666 }
667
668 self.paste_text(text);
669 self.reset_cursor_blink();
670 self.refresh_search_matches_if_needed();
671 self.cache.clear();
672 self.scroll_to_cursor()
673 }
674 Message::ImeClosed => {
675 self.ime_preedit = None;
681 self.cache.clear();
682 Task::none()
683 }
684 }
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691 use crate::canvas_editor::ArrowDirection;
692
693 #[test]
694 fn test_new_canvas_editor() {
695 let editor = CodeEditor::new("line1\nline2", "py");
696 assert_eq!(editor.cursor, (0, 0));
697 }
698
699 #[test]
700 fn test_home_key() {
701 let mut editor = CodeEditor::new("hello world", "py");
702 editor.cursor = (0, 5); let _ = editor.update(&Message::Home(false));
704 assert_eq!(editor.cursor, (0, 0));
705 }
706
707 #[test]
708 fn test_end_key() {
709 let mut editor = CodeEditor::new("hello world", "py");
710 editor.cursor = (0, 0);
711 let _ = editor.update(&Message::End(false));
712 assert_eq!(editor.cursor, (0, 11)); }
714
715 #[test]
716 fn test_arrow_key_with_shift_creates_selection() {
717 let mut editor = CodeEditor::new("hello world", "py");
718 editor.cursor = (0, 0);
719
720 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, true));
722 assert!(editor.selection_start.is_some());
723 assert!(editor.selection_end.is_some());
724 }
725
726 #[test]
727 fn test_arrow_key_without_shift_clears_selection() {
728 let mut editor = CodeEditor::new("hello world", "py");
729 editor.selection_start = Some((0, 0));
730 editor.selection_end = Some((0, 5));
731
732 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, false));
734 assert_eq!(editor.selection_start, None);
735 assert_eq!(editor.selection_end, None);
736 }
737
738 #[test]
739 fn test_typing_with_selection() {
740 let mut editor = CodeEditor::new("hello world", "py");
741 editor.selection_start = Some((0, 0));
742 editor.selection_end = Some((0, 5));
743
744 let _ = editor.update(&Message::CharacterInput('X'));
745 assert_eq!(editor.buffer.line(0), "Xhello world");
748 }
749
750 #[test]
751 fn test_ctrl_home() {
752 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
753 editor.cursor = (2, 5); let _ = editor.update(&Message::CtrlHome);
755 assert_eq!(editor.cursor, (0, 0)); }
757
758 #[test]
759 fn test_ctrl_end() {
760 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
761 editor.cursor = (0, 0); let _ = editor.update(&Message::CtrlEnd);
763 assert_eq!(editor.cursor, (2, 5)); }
765
766 #[test]
767 fn test_ctrl_home_clears_selection() {
768 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
769 editor.cursor = (2, 5);
770 editor.selection_start = Some((0, 0));
771 editor.selection_end = Some((2, 5));
772
773 let _ = editor.update(&Message::CtrlHome);
774 assert_eq!(editor.cursor, (0, 0));
775 assert_eq!(editor.selection_start, None);
776 assert_eq!(editor.selection_end, None);
777 }
778
779 #[test]
780 fn test_ctrl_end_clears_selection() {
781 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
782 editor.cursor = (0, 0);
783 editor.selection_start = Some((0, 0));
784 editor.selection_end = Some((1, 3));
785
786 let _ = editor.update(&Message::CtrlEnd);
787 assert_eq!(editor.cursor, (2, 5));
788 assert_eq!(editor.selection_start, None);
789 assert_eq!(editor.selection_end, None);
790 }
791
792 #[test]
793 fn test_scroll_sets_initial_cache_window() {
794 let content =
795 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
796 let mut editor = CodeEditor::new(&content, "py");
797
798 let height = 400.0;
800 let width = 800.0;
801 let scroll = 0.0;
802
803 let visible_lines_count =
805 (height / editor.line_height).ceil() as usize + 2;
806 let first_visible_line = (scroll / editor.line_height).floor() as usize;
807 let last_visible_line = first_visible_line + visible_lines_count;
808 let margin = visible_lines_count * 2;
809 let window_start = first_visible_line.saturating_sub(margin);
810 let window_end = last_visible_line + margin;
811
812 editor.viewport_height = height;
814 editor.viewport_width = width;
815 editor.viewport_scroll = -1.0;
816 let scroll_changed = (editor.viewport_scroll - scroll).abs() > 0.1;
817 let need_rewindow = true;
818 if (editor.viewport_height - height).abs() > 1.0
819 || (editor.viewport_width - width).abs() > 1.0
820 || (scroll_changed && need_rewindow)
821 {
822 editor.cache_window_start_line = window_start;
823 editor.cache_window_end_line = window_end;
824 editor.last_first_visible_line = first_visible_line;
825 }
826 editor.viewport_scroll = scroll;
827
828 assert_eq!(editor.last_first_visible_line, first_visible_line);
829 assert!(editor.cache_window_end_line > editor.cache_window_start_line);
830 assert_eq!(editor.cache_window_start_line, window_start);
831 assert_eq!(editor.cache_window_end_line, window_end);
832 }
833
834 #[test]
835 fn test_small_scroll_keeps_window() {
836 let content =
837 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
838 let mut editor = CodeEditor::new(&content, "py");
839 let height = 400.0;
840 let width = 800.0;
841 let initial_scroll = 0.0;
842 let visible_lines_count =
843 (height / editor.line_height).ceil() as usize + 2;
844 let first_visible_line =
845 (initial_scroll / editor.line_height).floor() as usize;
846 let last_visible_line = first_visible_line + visible_lines_count;
847 let margin = visible_lines_count * 2;
848 let window_start = first_visible_line.saturating_sub(margin);
849 let window_end = last_visible_line + margin;
850 editor.cache_window_start_line = window_start;
851 editor.cache_window_end_line = window_end;
852 editor.viewport_height = height;
853 editor.viewport_width = width;
854 editor.viewport_scroll = initial_scroll;
855
856 let small_scroll =
858 editor.line_height * (visible_lines_count as f32 / 4.0);
859 let first_visible_line2 =
860 (small_scroll / editor.line_height).floor() as usize;
861 let last_visible_line2 = first_visible_line2 + visible_lines_count;
862 let lower_boundary_trigger = editor.cache_window_start_line > 0
863 && first_visible_line2
864 < editor
865 .cache_window_start_line
866 .saturating_add(visible_lines_count / 2);
867 let upper_boundary_trigger = last_visible_line2
868 > editor
869 .cache_window_end_line
870 .saturating_sub(visible_lines_count / 2);
871 let need_rewindow = lower_boundary_trigger || upper_boundary_trigger;
872
873 assert!(!need_rewindow, "Small scroll should be inside the window");
874 assert_eq!(editor.cache_window_start_line, window_start);
876 assert_eq!(editor.cache_window_end_line, window_end);
877 }
878
879 #[test]
880 fn test_large_scroll_rewindows() {
881 let content =
882 (0..1000).map(|i| format!("line{}\n", i)).collect::<String>();
883 let mut editor = CodeEditor::new(&content, "py");
884 let height = 400.0;
885 let width = 800.0;
886 let initial_scroll = 0.0;
887 let visible_lines_count =
888 (height / editor.line_height).ceil() as usize + 2;
889 let first_visible_line =
890 (initial_scroll / editor.line_height).floor() as usize;
891 let last_visible_line = first_visible_line + visible_lines_count;
892 let margin = visible_lines_count * 2;
893 editor.cache_window_start_line =
894 first_visible_line.saturating_sub(margin);
895 editor.cache_window_end_line = last_visible_line + margin;
896 editor.viewport_height = height;
897 editor.viewport_width = width;
898 editor.viewport_scroll = initial_scroll;
899
900 let large_scroll =
902 editor.line_height * ((visible_lines_count * 4) as f32);
903 let first_visible_line2 =
904 (large_scroll / editor.line_height).floor() as usize;
905 let last_visible_line2 = first_visible_line2 + visible_lines_count;
906 let window_start2 = first_visible_line2.saturating_sub(margin);
907 let window_end2 = last_visible_line2 + margin;
908 let need_rewindow = first_visible_line2
909 < editor
910 .cache_window_start_line
911 .saturating_add(visible_lines_count / 2)
912 || last_visible_line2
913 > editor
914 .cache_window_end_line
915 .saturating_sub(visible_lines_count / 2);
916 assert!(need_rewindow, "Large scroll should trigger window update");
917
918 editor.cache_window_start_line = window_start2;
920 editor.cache_window_end_line = window_end2;
921 editor.last_first_visible_line = first_visible_line2;
922
923 assert_eq!(editor.cache_window_start_line, window_start2);
924 assert_eq!(editor.cache_window_end_line, window_end2);
925 assert_eq!(editor.last_first_visible_line, first_visible_line2);
926 }
927
928 #[test]
929 fn test_delete_selection_message() {
930 let mut editor = CodeEditor::new("hello world", "py");
931 editor.cursor = (0, 0);
932 editor.selection_start = Some((0, 0));
933 editor.selection_end = Some((0, 5));
934
935 let _ = editor.update(&Message::DeleteSelection);
936 assert_eq!(editor.buffer.line(0), " world");
937 assert_eq!(editor.cursor, (0, 0));
938 assert_eq!(editor.selection_start, None);
939 assert_eq!(editor.selection_end, None);
940 }
941
942 #[test]
943 fn test_delete_selection_multiline() {
944 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
945 editor.cursor = (0, 2);
946 editor.selection_start = Some((0, 2));
947 editor.selection_end = Some((2, 2));
948
949 let _ = editor.update(&Message::DeleteSelection);
950 assert_eq!(editor.buffer.line(0), "line3");
951 assert_eq!(editor.cursor, (0, 2));
952 assert_eq!(editor.selection_start, None);
953 }
954
955 #[test]
956 fn test_delete_selection_no_selection() {
957 let mut editor = CodeEditor::new("hello world", "py");
958 editor.cursor = (0, 5);
959
960 let _ = editor.update(&Message::DeleteSelection);
961 assert_eq!(editor.buffer.line(0), "hello world");
963 assert_eq!(editor.cursor, (0, 5));
964 }
965
966 #[test]
967 #[allow(clippy::unwrap_used)]
968 fn test_ime_preedit_and_commit_chinese() {
969 let mut editor = CodeEditor::new("", "py");
970 let _ = editor.update(&Message::ImeOpened);
972 assert!(editor.ime_preedit.is_none());
973
974 let content = "安全与合规".to_string();
976 let selection = Some(0..3); let _ = editor
978 .update(&Message::ImePreedit(content.clone(), selection.clone()));
979
980 assert!(editor.ime_preedit.is_some());
981 assert_eq!(
982 editor.ime_preedit.as_ref().unwrap().content.clone(),
983 content
984 );
985 assert_eq!(
986 editor.ime_preedit.as_ref().unwrap().selection.clone(),
987 selection
988 );
989
990 let _ = editor.update(&Message::ImeCommit("安全与合规".to_string()));
992 assert!(editor.ime_preedit.is_none());
993 assert_eq!(editor.buffer.line(0), "安全与合规");
994 assert_eq!(editor.cursor, (0, "安全与合规".chars().count()));
995 }
996
997 #[test]
998 fn test_undo_char_insert() {
999 let mut editor = CodeEditor::new("hello", "py");
1000 editor.cursor = (0, 5);
1001
1002 let _ = editor.update(&Message::CharacterInput('!'));
1004 assert_eq!(editor.buffer.line(0), "hello!");
1005 assert_eq!(editor.cursor, (0, 6));
1006
1007 editor.history.end_group();
1009 let _ = editor.update(&Message::Undo);
1010 assert_eq!(editor.buffer.line(0), "hello");
1011 assert_eq!(editor.cursor, (0, 5));
1012 }
1013
1014 #[test]
1015 fn test_undo_redo_char_insert() {
1016 let mut editor = CodeEditor::new("hello", "py");
1017 editor.cursor = (0, 5);
1018
1019 let _ = editor.update(&Message::CharacterInput('!'));
1021 editor.history.end_group();
1022
1023 let _ = editor.update(&Message::Undo);
1025 assert_eq!(editor.buffer.line(0), "hello");
1026
1027 let _ = editor.update(&Message::Redo);
1029 assert_eq!(editor.buffer.line(0), "hello!");
1030 assert_eq!(editor.cursor, (0, 6));
1031 }
1032
1033 #[test]
1034 fn test_undo_backspace() {
1035 let mut editor = CodeEditor::new("hello", "py");
1036 editor.cursor = (0, 5);
1037
1038 let _ = editor.update(&Message::Backspace);
1040 assert_eq!(editor.buffer.line(0), "hell");
1041 assert_eq!(editor.cursor, (0, 4));
1042
1043 let _ = editor.update(&Message::Undo);
1045 assert_eq!(editor.buffer.line(0), "hello");
1046 assert_eq!(editor.cursor, (0, 5));
1047 }
1048
1049 #[test]
1050 fn test_undo_newline() {
1051 let mut editor = CodeEditor::new("hello world", "py");
1052 editor.cursor = (0, 5);
1053
1054 let _ = editor.update(&Message::Enter);
1056 assert_eq!(editor.buffer.line(0), "hello");
1057 assert_eq!(editor.buffer.line(1), " world");
1058 assert_eq!(editor.cursor, (1, 0));
1059
1060 let _ = editor.update(&Message::Undo);
1062 assert_eq!(editor.buffer.line(0), "hello world");
1063 assert_eq!(editor.cursor, (0, 5));
1064 }
1065
1066 #[test]
1067 fn test_undo_grouped_typing() {
1068 let mut editor = CodeEditor::new("hello", "py");
1069 editor.cursor = (0, 5);
1070
1071 let _ = editor.update(&Message::CharacterInput(' '));
1073 let _ = editor.update(&Message::CharacterInput('w'));
1074 let _ = editor.update(&Message::CharacterInput('o'));
1075 let _ = editor.update(&Message::CharacterInput('r'));
1076 let _ = editor.update(&Message::CharacterInput('l'));
1077 let _ = editor.update(&Message::CharacterInput('d'));
1078
1079 assert_eq!(editor.buffer.line(0), "hello world");
1080
1081 editor.history.end_group();
1083
1084 let _ = editor.update(&Message::Undo);
1086 assert_eq!(editor.buffer.line(0), "hello");
1087 assert_eq!(editor.cursor, (0, 5));
1088 }
1089
1090 #[test]
1091 fn test_navigation_ends_grouping() {
1092 let mut editor = CodeEditor::new("hello", "py");
1093 editor.cursor = (0, 5);
1094
1095 let _ = editor.update(&Message::CharacterInput('!'));
1097 assert!(editor.is_grouping);
1098
1099 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Left, false));
1101 assert!(!editor.is_grouping);
1102
1103 let _ = editor.update(&Message::CharacterInput('?'));
1105 assert!(editor.is_grouping);
1106
1107 editor.history.end_group();
1108
1109 let _ = editor.update(&Message::Undo);
1111 assert_eq!(editor.buffer.line(0), "hello!");
1112
1113 let _ = editor.update(&Message::Undo);
1114 assert_eq!(editor.buffer.line(0), "hello");
1115 }
1116
1117 #[test]
1118 fn test_multiple_undo_redo() {
1119 let mut editor = CodeEditor::new("a", "py");
1120 editor.cursor = (0, 1);
1121
1122 let _ = editor.update(&Message::CharacterInput('b'));
1124 editor.history.end_group();
1125
1126 let _ = editor.update(&Message::CharacterInput('c'));
1127 editor.history.end_group();
1128
1129 let _ = editor.update(&Message::CharacterInput('d'));
1130 editor.history.end_group();
1131
1132 assert_eq!(editor.buffer.line(0), "abcd");
1133
1134 let _ = editor.update(&Message::Undo);
1136 assert_eq!(editor.buffer.line(0), "abc");
1137
1138 let _ = editor.update(&Message::Undo);
1139 assert_eq!(editor.buffer.line(0), "ab");
1140
1141 let _ = editor.update(&Message::Undo);
1142 assert_eq!(editor.buffer.line(0), "a");
1143
1144 let _ = editor.update(&Message::Redo);
1146 assert_eq!(editor.buffer.line(0), "ab");
1147
1148 let _ = editor.update(&Message::Redo);
1149 assert_eq!(editor.buffer.line(0), "abc");
1150
1151 let _ = editor.update(&Message::Redo);
1152 assert_eq!(editor.buffer.line(0), "abcd");
1153 }
1154
1155 #[test]
1156 fn test_delete_key_with_selection() {
1157 let mut editor = CodeEditor::new("hello world", "py");
1158 editor.selection_start = Some((0, 0));
1159 editor.selection_end = Some((0, 5));
1160 editor.cursor = (0, 5);
1161
1162 let _ = editor.update(&Message::Delete);
1163
1164 assert_eq!(editor.buffer.line(0), " world");
1165 assert_eq!(editor.cursor, (0, 0));
1166 assert_eq!(editor.selection_start, None);
1167 assert_eq!(editor.selection_end, None);
1168 }
1169
1170 #[test]
1171 fn test_delete_key_without_selection() {
1172 let mut editor = CodeEditor::new("hello", "py");
1173 editor.cursor = (0, 0);
1174
1175 let _ = editor.update(&Message::Delete);
1176
1177 assert_eq!(editor.buffer.line(0), "ello");
1179 assert_eq!(editor.cursor, (0, 0));
1180 }
1181
1182 #[test]
1183 fn test_backspace_with_selection() {
1184 let mut editor = CodeEditor::new("hello world", "py");
1185 editor.selection_start = Some((0, 6));
1186 editor.selection_end = Some((0, 11));
1187 editor.cursor = (0, 11);
1188
1189 let _ = editor.update(&Message::Backspace);
1190
1191 assert_eq!(editor.buffer.line(0), "hello ");
1192 assert_eq!(editor.cursor, (0, 6));
1193 assert_eq!(editor.selection_start, None);
1194 assert_eq!(editor.selection_end, None);
1195 }
1196
1197 #[test]
1198 fn test_backspace_without_selection() {
1199 let mut editor = CodeEditor::new("hello", "py");
1200 editor.cursor = (0, 5);
1201
1202 let _ = editor.update(&Message::Backspace);
1203
1204 assert_eq!(editor.buffer.line(0), "hell");
1206 assert_eq!(editor.cursor, (0, 4));
1207 }
1208
1209 #[test]
1210 fn test_delete_multiline_selection() {
1211 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1212 editor.selection_start = Some((0, 2));
1213 editor.selection_end = Some((2, 2));
1214 editor.cursor = (2, 2);
1215
1216 let _ = editor.update(&Message::Delete);
1217
1218 assert_eq!(editor.buffer.line(0), "line3");
1219 assert_eq!(editor.cursor, (0, 2));
1220 assert_eq!(editor.selection_start, None);
1221 }
1222
1223 #[test]
1224 fn test_canvas_focus_gained() {
1225 let mut editor = CodeEditor::new("hello world", "py");
1226 assert!(!editor.has_canvas_focus);
1227 assert!(!editor.show_cursor);
1228
1229 let _ = editor.update(&Message::CanvasFocusGained);
1230
1231 assert!(editor.has_canvas_focus);
1232 assert!(editor.show_cursor);
1233 }
1234
1235 #[test]
1236 fn test_canvas_focus_lost() {
1237 let mut editor = CodeEditor::new("hello world", "py");
1238 editor.has_canvas_focus = true;
1239 editor.show_cursor = true;
1240
1241 let _ = editor.update(&Message::CanvasFocusLost);
1242
1243 assert!(!editor.has_canvas_focus);
1244 assert!(!editor.show_cursor);
1245 }
1246
1247 #[test]
1248 fn test_mouse_click_gains_focus() {
1249 let mut editor = CodeEditor::new("hello world", "py");
1250 editor.has_canvas_focus = false;
1251 editor.show_cursor = false;
1252
1253 let _ =
1254 editor.update(&Message::MouseClick(iced::Point::new(100.0, 10.0)));
1255
1256 assert!(editor.has_canvas_focus);
1257 assert!(editor.show_cursor);
1258 }
1259}