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::{
11 ArrowDirection, CURSOR_BLINK_INTERVAL, CodeEditor, ImePreedit, IndentStyle,
12 Message, cursor_set,
13};
14
15#[derive(Clone, Copy)]
21enum EditType {
22 InsertChar,
24 DeleteCharBack,
26 DeleteCharForward,
28 InsertNewline { indent_len: usize },
30 MergePrev { prev_line_len: usize },
33 MergeNext { edit_line_len: usize },
36}
37
38fn adjust_pos(
40 pos: &mut (usize, usize),
41 edit_line: usize,
42 edit_col: usize,
43 kind: EditType,
44) {
45 match kind {
46 EditType::InsertChar => {
47 if pos.0 == edit_line && pos.1 >= edit_col {
48 pos.1 += 1;
49 }
50 }
51 EditType::DeleteCharBack => {
52 if edit_col > 0 && pos.0 == edit_line && pos.1 > edit_col - 1 {
53 pos.1 -= 1;
54 }
55 }
56 EditType::DeleteCharForward => {
57 if pos.0 == edit_line && pos.1 > edit_col {
58 pos.1 -= 1;
59 }
60 }
61 EditType::InsertNewline { indent_len } => {
62 if pos.0 > edit_line {
63 pos.0 += 1;
64 } else if pos.0 == edit_line && pos.1 >= edit_col {
65 pos.0 += 1;
66 pos.1 = pos.1 - edit_col + indent_len;
67 }
68 }
69 EditType::MergePrev { prev_line_len } => {
70 if pos.0 == edit_line {
71 pos.0 -= 1;
72 pos.1 += prev_line_len;
73 } else if pos.0 > edit_line {
74 pos.0 -= 1;
75 }
76 }
77 EditType::MergeNext { edit_line_len } => {
78 if pos.0 == edit_line + 1 {
79 pos.0 = edit_line;
80 pos.1 += edit_line_len;
81 } else if pos.0 > edit_line + 1 {
82 pos.0 -= 1;
83 }
84 }
85 }
86}
87
88fn adjust_other_cursors(
90 cursors: &mut [cursor_set::Cursor],
91 skip_idx: usize,
92 edit_line: usize,
93 edit_col: usize,
94 kind: EditType,
95) {
96 for (i, cursor) in cursors.iter_mut().enumerate() {
97 if i == skip_idx {
98 continue;
99 }
100 adjust_pos(&mut cursor.position, edit_line, edit_col, kind);
101 if let Some(ref mut anchor) = cursor.anchor {
102 adjust_pos(anchor, edit_line, edit_col, kind);
103 }
104 }
105}
106
107impl CodeEditor {
108 fn finish_edit_operation(&mut self) {
121 self.reset_cursor_blink();
122 self.refresh_search_matches_if_needed();
123 self.buffer_revision = self.buffer_revision.wrapping_add(1);
126 *self.visual_lines_cache.borrow_mut() = None;
127 self.content_cache.clear();
128 self.overlay_cache.clear();
129 self.enqueue_lsp_change();
130 }
131
132 fn finish_navigation_operation(&mut self) {
140 self.reset_cursor_blink();
141 self.overlay_cache.clear();
142 }
143
144 fn ensure_grouping_started(&mut self, label: &str) {
153 if !self.is_grouping {
154 self.history.begin_group(label);
155 self.is_grouping = true;
156 }
157 }
158
159 fn end_grouping_if_active(&mut self) {
165 if self.is_grouping {
166 self.history.end_group();
167 self.is_grouping = false;
168 }
169 }
170
171 fn delete_selection_if_present(&mut self) -> bool {
177 if self.cursors.iter().any(|c| c.has_selection()) {
178 self.delete_selection();
179 self.finish_edit_operation();
180 true
181 } else {
182 false
183 }
184 }
185
186 fn handle_character_input_msg(&mut self, ch: char) -> Task<Message> {
205 if !self.has_focus() {
207 return Task::none();
208 }
209
210 self.ensure_grouping_started("Typing");
212
213 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
216 order.sort_by(|&a, &b| {
217 self.cursors.as_slice()[b]
218 .position
219 .cmp(&self.cursors.as_slice()[a].position)
220 });
221
222 for &idx in &order {
223 let cursor = &self.cursors.as_slice()[idx];
224 let pos = match cursor.anchor {
227 Some(anchor) if anchor < cursor.position => anchor,
228 _ => cursor.position,
229 };
230 let mut cmd = InsertCharCommand::new(pos.0, pos.1, ch, pos);
231 let mut cursor_pos = pos;
232 cmd.execute(&mut self.buffer, &mut cursor_pos);
233 self.cursors.as_mut_slice()[idx].position = cursor_pos;
234 adjust_other_cursors(
235 self.cursors.as_mut_slice(),
236 idx,
237 pos.0,
238 pos.1,
239 EditType::InsertChar,
240 );
241 self.history.push(Box::new(cmd));
242 }
243
244 self.finish_edit_operation();
245
246 if ch.is_alphanumeric() || ch == '_' || ch == '.' {
248 self.lsp_flush_pending_changes();
249 self.lsp_request_completion();
250 }
251
252 self.scroll_to_cursor()
253 }
254
255 fn handle_tab(&mut self) -> Task<Message> {
262 self.ensure_grouping_started("Tab");
263
264 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
266 order.sort_by(|&a, &b| {
267 self.cursors.as_slice()[b]
268 .position
269 .cmp(&self.cursors.as_slice()[a].position)
270 });
271
272 for &idx in &order {
273 let pos = self.cursors.as_slice()[idx].position;
274 match self.indent_style {
275 IndentStyle::Spaces(n) => {
276 let mut cursor_pos = pos;
277 for _i in 0..n as usize {
278 let current_col = cursor_pos.1;
279 let mut cmd = InsertCharCommand::new(
280 pos.0,
281 current_col,
282 ' ',
283 cursor_pos,
284 );
285 cmd.execute(&mut self.buffer, &mut cursor_pos);
286 adjust_other_cursors(
287 self.cursors.as_mut_slice(),
288 idx,
289 pos.0,
290 current_col,
291 EditType::InsertChar,
292 );
293 self.history.push(Box::new(cmd));
294 }
295 self.cursors.as_mut_slice()[idx].position = cursor_pos;
296 }
297 IndentStyle::Tab => {
298 let mut cmd =
299 InsertCharCommand::new(pos.0, pos.1, '\t', pos);
300 let mut cursor_pos = pos;
301 cmd.execute(&mut self.buffer, &mut cursor_pos);
302 adjust_other_cursors(
303 self.cursors.as_mut_slice(),
304 idx,
305 pos.0,
306 pos.1,
307 EditType::InsertChar,
308 );
309 self.cursors.as_mut_slice()[idx].position = cursor_pos;
310 self.history.push(Box::new(cmd));
311 }
312 }
313 }
314
315 self.finish_edit_operation();
316 self.scroll_to_cursor()
317 }
318
319 fn handle_focus_navigation_tab(&mut self) -> Task<Message> {
325 if !self.search_state.is_open {
327 self.has_canvas_focus = false;
329 self.show_cursor = false;
330
331 Task::none()
335 } else {
336 Task::none()
337 }
338 }
339
340 fn handle_focus_navigation_shift_tab(&mut self) -> Task<Message> {
346 if !self.search_state.is_open {
348 self.has_canvas_focus = false;
350 self.show_cursor = false;
351
352 Task::none()
356 } else {
357 Task::none()
358 }
359 }
360
361 fn handle_enter(&mut self) -> Task<Message> {
367 self.end_grouping_if_active();
369
370 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
372 order.sort_by(|&a, &b| {
373 self.cursors.as_slice()[b]
374 .position
375 .cmp(&self.cursors.as_slice()[a].position)
376 });
377
378 for &idx in &order {
379 let pos = self.cursors.as_slice()[idx].position;
380
381 let indent: String = if self.auto_indent_enabled {
383 self.buffer
384 .line(pos.0)
385 .chars()
386 .take_while(|c| c.is_whitespace())
387 .collect()
388 } else {
389 String::new()
390 };
391 let indent_len = indent.chars().count();
392
393 let mut cmd =
394 InsertNewlineCommand::with_indent(pos.0, pos.1, pos, indent);
395 let mut cursor_pos = pos;
396 cmd.execute(&mut self.buffer, &mut cursor_pos);
397 self.cursors.as_mut_slice()[idx].position = cursor_pos;
398 adjust_other_cursors(
399 self.cursors.as_mut_slice(),
400 idx,
401 pos.0,
402 pos.1,
403 EditType::InsertNewline { indent_len },
404 );
405 self.history.push(Box::new(cmd));
406 }
407
408 self.finish_edit_operation();
409 self.scroll_to_cursor()
410 }
411
412 fn handle_backspace(&mut self) -> Task<Message> {
425 self.end_grouping_if_active();
427
428 if self.delete_selection_if_present() {
430 return self.scroll_to_cursor();
431 }
432
433 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
435 order.sort_by(|&a, &b| {
436 self.cursors.as_slice()[b]
437 .position
438 .cmp(&self.cursors.as_slice()[a].position)
439 });
440
441 for &idx in &order {
442 let pos = self.cursors.as_slice()[idx].position;
443 let edit_kind = if pos.1 > 0 {
445 EditType::DeleteCharBack
446 } else if pos.0 > 0 {
447 let prev_line_len = self.buffer.line_len(pos.0 - 1);
448 EditType::MergePrev { prev_line_len }
449 } else {
450 continue;
452 };
453 let mut cmd =
454 DeleteCharCommand::new(&self.buffer, pos.0, pos.1, pos);
455 let mut cursor_pos = pos;
456 cmd.execute(&mut self.buffer, &mut cursor_pos);
457 self.cursors.as_mut_slice()[idx].position = cursor_pos;
458 adjust_other_cursors(
459 self.cursors.as_mut_slice(),
460 idx,
461 pos.0,
462 pos.1,
463 edit_kind,
464 );
465 self.history.push(Box::new(cmd));
466 }
467
468 self.finish_edit_operation();
469 self.scroll_to_cursor()
470 }
471
472 fn handle_delete(&mut self) -> Task<Message> {
481 self.end_grouping_if_active();
483
484 if self.delete_selection_if_present() {
486 return self.scroll_to_cursor();
487 }
488
489 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
491 order.sort_by(|&a, &b| {
492 self.cursors.as_slice()[b]
493 .position
494 .cmp(&self.cursors.as_slice()[a].position)
495 });
496
497 for &idx in &order {
498 let pos = self.cursors.as_slice()[idx].position;
499 let line_len = self.buffer.line_len(pos.0);
500 let edit_kind = if pos.1 < line_len {
501 EditType::DeleteCharForward
502 } else if pos.0 + 1 < self.buffer.line_count() {
503 EditType::MergeNext { edit_line_len: line_len }
504 } else {
505 continue;
507 };
508 let mut cmd =
509 DeleteForwardCommand::new(&self.buffer, pos.0, pos.1, pos);
510 let mut cursor_pos = pos;
511 cmd.execute(&mut self.buffer, &mut cursor_pos);
512 self.cursors.as_mut_slice()[idx].position = cursor_pos;
513 adjust_other_cursors(
514 self.cursors.as_mut_slice(),
515 idx,
516 pos.0,
517 pos.1,
518 edit_kind,
519 );
520 self.history.push(Box::new(cmd));
521 }
522
523 self.finish_edit_operation();
524 Task::none()
525 }
526
527 fn handle_delete_selection(&mut self) -> Task<Message> {
535 self.end_grouping_if_active();
537
538 if self.cursors.iter().any(|c| c.has_selection()) {
539 self.delete_selection();
540 self.finish_edit_operation();
541 self.scroll_to_cursor()
542 } else {
543 Task::none()
544 }
545 }
546
547 fn handle_arrow_key(
562 &mut self,
563 direction: ArrowDirection,
564 shift_pressed: bool,
565 ) -> Task<Message> {
566 self.end_grouping_if_active();
568
569 if shift_pressed {
570 for cursor in self.cursors.as_mut_slice() {
572 if cursor.anchor.is_none() {
573 cursor.set_anchor();
574 }
575 }
576 self.move_cursor(direction);
577 } else {
578 self.clear_selection();
580 self.move_cursor(direction);
581 }
582 self.finish_navigation_operation();
583 self.scroll_to_cursor()
584 }
585
586 fn handle_home(&mut self, shift_pressed: bool) -> Task<Message> {
599 if shift_pressed {
600 for cursor in self.cursors.as_mut_slice() {
601 if cursor.anchor.is_none() {
602 cursor.set_anchor();
603 }
604 cursor.position.1 = 0;
605 }
606 } else {
607 self.clear_selection();
608 for cursor in self.cursors.as_mut_slice() {
609 cursor.position.1 = 0;
610 }
611 }
612 self.cursors.sort_and_merge();
613 self.finish_navigation_operation();
614 self.scroll_to_cursor()
615 }
616
617 fn handle_end(&mut self, shift_pressed: bool) -> Task<Message> {
630 if shift_pressed {
631 for cursor in self.cursors.as_mut_slice() {
632 if cursor.anchor.is_none() {
633 cursor.set_anchor();
634 }
635 cursor.position.1 = self.buffer.line_len(cursor.position.0);
636 }
637 } else {
638 self.clear_selection();
639 for cursor in self.cursors.as_mut_slice() {
640 cursor.position.1 = self.buffer.line_len(cursor.position.0);
641 }
642 }
643 self.cursors.sort_and_merge();
644 self.finish_navigation_operation();
645 self.scroll_to_cursor()
646 }
647
648 fn handle_ctrl_home(&mut self) -> Task<Message> {
656 self.clear_selection();
658 self.cursors.set_single((0, 0));
659 self.finish_navigation_operation();
660 self.scroll_to_cursor()
661 }
662
663 fn handle_ctrl_end(&mut self) -> Task<Message> {
671 self.clear_selection();
673 let last_line = self.buffer.line_count().saturating_sub(1);
674 let last_col = self.buffer.line_len(last_line);
675 self.cursors.set_single((last_line, last_col));
676 self.finish_navigation_operation();
677 self.scroll_to_cursor()
678 }
679
680 fn handle_page_up(&mut self) -> Task<Message> {
688 self.page_up();
689 self.finish_navigation_operation();
690 self.scroll_to_cursor()
691 }
692
693 fn handle_page_down(&mut self) -> Task<Message> {
701 self.page_down();
702 self.finish_navigation_operation();
703 self.scroll_to_cursor()
704 }
705
706 fn handle_goto_position(
717 &mut self,
718 line: usize,
719 col: usize,
720 ) -> Task<Message> {
721 self.end_grouping_if_active();
723 self.set_cursor(line, col)
724 }
725
726 fn handle_mouse_click_msg(&mut self, point: iced::Point) -> Task<Message> {
742 self.request_focus();
744
745 self.has_canvas_focus = true;
747
748 self.end_grouping_if_active();
750
751 self.cursors.remove_all_but_primary();
754
755 self.handle_mouse_click(point);
756 self.reset_cursor_blink();
757 self.clear_selection();
759 self.is_dragging = true;
760 self.cursors.primary_mut().set_anchor();
761
762 self.show_cursor = true;
764
765 Task::none()
766 }
767
768 fn handle_mouse_drag_msg(&mut self, point: iced::Point) -> Task<Message> {
778 if self.is_dragging {
779 let before_pos = self.cursors.primary_position();
780 self.handle_mouse_drag(point);
781 if self.cursors.primary_position() != before_pos {
782 self.overlay_cache.clear();
785 }
786 }
787 Task::none()
788 }
789
790 fn handle_mouse_release_msg(&mut self) -> Task<Message> {
796 self.is_dragging = false;
797 Task::none()
798 }
799
800 fn handle_paste_msg(&mut self, text: &str) -> Task<Message> {
817 self.end_grouping_if_active();
819
820 if text.is_empty() {
822 iced::clipboard::read().and_then(|clipboard_text| {
824 Task::done(Message::Paste(clipboard_text))
825 })
826 } else {
827 self.paste_text(text);
829 self.finish_edit_operation();
830 self.scroll_to_cursor()
831 }
832 }
833
834 fn handle_undo_msg(&mut self) -> Task<Message> {
844 self.end_grouping_if_active();
846
847 let mut cursor_pos = self.cursors.primary_position();
848 if self.history.undo(&mut self.buffer, &mut cursor_pos) {
849 self.cursors.primary_mut().position = cursor_pos;
850 self.clear_selection();
851 self.finish_edit_operation();
852 self.scroll_to_cursor()
853 } else {
854 Task::none()
855 }
856 }
857
858 fn handle_redo_msg(&mut self) -> Task<Message> {
864 let mut cursor_pos = self.cursors.primary_position();
865 if self.history.redo(&mut self.buffer, &mut cursor_pos) {
866 self.cursors.primary_mut().position = cursor_pos;
867 self.clear_selection();
868 self.finish_edit_operation();
869 self.scroll_to_cursor()
870 } else {
871 Task::none()
872 }
873 }
874
875 fn handle_open_search_msg(&mut self) -> Task<Message> {
885 self.search_state.open_search();
886 self.overlay_cache.clear();
887
888 Task::batch([
890 focus(self.search_state.search_input_id.clone()),
891 select_all(self.search_state.search_input_id.clone()),
892 ])
893 }
894
895 fn handle_open_search_replace_msg(&mut self) -> Task<Message> {
901 self.search_state.open_replace();
902 self.overlay_cache.clear();
903
904 Task::batch([
906 focus(self.search_state.search_input_id.clone()),
907 select_all(self.search_state.search_input_id.clone()),
908 ])
909 }
910
911 fn handle_close_search_msg(&mut self) -> Task<Message> {
917 if self.cursors.is_multi() && !self.search_state.is_open {
919 self.cursors.remove_all_but_primary();
920 self.overlay_cache.clear();
921 return Task::none();
922 }
923 self.search_state.close();
924 self.overlay_cache.clear();
925 Task::none()
926 }
927
928 fn handle_search_query_changed_msg(
938 &mut self,
939 query: &str,
940 ) -> Task<Message> {
941 self.search_state.set_query(query.to_string(), &self.buffer);
942 self.overlay_cache.clear();
943
944 if let Some(match_pos) = self.search_state.current_match() {
946 self.cursors.primary_mut().position =
947 (match_pos.line, match_pos.col);
948 self.clear_selection();
949 return self.scroll_to_cursor();
950 }
951 Task::none()
952 }
953
954 fn handle_replace_query_changed_msg(
964 &mut self,
965 replace_text: &str,
966 ) -> Task<Message> {
967 self.search_state.set_replace_with(replace_text.to_string());
968 Task::none()
969 }
970
971 fn handle_toggle_case_sensitive_msg(&mut self) -> Task<Message> {
977 self.search_state.toggle_case_sensitive(&self.buffer);
978 self.overlay_cache.clear();
979
980 if let Some(match_pos) = self.search_state.current_match() {
982 self.cursors.primary_mut().position =
983 (match_pos.line, match_pos.col);
984 self.clear_selection();
985 return self.scroll_to_cursor();
986 }
987 Task::none()
988 }
989
990 fn handle_find_next_msg(&mut self) -> Task<Message> {
996 if !self.search_state.matches.is_empty() {
997 self.search_state.next_match();
998 if let Some(match_pos) = self.search_state.current_match() {
999 self.cursors.primary_mut().position =
1000 (match_pos.line, match_pos.col);
1001 self.clear_selection();
1002 self.overlay_cache.clear();
1003 return self.scroll_to_cursor();
1004 }
1005 }
1006 Task::none()
1007 }
1008
1009 fn handle_find_previous_msg(&mut self) -> Task<Message> {
1015 if !self.search_state.matches.is_empty() {
1016 self.search_state.previous_match();
1017 if let Some(match_pos) = self.search_state.current_match() {
1018 self.cursors.primary_mut().position =
1019 (match_pos.line, match_pos.col);
1020 self.clear_selection();
1021 self.overlay_cache.clear();
1022 return self.scroll_to_cursor();
1023 }
1024 }
1025 Task::none()
1026 }
1027
1028 fn handle_replace_next_msg(&mut self) -> Task<Message> {
1034 if let Some(match_pos) = self.search_state.current_match() {
1036 let query_len = self.search_state.query.chars().count();
1037 let replace_text = self.search_state.replace_with.clone();
1038
1039 let pos = self.cursors.primary_position();
1041 let mut cmd = ReplaceTextCommand::new(
1042 &self.buffer,
1043 (match_pos.line, match_pos.col),
1044 query_len,
1045 replace_text,
1046 pos,
1047 );
1048 let mut cursor_pos = pos;
1049 cmd.execute(&mut self.buffer, &mut cursor_pos);
1050 self.cursors.primary_mut().position = cursor_pos;
1051 self.history.push(Box::new(cmd));
1052
1053 self.search_state.update_matches(&self.buffer);
1055
1056 if !self.search_state.matches.is_empty()
1058 && let Some(next_match) = self.search_state.current_match()
1059 {
1060 self.cursors.primary_mut().position =
1061 (next_match.line, next_match.col);
1062 }
1063
1064 self.clear_selection();
1065 self.finish_edit_operation();
1066 return self.scroll_to_cursor();
1067 }
1068 Task::none()
1069 }
1070
1071 fn handle_replace_all_msg(&mut self) -> Task<Message> {
1077 let all_matches = super::search::find_matches(
1079 &self.buffer,
1080 &self.search_state.query,
1081 self.search_state.case_sensitive,
1082 None, );
1084
1085 if !all_matches.is_empty() {
1086 let query_len = self.search_state.query.chars().count();
1087 let replace_text = self.search_state.replace_with.clone();
1088
1089 let mut composite =
1091 CompositeCommand::new("Replace All".to_string());
1092
1093 for match_pos in all_matches.iter().rev() {
1095 let pos = self.cursors.primary_position();
1096 let cmd = ReplaceTextCommand::new(
1097 &self.buffer,
1098 (match_pos.line, match_pos.col),
1099 query_len,
1100 replace_text.clone(),
1101 pos,
1102 );
1103 composite.add(Box::new(cmd));
1104 }
1105
1106 let mut cursor_pos = self.cursors.primary_position();
1108 composite.execute(&mut self.buffer, &mut cursor_pos);
1109 self.cursors.primary_mut().position = cursor_pos;
1110 self.history.push(Box::new(composite));
1111
1112 self.search_state.update_matches(&self.buffer);
1114 self.clear_selection();
1115 self.finish_edit_operation();
1116 self.scroll_to_cursor()
1117 } else {
1118 Task::none()
1119 }
1120 }
1121
1122 fn handle_search_dialog_tab_msg(&mut self) -> Task<Message> {
1128 self.search_state.focus_next_field();
1130
1131 match self.search_state.focused_field {
1133 crate::canvas_editor::search::SearchFocusedField::Search => {
1134 focus(self.search_state.search_input_id.clone())
1135 }
1136 crate::canvas_editor::search::SearchFocusedField::Replace => {
1137 focus(self.search_state.replace_input_id.clone())
1138 }
1139 }
1140 }
1141
1142 fn handle_search_dialog_shift_tab_msg(&mut self) -> Task<Message> {
1148 self.search_state.focus_previous_field();
1150
1151 match self.search_state.focused_field {
1153 crate::canvas_editor::search::SearchFocusedField::Search => {
1154 focus(self.search_state.search_input_id.clone())
1155 }
1156 crate::canvas_editor::search::SearchFocusedField::Replace => {
1157 focus(self.search_state.replace_input_id.clone())
1158 }
1159 }
1160 }
1161
1162 fn handle_canvas_focus_gained_msg(&mut self) -> Task<Message> {
1172 self.has_canvas_focus = true;
1173 self.focus_locked = false; self.show_cursor = true;
1175 self.reset_cursor_blink();
1176 self.overlay_cache.clear();
1177 Task::none()
1178 }
1179
1180 fn handle_canvas_focus_lost_msg(&mut self) -> Task<Message> {
1186 self.has_canvas_focus = false;
1187 self.focus_locked = true; self.show_cursor = false;
1189 self.ime_preedit = None;
1190 self.overlay_cache.clear();
1191 Task::none()
1192 }
1193
1194 fn handle_ime_opened_msg(&mut self) -> Task<Message> {
1202 self.ime_preedit = None;
1203 self.overlay_cache.clear();
1204 Task::none()
1205 }
1206
1207 fn handle_ime_preedit_msg(
1220 &mut self,
1221 content: &str,
1222 selection: &Option<std::ops::Range<usize>>,
1223 ) -> Task<Message> {
1224 if content.is_empty() {
1225 self.ime_preedit = None;
1226 } else {
1227 self.ime_preedit = Some(ImePreedit {
1228 content: content.to_string(),
1229 selection: selection.clone(),
1230 });
1231 }
1232
1233 self.overlay_cache.clear();
1234 Task::none()
1235 }
1236
1237 fn handle_ime_commit_msg(&mut self, text: &str) -> Task<Message> {
1249 self.ime_preedit = None;
1250
1251 if text.is_empty() {
1252 self.overlay_cache.clear();
1253 return Task::none();
1254 }
1255
1256 self.ensure_grouping_started("Typing");
1257
1258 self.paste_text(text);
1259 self.finish_edit_operation();
1260 self.scroll_to_cursor()
1261 }
1262
1263 fn handle_ime_closed_msg(&mut self) -> Task<Message> {
1271 self.ime_preedit = None;
1272 self.overlay_cache.clear();
1273 Task::none()
1274 }
1275
1276 fn handle_tick_msg(&mut self) -> Task<Message> {
1288 if self.has_focus()
1290 && self.last_blink.elapsed() >= CURSOR_BLINK_INTERVAL
1291 {
1292 self.cursor_visible = !self.cursor_visible;
1293 self.last_blink = super::Instant::now();
1294 self.overlay_cache.clear();
1295 }
1296
1297 if !self.has_focus() {
1299 self.show_cursor = false;
1300 }
1301
1302 Task::none()
1303 }
1304
1305 fn handle_scrolled_msg(
1319 &mut self,
1320 viewport: iced::widget::scrollable::Viewport,
1321 ) -> Task<Message> {
1322 let new_scroll = viewport.absolute_offset().y;
1331 let new_height = viewport.bounds().height;
1332 let new_width = viewport.bounds().width;
1333 let scroll_changed = (self.viewport_scroll - new_scroll).abs() > 0.1;
1334 let visible_lines_count =
1335 (new_height / self.line_height).ceil() as usize + 2;
1336 let first_visible_line =
1337 (new_scroll / self.line_height).floor() as usize;
1338 let last_visible_line = first_visible_line + visible_lines_count;
1339 let margin = visible_lines_count
1340 * crate::canvas_editor::CACHE_WINDOW_MARGIN_MULTIPLIER;
1341 let window_start = first_visible_line.saturating_sub(margin);
1342 let window_end = last_visible_line + margin;
1343 let need_rewindow =
1347 if self.cache_window_end_line > self.cache_window_start_line {
1348 let lower_boundary_trigger = self.cache_window_start_line > 0
1349 && first_visible_line
1350 < self
1351 .cache_window_start_line
1352 .saturating_add(visible_lines_count / 2);
1353 let upper_boundary_trigger = last_visible_line
1354 > self
1355 .cache_window_end_line
1356 .saturating_sub(visible_lines_count / 2);
1357 lower_boundary_trigger || upper_boundary_trigger
1358 } else {
1359 true
1360 };
1361 if (self.viewport_height - new_height).abs() > 1.0
1364 || (self.viewport_width - new_width).abs() > 1.0
1365 || (scroll_changed && need_rewindow)
1366 {
1367 self.cache_window_start_line = window_start;
1368 self.cache_window_end_line = window_end;
1369 self.last_first_visible_line = first_visible_line;
1370 self.content_cache.clear();
1371 self.overlay_cache.clear();
1372 }
1373 self.viewport_scroll = new_scroll;
1374 self.viewport_height = new_height;
1375 self.viewport_width = new_width;
1376 Task::none()
1377 }
1378
1379 fn handle_horizontal_scrolled_msg(
1392 &mut self,
1393 viewport: iced::widget::scrollable::Viewport,
1394 ) -> Task<Message> {
1395 let new_x = viewport.absolute_offset().x;
1396 if (self.horizontal_scroll_offset - new_x).abs() > 0.1 {
1397 self.horizontal_scroll_offset = new_x;
1398 self.content_cache.clear();
1399 self.overlay_cache.clear();
1400 }
1401 Task::none()
1402 }
1403
1404 fn handle_alt_click_msg(&mut self, point: iced::Point) -> Task<Message> {
1419 if let Some(pos) = self.calculate_cursor_from_point(point) {
1420 self.cursors.add_cursor(pos);
1421 self.overlay_cache.clear();
1422 self.reset_cursor_blink();
1423 }
1424 Task::none()
1425 }
1426
1427 fn handle_add_cursor_above_msg(&mut self) -> Task<Message> {
1434 let (line, col) = self.cursors.primary_position();
1435 if line == 0 {
1436 return Task::none();
1437 }
1438 let new_line = line - 1;
1439 let new_col = col.min(self.buffer.line_len(new_line));
1440 self.cursors.add_cursor((new_line, new_col));
1441 self.overlay_cache.clear();
1442 self.reset_cursor_blink();
1443 Task::none()
1444 }
1445
1446 fn handle_add_cursor_below_msg(&mut self) -> Task<Message> {
1453 let (line, col) = self.cursors.primary_position();
1454 let last_line = self.buffer.line_count().saturating_sub(1);
1455 if line >= last_line {
1456 return Task::none();
1457 }
1458 let new_line = line + 1;
1459 let new_col = col.min(self.buffer.line_len(new_line));
1460 self.cursors.add_cursor((new_line, new_col));
1461 self.overlay_cache.clear();
1462 self.reset_cursor_blink();
1463 Task::none()
1464 }
1465
1466 fn handle_select_next_occurrence_msg(&mut self) -> Task<Message> {
1474 let search_text = if let Some(text) = self.get_selected_text() {
1476 text
1477 } else {
1478 let (line, col) = self.cursors.primary_position();
1480 let line_str = self.buffer.line(line).to_string();
1481 let word_start = Self::word_start_in_line(&line_str, col);
1482 let word_end = Self::word_end_in_line(&line_str, col);
1483 if word_start == word_end {
1484 return Task::none();
1485 }
1486 self.cursors.primary_mut().anchor = Some((line, word_start));
1489 self.cursors.primary_mut().position = (line, word_end);
1490 self.overlay_cache.clear();
1491 return Task::none();
1492 };
1493
1494 if search_text.is_empty() {
1495 return Task::none();
1496 }
1497
1498 let search_start = self
1500 .cursors
1501 .as_slice()
1502 .last()
1503 .map(|last| {
1504 last.selection_range()
1505 .map(|(_, end)| end)
1506 .unwrap_or(last.position)
1507 })
1508 .unwrap_or((0, 0));
1509
1510 let (start_line, start_col) = search_start;
1512 let line_count = self.buffer.line_count();
1513
1514 for line_offset in 0..=line_count {
1515 let line_idx = (start_line + line_offset) % line_count;
1516 let line_str = self.buffer.line(line_idx).to_string();
1517 let chars: Vec<char> = line_str.chars().collect();
1518
1519 let search_col = if line_offset == 0 { start_col } else { 0 };
1521
1522 let prefix_bytes: usize =
1524 chars.iter().take(search_col).map(|c| c.len_utf8()).sum();
1525 let haystack = &line_str[prefix_bytes..];
1526
1527 if let Some(byte_offset) = haystack.find(search_text.as_str()) {
1529 let char_start =
1531 search_col + haystack[..byte_offset].chars().count();
1532 let char_end = char_start + search_text.chars().count();
1533
1534 let found_cursor = cursor_set::Cursor {
1536 position: (line_idx, char_end),
1537 anchor: Some((line_idx, char_start)),
1538 };
1539 self.cursors.add_cursor_with_selection(found_cursor);
1540 self.overlay_cache.clear();
1541 self.reset_cursor_blink();
1542 return self.scroll_to_cursor();
1543 }
1544 }
1545
1546 Task::none()
1547 }
1548
1549 pub fn update(&mut self, message: &Message) -> Task<Message> {
1562 match message {
1563 Message::CharacterInput(ch) => self.handle_character_input_msg(*ch),
1565 Message::Tab => self.handle_tab(),
1566 Message::Enter => self.handle_enter(),
1567
1568 Message::Backspace => self.handle_backspace(),
1570 Message::Delete => self.handle_delete(),
1571 Message::DeleteSelection => self.handle_delete_selection(),
1572
1573 Message::ArrowKey(direction, shift) => {
1575 self.handle_arrow_key(*direction, *shift)
1576 }
1577 Message::Home(shift) => self.handle_home(*shift),
1578 Message::End(shift) => self.handle_end(*shift),
1579 Message::CtrlHome => self.handle_ctrl_home(),
1580 Message::CtrlEnd => self.handle_ctrl_end(),
1581 Message::GotoPosition(line, col) => {
1582 self.handle_goto_position(*line, *col)
1583 }
1584 Message::PageUp => self.handle_page_up(),
1585 Message::PageDown => self.handle_page_down(),
1586
1587 Message::MouseClick(point) => self.handle_mouse_click_msg(*point),
1589 Message::MouseDrag(point) => self.handle_mouse_drag_msg(*point),
1590 Message::MouseHover(point) => self.handle_mouse_drag_msg(*point),
1591 Message::MouseRelease => self.handle_mouse_release_msg(),
1592
1593 Message::Copy => self.copy_selection(),
1595 Message::Paste(text) => self.handle_paste_msg(text),
1596
1597 Message::Undo => self.handle_undo_msg(),
1599 Message::Redo => self.handle_redo_msg(),
1600
1601 Message::OpenSearch => self.handle_open_search_msg(),
1603 Message::OpenSearchReplace => self.handle_open_search_replace_msg(),
1604 Message::CloseSearch => self.handle_close_search_msg(),
1605 Message::SearchQueryChanged(query) => {
1606 self.handle_search_query_changed_msg(query)
1607 }
1608 Message::ReplaceQueryChanged(text) => {
1609 self.handle_replace_query_changed_msg(text)
1610 }
1611 Message::ToggleCaseSensitive => {
1612 self.handle_toggle_case_sensitive_msg()
1613 }
1614 Message::FindNext => self.handle_find_next_msg(),
1615 Message::FindPrevious => self.handle_find_previous_msg(),
1616 Message::ReplaceNext => self.handle_replace_next_msg(),
1617 Message::ReplaceAll => self.handle_replace_all_msg(),
1618 Message::SearchDialogTab => self.handle_search_dialog_tab_msg(),
1619 Message::SearchDialogShiftTab => {
1620 self.handle_search_dialog_shift_tab_msg()
1621 }
1622 Message::FocusNavigationTab => self.handle_focus_navigation_tab(),
1623 Message::FocusNavigationShiftTab => {
1624 self.handle_focus_navigation_shift_tab()
1625 }
1626
1627 Message::CanvasFocusGained => self.handle_canvas_focus_gained_msg(),
1629 Message::CanvasFocusLost => self.handle_canvas_focus_lost_msg(),
1630 Message::ImeOpened => self.handle_ime_opened_msg(),
1631 Message::ImePreedit(content, selection) => {
1632 self.handle_ime_preedit_msg(content, selection)
1633 }
1634 Message::ImeCommit(text) => self.handle_ime_commit_msg(text),
1635 Message::ImeClosed => self.handle_ime_closed_msg(),
1636
1637 Message::Tick => self.handle_tick_msg(),
1639 Message::Scrolled(viewport) => self.handle_scrolled_msg(*viewport),
1640 Message::HorizontalScrolled(viewport) => {
1641 self.handle_horizontal_scrolled_msg(*viewport)
1642 }
1643
1644 Message::JumpClick(_point) => Task::none(),
1648
1649 Message::AltClick(point) => self.handle_alt_click_msg(*point),
1651 Message::AddCursorAbove => self.handle_add_cursor_above_msg(),
1652 Message::AddCursorBelow => self.handle_add_cursor_below_msg(),
1653 Message::SelectNextOccurrence => {
1654 self.handle_select_next_occurrence_msg()
1655 }
1656 }
1657 }
1658}
1659
1660#[cfg(test)]
1661mod tests {
1662 use super::*;
1663 use crate::canvas_editor::ArrowDirection;
1664
1665 #[test]
1666 fn test_horizontal_scroll_initial_state() {
1667 let editor = CodeEditor::new("short line", "rs");
1668 assert!(
1669 (editor.horizontal_scroll_offset - 0.0).abs() < f32::EPSILON,
1670 "Initial horizontal scroll offset should be 0"
1671 );
1672 }
1673
1674 #[test]
1675 fn test_set_wrap_enabled_resets_horizontal_offset() {
1676 let mut editor = CodeEditor::new("long line", "rs");
1677 editor.wrap_enabled = false;
1678 editor.horizontal_scroll_offset = 100.0;
1680
1681 editor.set_wrap_enabled(true);
1683 assert!(
1684 (editor.horizontal_scroll_offset - 0.0).abs() < f32::EPSILON,
1685 "Horizontal scroll offset should be reset when wrap is re-enabled"
1686 );
1687 }
1688
1689 #[test]
1690 fn test_canvas_focus_lost() {
1691 let mut editor = CodeEditor::new("test", "rs");
1692 editor.has_canvas_focus = true;
1693
1694 let _ = editor.update(&Message::CanvasFocusLost);
1695
1696 assert!(!editor.has_canvas_focus);
1697 assert!(!editor.show_cursor);
1698 assert!(editor.focus_locked, "Focus should be locked when lost");
1699 }
1700
1701 #[test]
1702 fn test_canvas_focus_gained_resets_lock() {
1703 let mut editor = CodeEditor::new("test", "rs");
1704 editor.has_canvas_focus = false;
1705 editor.focus_locked = true;
1706
1707 let _ = editor.update(&Message::CanvasFocusGained);
1708
1709 assert!(editor.has_canvas_focus);
1710 assert!(
1711 !editor.focus_locked,
1712 "Focus lock should be reset when focus is gained"
1713 );
1714 }
1715
1716 #[test]
1717 fn test_focus_lock_state() {
1718 let mut editor = CodeEditor::new("test", "rs");
1719
1720 assert!(!editor.focus_locked);
1722
1723 let _ = editor.update(&Message::CanvasFocusLost);
1725 assert!(editor.focus_locked, "Focus should be locked when lost");
1726
1727 editor.request_focus();
1729 let _ = editor.update(&Message::CanvasFocusGained);
1730 assert!(!editor.focus_locked, "Focus should be unlocked when regained");
1731
1732 editor.focus_locked = true;
1734 editor.reset_focus_lock();
1735 assert!(!editor.focus_locked, "Focus lock should be resetable");
1736 }
1737
1738 #[test]
1739 fn test_reset_focus_lock() {
1740 let mut editor = CodeEditor::new("test", "rs");
1741 editor.focus_locked = true;
1742
1743 editor.reset_focus_lock();
1744
1745 assert!(!editor.focus_locked);
1746 }
1747
1748 #[test]
1749 fn test_home_key() {
1750 let mut editor = CodeEditor::new("hello world", "py");
1751 editor.cursors.primary_mut().position = (0, 5); let _ = editor.update(&Message::Home(false));
1753 assert_eq!(editor.cursors.primary_position(), (0, 0));
1754 }
1755
1756 #[test]
1757 fn test_end_key() {
1758 let mut editor = CodeEditor::new("hello world", "py");
1759 editor.cursors.primary_mut().position = (0, 0);
1760 let _ = editor.update(&Message::End(false));
1761 assert_eq!(editor.cursors.primary_position(), (0, 11)); }
1763
1764 #[test]
1765 fn test_arrow_key_with_shift_creates_selection() {
1766 let mut editor = CodeEditor::new("hello world", "py");
1767 editor.cursors.primary_mut().position = (0, 0);
1768
1769 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, true));
1771 assert!(editor.cursors.primary().anchor.is_some());
1772 assert!(editor.cursors.primary().has_selection());
1773 }
1774
1775 #[test]
1776 fn test_arrow_key_without_shift_clears_selection() {
1777 let mut editor = CodeEditor::new("hello world", "py");
1778 editor.cursors.primary_mut().anchor = Some((0, 0));
1779 editor.cursors.primary_mut().position = (0, 5);
1780
1781 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, false));
1783 assert!(editor.cursors.primary().anchor.is_none());
1784 assert!(!editor.cursors.primary().has_selection());
1785 }
1786
1787 #[test]
1788 fn test_typing_with_selection() {
1789 let mut editor = CodeEditor::new("hello world", "py");
1790 editor.request_focus();
1792 editor.has_canvas_focus = true;
1793 editor.focus_locked = false;
1794
1795 editor.cursors.primary_mut().anchor = Some((0, 0));
1796 editor.cursors.primary_mut().position = (0, 5);
1797
1798 let _ = editor.update(&Message::CharacterInput('X'));
1799 assert_eq!(editor.buffer.line(0), "Xhello world");
1802 }
1803
1804 #[test]
1805 fn test_ctrl_home() {
1806 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1807 editor.cursors.primary_mut().position = (2, 5); let _ = editor.update(&Message::CtrlHome);
1809 assert_eq!(editor.cursors.primary_position(), (0, 0)); }
1811
1812 #[test]
1813 fn test_ctrl_end() {
1814 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1815 editor.cursors.primary_mut().position = (0, 0); let _ = editor.update(&Message::CtrlEnd);
1817 assert_eq!(editor.cursors.primary_position(), (2, 5)); }
1819
1820 #[test]
1821 fn test_ctrl_home_clears_selection() {
1822 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1823 editor.cursors.primary_mut().position = (2, 5);
1824 editor.cursors.primary_mut().anchor = Some((0, 0));
1825 editor.cursors.primary_mut().position = (2, 5);
1826
1827 let _ = editor.update(&Message::CtrlHome);
1828 assert_eq!(editor.cursors.primary_position(), (0, 0));
1829 assert!(editor.cursors.primary().anchor.is_none());
1830 assert!(!editor.cursors.primary().has_selection());
1831 }
1832
1833 #[test]
1834 fn test_ctrl_end_clears_selection() {
1835 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1836 editor.cursors.primary_mut().position = (0, 0);
1837 editor.cursors.primary_mut().anchor = Some((0, 0));
1838 editor.cursors.primary_mut().position = (1, 3);
1839
1840 let _ = editor.update(&Message::CtrlEnd);
1841 assert_eq!(editor.cursors.primary_position(), (2, 5));
1842 assert!(editor.cursors.primary().anchor.is_none());
1843 assert!(!editor.cursors.primary().has_selection());
1844 }
1845
1846 #[test]
1847 fn test_goto_position_sets_cursor_and_clears_selection() {
1848 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1849 editor.cursors.primary_mut().anchor = Some((0, 0));
1850 editor.cursors.primary_mut().position = (1, 2);
1851
1852 let _ = editor.update(&Message::GotoPosition(1, 3));
1853
1854 assert_eq!(editor.cursors.primary_position(), (1, 3));
1855 assert!(editor.cursors.primary().anchor.is_none());
1856 assert!(!editor.cursors.primary().has_selection());
1857 }
1858
1859 #[test]
1860 fn test_goto_position_clamps_out_of_range() {
1861 let mut editor = CodeEditor::new("a\nbb", "py");
1862
1863 let _ = editor.update(&Message::GotoPosition(99, 99));
1864
1865 assert_eq!(editor.cursors.primary_position(), (1, 2));
1867 }
1868
1869 #[test]
1870 fn test_scroll_sets_initial_cache_window() {
1871 let content =
1872 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
1873 let mut editor = CodeEditor::new(&content, "py");
1874
1875 let height = 400.0;
1877 let width = 800.0;
1878 let scroll = 0.0;
1879
1880 let visible_lines_count =
1882 (height / editor.line_height).ceil() as usize + 2;
1883 let first_visible_line = (scroll / editor.line_height).floor() as usize;
1884 let last_visible_line = first_visible_line + visible_lines_count;
1885 let margin = visible_lines_count * 2;
1886 let window_start = first_visible_line.saturating_sub(margin);
1887 let window_end = last_visible_line + margin;
1888
1889 editor.viewport_height = height;
1891 editor.viewport_width = width;
1892 editor.viewport_scroll = -1.0;
1893 let scroll_changed = (editor.viewport_scroll - scroll).abs() > 0.1;
1894 let need_rewindow = true;
1895 if (editor.viewport_height - height).abs() > 1.0
1896 || (editor.viewport_width - width).abs() > 1.0
1897 || (scroll_changed && need_rewindow)
1898 {
1899 editor.cache_window_start_line = window_start;
1900 editor.cache_window_end_line = window_end;
1901 editor.last_first_visible_line = first_visible_line;
1902 }
1903 editor.viewport_scroll = scroll;
1904
1905 assert_eq!(editor.last_first_visible_line, first_visible_line);
1906 assert!(editor.cache_window_end_line > editor.cache_window_start_line);
1907 assert_eq!(editor.cache_window_start_line, window_start);
1908 assert_eq!(editor.cache_window_end_line, window_end);
1909 }
1910
1911 #[test]
1912 fn test_small_scroll_keeps_window() {
1913 let content =
1914 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
1915 let mut editor = CodeEditor::new(&content, "py");
1916 let height = 400.0;
1917 let width = 800.0;
1918 let initial_scroll = 0.0;
1919 let visible_lines_count =
1920 (height / editor.line_height).ceil() as usize + 2;
1921 let first_visible_line =
1922 (initial_scroll / editor.line_height).floor() as usize;
1923 let last_visible_line = first_visible_line + visible_lines_count;
1924 let margin = visible_lines_count * 2;
1925 let window_start = first_visible_line.saturating_sub(margin);
1926 let window_end = last_visible_line + margin;
1927 editor.cache_window_start_line = window_start;
1928 editor.cache_window_end_line = window_end;
1929 editor.viewport_height = height;
1930 editor.viewport_width = width;
1931 editor.viewport_scroll = initial_scroll;
1932
1933 let small_scroll =
1935 editor.line_height * (visible_lines_count as f32 / 4.0);
1936 let first_visible_line2 =
1937 (small_scroll / editor.line_height).floor() as usize;
1938 let last_visible_line2 = first_visible_line2 + visible_lines_count;
1939 let lower_boundary_trigger = editor.cache_window_start_line > 0
1940 && first_visible_line2
1941 < editor
1942 .cache_window_start_line
1943 .saturating_add(visible_lines_count / 2);
1944 let upper_boundary_trigger = last_visible_line2
1945 > editor
1946 .cache_window_end_line
1947 .saturating_sub(visible_lines_count / 2);
1948 let need_rewindow = lower_boundary_trigger || upper_boundary_trigger;
1949
1950 assert!(!need_rewindow, "Small scroll should be inside the window");
1951 assert_eq!(editor.cache_window_start_line, window_start);
1953 assert_eq!(editor.cache_window_end_line, window_end);
1954 }
1955
1956 #[test]
1957 fn test_large_scroll_rewindows() {
1958 let content =
1959 (0..1000).map(|i| format!("line{}\n", i)).collect::<String>();
1960 let mut editor = CodeEditor::new(&content, "py");
1961 let height = 400.0;
1962 let width = 800.0;
1963 let initial_scroll = 0.0;
1964 let visible_lines_count =
1965 (height / editor.line_height).ceil() as usize + 2;
1966 let first_visible_line =
1967 (initial_scroll / editor.line_height).floor() as usize;
1968 let last_visible_line = first_visible_line + visible_lines_count;
1969 let margin = visible_lines_count * 2;
1970 editor.cache_window_start_line =
1971 first_visible_line.saturating_sub(margin);
1972 editor.cache_window_end_line = last_visible_line + margin;
1973 editor.viewport_height = height;
1974 editor.viewport_width = width;
1975 editor.viewport_scroll = initial_scroll;
1976
1977 let large_scroll =
1979 editor.line_height * ((visible_lines_count * 4) as f32);
1980 let first_visible_line2 =
1981 (large_scroll / editor.line_height).floor() as usize;
1982 let last_visible_line2 = first_visible_line2 + visible_lines_count;
1983 let window_start2 = first_visible_line2.saturating_sub(margin);
1984 let window_end2 = last_visible_line2 + margin;
1985 let need_rewindow = first_visible_line2
1986 < editor
1987 .cache_window_start_line
1988 .saturating_add(visible_lines_count / 2)
1989 || last_visible_line2
1990 > editor
1991 .cache_window_end_line
1992 .saturating_sub(visible_lines_count / 2);
1993 assert!(need_rewindow, "Large scroll should trigger window update");
1994
1995 editor.cache_window_start_line = window_start2;
1997 editor.cache_window_end_line = window_end2;
1998 editor.last_first_visible_line = first_visible_line2;
1999
2000 assert_eq!(editor.cache_window_start_line, window_start2);
2001 assert_eq!(editor.cache_window_end_line, window_end2);
2002 assert_eq!(editor.last_first_visible_line, first_visible_line2);
2003 }
2004
2005 #[test]
2006 fn test_delete_selection_message() {
2007 let mut editor = CodeEditor::new("hello world", "py");
2008 editor.cursors.primary_mut().position = (0, 0);
2009 editor.cursors.primary_mut().anchor = Some((0, 0));
2010 editor.cursors.primary_mut().position = (0, 5);
2011
2012 let _ = editor.update(&Message::DeleteSelection);
2013 assert_eq!(editor.buffer.line(0), " world");
2014 assert_eq!(editor.cursors.primary_position(), (0, 0));
2015 assert!(editor.cursors.primary().anchor.is_none());
2016 assert!(!editor.cursors.primary().has_selection());
2017 }
2018
2019 #[test]
2020 fn test_delete_selection_multiline() {
2021 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2022 editor.cursors.primary_mut().position = (0, 2);
2023 editor.cursors.primary_mut().anchor = Some((0, 2));
2024 editor.cursors.primary_mut().position = (2, 2);
2025
2026 let _ = editor.update(&Message::DeleteSelection);
2027 assert_eq!(editor.buffer.line(0), "line3");
2028 assert_eq!(editor.cursors.primary_position(), (0, 2));
2029 assert!(editor.cursors.primary().anchor.is_none());
2030 }
2031
2032 #[test]
2033 fn test_delete_selection_no_selection() {
2034 let mut editor = CodeEditor::new("hello world", "py");
2035 editor.cursors.primary_mut().position = (0, 5);
2036
2037 let _ = editor.update(&Message::DeleteSelection);
2038 assert_eq!(editor.buffer.line(0), "hello world");
2040 assert_eq!(editor.cursors.primary_position(), (0, 5));
2041 }
2042
2043 #[test]
2044 #[allow(clippy::unwrap_used)]
2045 fn test_ime_preedit_and_commit_chinese() {
2046 let mut editor = CodeEditor::new("", "py");
2047 let _ = editor.update(&Message::ImeOpened);
2049 assert!(editor.ime_preedit.is_none());
2050
2051 let content = "安全与合规".to_string();
2053 let selection = Some(0..3); let _ = editor
2055 .update(&Message::ImePreedit(content.clone(), selection.clone()));
2056
2057 assert!(editor.ime_preedit.is_some());
2058 assert_eq!(
2059 editor.ime_preedit.as_ref().unwrap().content.clone(),
2060 content
2061 );
2062 assert_eq!(
2063 editor.ime_preedit.as_ref().unwrap().selection.clone(),
2064 selection
2065 );
2066
2067 let _ = editor.update(&Message::ImeCommit("安全与合规".to_string()));
2069 assert!(editor.ime_preedit.is_none());
2070 assert_eq!(editor.buffer.line(0), "安全与合规");
2071 assert_eq!(
2072 editor.cursors.primary_position(),
2073 (0, "安全与合规".chars().count())
2074 );
2075 }
2076
2077 #[test]
2078 fn test_undo_char_insert() {
2079 let mut editor = CodeEditor::new("hello", "py");
2080 editor.request_focus();
2082 editor.has_canvas_focus = true;
2083 editor.focus_locked = false;
2084
2085 editor.cursors.primary_mut().position = (0, 5);
2086
2087 let _ = editor.update(&Message::CharacterInput('!'));
2089 assert_eq!(editor.buffer.line(0), "hello!");
2090 assert_eq!(editor.cursors.primary_position(), (0, 6));
2091
2092 editor.history.end_group();
2094 let _ = editor.update(&Message::Undo);
2095 assert_eq!(editor.buffer.line(0), "hello");
2096 assert_eq!(editor.cursors.primary_position(), (0, 5));
2097 }
2098
2099 #[test]
2100 fn test_undo_redo_char_insert() {
2101 let mut editor = CodeEditor::new("hello", "py");
2102 editor.request_focus();
2104 editor.has_canvas_focus = true;
2105 editor.focus_locked = false;
2106
2107 editor.cursors.primary_mut().position = (0, 5);
2108
2109 let _ = editor.update(&Message::CharacterInput('!'));
2111 editor.history.end_group();
2112
2113 let _ = editor.update(&Message::Undo);
2115 assert_eq!(editor.buffer.line(0), "hello");
2116
2117 let _ = editor.update(&Message::Redo);
2119 assert_eq!(editor.buffer.line(0), "hello!");
2120 assert_eq!(editor.cursors.primary_position(), (0, 6));
2121 }
2122
2123 #[test]
2124 fn test_undo_backspace() {
2125 let mut editor = CodeEditor::new("hello", "py");
2126 editor.cursors.primary_mut().position = (0, 5);
2127
2128 let _ = editor.update(&Message::Backspace);
2130 assert_eq!(editor.buffer.line(0), "hell");
2131 assert_eq!(editor.cursors.primary_position(), (0, 4));
2132
2133 let _ = editor.update(&Message::Undo);
2135 assert_eq!(editor.buffer.line(0), "hello");
2136 assert_eq!(editor.cursors.primary_position(), (0, 5));
2137 }
2138
2139 #[test]
2140 fn test_undo_newline() {
2141 let mut editor = CodeEditor::new("hello world", "py");
2142 editor.cursors.primary_mut().position = (0, 5);
2143
2144 let _ = editor.update(&Message::Enter);
2146 assert_eq!(editor.buffer.line(0), "hello");
2147 assert_eq!(editor.buffer.line(1), " world");
2148 assert_eq!(editor.cursors.primary_position(), (1, 0));
2149
2150 let _ = editor.update(&Message::Undo);
2152 assert_eq!(editor.buffer.line(0), "hello world");
2153 assert_eq!(editor.cursors.primary_position(), (0, 5));
2154 }
2155
2156 #[test]
2157 fn test_undo_grouped_typing() {
2158 let mut editor = CodeEditor::new("hello", "py");
2159 editor.request_focus();
2161 editor.has_canvas_focus = true;
2162 editor.focus_locked = false;
2163
2164 editor.cursors.primary_mut().position = (0, 5);
2165
2166 let _ = editor.update(&Message::CharacterInput(' '));
2168 let _ = editor.update(&Message::CharacterInput('w'));
2169 let _ = editor.update(&Message::CharacterInput('o'));
2170 let _ = editor.update(&Message::CharacterInput('r'));
2171 let _ = editor.update(&Message::CharacterInput('l'));
2172 let _ = editor.update(&Message::CharacterInput('d'));
2173
2174 assert_eq!(editor.buffer.line(0), "hello world");
2175
2176 editor.history.end_group();
2178
2179 let _ = editor.update(&Message::Undo);
2181 assert_eq!(editor.buffer.line(0), "hello");
2182 assert_eq!(editor.cursors.primary_position(), (0, 5));
2183 }
2184
2185 #[test]
2186 fn test_navigation_ends_grouping() {
2187 let mut editor = CodeEditor::new("hello", "py");
2188 editor.request_focus();
2190 editor.has_canvas_focus = true;
2191 editor.focus_locked = false;
2192
2193 editor.cursors.primary_mut().position = (0, 5);
2194
2195 let _ = editor.update(&Message::CharacterInput('!'));
2197 assert!(editor.is_grouping);
2198
2199 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Left, false));
2201 assert!(!editor.is_grouping);
2202
2203 let _ = editor.update(&Message::CharacterInput('?'));
2205 assert!(editor.is_grouping);
2206
2207 editor.history.end_group();
2208
2209 let _ = editor.update(&Message::Undo);
2211 assert_eq!(editor.buffer.line(0), "hello!");
2212
2213 let _ = editor.update(&Message::Undo);
2214 assert_eq!(editor.buffer.line(0), "hello");
2215 }
2216
2217 #[test]
2218 fn test_edit_increments_revision_and_clears_visual_lines_cache() {
2219 let mut editor = CodeEditor::new("hello", "rs");
2220 editor.request_focus();
2221 editor.has_canvas_focus = true;
2222 editor.focus_locked = false;
2223 editor.cursors.primary_mut().position = (0, 5);
2224
2225 let _ = editor.visual_lines_cached(800.0);
2226 assert!(
2227 editor.visual_lines_cache.borrow().is_some(),
2228 "visual_lines_cached should populate the cache"
2229 );
2230
2231 let previous_revision = editor.buffer_revision;
2232
2233 let _ = editor.update(&Message::CharacterInput('!'));
2234 assert_eq!(
2235 editor.buffer_revision,
2236 previous_revision.wrapping_add(1),
2237 "buffer_revision should change on buffer edits"
2238 );
2239 assert!(
2243 editor
2244 .visual_lines_cache
2245 .borrow()
2246 .as_ref()
2247 .is_none_or(|c| c.key.buffer_revision == editor.buffer_revision),
2248 "buffer edits should not leave stale data in the visual lines cache"
2249 );
2250 }
2251
2252 #[test]
2253 fn test_multiple_undo_redo() {
2254 let mut editor = CodeEditor::new("a", "py");
2255 editor.request_focus();
2257 editor.has_canvas_focus = true;
2258 editor.focus_locked = false;
2259
2260 editor.cursors.primary_mut().position = (0, 1);
2261
2262 let _ = editor.update(&Message::CharacterInput('b'));
2264 editor.history.end_group();
2265
2266 let _ = editor.update(&Message::CharacterInput('c'));
2267 editor.history.end_group();
2268
2269 let _ = editor.update(&Message::CharacterInput('d'));
2270 editor.history.end_group();
2271
2272 assert_eq!(editor.buffer.line(0), "abcd");
2273
2274 let _ = editor.update(&Message::Undo);
2276 assert_eq!(editor.buffer.line(0), "abc");
2277
2278 let _ = editor.update(&Message::Undo);
2279 assert_eq!(editor.buffer.line(0), "ab");
2280
2281 let _ = editor.update(&Message::Undo);
2282 assert_eq!(editor.buffer.line(0), "a");
2283
2284 let _ = editor.update(&Message::Redo);
2286 assert_eq!(editor.buffer.line(0), "ab");
2287
2288 let _ = editor.update(&Message::Redo);
2289 assert_eq!(editor.buffer.line(0), "abc");
2290
2291 let _ = editor.update(&Message::Redo);
2292 assert_eq!(editor.buffer.line(0), "abcd");
2293 }
2294
2295 #[test]
2296 fn test_delete_key_with_selection() {
2297 let mut editor = CodeEditor::new("hello world", "py");
2298 editor.cursors.primary_mut().anchor = Some((0, 0));
2299 editor.cursors.primary_mut().position = (0, 5);
2300 editor.cursors.primary_mut().position = (0, 5);
2301
2302 let _ = editor.update(&Message::Delete);
2303
2304 assert_eq!(editor.buffer.line(0), " world");
2305 assert_eq!(editor.cursors.primary_position(), (0, 0));
2306 assert!(editor.cursors.primary().anchor.is_none());
2307 assert!(!editor.cursors.primary().has_selection());
2308 }
2309
2310 #[test]
2311 fn test_delete_key_without_selection() {
2312 let mut editor = CodeEditor::new("hello", "py");
2313 editor.cursors.primary_mut().position = (0, 0);
2314
2315 let _ = editor.update(&Message::Delete);
2316
2317 assert_eq!(editor.buffer.line(0), "ello");
2319 assert_eq!(editor.cursors.primary_position(), (0, 0));
2320 }
2321
2322 #[test]
2323 fn test_backspace_with_selection() {
2324 let mut editor = CodeEditor::new("hello world", "py");
2325 editor.cursors.primary_mut().anchor = Some((0, 6));
2326 editor.cursors.primary_mut().position = (0, 11);
2327 editor.cursors.primary_mut().position = (0, 11);
2328
2329 let _ = editor.update(&Message::Backspace);
2330
2331 assert_eq!(editor.buffer.line(0), "hello ");
2332 assert_eq!(editor.cursors.primary_position(), (0, 6));
2333 assert!(editor.cursors.primary().anchor.is_none());
2334 assert!(!editor.cursors.primary().has_selection());
2335 }
2336
2337 #[test]
2338 fn test_backspace_without_selection() {
2339 let mut editor = CodeEditor::new("hello", "py");
2340 editor.cursors.primary_mut().position = (0, 5);
2341
2342 let _ = editor.update(&Message::Backspace);
2343
2344 assert_eq!(editor.buffer.line(0), "hell");
2346 assert_eq!(editor.cursors.primary_position(), (0, 4));
2347 }
2348
2349 #[test]
2350 fn test_delete_multiline_selection() {
2351 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2352 editor.cursors.primary_mut().anchor = Some((0, 2));
2353 editor.cursors.primary_mut().position = (2, 2);
2354 editor.cursors.primary_mut().position = (2, 2);
2355
2356 let _ = editor.update(&Message::Delete);
2357
2358 assert_eq!(editor.buffer.line(0), "line3");
2359 assert_eq!(editor.cursors.primary_position(), (0, 2));
2360 assert!(editor.cursors.primary().anchor.is_none());
2361 }
2362
2363 #[test]
2364 fn test_canvas_focus_gained() {
2365 let mut editor = CodeEditor::new("hello world", "py");
2366 assert!(!editor.has_canvas_focus);
2367 assert!(!editor.show_cursor);
2368
2369 let _ = editor.update(&Message::CanvasFocusGained);
2370
2371 assert!(editor.has_canvas_focus);
2372 assert!(editor.show_cursor);
2373 }
2374
2375 #[test]
2376 fn test_mouse_click_gains_focus() {
2377 let mut editor = CodeEditor::new("hello world", "py");
2378 editor.has_canvas_focus = false;
2379 editor.show_cursor = false;
2380
2381 let _ =
2382 editor.update(&Message::MouseClick(iced::Point::new(100.0, 10.0)));
2383
2384 assert!(editor.has_canvas_focus);
2385 assert!(editor.show_cursor);
2386 }
2387
2388 #[test]
2389 fn test_enter_no_indent() {
2390 let mut editor = CodeEditor::new("hello", "rs");
2391 editor.cursors.primary_mut().position = (0, 5);
2392 let _ = editor.update(&Message::Enter);
2393 assert_eq!(editor.buffer.line(0), "hello");
2394 assert_eq!(editor.buffer.line(1), "");
2395 assert_eq!(editor.cursors.primary_position(), (1, 0));
2396 }
2397
2398 #[test]
2399 fn test_enter_auto_indent_spaces() {
2400 let mut editor = CodeEditor::new(" hello", "rs");
2401 editor.cursors.primary_mut().position = (0, 9);
2402 let _ = editor.update(&Message::Enter);
2403 assert_eq!(editor.buffer.line(0), " hello");
2404 assert_eq!(editor.buffer.line(1), " ");
2405 assert_eq!(editor.cursors.primary_position(), (1, 4));
2406 }
2407
2408 #[test]
2409 fn test_enter_auto_indent_tab() {
2410 let mut editor = CodeEditor::new("\thello", "rs");
2411 editor.cursors.primary_mut().position = (0, 6);
2412 let _ = editor.update(&Message::Enter);
2413 assert_eq!(editor.buffer.line(0), "\thello");
2414 assert_eq!(editor.buffer.line(1), "\t");
2415 assert_eq!(editor.cursors.primary_position(), (1, 1));
2416 }
2417
2418 #[test]
2419 fn test_enter_auto_indent_undo() {
2420 let mut editor = CodeEditor::new(" hello", "rs");
2421 editor.cursors.primary_mut().position = (0, 9);
2422 let _ = editor.update(&Message::Enter);
2423 assert_eq!(editor.buffer.line_count(), 2);
2424
2425 let _ = editor.update(&Message::Undo);
2426 assert_eq!(editor.buffer.line_count(), 1);
2427 assert_eq!(editor.buffer.line(0), " hello");
2428 assert_eq!(editor.cursors.primary_position(), (0, 9));
2429 }
2430
2431 #[test]
2436 fn test_multi_cursor_char_input_different_lines() {
2437 let mut editor = CodeEditor::new("aaa\nbbb", "rs");
2438 editor.request_focus();
2439 editor.has_canvas_focus = true;
2440 editor.focus_locked = false;
2441 editor.cursors.primary_mut().position = (0, 1);
2443 editor.cursors.add_cursor((1, 1));
2444
2445 let _ = editor.update(&Message::CharacterInput('X'));
2446
2447 assert_eq!(editor.buffer.line(0), "aXaa");
2449 assert_eq!(editor.buffer.line(1), "bXbb");
2450 }
2451
2452 #[test]
2453 fn test_multi_cursor_char_input_same_line() {
2454 let mut editor = CodeEditor::new("abcd", "rs");
2455 editor.request_focus();
2456 editor.has_canvas_focus = true;
2457 editor.focus_locked = false;
2458 editor.cursors.primary_mut().position = (0, 1);
2460 editor.cursors.add_cursor((0, 3));
2461
2462 let _ = editor.update(&Message::CharacterInput('X'));
2463
2464 assert_eq!(editor.buffer.line(0), "aXbcXd");
2467 }
2468
2469 #[test]
2470 fn test_add_cursor_above() {
2471 let mut editor = CodeEditor::new("line0\nline1\nline2", "rs");
2472 editor.cursors.primary_mut().position = (1, 3);
2473
2474 let _ = editor.update(&Message::AddCursorAbove);
2475
2476 assert!(editor.cursors.is_multi());
2477 assert_eq!(editor.cursors.as_slice()[0].position, (0, 3));
2479 }
2480
2481 #[test]
2482 fn test_add_cursor_below() {
2483 let mut editor = CodeEditor::new("line0\nline1\nline2", "rs");
2484 editor.cursors.primary_mut().position = (1, 3);
2485
2486 let _ = editor.update(&Message::AddCursorBelow);
2487
2488 assert!(editor.cursors.is_multi());
2489 assert_eq!(
2491 editor
2492 .cursors
2493 .as_slice()
2494 .iter()
2495 .find(|c| c.position.0 == 2)
2496 .map(|c| c.position),
2497 Some((2, 3))
2498 );
2499 }
2500
2501 #[test]
2502 fn test_escape_collapses_multi_cursor() {
2503 let mut editor = CodeEditor::new("line0\nline1", "rs");
2504 editor.cursors.primary_mut().position = (0, 0);
2505 editor.cursors.add_cursor((1, 0));
2506 assert!(editor.cursors.is_multi());
2507
2508 let _ = editor.update(&Message::CloseSearch);
2509
2510 assert!(!editor.cursors.is_multi());
2511 }
2512
2513 #[test]
2514 fn test_select_next_occurrence_selects_word() {
2515 let mut editor = CodeEditor::new("foo bar foo", "rs");
2516 editor.cursors.primary_mut().position = (0, 1); let _ = editor.update(&Message::SelectNextOccurrence);
2519
2520 let range = editor.cursors.primary().selection_range();
2522 assert_eq!(range, Some(((0, 0), (0, 3))));
2523 }
2524
2525 #[test]
2526 fn test_select_next_occurrence_adds_cursor_for_second_occurrence() {
2527 let mut editor = CodeEditor::new("foo bar foo", "rs");
2528 editor.cursors.primary_mut().anchor = Some((0, 0));
2530 editor.cursors.primary_mut().position = (0, 3);
2531
2532 let _ = editor.update(&Message::SelectNextOccurrence);
2533
2534 assert_eq!(editor.cursors.len(), 2);
2536 }
2537
2538 #[test]
2539 fn test_multi_cursor_backspace() {
2540 let mut editor = CodeEditor::new("abc\ndef", "rs");
2541 editor.cursors.primary_mut().position = (0, 2);
2542 editor.cursors.add_cursor((1, 2));
2543
2544 let _ = editor.update(&Message::Backspace);
2545
2546 assert_eq!(editor.buffer.line(0), "ac");
2547 assert_eq!(editor.buffer.line(1), "df");
2548 }
2549}