1use iced::Task;
4use iced::widget::operation::{focus, select_all};
5
6use super::command::{
7 Command, CompositeCommand, DeleteCharCommand, DeleteForwardCommand,
8 DuplicateLinesCommand, InsertCharCommand, InsertNewlineCommand,
9 MoveLinesCommand, ReplaceTextCommand,
10};
11use super::{
12 ArrowDirection, CURSOR_BLINK_INTERVAL, CodeEditor, ImePreedit, IndentStyle,
13 Message, cursor_set,
14};
15
16#[derive(Clone, Copy)]
22enum EditType {
23 InsertChar,
25 DeleteCharBack,
27 DeleteCharForward,
29 InsertNewline { indent_len: usize },
31 MergePrev { prev_line_len: usize },
34 MergeNext { edit_line_len: usize },
37}
38
39fn adjust_pos(
41 pos: &mut (usize, usize),
42 edit_line: usize,
43 edit_col: usize,
44 kind: EditType,
45) {
46 match kind {
47 EditType::InsertChar => {
48 if pos.0 == edit_line && pos.1 >= edit_col {
49 pos.1 += 1;
50 }
51 }
52 EditType::DeleteCharBack => {
53 if edit_col > 0 && pos.0 == edit_line && pos.1 > edit_col - 1 {
54 pos.1 -= 1;
55 }
56 }
57 EditType::DeleteCharForward => {
58 if pos.0 == edit_line && pos.1 > edit_col {
59 pos.1 -= 1;
60 }
61 }
62 EditType::InsertNewline { indent_len } => {
63 if pos.0 > edit_line {
64 pos.0 += 1;
65 } else if pos.0 == edit_line && pos.1 >= edit_col {
66 pos.0 += 1;
67 pos.1 = pos.1 - edit_col + indent_len;
68 }
69 }
70 EditType::MergePrev { prev_line_len } => {
71 if pos.0 == edit_line {
72 pos.0 -= 1;
73 pos.1 += prev_line_len;
74 } else if pos.0 > edit_line {
75 pos.0 -= 1;
76 }
77 }
78 EditType::MergeNext { edit_line_len } => {
79 if pos.0 == edit_line + 1 {
80 pos.0 = edit_line;
81 pos.1 += edit_line_len;
82 } else if pos.0 > edit_line + 1 {
83 pos.0 -= 1;
84 }
85 }
86 }
87}
88
89fn adjust_other_cursors(
91 cursors: &mut [cursor_set::Cursor],
92 skip_idx: usize,
93 edit_line: usize,
94 edit_col: usize,
95 kind: EditType,
96) {
97 for (i, cursor) in cursors.iter_mut().enumerate() {
98 if i == skip_idx {
99 continue;
100 }
101 adjust_pos(&mut cursor.position, edit_line, edit_col, kind);
102 if let Some(ref mut anchor) = cursor.anchor {
103 adjust_pos(anchor, edit_line, edit_col, kind);
104 }
105 }
106}
107
108impl CodeEditor {
109 fn finish_edit_operation(&mut self) {
122 self.reset_cursor_blink();
123 self.refresh_search_matches_if_needed();
124 self.buffer_revision = self.buffer_revision.wrapping_add(1);
127 *self.visual_lines_cache.borrow_mut() = None;
128 self.invalidate_highlight_from(self.pre_edit_line.saturating_sub(1));
133 self.content_cache.clear();
134 self.overlay_cache.clear();
135 self.enqueue_lsp_change();
136 }
137
138 pub(crate) fn min_active_line(&self) -> usize {
144 self.cursors
145 .iter()
146 .map(|cursor| match cursor.anchor {
147 Some(anchor) => cursor.position.0.min(anchor.0),
148 None => cursor.position.0,
149 })
150 .min()
151 .unwrap_or(0)
152 }
153
154 pub(crate) fn invalidate_highlight_from(&self, line: usize) {
165 if let Some(cache) = self.highlight_cache.borrow_mut().as_mut() {
166 cache.truncate(line);
167 }
168 }
169
170 fn finish_navigation_operation(&mut self) {
178 self.reset_cursor_blink();
179 self.overlay_cache.clear();
180 }
181
182 fn ensure_grouping_started(&mut self, label: &str) {
191 if !self.is_grouping {
192 self.history.begin_group(label);
193 self.is_grouping = true;
194 }
195 }
196
197 fn end_grouping_if_active(&mut self) {
203 if self.is_grouping {
204 self.history.end_group();
205 self.is_grouping = false;
206 }
207 }
208
209 fn delete_selection_if_present(&mut self) -> bool {
215 if self.cursors.iter().any(|c| c.has_selection()) {
216 self.delete_selection();
217 self.finish_edit_operation();
218 true
219 } else {
220 false
221 }
222 }
223
224 fn handle_character_input_msg(&mut self, ch: char) -> Task<Message> {
243 if !self.has_focus() {
245 return Task::none();
246 }
247
248 self.ensure_grouping_started("Typing");
250
251 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
254 order.sort_by(|&a, &b| {
255 self.cursors.as_slice()[b]
256 .position
257 .cmp(&self.cursors.as_slice()[a].position)
258 });
259
260 for &idx in &order {
261 let cursor = &self.cursors.as_slice()[idx];
262 let pos = match cursor.anchor {
265 Some(anchor) if anchor < cursor.position => anchor,
266 _ => cursor.position,
267 };
268 let mut cmd = InsertCharCommand::new(pos.0, pos.1, ch, pos);
269 let mut cursor_pos = pos;
270 cmd.execute(&mut self.buffer, &mut cursor_pos);
271 self.cursors.as_mut_slice()[idx].position = cursor_pos;
272 adjust_other_cursors(
273 self.cursors.as_mut_slice(),
274 idx,
275 pos.0,
276 pos.1,
277 EditType::InsertChar,
278 );
279 self.history.push(Box::new(cmd));
280 }
281
282 self.finish_edit_operation();
283
284 if ch.is_alphanumeric() || ch == '_' || ch == '.' {
286 self.lsp_flush_pending_changes();
287 self.lsp_request_completion();
288 }
289
290 self.scroll_to_cursor()
291 }
292
293 fn handle_tab(&mut self) -> Task<Message> {
300 self.ensure_grouping_started("Tab");
301
302 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
304 order.sort_by(|&a, &b| {
305 self.cursors.as_slice()[b]
306 .position
307 .cmp(&self.cursors.as_slice()[a].position)
308 });
309
310 for &idx in &order {
311 let pos = self.cursors.as_slice()[idx].position;
312 match self.indent_style {
313 IndentStyle::Spaces(n) => {
314 let mut cursor_pos = pos;
315 for _i in 0..n as usize {
316 let current_col = cursor_pos.1;
317 let mut cmd = InsertCharCommand::new(
318 pos.0,
319 current_col,
320 ' ',
321 cursor_pos,
322 );
323 cmd.execute(&mut self.buffer, &mut cursor_pos);
324 adjust_other_cursors(
325 self.cursors.as_mut_slice(),
326 idx,
327 pos.0,
328 current_col,
329 EditType::InsertChar,
330 );
331 self.history.push(Box::new(cmd));
332 }
333 self.cursors.as_mut_slice()[idx].position = cursor_pos;
334 }
335 IndentStyle::Tab => {
336 let mut cmd =
337 InsertCharCommand::new(pos.0, pos.1, '\t', pos);
338 let mut cursor_pos = pos;
339 cmd.execute(&mut self.buffer, &mut cursor_pos);
340 adjust_other_cursors(
341 self.cursors.as_mut_slice(),
342 idx,
343 pos.0,
344 pos.1,
345 EditType::InsertChar,
346 );
347 self.cursors.as_mut_slice()[idx].position = cursor_pos;
348 self.history.push(Box::new(cmd));
349 }
350 }
351 }
352
353 self.finish_edit_operation();
354 self.scroll_to_cursor()
355 }
356
357 fn handle_focus_navigation_tab(&mut self) -> Task<Message> {
363 if !self.search_state.is_open {
365 self.has_canvas_focus = false;
367 self.show_cursor = false;
368
369 Task::none()
373 } else {
374 Task::none()
375 }
376 }
377
378 fn handle_focus_navigation_shift_tab(&mut self) -> Task<Message> {
384 if !self.search_state.is_open {
386 self.has_canvas_focus = false;
388 self.show_cursor = false;
389
390 Task::none()
394 } else {
395 Task::none()
396 }
397 }
398
399 fn handle_enter(&mut self) -> Task<Message> {
405 self.end_grouping_if_active();
407
408 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
410 order.sort_by(|&a, &b| {
411 self.cursors.as_slice()[b]
412 .position
413 .cmp(&self.cursors.as_slice()[a].position)
414 });
415
416 for &idx in &order {
417 let pos = self.cursors.as_slice()[idx].position;
418
419 let indent: String = if self.auto_indent_enabled {
421 self.buffer
422 .line(pos.0)
423 .chars()
424 .take_while(|c| c.is_whitespace())
425 .collect()
426 } else {
427 String::new()
428 };
429 let indent_len = indent.chars().count();
430
431 let mut cmd =
432 InsertNewlineCommand::with_indent(pos.0, pos.1, pos, indent);
433 let mut cursor_pos = pos;
434 cmd.execute(&mut self.buffer, &mut cursor_pos);
435 self.cursors.as_mut_slice()[idx].position = cursor_pos;
436 adjust_other_cursors(
437 self.cursors.as_mut_slice(),
438 idx,
439 pos.0,
440 pos.1,
441 EditType::InsertNewline { indent_len },
442 );
443 self.history.push(Box::new(cmd));
444 }
445
446 self.finish_edit_operation();
447 self.scroll_to_cursor()
448 }
449
450 fn primary_line_range(&self) -> (usize, usize) {
461 let primary = self.cursors.primary();
462 match primary.selection_range() {
463 Some((sel_start, sel_end)) => {
464 let end_line = if sel_end.1 == 0 && sel_end.0 > sel_start.0 {
465 sel_end.0 - 1
466 } else {
467 sel_end.0
468 };
469 (sel_start.0, end_line)
470 }
471 None => {
472 let line = primary.position.0;
473 (line, line)
474 }
475 }
476 }
477
478 fn shift_primary_cursor_lines(&mut self, delta: isize) {
481 let primary = self.cursors.primary_mut();
482 primary.position.0 = primary.position.0.saturating_add_signed(delta);
483 if let Some(anchor) = primary.anchor.as_mut() {
484 anchor.0 = anchor.0.saturating_add_signed(delta);
485 }
486 }
487
488 fn move_lines(&mut self, down: bool) -> Task<Message> {
503 self.end_grouping_if_active();
504 self.cursors.remove_all_but_primary();
505
506 let (start, end) = self.primary_line_range();
507
508 if down {
510 if end + 1 >= self.buffer.line_count() {
511 return Task::none();
512 }
513 } else if start == 0 {
514 return Task::none();
515 }
516
517 let pos = self.cursors.primary_position();
518 let mut cmd = MoveLinesCommand::new(start, end, down, pos);
519 let mut cursor_pos = pos;
520 cmd.execute(&mut self.buffer, &mut cursor_pos);
521 self.shift_primary_cursor_lines(if down { 1 } else { -1 });
522 self.history.push(Box::new(cmd));
523
524 self.finish_edit_operation();
525 self.scroll_to_cursor()
526 }
527
528 fn duplicate_lines(&mut self, down: bool) -> Task<Message> {
543 self.end_grouping_if_active();
544 self.cursors.remove_all_but_primary();
545
546 let (start, end) = self.primary_line_range();
547 let pos = self.cursors.primary_position();
548 let mut cmd = DuplicateLinesCommand::new(start, end, down, pos);
549 let mut cursor_pos = pos;
550 cmd.execute(&mut self.buffer, &mut cursor_pos);
551 if down {
552 let block_len = (end - start + 1) as isize;
553 self.shift_primary_cursor_lines(block_len);
554 }
555 self.history.push(Box::new(cmd));
556
557 self.finish_edit_operation();
558 self.scroll_to_cursor()
559 }
560
561 fn handle_backspace(&mut self) -> Task<Message> {
574 self.end_grouping_if_active();
576
577 if self.delete_selection_if_present() {
579 return self.scroll_to_cursor();
580 }
581
582 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
584 order.sort_by(|&a, &b| {
585 self.cursors.as_slice()[b]
586 .position
587 .cmp(&self.cursors.as_slice()[a].position)
588 });
589
590 for &idx in &order {
591 let pos = self.cursors.as_slice()[idx].position;
592 let edit_kind = if pos.1 > 0 {
594 EditType::DeleteCharBack
595 } else if pos.0 > 0 {
596 let prev_line_len = self.buffer.line_len(pos.0 - 1);
597 EditType::MergePrev { prev_line_len }
598 } else {
599 continue;
601 };
602 let mut cmd =
603 DeleteCharCommand::new(&self.buffer, pos.0, pos.1, pos);
604 let mut cursor_pos = pos;
605 cmd.execute(&mut self.buffer, &mut cursor_pos);
606 self.cursors.as_mut_slice()[idx].position = cursor_pos;
607 adjust_other_cursors(
608 self.cursors.as_mut_slice(),
609 idx,
610 pos.0,
611 pos.1,
612 edit_kind,
613 );
614 self.history.push(Box::new(cmd));
615 }
616
617 self.finish_edit_operation();
618 self.scroll_to_cursor()
619 }
620
621 fn handle_delete(&mut self) -> Task<Message> {
630 self.end_grouping_if_active();
632
633 if self.delete_selection_if_present() {
635 return self.scroll_to_cursor();
636 }
637
638 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
640 order.sort_by(|&a, &b| {
641 self.cursors.as_slice()[b]
642 .position
643 .cmp(&self.cursors.as_slice()[a].position)
644 });
645
646 for &idx in &order {
647 let pos = self.cursors.as_slice()[idx].position;
648 let line_len = self.buffer.line_len(pos.0);
649 let edit_kind = if pos.1 < line_len {
650 EditType::DeleteCharForward
651 } else if pos.0 + 1 < self.buffer.line_count() {
652 EditType::MergeNext { edit_line_len: line_len }
653 } else {
654 continue;
656 };
657 let mut cmd =
658 DeleteForwardCommand::new(&self.buffer, pos.0, pos.1, pos);
659 let mut cursor_pos = pos;
660 cmd.execute(&mut self.buffer, &mut cursor_pos);
661 self.cursors.as_mut_slice()[idx].position = cursor_pos;
662 adjust_other_cursors(
663 self.cursors.as_mut_slice(),
664 idx,
665 pos.0,
666 pos.1,
667 edit_kind,
668 );
669 self.history.push(Box::new(cmd));
670 }
671
672 self.finish_edit_operation();
673 Task::none()
674 }
675
676 fn handle_delete_selection(&mut self) -> Task<Message> {
684 self.end_grouping_if_active();
686
687 if self.cursors.iter().any(|c| c.has_selection()) {
688 self.delete_selection();
689 self.finish_edit_operation();
690 self.scroll_to_cursor()
691 } else {
692 Task::none()
693 }
694 }
695
696 fn handle_arrow_key(
711 &mut self,
712 direction: ArrowDirection,
713 shift_pressed: bool,
714 ) -> Task<Message> {
715 self.end_grouping_if_active();
717
718 if shift_pressed {
719 for cursor in self.cursors.as_mut_slice() {
721 if cursor.anchor.is_none() {
722 cursor.set_anchor();
723 }
724 }
725 self.move_cursor(direction);
726 } else {
727 self.clear_selection();
729 self.move_cursor(direction);
730 }
731 self.finish_navigation_operation();
732 self.scroll_to_cursor()
733 }
734
735 fn handle_home(&mut self, shift_pressed: bool) -> Task<Message> {
748 if shift_pressed {
749 for cursor in self.cursors.as_mut_slice() {
750 if cursor.anchor.is_none() {
751 cursor.set_anchor();
752 }
753 cursor.position.1 = 0;
754 }
755 } else {
756 self.clear_selection();
757 for cursor in self.cursors.as_mut_slice() {
758 cursor.position.1 = 0;
759 }
760 }
761 self.cursors.sort_and_merge();
762 self.finish_navigation_operation();
763 self.scroll_to_cursor()
764 }
765
766 fn handle_end(&mut self, shift_pressed: bool) -> Task<Message> {
779 if shift_pressed {
780 for cursor in self.cursors.as_mut_slice() {
781 if cursor.anchor.is_none() {
782 cursor.set_anchor();
783 }
784 cursor.position.1 = self.buffer.line_len(cursor.position.0);
785 }
786 } else {
787 self.clear_selection();
788 for cursor in self.cursors.as_mut_slice() {
789 cursor.position.1 = self.buffer.line_len(cursor.position.0);
790 }
791 }
792 self.cursors.sort_and_merge();
793 self.finish_navigation_operation();
794 self.scroll_to_cursor()
795 }
796
797 fn handle_ctrl_home(&mut self) -> Task<Message> {
805 self.clear_selection();
807 self.cursors.set_single((0, 0));
808 self.finish_navigation_operation();
809 self.scroll_to_cursor()
810 }
811
812 fn handle_ctrl_end(&mut self) -> Task<Message> {
820 self.clear_selection();
822 let last_line = self.buffer.line_count().saturating_sub(1);
823 let last_col = self.buffer.line_len(last_line);
824 self.cursors.set_single((last_line, last_col));
825 self.finish_navigation_operation();
826 self.scroll_to_cursor()
827 }
828
829 fn handle_page_up(&mut self) -> Task<Message> {
837 self.page_up();
838 self.finish_navigation_operation();
839 self.scroll_to_cursor()
840 }
841
842 fn handle_page_down(&mut self) -> Task<Message> {
850 self.page_down();
851 self.finish_navigation_operation();
852 self.scroll_to_cursor()
853 }
854
855 fn handle_goto_position(
866 &mut self,
867 line: usize,
868 col: usize,
869 ) -> Task<Message> {
870 self.end_grouping_if_active();
872 self.set_cursor(line, col)
873 }
874
875 fn handle_mouse_click_msg(&mut self, point: iced::Point) -> Task<Message> {
891 self.request_focus();
893
894 self.has_canvas_focus = true;
896
897 self.end_grouping_if_active();
899
900 self.cursors.remove_all_but_primary();
903
904 self.handle_mouse_click(point);
905 self.reset_cursor_blink();
906 self.clear_selection();
908 self.is_dragging = true;
909 self.cursors.primary_mut().set_anchor();
910
911 self.show_cursor = true;
913
914 Task::none()
915 }
916
917 fn handle_mouse_drag_msg(&mut self, point: iced::Point) -> Task<Message> {
927 if self.is_dragging {
928 let before_pos = self.cursors.primary_position();
929 self.handle_mouse_drag(point);
930 if self.cursors.primary_position() != before_pos {
931 self.overlay_cache.clear();
934 }
935 }
936 Task::none()
937 }
938
939 fn handle_mouse_release_msg(&mut self) -> Task<Message> {
945 self.is_dragging = false;
946 Task::none()
947 }
948
949 fn handle_paste_msg(&mut self, text: &str) -> Task<Message> {
966 self.end_grouping_if_active();
968
969 if text.is_empty() {
971 iced::clipboard::read().and_then(|clipboard_text| {
973 Task::done(Message::Paste(clipboard_text))
974 })
975 } else {
976 self.paste_text(text);
978 self.finish_edit_operation();
979 self.scroll_to_cursor()
980 }
981 }
982
983 fn handle_undo_msg(&mut self) -> Task<Message> {
993 self.end_grouping_if_active();
995
996 let mut cursor_pos = self.cursors.primary_position();
997 if self.history.undo(&mut self.buffer, &mut cursor_pos) {
998 self.cursors.primary_mut().position = cursor_pos;
999 self.clear_selection();
1000 self.pre_edit_line = 0;
1004 self.finish_edit_operation();
1005 self.scroll_to_cursor()
1006 } else {
1007 Task::none()
1008 }
1009 }
1010
1011 fn handle_redo_msg(&mut self) -> Task<Message> {
1017 let mut cursor_pos = self.cursors.primary_position();
1018 if self.history.redo(&mut self.buffer, &mut cursor_pos) {
1019 self.cursors.primary_mut().position = cursor_pos;
1020 self.clear_selection();
1021 self.pre_edit_line = 0;
1024 self.finish_edit_operation();
1025 self.scroll_to_cursor()
1026 } else {
1027 Task::none()
1028 }
1029 }
1030
1031 fn handle_open_search_msg(&mut self) -> Task<Message> {
1041 self.search_state.open_search();
1042 self.overlay_cache.clear();
1043
1044 Task::batch([
1046 focus(self.search_state.search_input_id.clone()),
1047 select_all(self.search_state.search_input_id.clone()),
1048 ])
1049 }
1050
1051 fn handle_open_search_replace_msg(&mut self) -> Task<Message> {
1057 self.search_state.open_replace();
1058 self.overlay_cache.clear();
1059
1060 Task::batch([
1062 focus(self.search_state.search_input_id.clone()),
1063 select_all(self.search_state.search_input_id.clone()),
1064 ])
1065 }
1066
1067 fn handle_close_search_msg(&mut self) -> Task<Message> {
1073 if self.cursors.is_multi() && !self.search_state.is_open {
1075 self.cursors.remove_all_but_primary();
1076 self.overlay_cache.clear();
1077 return Task::none();
1078 }
1079 self.search_state.close();
1080 self.overlay_cache.clear();
1081 Task::none()
1082 }
1083
1084 fn handle_search_query_changed_msg(
1094 &mut self,
1095 query: &str,
1096 ) -> Task<Message> {
1097 self.search_state.set_query(query.to_string(), &self.buffer);
1098 self.overlay_cache.clear();
1099
1100 if let Some(match_pos) = self.search_state.current_match() {
1102 self.cursors.primary_mut().position =
1103 (match_pos.line, match_pos.col);
1104 self.clear_selection();
1105 return self.scroll_to_cursor();
1106 }
1107 Task::none()
1108 }
1109
1110 fn handle_replace_query_changed_msg(
1120 &mut self,
1121 replace_text: &str,
1122 ) -> Task<Message> {
1123 self.search_state.set_replace_with(replace_text.to_string());
1124 Task::none()
1125 }
1126
1127 fn handle_toggle_case_sensitive_msg(&mut self) -> Task<Message> {
1133 self.search_state.toggle_case_sensitive(&self.buffer);
1134 self.overlay_cache.clear();
1135
1136 if let Some(match_pos) = self.search_state.current_match() {
1138 self.cursors.primary_mut().position =
1139 (match_pos.line, match_pos.col);
1140 self.clear_selection();
1141 return self.scroll_to_cursor();
1142 }
1143 Task::none()
1144 }
1145
1146 fn handle_find_next_msg(&mut self) -> Task<Message> {
1152 if !self.search_state.matches.is_empty() {
1153 self.search_state.next_match();
1154 if let Some(match_pos) = self.search_state.current_match() {
1155 self.cursors.primary_mut().position =
1156 (match_pos.line, match_pos.col);
1157 self.clear_selection();
1158 self.overlay_cache.clear();
1159 return self.scroll_to_cursor();
1160 }
1161 }
1162 Task::none()
1163 }
1164
1165 fn handle_find_previous_msg(&mut self) -> Task<Message> {
1171 if !self.search_state.matches.is_empty() {
1172 self.search_state.previous_match();
1173 if let Some(match_pos) = self.search_state.current_match() {
1174 self.cursors.primary_mut().position =
1175 (match_pos.line, match_pos.col);
1176 self.clear_selection();
1177 self.overlay_cache.clear();
1178 return self.scroll_to_cursor();
1179 }
1180 }
1181 Task::none()
1182 }
1183
1184 fn handle_replace_next_msg(&mut self) -> Task<Message> {
1190 if let Some(match_pos) = self.search_state.current_match() {
1192 let query_len = self.search_state.query.chars().count();
1193 let replace_text = self.search_state.replace_with.clone();
1194
1195 let pos = self.cursors.primary_position();
1197 let mut cmd = ReplaceTextCommand::new(
1198 &self.buffer,
1199 (match_pos.line, match_pos.col),
1200 query_len,
1201 replace_text,
1202 pos,
1203 );
1204 let mut cursor_pos = pos;
1205 cmd.execute(&mut self.buffer, &mut cursor_pos);
1206 self.cursors.primary_mut().position = cursor_pos;
1207 self.history.push(Box::new(cmd));
1208
1209 self.search_state.update_matches(&self.buffer);
1211
1212 self.pre_edit_line = self.pre_edit_line.min(match_pos.line);
1215
1216 if !self.search_state.matches.is_empty()
1218 && let Some(next_match) = self.search_state.current_match()
1219 {
1220 self.cursors.primary_mut().position =
1221 (next_match.line, next_match.col);
1222 }
1223
1224 self.clear_selection();
1225 self.finish_edit_operation();
1226 return self.scroll_to_cursor();
1227 }
1228 Task::none()
1229 }
1230
1231 fn handle_replace_all_msg(&mut self) -> Task<Message> {
1237 let all_matches = super::search::find_matches(
1239 &self.buffer,
1240 &self.search_state.query,
1241 self.search_state.case_sensitive,
1242 None, );
1244
1245 if !all_matches.is_empty() {
1246 let query_len = self.search_state.query.chars().count();
1247 let replace_text = self.search_state.replace_with.clone();
1248
1249 let mut composite =
1251 CompositeCommand::new("Replace All".to_string());
1252
1253 for match_pos in all_matches.iter().rev() {
1255 let pos = self.cursors.primary_position();
1256 let cmd = ReplaceTextCommand::new(
1257 &self.buffer,
1258 (match_pos.line, match_pos.col),
1259 query_len,
1260 replace_text.clone(),
1261 pos,
1262 );
1263 composite.add(Box::new(cmd));
1264 }
1265
1266 let mut cursor_pos = self.cursors.primary_position();
1268 composite.execute(&mut self.buffer, &mut cursor_pos);
1269 self.cursors.primary_mut().position = cursor_pos;
1270 self.history.push(Box::new(composite));
1271
1272 self.pre_edit_line = 0;
1275
1276 self.search_state.update_matches(&self.buffer);
1278 self.clear_selection();
1279 self.finish_edit_operation();
1280 self.scroll_to_cursor()
1281 } else {
1282 Task::none()
1283 }
1284 }
1285
1286 fn handle_search_dialog_tab_msg(&mut self) -> Task<Message> {
1292 self.search_state.focus_next_field();
1294
1295 match self.search_state.focused_field {
1297 crate::canvas_editor::search::SearchFocusedField::Search => {
1298 focus(self.search_state.search_input_id.clone())
1299 }
1300 crate::canvas_editor::search::SearchFocusedField::Replace => {
1301 focus(self.search_state.replace_input_id.clone())
1302 }
1303 }
1304 }
1305
1306 fn handle_search_dialog_shift_tab_msg(&mut self) -> Task<Message> {
1312 self.search_state.focus_previous_field();
1314
1315 match self.search_state.focused_field {
1317 crate::canvas_editor::search::SearchFocusedField::Search => {
1318 focus(self.search_state.search_input_id.clone())
1319 }
1320 crate::canvas_editor::search::SearchFocusedField::Replace => {
1321 focus(self.search_state.replace_input_id.clone())
1322 }
1323 }
1324 }
1325
1326 fn handle_canvas_focus_gained_msg(&mut self) -> Task<Message> {
1336 self.has_canvas_focus = true;
1337 self.focus_locked = false; self.show_cursor = true;
1339 self.reset_cursor_blink();
1340 self.overlay_cache.clear();
1341 Task::none()
1342 }
1343
1344 fn handle_canvas_focus_lost_msg(&mut self) -> Task<Message> {
1350 self.has_canvas_focus = false;
1351 self.focus_locked = true; self.show_cursor = false;
1353 self.ime_preedit = None;
1354 self.overlay_cache.clear();
1355 Task::none()
1356 }
1357
1358 fn handle_ime_opened_msg(&mut self) -> Task<Message> {
1366 self.ime_preedit = None;
1367 self.overlay_cache.clear();
1368 Task::none()
1369 }
1370
1371 fn handle_ime_preedit_msg(
1384 &mut self,
1385 content: &str,
1386 selection: &Option<std::ops::Range<usize>>,
1387 ) -> Task<Message> {
1388 if content.is_empty() {
1389 self.ime_preedit = None;
1390 } else {
1391 self.ime_preedit = Some(ImePreedit {
1392 content: content.to_string(),
1393 selection: selection.clone(),
1394 });
1395 }
1396
1397 self.overlay_cache.clear();
1398 Task::none()
1399 }
1400
1401 fn handle_ime_commit_msg(&mut self, text: &str) -> Task<Message> {
1413 self.ime_preedit = None;
1414
1415 if text.is_empty() {
1416 self.overlay_cache.clear();
1417 return Task::none();
1418 }
1419
1420 self.ensure_grouping_started("Typing");
1421
1422 self.paste_text(text);
1423 self.finish_edit_operation();
1424 self.scroll_to_cursor()
1425 }
1426
1427 fn handle_ime_closed_msg(&mut self) -> Task<Message> {
1435 self.ime_preedit = None;
1436 self.overlay_cache.clear();
1437 Task::none()
1438 }
1439
1440 fn handle_tick_msg(&mut self) -> Task<Message> {
1452 if self.has_focus()
1454 && self.last_blink.elapsed() >= CURSOR_BLINK_INTERVAL
1455 {
1456 self.cursor_visible = !self.cursor_visible;
1457 self.last_blink = super::Instant::now();
1458 self.overlay_cache.clear();
1459 }
1460
1461 if !self.has_focus() {
1463 self.show_cursor = false;
1464 }
1465
1466 Task::none()
1467 }
1468
1469 fn handle_scrolled_msg(
1483 &mut self,
1484 viewport: iced::widget::scrollable::Viewport,
1485 ) -> Task<Message> {
1486 let new_scroll = viewport.absolute_offset().y;
1495 let new_height = viewport.bounds().height;
1496 let new_width = viewport.bounds().width;
1497 let scroll_changed = (self.viewport_scroll - new_scroll).abs() > 0.1;
1498 let visible_lines_count =
1499 (new_height / self.line_height).ceil() as usize + 2;
1500 let first_visible_line =
1501 (new_scroll / self.line_height).floor() as usize;
1502 let last_visible_line = first_visible_line + visible_lines_count;
1503 let margin = visible_lines_count
1504 * crate::canvas_editor::CACHE_WINDOW_MARGIN_MULTIPLIER;
1505 let window_start = first_visible_line.saturating_sub(margin);
1506 let window_end = last_visible_line + margin;
1507 let need_rewindow =
1511 if self.cache_window_end_line > self.cache_window_start_line {
1512 let lower_boundary_trigger = self.cache_window_start_line > 0
1513 && first_visible_line
1514 < self
1515 .cache_window_start_line
1516 .saturating_add(visible_lines_count / 2);
1517 let upper_boundary_trigger = last_visible_line
1518 > self
1519 .cache_window_end_line
1520 .saturating_sub(visible_lines_count / 2);
1521 lower_boundary_trigger || upper_boundary_trigger
1522 } else {
1523 true
1524 };
1525 if (self.viewport_height - new_height).abs() > 1.0
1528 || (self.viewport_width - new_width).abs() > 1.0
1529 || (scroll_changed && need_rewindow)
1530 {
1531 self.cache_window_start_line = window_start;
1532 self.cache_window_end_line = window_end;
1533 self.last_first_visible_line = first_visible_line;
1534 self.content_cache.clear();
1535 self.overlay_cache.clear();
1536 }
1537 self.viewport_scroll = new_scroll;
1538 self.viewport_height = new_height;
1539 self.viewport_width = new_width;
1540 Task::none()
1541 }
1542
1543 fn handle_horizontal_scrolled_msg(
1556 &mut self,
1557 viewport: iced::widget::scrollable::Viewport,
1558 ) -> Task<Message> {
1559 let new_x = viewport.absolute_offset().x;
1560 if (self.horizontal_scroll_offset - new_x).abs() > 0.1 {
1561 self.horizontal_scroll_offset = new_x;
1562 self.content_cache.clear();
1563 self.overlay_cache.clear();
1564 }
1565 Task::none()
1566 }
1567
1568 fn handle_alt_click_msg(&mut self, point: iced::Point) -> Task<Message> {
1583 if let Some(pos) = self.calculate_cursor_from_point(point) {
1584 self.cursors.add_cursor(pos);
1585 self.overlay_cache.clear();
1586 self.reset_cursor_blink();
1587 }
1588 Task::none()
1589 }
1590
1591 fn handle_add_cursor_above_msg(&mut self) -> Task<Message> {
1598 let (line, col) = self.cursors.primary_position();
1599 if line == 0 {
1600 return Task::none();
1601 }
1602 let new_line = line - 1;
1603 let new_col = col.min(self.buffer.line_len(new_line));
1604 self.cursors.add_cursor((new_line, new_col));
1605 self.overlay_cache.clear();
1606 self.reset_cursor_blink();
1607 Task::none()
1608 }
1609
1610 fn handle_add_cursor_below_msg(&mut self) -> Task<Message> {
1617 let (line, col) = self.cursors.primary_position();
1618 let last_line = self.buffer.line_count().saturating_sub(1);
1619 if line >= last_line {
1620 return Task::none();
1621 }
1622 let new_line = line + 1;
1623 let new_col = col.min(self.buffer.line_len(new_line));
1624 self.cursors.add_cursor((new_line, new_col));
1625 self.overlay_cache.clear();
1626 self.reset_cursor_blink();
1627 Task::none()
1628 }
1629
1630 fn handle_select_next_occurrence_msg(&mut self) -> Task<Message> {
1638 let search_text = if let Some(text) = self.get_selected_text() {
1640 text
1641 } else {
1642 let (line, col) = self.cursors.primary_position();
1644 let line_str = self.buffer.line(line).to_string();
1645 let word_start = Self::word_start_in_line(&line_str, col);
1646 let word_end = Self::word_end_in_line(&line_str, col);
1647 if word_start == word_end {
1648 return Task::none();
1649 }
1650 self.cursors.primary_mut().anchor = Some((line, word_start));
1653 self.cursors.primary_mut().position = (line, word_end);
1654 self.overlay_cache.clear();
1655 return Task::none();
1656 };
1657
1658 if search_text.is_empty() {
1659 return Task::none();
1660 }
1661
1662 let search_start = self
1664 .cursors
1665 .as_slice()
1666 .last()
1667 .map(|last| {
1668 last.selection_range()
1669 .map(|(_, end)| end)
1670 .unwrap_or(last.position)
1671 })
1672 .unwrap_or((0, 0));
1673
1674 let (start_line, start_col) = search_start;
1676 let line_count = self.buffer.line_count();
1677
1678 for line_offset in 0..=line_count {
1679 let line_idx = (start_line + line_offset) % line_count;
1680 let line_str = self.buffer.line(line_idx).to_string();
1681 let chars: Vec<char> = line_str.chars().collect();
1682
1683 let search_col = if line_offset == 0 { start_col } else { 0 };
1685
1686 let prefix_bytes: usize =
1688 chars.iter().take(search_col).map(|c| c.len_utf8()).sum();
1689 let haystack = &line_str[prefix_bytes..];
1690
1691 if let Some(byte_offset) = haystack.find(search_text.as_str()) {
1693 let char_start =
1695 search_col + haystack[..byte_offset].chars().count();
1696 let char_end = char_start + search_text.chars().count();
1697
1698 let found_cursor = cursor_set::Cursor {
1700 position: (line_idx, char_end),
1701 anchor: Some((line_idx, char_start)),
1702 };
1703 self.cursors.add_cursor_with_selection(found_cursor);
1704 self.overlay_cache.clear();
1705 self.reset_cursor_blink();
1706 return self.scroll_to_cursor();
1707 }
1708 }
1709
1710 Task::none()
1711 }
1712
1713 pub fn update(&mut self, message: &Message) -> Task<Message> {
1726 self.pre_edit_line = self.min_active_line();
1729 match message {
1730 Message::CharacterInput(ch) => self.handle_character_input_msg(*ch),
1732 Message::Tab => self.handle_tab(),
1733 Message::Enter => self.handle_enter(),
1734
1735 Message::Backspace => self.handle_backspace(),
1737 Message::Delete => self.handle_delete(),
1738 Message::DeleteSelection => self.handle_delete_selection(),
1739
1740 Message::ArrowKey(direction, shift) => {
1742 self.handle_arrow_key(*direction, *shift)
1743 }
1744 Message::Home(shift) => self.handle_home(*shift),
1745 Message::End(shift) => self.handle_end(*shift),
1746 Message::CtrlHome => self.handle_ctrl_home(),
1747 Message::CtrlEnd => self.handle_ctrl_end(),
1748 Message::GotoPosition(line, col) => {
1749 self.handle_goto_position(*line, *col)
1750 }
1751 Message::PageUp => self.handle_page_up(),
1752 Message::PageDown => self.handle_page_down(),
1753
1754 Message::MouseClick(point) => self.handle_mouse_click_msg(*point),
1756 Message::MouseDrag(point) => self.handle_mouse_drag_msg(*point),
1757 Message::MouseHover(point) => self.handle_mouse_drag_msg(*point),
1758 Message::MouseRelease => self.handle_mouse_release_msg(),
1759
1760 Message::Copy => self.copy_selection(),
1762 Message::Paste(text) => self.handle_paste_msg(text),
1763
1764 Message::Undo => self.handle_undo_msg(),
1766 Message::Redo => self.handle_redo_msg(),
1767
1768 Message::OpenSearch => self.handle_open_search_msg(),
1770 Message::OpenSearchReplace => self.handle_open_search_replace_msg(),
1771 Message::CloseSearch => self.handle_close_search_msg(),
1772 Message::SearchQueryChanged(query) => {
1773 self.handle_search_query_changed_msg(query)
1774 }
1775 Message::ReplaceQueryChanged(text) => {
1776 self.handle_replace_query_changed_msg(text)
1777 }
1778 Message::ToggleCaseSensitive => {
1779 self.handle_toggle_case_sensitive_msg()
1780 }
1781 Message::FindNext => self.handle_find_next_msg(),
1782 Message::FindPrevious => self.handle_find_previous_msg(),
1783 Message::ReplaceNext => self.handle_replace_next_msg(),
1784 Message::ReplaceAll => self.handle_replace_all_msg(),
1785 Message::SearchDialogTab => self.handle_search_dialog_tab_msg(),
1786 Message::SearchDialogShiftTab => {
1787 self.handle_search_dialog_shift_tab_msg()
1788 }
1789 Message::FocusNavigationTab => self.handle_focus_navigation_tab(),
1790 Message::FocusNavigationShiftTab => {
1791 self.handle_focus_navigation_shift_tab()
1792 }
1793
1794 Message::CanvasFocusGained => self.handle_canvas_focus_gained_msg(),
1796 Message::CanvasFocusLost => self.handle_canvas_focus_lost_msg(),
1797 Message::ImeOpened => self.handle_ime_opened_msg(),
1798 Message::ImePreedit(content, selection) => {
1799 self.handle_ime_preedit_msg(content, selection)
1800 }
1801 Message::ImeCommit(text) => self.handle_ime_commit_msg(text),
1802 Message::ImeClosed => self.handle_ime_closed_msg(),
1803
1804 Message::Tick => self.handle_tick_msg(),
1806 Message::Scrolled(viewport) => self.handle_scrolled_msg(*viewport),
1807 Message::HorizontalScrolled(viewport) => {
1808 self.handle_horizontal_scrolled_msg(*viewport)
1809 }
1810
1811 Message::JumpClick(_point) => Task::none(),
1815
1816 Message::AltClick(point) => self.handle_alt_click_msg(*point),
1818 Message::AddCursorAbove => self.handle_add_cursor_above_msg(),
1819 Message::AddCursorBelow => self.handle_add_cursor_below_msg(),
1820 Message::SelectNextOccurrence => {
1821 self.handle_select_next_occurrence_msg()
1822 }
1823 Message::ToggleFold(header_line) => {
1824 self.toggle_fold(*header_line);
1825 Task::none()
1826 }
1827 Message::ToggleFoldAtCursor => {
1828 self.toggle_fold_at(self.cursors.primary_position().0);
1829 Task::none()
1830 }
1831 Message::FoldAll => {
1832 self.fold_all();
1833 Task::none()
1834 }
1835 Message::UnfoldAll => {
1836 self.unfold_all();
1837 Task::none()
1838 }
1839
1840 Message::MoveLineUp => self.move_lines(false),
1842 Message::MoveLineDown => self.move_lines(true),
1843 Message::DuplicateLineUp => self.duplicate_lines(false),
1844 Message::DuplicateLineDown => self.duplicate_lines(true),
1845 }
1846 }
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851 use super::*;
1852 use crate::canvas_editor::ArrowDirection;
1853
1854 #[test]
1855 fn test_horizontal_scroll_initial_state() {
1856 let editor = CodeEditor::new("short line", "rs");
1857 assert!(
1858 (editor.horizontal_scroll_offset - 0.0).abs() < f32::EPSILON,
1859 "Initial horizontal scroll offset should be 0"
1860 );
1861 }
1862
1863 #[test]
1864 fn test_set_wrap_enabled_resets_horizontal_offset() {
1865 let mut editor = CodeEditor::new("long line", "rs");
1866 editor.wrap_enabled = false;
1867 editor.horizontal_scroll_offset = 100.0;
1869
1870 editor.set_wrap_enabled(true);
1872 assert!(
1873 (editor.horizontal_scroll_offset - 0.0).abs() < f32::EPSILON,
1874 "Horizontal scroll offset should be reset when wrap is re-enabled"
1875 );
1876 }
1877
1878 #[test]
1879 fn test_canvas_focus_lost() {
1880 let mut editor = CodeEditor::new("test", "rs");
1881 editor.has_canvas_focus = true;
1882
1883 let _ = editor.update(&Message::CanvasFocusLost);
1884
1885 assert!(!editor.has_canvas_focus);
1886 assert!(!editor.show_cursor);
1887 assert!(editor.focus_locked, "Focus should be locked when lost");
1888 }
1889
1890 #[test]
1891 fn test_canvas_focus_gained_resets_lock() {
1892 let mut editor = CodeEditor::new("test", "rs");
1893 editor.has_canvas_focus = false;
1894 editor.focus_locked = true;
1895
1896 let _ = editor.update(&Message::CanvasFocusGained);
1897
1898 assert!(editor.has_canvas_focus);
1899 assert!(
1900 !editor.focus_locked,
1901 "Focus lock should be reset when focus is gained"
1902 );
1903 }
1904
1905 #[test]
1906 fn test_focus_lock_state() {
1907 let mut editor = CodeEditor::new("test", "rs");
1908
1909 assert!(!editor.focus_locked);
1911
1912 let _ = editor.update(&Message::CanvasFocusLost);
1914 assert!(editor.focus_locked, "Focus should be locked when lost");
1915
1916 editor.request_focus();
1918 let _ = editor.update(&Message::CanvasFocusGained);
1919 assert!(!editor.focus_locked, "Focus should be unlocked when regained");
1920
1921 editor.focus_locked = true;
1923 editor.reset_focus_lock();
1924 assert!(!editor.focus_locked, "Focus lock should be resetable");
1925 }
1926
1927 #[test]
1928 fn test_reset_focus_lock() {
1929 let mut editor = CodeEditor::new("test", "rs");
1930 editor.focus_locked = true;
1931
1932 editor.reset_focus_lock();
1933
1934 assert!(!editor.focus_locked);
1935 }
1936
1937 #[test]
1938 fn test_home_key() {
1939 let mut editor = CodeEditor::new("hello world", "py");
1940 editor.cursors.primary_mut().position = (0, 5); let _ = editor.update(&Message::Home(false));
1942 assert_eq!(editor.cursors.primary_position(), (0, 0));
1943 }
1944
1945 #[test]
1946 fn test_end_key() {
1947 let mut editor = CodeEditor::new("hello world", "py");
1948 editor.cursors.primary_mut().position = (0, 0);
1949 let _ = editor.update(&Message::End(false));
1950 assert_eq!(editor.cursors.primary_position(), (0, 11)); }
1952
1953 #[test]
1954 fn test_arrow_key_with_shift_creates_selection() {
1955 let mut editor = CodeEditor::new("hello world", "py");
1956 editor.cursors.primary_mut().position = (0, 0);
1957
1958 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, true));
1960 assert!(editor.cursors.primary().anchor.is_some());
1961 assert!(editor.cursors.primary().has_selection());
1962 }
1963
1964 #[test]
1965 fn test_arrow_key_without_shift_clears_selection() {
1966 let mut editor = CodeEditor::new("hello world", "py");
1967 editor.cursors.primary_mut().anchor = Some((0, 0));
1968 editor.cursors.primary_mut().position = (0, 5);
1969
1970 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, false));
1972 assert!(editor.cursors.primary().anchor.is_none());
1973 assert!(!editor.cursors.primary().has_selection());
1974 }
1975
1976 #[test]
1977 fn test_typing_with_selection() {
1978 let mut editor = CodeEditor::new("hello world", "py");
1979 editor.request_focus();
1981 editor.has_canvas_focus = true;
1982 editor.focus_locked = false;
1983
1984 editor.cursors.primary_mut().anchor = Some((0, 0));
1985 editor.cursors.primary_mut().position = (0, 5);
1986
1987 let _ = editor.update(&Message::CharacterInput('X'));
1988 assert_eq!(editor.buffer.line(0), "Xhello world");
1991 }
1992
1993 #[test]
1994 fn test_ctrl_home() {
1995 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
1996 editor.cursors.primary_mut().position = (2, 5); let _ = editor.update(&Message::CtrlHome);
1998 assert_eq!(editor.cursors.primary_position(), (0, 0)); }
2000
2001 #[test]
2002 fn test_ctrl_end() {
2003 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2004 editor.cursors.primary_mut().position = (0, 0); let _ = editor.update(&Message::CtrlEnd);
2006 assert_eq!(editor.cursors.primary_position(), (2, 5)); }
2008
2009 #[test]
2010 fn test_ctrl_home_clears_selection() {
2011 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2012 editor.cursors.primary_mut().position = (2, 5);
2013 editor.cursors.primary_mut().anchor = Some((0, 0));
2014 editor.cursors.primary_mut().position = (2, 5);
2015
2016 let _ = editor.update(&Message::CtrlHome);
2017 assert_eq!(editor.cursors.primary_position(), (0, 0));
2018 assert!(editor.cursors.primary().anchor.is_none());
2019 assert!(!editor.cursors.primary().has_selection());
2020 }
2021
2022 #[test]
2023 fn test_ctrl_end_clears_selection() {
2024 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2025 editor.cursors.primary_mut().position = (0, 0);
2026 editor.cursors.primary_mut().anchor = Some((0, 0));
2027 editor.cursors.primary_mut().position = (1, 3);
2028
2029 let _ = editor.update(&Message::CtrlEnd);
2030 assert_eq!(editor.cursors.primary_position(), (2, 5));
2031 assert!(editor.cursors.primary().anchor.is_none());
2032 assert!(!editor.cursors.primary().has_selection());
2033 }
2034
2035 #[test]
2036 fn test_goto_position_sets_cursor_and_clears_selection() {
2037 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2038 editor.cursors.primary_mut().anchor = Some((0, 0));
2039 editor.cursors.primary_mut().position = (1, 2);
2040
2041 let _ = editor.update(&Message::GotoPosition(1, 3));
2042
2043 assert_eq!(editor.cursors.primary_position(), (1, 3));
2044 assert!(editor.cursors.primary().anchor.is_none());
2045 assert!(!editor.cursors.primary().has_selection());
2046 }
2047
2048 #[test]
2049 fn test_goto_position_clamps_out_of_range() {
2050 let mut editor = CodeEditor::new("a\nbb", "py");
2051
2052 let _ = editor.update(&Message::GotoPosition(99, 99));
2053
2054 assert_eq!(editor.cursors.primary_position(), (1, 2));
2056 }
2057
2058 #[test]
2059 fn test_scroll_sets_initial_cache_window() {
2060 let content =
2061 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
2062 let mut editor = CodeEditor::new(&content, "py");
2063
2064 let height = 400.0;
2066 let width = 800.0;
2067 let scroll = 0.0;
2068
2069 let visible_lines_count =
2071 (height / editor.line_height).ceil() as usize + 2;
2072 let first_visible_line = (scroll / editor.line_height).floor() as usize;
2073 let last_visible_line = first_visible_line + visible_lines_count;
2074 let margin = visible_lines_count * 2;
2075 let window_start = first_visible_line.saturating_sub(margin);
2076 let window_end = last_visible_line + margin;
2077
2078 editor.viewport_height = height;
2080 editor.viewport_width = width;
2081 editor.viewport_scroll = -1.0;
2082 let scroll_changed = (editor.viewport_scroll - scroll).abs() > 0.1;
2083 let need_rewindow = true;
2084 if (editor.viewport_height - height).abs() > 1.0
2085 || (editor.viewport_width - width).abs() > 1.0
2086 || (scroll_changed && need_rewindow)
2087 {
2088 editor.cache_window_start_line = window_start;
2089 editor.cache_window_end_line = window_end;
2090 editor.last_first_visible_line = first_visible_line;
2091 }
2092 editor.viewport_scroll = scroll;
2093
2094 assert_eq!(editor.last_first_visible_line, first_visible_line);
2095 assert!(editor.cache_window_end_line > editor.cache_window_start_line);
2096 assert_eq!(editor.cache_window_start_line, window_start);
2097 assert_eq!(editor.cache_window_end_line, window_end);
2098 }
2099
2100 #[test]
2101 fn test_small_scroll_keeps_window() {
2102 let content =
2103 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
2104 let mut editor = CodeEditor::new(&content, "py");
2105 let height = 400.0;
2106 let width = 800.0;
2107 let initial_scroll = 0.0;
2108 let visible_lines_count =
2109 (height / editor.line_height).ceil() as usize + 2;
2110 let first_visible_line =
2111 (initial_scroll / editor.line_height).floor() as usize;
2112 let last_visible_line = first_visible_line + visible_lines_count;
2113 let margin = visible_lines_count * 2;
2114 let window_start = first_visible_line.saturating_sub(margin);
2115 let window_end = last_visible_line + margin;
2116 editor.cache_window_start_line = window_start;
2117 editor.cache_window_end_line = window_end;
2118 editor.viewport_height = height;
2119 editor.viewport_width = width;
2120 editor.viewport_scroll = initial_scroll;
2121
2122 let small_scroll =
2124 editor.line_height * (visible_lines_count as f32 / 4.0);
2125 let first_visible_line2 =
2126 (small_scroll / editor.line_height).floor() as usize;
2127 let last_visible_line2 = first_visible_line2 + visible_lines_count;
2128 let lower_boundary_trigger = editor.cache_window_start_line > 0
2129 && first_visible_line2
2130 < editor
2131 .cache_window_start_line
2132 .saturating_add(visible_lines_count / 2);
2133 let upper_boundary_trigger = last_visible_line2
2134 > editor
2135 .cache_window_end_line
2136 .saturating_sub(visible_lines_count / 2);
2137 let need_rewindow = lower_boundary_trigger || upper_boundary_trigger;
2138
2139 assert!(!need_rewindow, "Small scroll should be inside the window");
2140 assert_eq!(editor.cache_window_start_line, window_start);
2142 assert_eq!(editor.cache_window_end_line, window_end);
2143 }
2144
2145 #[test]
2146 fn test_large_scroll_rewindows() {
2147 let content =
2148 (0..1000).map(|i| format!("line{}\n", i)).collect::<String>();
2149 let mut editor = CodeEditor::new(&content, "py");
2150 let height = 400.0;
2151 let width = 800.0;
2152 let initial_scroll = 0.0;
2153 let visible_lines_count =
2154 (height / editor.line_height).ceil() as usize + 2;
2155 let first_visible_line =
2156 (initial_scroll / editor.line_height).floor() as usize;
2157 let last_visible_line = first_visible_line + visible_lines_count;
2158 let margin = visible_lines_count * 2;
2159 editor.cache_window_start_line =
2160 first_visible_line.saturating_sub(margin);
2161 editor.cache_window_end_line = last_visible_line + margin;
2162 editor.viewport_height = height;
2163 editor.viewport_width = width;
2164 editor.viewport_scroll = initial_scroll;
2165
2166 let large_scroll =
2168 editor.line_height * ((visible_lines_count * 4) as f32);
2169 let first_visible_line2 =
2170 (large_scroll / editor.line_height).floor() as usize;
2171 let last_visible_line2 = first_visible_line2 + visible_lines_count;
2172 let window_start2 = first_visible_line2.saturating_sub(margin);
2173 let window_end2 = last_visible_line2 + margin;
2174 let need_rewindow = first_visible_line2
2175 < editor
2176 .cache_window_start_line
2177 .saturating_add(visible_lines_count / 2)
2178 || last_visible_line2
2179 > editor
2180 .cache_window_end_line
2181 .saturating_sub(visible_lines_count / 2);
2182 assert!(need_rewindow, "Large scroll should trigger window update");
2183
2184 editor.cache_window_start_line = window_start2;
2186 editor.cache_window_end_line = window_end2;
2187 editor.last_first_visible_line = first_visible_line2;
2188
2189 assert_eq!(editor.cache_window_start_line, window_start2);
2190 assert_eq!(editor.cache_window_end_line, window_end2);
2191 assert_eq!(editor.last_first_visible_line, first_visible_line2);
2192 }
2193
2194 #[test]
2195 fn test_delete_selection_message() {
2196 let mut editor = CodeEditor::new("hello world", "py");
2197 editor.cursors.primary_mut().position = (0, 0);
2198 editor.cursors.primary_mut().anchor = Some((0, 0));
2199 editor.cursors.primary_mut().position = (0, 5);
2200
2201 let _ = editor.update(&Message::DeleteSelection);
2202 assert_eq!(editor.buffer.line(0), " world");
2203 assert_eq!(editor.cursors.primary_position(), (0, 0));
2204 assert!(editor.cursors.primary().anchor.is_none());
2205 assert!(!editor.cursors.primary().has_selection());
2206 }
2207
2208 #[test]
2209 fn test_delete_selection_multiline() {
2210 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2211 editor.cursors.primary_mut().position = (0, 2);
2212 editor.cursors.primary_mut().anchor = Some((0, 2));
2213 editor.cursors.primary_mut().position = (2, 2);
2214
2215 let _ = editor.update(&Message::DeleteSelection);
2216 assert_eq!(editor.buffer.line(0), "line3");
2217 assert_eq!(editor.cursors.primary_position(), (0, 2));
2218 assert!(editor.cursors.primary().anchor.is_none());
2219 }
2220
2221 #[test]
2222 fn test_delete_selection_no_selection() {
2223 let mut editor = CodeEditor::new("hello world", "py");
2224 editor.cursors.primary_mut().position = (0, 5);
2225
2226 let _ = editor.update(&Message::DeleteSelection);
2227 assert_eq!(editor.buffer.line(0), "hello world");
2229 assert_eq!(editor.cursors.primary_position(), (0, 5));
2230 }
2231
2232 #[test]
2233 #[allow(clippy::unwrap_used)]
2234 fn test_ime_preedit_and_commit_chinese() {
2235 let mut editor = CodeEditor::new("", "py");
2236 let _ = editor.update(&Message::ImeOpened);
2238 assert!(editor.ime_preedit.is_none());
2239
2240 let content = "安全与合规".to_string();
2242 let selection = Some(0..3); let _ = editor
2244 .update(&Message::ImePreedit(content.clone(), selection.clone()));
2245
2246 assert!(editor.ime_preedit.is_some());
2247 assert_eq!(
2248 editor.ime_preedit.as_ref().unwrap().content.clone(),
2249 content
2250 );
2251 assert_eq!(
2252 editor.ime_preedit.as_ref().unwrap().selection.clone(),
2253 selection
2254 );
2255
2256 let _ = editor.update(&Message::ImeCommit("安全与合规".to_string()));
2258 assert!(editor.ime_preedit.is_none());
2259 assert_eq!(editor.buffer.line(0), "安全与合规");
2260 assert_eq!(
2261 editor.cursors.primary_position(),
2262 (0, "安全与合规".chars().count())
2263 );
2264 }
2265
2266 #[test]
2267 fn test_undo_char_insert() {
2268 let mut editor = CodeEditor::new("hello", "py");
2269 editor.request_focus();
2271 editor.has_canvas_focus = true;
2272 editor.focus_locked = false;
2273
2274 editor.cursors.primary_mut().position = (0, 5);
2275
2276 let _ = editor.update(&Message::CharacterInput('!'));
2278 assert_eq!(editor.buffer.line(0), "hello!");
2279 assert_eq!(editor.cursors.primary_position(), (0, 6));
2280
2281 editor.history.end_group();
2283 let _ = editor.update(&Message::Undo);
2284 assert_eq!(editor.buffer.line(0), "hello");
2285 assert_eq!(editor.cursors.primary_position(), (0, 5));
2286 }
2287
2288 #[test]
2289 fn test_undo_redo_char_insert() {
2290 let mut editor = CodeEditor::new("hello", "py");
2291 editor.request_focus();
2293 editor.has_canvas_focus = true;
2294 editor.focus_locked = false;
2295
2296 editor.cursors.primary_mut().position = (0, 5);
2297
2298 let _ = editor.update(&Message::CharacterInput('!'));
2300 editor.history.end_group();
2301
2302 let _ = editor.update(&Message::Undo);
2304 assert_eq!(editor.buffer.line(0), "hello");
2305
2306 let _ = editor.update(&Message::Redo);
2308 assert_eq!(editor.buffer.line(0), "hello!");
2309 assert_eq!(editor.cursors.primary_position(), (0, 6));
2310 }
2311
2312 #[test]
2313 fn test_undo_backspace() {
2314 let mut editor = CodeEditor::new("hello", "py");
2315 editor.cursors.primary_mut().position = (0, 5);
2316
2317 let _ = editor.update(&Message::Backspace);
2319 assert_eq!(editor.buffer.line(0), "hell");
2320 assert_eq!(editor.cursors.primary_position(), (0, 4));
2321
2322 let _ = editor.update(&Message::Undo);
2324 assert_eq!(editor.buffer.line(0), "hello");
2325 assert_eq!(editor.cursors.primary_position(), (0, 5));
2326 }
2327
2328 #[test]
2329 fn test_undo_newline() {
2330 let mut editor = CodeEditor::new("hello world", "py");
2331 editor.cursors.primary_mut().position = (0, 5);
2332
2333 let _ = editor.update(&Message::Enter);
2335 assert_eq!(editor.buffer.line(0), "hello");
2336 assert_eq!(editor.buffer.line(1), " world");
2337 assert_eq!(editor.cursors.primary_position(), (1, 0));
2338
2339 let _ = editor.update(&Message::Undo);
2341 assert_eq!(editor.buffer.line(0), "hello world");
2342 assert_eq!(editor.cursors.primary_position(), (0, 5));
2343 }
2344
2345 #[test]
2346 fn test_undo_grouped_typing() {
2347 let mut editor = CodeEditor::new("hello", "py");
2348 editor.request_focus();
2350 editor.has_canvas_focus = true;
2351 editor.focus_locked = false;
2352
2353 editor.cursors.primary_mut().position = (0, 5);
2354
2355 let _ = editor.update(&Message::CharacterInput(' '));
2357 let _ = editor.update(&Message::CharacterInput('w'));
2358 let _ = editor.update(&Message::CharacterInput('o'));
2359 let _ = editor.update(&Message::CharacterInput('r'));
2360 let _ = editor.update(&Message::CharacterInput('l'));
2361 let _ = editor.update(&Message::CharacterInput('d'));
2362
2363 assert_eq!(editor.buffer.line(0), "hello world");
2364
2365 editor.history.end_group();
2367
2368 let _ = editor.update(&Message::Undo);
2370 assert_eq!(editor.buffer.line(0), "hello");
2371 assert_eq!(editor.cursors.primary_position(), (0, 5));
2372 }
2373
2374 #[test]
2375 fn test_navigation_ends_grouping() {
2376 let mut editor = CodeEditor::new("hello", "py");
2377 editor.request_focus();
2379 editor.has_canvas_focus = true;
2380 editor.focus_locked = false;
2381
2382 editor.cursors.primary_mut().position = (0, 5);
2383
2384 let _ = editor.update(&Message::CharacterInput('!'));
2386 assert!(editor.is_grouping);
2387
2388 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Left, false));
2390 assert!(!editor.is_grouping);
2391
2392 let _ = editor.update(&Message::CharacterInput('?'));
2394 assert!(editor.is_grouping);
2395
2396 editor.history.end_group();
2397
2398 let _ = editor.update(&Message::Undo);
2400 assert_eq!(editor.buffer.line(0), "hello!");
2401
2402 let _ = editor.update(&Message::Undo);
2403 assert_eq!(editor.buffer.line(0), "hello");
2404 }
2405
2406 #[test]
2407 fn test_edit_increments_revision_and_clears_visual_lines_cache() {
2408 let mut editor = CodeEditor::new("hello", "rs");
2409 editor.request_focus();
2410 editor.has_canvas_focus = true;
2411 editor.focus_locked = false;
2412 editor.cursors.primary_mut().position = (0, 5);
2413
2414 let _ = editor.visual_lines_cached(800.0);
2415 assert!(
2416 editor.visual_lines_cache.borrow().is_some(),
2417 "visual_lines_cached should populate the cache"
2418 );
2419
2420 let previous_revision = editor.buffer_revision;
2421
2422 let _ = editor.update(&Message::CharacterInput('!'));
2423 assert_eq!(
2424 editor.buffer_revision,
2425 previous_revision.wrapping_add(1),
2426 "buffer_revision should change on buffer edits"
2427 );
2428 assert!(
2432 editor
2433 .visual_lines_cache
2434 .borrow()
2435 .as_ref()
2436 .is_none_or(|c| c.key.buffer_revision == editor.buffer_revision),
2437 "buffer edits should not leave stale data in the visual lines cache"
2438 );
2439 }
2440
2441 #[test]
2442 fn test_multiple_undo_redo() {
2443 let mut editor = CodeEditor::new("a", "py");
2444 editor.request_focus();
2446 editor.has_canvas_focus = true;
2447 editor.focus_locked = false;
2448
2449 editor.cursors.primary_mut().position = (0, 1);
2450
2451 let _ = editor.update(&Message::CharacterInput('b'));
2453 editor.history.end_group();
2454
2455 let _ = editor.update(&Message::CharacterInput('c'));
2456 editor.history.end_group();
2457
2458 let _ = editor.update(&Message::CharacterInput('d'));
2459 editor.history.end_group();
2460
2461 assert_eq!(editor.buffer.line(0), "abcd");
2462
2463 let _ = editor.update(&Message::Undo);
2465 assert_eq!(editor.buffer.line(0), "abc");
2466
2467 let _ = editor.update(&Message::Undo);
2468 assert_eq!(editor.buffer.line(0), "ab");
2469
2470 let _ = editor.update(&Message::Undo);
2471 assert_eq!(editor.buffer.line(0), "a");
2472
2473 let _ = editor.update(&Message::Redo);
2475 assert_eq!(editor.buffer.line(0), "ab");
2476
2477 let _ = editor.update(&Message::Redo);
2478 assert_eq!(editor.buffer.line(0), "abc");
2479
2480 let _ = editor.update(&Message::Redo);
2481 assert_eq!(editor.buffer.line(0), "abcd");
2482 }
2483
2484 #[test]
2485 fn test_delete_key_with_selection() {
2486 let mut editor = CodeEditor::new("hello world", "py");
2487 editor.cursors.primary_mut().anchor = Some((0, 0));
2488 editor.cursors.primary_mut().position = (0, 5);
2489 editor.cursors.primary_mut().position = (0, 5);
2490
2491 let _ = editor.update(&Message::Delete);
2492
2493 assert_eq!(editor.buffer.line(0), " world");
2494 assert_eq!(editor.cursors.primary_position(), (0, 0));
2495 assert!(editor.cursors.primary().anchor.is_none());
2496 assert!(!editor.cursors.primary().has_selection());
2497 }
2498
2499 #[test]
2500 fn test_delete_key_without_selection() {
2501 let mut editor = CodeEditor::new("hello", "py");
2502 editor.cursors.primary_mut().position = (0, 0);
2503
2504 let _ = editor.update(&Message::Delete);
2505
2506 assert_eq!(editor.buffer.line(0), "ello");
2508 assert_eq!(editor.cursors.primary_position(), (0, 0));
2509 }
2510
2511 #[test]
2512 fn test_backspace_with_selection() {
2513 let mut editor = CodeEditor::new("hello world", "py");
2514 editor.cursors.primary_mut().anchor = Some((0, 6));
2515 editor.cursors.primary_mut().position = (0, 11);
2516 editor.cursors.primary_mut().position = (0, 11);
2517
2518 let _ = editor.update(&Message::Backspace);
2519
2520 assert_eq!(editor.buffer.line(0), "hello ");
2521 assert_eq!(editor.cursors.primary_position(), (0, 6));
2522 assert!(editor.cursors.primary().anchor.is_none());
2523 assert!(!editor.cursors.primary().has_selection());
2524 }
2525
2526 #[test]
2527 fn test_backspace_without_selection() {
2528 let mut editor = CodeEditor::new("hello", "py");
2529 editor.cursors.primary_mut().position = (0, 5);
2530
2531 let _ = editor.update(&Message::Backspace);
2532
2533 assert_eq!(editor.buffer.line(0), "hell");
2535 assert_eq!(editor.cursors.primary_position(), (0, 4));
2536 }
2537
2538 #[test]
2539 fn test_delete_multiline_selection() {
2540 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
2541 editor.cursors.primary_mut().anchor = Some((0, 2));
2542 editor.cursors.primary_mut().position = (2, 2);
2543 editor.cursors.primary_mut().position = (2, 2);
2544
2545 let _ = editor.update(&Message::Delete);
2546
2547 assert_eq!(editor.buffer.line(0), "line3");
2548 assert_eq!(editor.cursors.primary_position(), (0, 2));
2549 assert!(editor.cursors.primary().anchor.is_none());
2550 }
2551
2552 #[test]
2553 fn test_canvas_focus_gained() {
2554 let mut editor = CodeEditor::new("hello world", "py");
2555 assert!(!editor.has_canvas_focus);
2556 assert!(!editor.show_cursor);
2557
2558 let _ = editor.update(&Message::CanvasFocusGained);
2559
2560 assert!(editor.has_canvas_focus);
2561 assert!(editor.show_cursor);
2562 }
2563
2564 #[test]
2565 fn test_mouse_click_gains_focus() {
2566 let mut editor = CodeEditor::new("hello world", "py");
2567 editor.has_canvas_focus = false;
2568 editor.show_cursor = false;
2569
2570 let _ =
2571 editor.update(&Message::MouseClick(iced::Point::new(100.0, 10.0)));
2572
2573 assert!(editor.has_canvas_focus);
2574 assert!(editor.show_cursor);
2575 }
2576
2577 #[test]
2578 fn test_enter_no_indent() {
2579 let mut editor = CodeEditor::new("hello", "rs");
2580 editor.cursors.primary_mut().position = (0, 5);
2581 let _ = editor.update(&Message::Enter);
2582 assert_eq!(editor.buffer.line(0), "hello");
2583 assert_eq!(editor.buffer.line(1), "");
2584 assert_eq!(editor.cursors.primary_position(), (1, 0));
2585 }
2586
2587 #[test]
2588 fn test_enter_auto_indent_spaces() {
2589 let mut editor = CodeEditor::new(" hello", "rs");
2590 editor.cursors.primary_mut().position = (0, 9);
2591 let _ = editor.update(&Message::Enter);
2592 assert_eq!(editor.buffer.line(0), " hello");
2593 assert_eq!(editor.buffer.line(1), " ");
2594 assert_eq!(editor.cursors.primary_position(), (1, 4));
2595 }
2596
2597 #[test]
2598 fn test_enter_auto_indent_tab() {
2599 let mut editor = CodeEditor::new("\thello", "rs");
2600 editor.cursors.primary_mut().position = (0, 6);
2601 let _ = editor.update(&Message::Enter);
2602 assert_eq!(editor.buffer.line(0), "\thello");
2603 assert_eq!(editor.buffer.line(1), "\t");
2604 assert_eq!(editor.cursors.primary_position(), (1, 1));
2605 }
2606
2607 #[test]
2608 fn test_enter_auto_indent_undo() {
2609 let mut editor = CodeEditor::new(" hello", "rs");
2610 editor.cursors.primary_mut().position = (0, 9);
2611 let _ = editor.update(&Message::Enter);
2612 assert_eq!(editor.buffer.line_count(), 2);
2613
2614 let _ = editor.update(&Message::Undo);
2615 assert_eq!(editor.buffer.line_count(), 1);
2616 assert_eq!(editor.buffer.line(0), " hello");
2617 assert_eq!(editor.cursors.primary_position(), (0, 9));
2618 }
2619
2620 #[test]
2625 fn test_multi_cursor_char_input_different_lines() {
2626 let mut editor = CodeEditor::new("aaa\nbbb", "rs");
2627 editor.request_focus();
2628 editor.has_canvas_focus = true;
2629 editor.focus_locked = false;
2630 editor.cursors.primary_mut().position = (0, 1);
2632 editor.cursors.add_cursor((1, 1));
2633
2634 let _ = editor.update(&Message::CharacterInput('X'));
2635
2636 assert_eq!(editor.buffer.line(0), "aXaa");
2638 assert_eq!(editor.buffer.line(1), "bXbb");
2639 }
2640
2641 #[test]
2642 fn test_multi_cursor_char_input_same_line() {
2643 let mut editor = CodeEditor::new("abcd", "rs");
2644 editor.request_focus();
2645 editor.has_canvas_focus = true;
2646 editor.focus_locked = false;
2647 editor.cursors.primary_mut().position = (0, 1);
2649 editor.cursors.add_cursor((0, 3));
2650
2651 let _ = editor.update(&Message::CharacterInput('X'));
2652
2653 assert_eq!(editor.buffer.line(0), "aXbcXd");
2656 }
2657
2658 #[test]
2659 fn test_add_cursor_above() {
2660 let mut editor = CodeEditor::new("line0\nline1\nline2", "rs");
2661 editor.cursors.primary_mut().position = (1, 3);
2662
2663 let _ = editor.update(&Message::AddCursorAbove);
2664
2665 assert!(editor.cursors.is_multi());
2666 assert_eq!(editor.cursors.as_slice()[0].position, (0, 3));
2668 }
2669
2670 #[test]
2671 fn test_add_cursor_below() {
2672 let mut editor = CodeEditor::new("line0\nline1\nline2", "rs");
2673 editor.cursors.primary_mut().position = (1, 3);
2674
2675 let _ = editor.update(&Message::AddCursorBelow);
2676
2677 assert!(editor.cursors.is_multi());
2678 assert_eq!(
2680 editor
2681 .cursors
2682 .as_slice()
2683 .iter()
2684 .find(|c| c.position.0 == 2)
2685 .map(|c| c.position),
2686 Some((2, 3))
2687 );
2688 }
2689
2690 #[test]
2691 fn test_escape_collapses_multi_cursor() {
2692 let mut editor = CodeEditor::new("line0\nline1", "rs");
2693 editor.cursors.primary_mut().position = (0, 0);
2694 editor.cursors.add_cursor((1, 0));
2695 assert!(editor.cursors.is_multi());
2696
2697 let _ = editor.update(&Message::CloseSearch);
2698
2699 assert!(!editor.cursors.is_multi());
2700 }
2701
2702 #[test]
2703 fn test_select_next_occurrence_selects_word() {
2704 let mut editor = CodeEditor::new("foo bar foo", "rs");
2705 editor.cursors.primary_mut().position = (0, 1); let _ = editor.update(&Message::SelectNextOccurrence);
2708
2709 let range = editor.cursors.primary().selection_range();
2711 assert_eq!(range, Some(((0, 0), (0, 3))));
2712 }
2713
2714 #[test]
2715 fn test_select_next_occurrence_adds_cursor_for_second_occurrence() {
2716 let mut editor = CodeEditor::new("foo bar foo", "rs");
2717 editor.cursors.primary_mut().anchor = Some((0, 0));
2719 editor.cursors.primary_mut().position = (0, 3);
2720
2721 let _ = editor.update(&Message::SelectNextOccurrence);
2722
2723 assert_eq!(editor.cursors.len(), 2);
2725 }
2726
2727 #[test]
2728 fn test_multi_cursor_backspace() {
2729 let mut editor = CodeEditor::new("abc\ndef", "rs");
2730 editor.cursors.primary_mut().position = (0, 2);
2731 editor.cursors.add_cursor((1, 2));
2732
2733 let _ = editor.update(&Message::Backspace);
2734
2735 assert_eq!(editor.buffer.line(0), "ac");
2736 assert_eq!(editor.buffer.line(1), "df");
2737 }
2738}