1use iced::Task;
4use iced::widget::operation::{focus, select_all};
5
6use crate::text_utils::char_to_byte_index;
7
8use super::command::{
9 Command, CompositeCommand, DeleteCharCommand, DeleteForwardCommand,
10 DeleteRangeCommand, DuplicateLinesCommand, InsertCharCommand,
11 InsertNewlineCommand, InsertTextCommand, MoveLinesCommand,
12 ReplaceTextCommand, ToggleCommentCommand, line_comment_token,
13};
14use super::vim::{
15 VimAction, VimInsertPosition, VimMotion, VimOperator, VimPastePosition,
16 VimRegister, VimRegisterKind,
17};
18use super::{
19 ArrowDirection, CURSOR_BLINK_INTERVAL, CodeEditor, ImePreedit, IndentStyle,
20 LspEditSnapshot, Message, VimMode, cursor_set, lsp,
21};
22
23#[derive(Clone, Copy)]
29enum EditType {
30 InsertChar,
32 DeleteCharBack,
34 DeleteCharForward,
36 InsertNewline { indent_len: usize },
38 MergePrev { prev_line_len: usize },
41 MergeNext { edit_line_len: usize },
44}
45
46fn adjust_pos(
48 pos: &mut (usize, usize),
49 edit_line: usize,
50 edit_col: usize,
51 kind: EditType,
52) {
53 match kind {
54 EditType::InsertChar => {
55 if pos.0 == edit_line && pos.1 >= edit_col {
56 pos.1 += 1;
57 }
58 }
59 EditType::DeleteCharBack => {
60 if edit_col > 0 && pos.0 == edit_line && pos.1 > edit_col - 1 {
61 pos.1 -= 1;
62 }
63 }
64 EditType::DeleteCharForward => {
65 if pos.0 == edit_line && pos.1 > edit_col {
66 pos.1 -= 1;
67 }
68 }
69 EditType::InsertNewline { indent_len } => {
70 if pos.0 > edit_line {
71 pos.0 += 1;
72 } else if pos.0 == edit_line && pos.1 >= edit_col {
73 pos.0 += 1;
74 pos.1 = pos.1 - edit_col + indent_len;
75 }
76 }
77 EditType::MergePrev { prev_line_len } => {
78 if pos.0 == edit_line {
79 pos.0 -= 1;
80 pos.1 += prev_line_len;
81 } else if pos.0 > edit_line {
82 pos.0 -= 1;
83 }
84 }
85 EditType::MergeNext { edit_line_len } => {
86 if pos.0 == edit_line + 1 {
87 pos.0 = edit_line;
88 pos.1 += edit_line_len;
89 } else if pos.0 > edit_line + 1 {
90 pos.0 -= 1;
91 }
92 }
93 }
94}
95
96fn adjust_other_cursors(
98 cursors: &mut [cursor_set::Cursor],
99 skip_idx: usize,
100 edit_line: usize,
101 edit_col: usize,
102 kind: EditType,
103) {
104 for (i, cursor) in cursors.iter_mut().enumerate() {
105 if i == skip_idx {
106 continue;
107 }
108 adjust_pos(&mut cursor.position, edit_line, edit_col, kind);
109 if let Some(ref mut anchor) = cursor.anchor {
110 adjust_pos(anchor, edit_line, edit_col, kind);
111 }
112 }
113}
114
115impl CodeEditor {
116 fn finish_edit_operation(&mut self) {
129 self.reset_cursor_blink();
130 self.refresh_search_matches_if_needed();
131 let previous_revision = self.buffer_revision;
134 self.buffer_revision = self.buffer_revision.wrapping_add(1);
135 self.refresh_visual_lines_after_edit(previous_revision);
136 self.refresh_max_content_width_after_edit(previous_revision);
137 self.invalidate_highlight_from(self.pre_edit_line.saturating_sub(1));
142 self.content_cache.clear();
143 self.overlay_cache.clear();
144 self.enqueue_incremental_lsp_change();
145 }
146
147 pub(crate) fn min_active_line(&self) -> usize {
153 self.cursors
154 .iter()
155 .map(|cursor| match cursor.anchor {
156 Some(anchor) => cursor.position.0.min(anchor.0),
157 None => cursor.position.0,
158 })
159 .min()
160 .unwrap_or(0)
161 }
162
163 fn max_active_line(&self) -> usize {
165 self.cursors
166 .iter()
167 .map(|cursor| match cursor.anchor {
168 Some(anchor) => cursor.position.0.max(anchor.0),
169 None => cursor.position.0,
170 })
171 .max()
172 .unwrap_or(0)
173 }
174
175 fn capture_lsp_edit_snapshot(&mut self, message: &Message) {
178 if self.lsp_document.is_none() {
179 self.lsp_edit_snapshot = None;
180 return;
181 }
182
183 let is_local_edit = matches!(
184 message,
185 Message::CharacterInput(_)
186 | Message::Tab
187 | Message::Enter
188 | Message::Backspace
189 | Message::Delete
190 | Message::DeleteSelection
191 | Message::Paste(_)
192 | Message::ImeCommit(_)
193 | Message::MoveLineUp
194 | Message::MoveLineDown
195 | Message::DuplicateLineUp
196 | Message::DuplicateLineDown
197 | Message::ToggleComment
198 );
199 let is_global_edit = matches!(
200 message,
201 Message::Undo | Message::Redo | Message::ReplaceAll
202 );
203 let is_replace_next = matches!(message, Message::ReplaceNext);
204 if !is_local_edit && !is_global_edit && !is_replace_next {
205 self.lsp_edit_snapshot = None;
206 return;
207 }
208
209 let line_count = self.buffer.line_count();
210 let (mut first_line, mut last_line) = if is_global_edit {
211 (0, line_count.saturating_sub(1))
212 } else {
213 (self.pre_edit_line, self.pre_edit_last_line)
214 };
215 if is_replace_next
216 && let Some(search_match) = self.search_state.current_match()
217 {
218 first_line = first_line.min(search_match.line);
219 last_line = last_line.max(search_match.line);
220 }
221
222 let start_line =
223 if is_global_edit { 0 } else { first_line.saturating_sub(1) };
224 let old_end_exclusive = if is_global_edit {
225 line_count
226 } else {
227 last_line.saturating_add(2).min(line_count)
228 };
229 let old_end = if old_end_exclusive < line_count {
230 lsp::LspPosition {
231 line: u32::try_from(old_end_exclusive).unwrap_or(u32::MAX),
232 character: 0,
233 }
234 } else {
235 let last_line = line_count.saturating_sub(1);
236 lsp::LspPosition {
237 line: u32::try_from(last_line).unwrap_or(u32::MAX),
238 character: u32::try_from(self.buffer.line_len(last_line))
239 .unwrap_or(u32::MAX),
240 }
241 };
242
243 self.lsp_edit_snapshot = Some(LspEditSnapshot {
244 start_line,
245 old_end_exclusive,
246 old_line_count: line_count,
247 old_end,
248 });
249 }
250
251 pub(crate) fn invalidate_highlight_from(&self, line: usize) {
262 if let Some(cache) = self.highlight_cache.borrow_mut().as_mut() {
263 cache.truncate(line);
264 }
265 }
266
267 fn finish_navigation_operation(&mut self) {
275 self.sync_search_match_from_primary_cursor();
276 self.reset_cursor_blink();
277 self.overlay_cache.clear();
278 }
279
280 fn ensure_grouping_started(&mut self, label: &str) {
289 if !self.is_grouping {
290 self.history.begin_group(label);
291 self.is_grouping = true;
292 }
293 }
294
295 fn end_grouping_if_active(&mut self) {
301 if self.is_grouping {
302 self.history.end_group();
303 self.is_grouping = false;
304 }
305 }
306
307 fn keep_vim_insert_group(&self) -> bool {
308 self.vim_enabled
309 && self.vim_state.mode() == VimMode::Insert
310 && self.is_grouping
311 }
312
313 fn delete_selection_if_present(&mut self) -> bool {
319 if self.cursors.iter().any(|c| c.has_selection()) {
320 self.delete_selection();
321 self.finish_edit_operation();
322 true
323 } else {
324 false
325 }
326 }
327
328 fn handle_character_input_msg(&mut self, ch: char) -> Task<Message> {
347 if !self.has_focus() {
349 return Task::none();
350 }
351
352 self.ensure_grouping_started("Typing");
354
355 if self.cursors.iter().any(|cursor| cursor.has_selection()) {
359 self.delete_selection();
360 } else {
361 self.clear_selection();
365 }
366
367 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
370 order.sort_by(|&a, &b| {
371 self.cursors.as_slice()[b]
372 .position
373 .cmp(&self.cursors.as_slice()[a].position)
374 });
375
376 for &idx in &order {
377 let pos = self.cursors.as_slice()[idx].position;
381 let mut cmd = InsertCharCommand::new(pos.0, pos.1, ch, pos);
382 let mut cursor_pos = pos;
383 cmd.execute(&mut self.buffer, &mut cursor_pos);
384 self.cursors.as_mut_slice()[idx].position = cursor_pos;
385 adjust_other_cursors(
386 self.cursors.as_mut_slice(),
387 idx,
388 pos.0,
389 pos.1,
390 EditType::InsertChar,
391 );
392 self.history.push(Box::new(cmd));
393 }
394
395 self.finish_edit_operation();
396
397 if ch.is_alphanumeric() || ch == '_' || ch == '.' {
399 self.lsp_flush_pending_changes();
400 self.lsp_request_completion();
401 }
402
403 self.scroll_to_cursor()
404 }
405
406 fn handle_tab(&mut self) -> Task<Message> {
413 self.ensure_grouping_started("Tab");
414
415 self.clear_selection();
420
421 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
423 order.sort_by(|&a, &b| {
424 self.cursors.as_slice()[b]
425 .position
426 .cmp(&self.cursors.as_slice()[a].position)
427 });
428
429 for &idx in &order {
430 let pos = self.cursors.as_slice()[idx].position;
431 match self.indent_style {
432 IndentStyle::Spaces(n) => {
433 let mut cursor_pos = pos;
434 for _i in 0..n as usize {
435 let current_col = cursor_pos.1;
436 let mut cmd = InsertCharCommand::new(
437 pos.0,
438 current_col,
439 ' ',
440 cursor_pos,
441 );
442 cmd.execute(&mut self.buffer, &mut cursor_pos);
443 adjust_other_cursors(
444 self.cursors.as_mut_slice(),
445 idx,
446 pos.0,
447 current_col,
448 EditType::InsertChar,
449 );
450 self.history.push(Box::new(cmd));
451 }
452 self.cursors.as_mut_slice()[idx].position = cursor_pos;
453 }
454 IndentStyle::Tab => {
455 let mut cmd =
456 InsertCharCommand::new(pos.0, pos.1, '\t', pos);
457 let mut cursor_pos = pos;
458 cmd.execute(&mut self.buffer, &mut cursor_pos);
459 adjust_other_cursors(
460 self.cursors.as_mut_slice(),
461 idx,
462 pos.0,
463 pos.1,
464 EditType::InsertChar,
465 );
466 self.cursors.as_mut_slice()[idx].position = cursor_pos;
467 self.history.push(Box::new(cmd));
468 }
469 }
470 }
471
472 self.finish_edit_operation();
473 self.scroll_to_cursor()
474 }
475
476 fn handle_focus_navigation_tab(&mut self) -> Task<Message> {
482 if !self.search_state.is_open {
484 self.has_canvas_focus = false;
486 self.show_cursor = false;
487
488 Task::none()
492 } else {
493 Task::none()
494 }
495 }
496
497 fn handle_focus_navigation_shift_tab(&mut self) -> Task<Message> {
503 if !self.search_state.is_open {
505 self.has_canvas_focus = false;
507 self.show_cursor = false;
508
509 Task::none()
513 } else {
514 Task::none()
515 }
516 }
517
518 fn handle_enter(&mut self) -> Task<Message> {
524 let keep_vim_group = self.keep_vim_insert_group();
527 if !keep_vim_group {
528 self.end_grouping_if_active();
529 }
530
531 let replaces_selection =
539 self.cursors.iter().any(|cursor| cursor.has_selection());
540 if replaces_selection {
541 self.ensure_grouping_started("Enter");
542 self.delete_selection();
543 } else {
544 self.clear_selection();
545 }
546
547 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
549 order.sort_by(|&a, &b| {
550 self.cursors.as_slice()[b]
551 .position
552 .cmp(&self.cursors.as_slice()[a].position)
553 });
554
555 for &idx in &order {
556 let pos = self.cursors.as_slice()[idx].position;
557
558 let indent: String = if self.auto_indent_enabled {
560 self.buffer
561 .line(pos.0)
562 .chars()
563 .take_while(|c| c.is_whitespace())
564 .collect()
565 } else {
566 String::new()
567 };
568 let indent_len = indent.chars().count();
569
570 let mut cmd =
571 InsertNewlineCommand::with_indent(pos.0, pos.1, pos, indent);
572 let mut cursor_pos = pos;
573 cmd.execute(&mut self.buffer, &mut cursor_pos);
574 self.cursors.as_mut_slice()[idx].position = cursor_pos;
575 adjust_other_cursors(
576 self.cursors.as_mut_slice(),
577 idx,
578 pos.0,
579 pos.1,
580 EditType::InsertNewline { indent_len },
581 );
582 self.history.push(Box::new(cmd));
583 }
584
585 if replaces_selection && !keep_vim_group {
586 self.end_grouping_if_active();
587 }
588
589 self.finish_edit_operation();
590 self.scroll_to_cursor()
591 }
592
593 fn primary_line_range(&self) -> (usize, usize) {
604 let primary = self.cursors.primary();
605 match primary.selection_range() {
606 Some((sel_start, sel_end)) => {
607 let end_line = if sel_end.1 == 0 && sel_end.0 > sel_start.0 {
608 sel_end.0 - 1
609 } else {
610 sel_end.0
611 };
612 (sel_start.0, end_line)
613 }
614 None => {
615 let line = primary.position.0;
616 (line, line)
617 }
618 }
619 }
620
621 fn shift_primary_cursor_lines(&mut self, delta: isize) {
624 let primary = self.cursors.primary_mut();
625 primary.position.0 = primary.position.0.saturating_add_signed(delta);
626 if let Some(anchor) = primary.anchor.as_mut() {
627 anchor.0 = anchor.0.saturating_add_signed(delta);
628 }
629 }
630
631 fn move_lines(&mut self, down: bool) -> Task<Message> {
646 self.end_grouping_if_active();
647 self.cursors.remove_all_but_primary();
648
649 let (start, end) = self.primary_line_range();
650
651 if down {
653 if end + 1 >= self.buffer.line_count() {
654 return Task::none();
655 }
656 } else if start == 0 {
657 return Task::none();
658 }
659
660 let pos = self.cursors.primary_position();
661 let mut cmd = MoveLinesCommand::new(start, end, down, pos);
662 let mut cursor_pos = pos;
663 cmd.execute(&mut self.buffer, &mut cursor_pos);
664 self.shift_primary_cursor_lines(if down { 1 } else { -1 });
665 self.history.push(Box::new(cmd));
666
667 self.finish_edit_operation();
668 self.scroll_to_cursor()
669 }
670
671 fn duplicate_lines(&mut self, down: bool) -> Task<Message> {
686 self.end_grouping_if_active();
687 self.cursors.remove_all_but_primary();
688
689 let (start, end) = self.primary_line_range();
690 let pos = self.cursors.primary_position();
691 let mut cmd = DuplicateLinesCommand::new(start, end, down, pos);
692 let mut cursor_pos = pos;
693 cmd.execute(&mut self.buffer, &mut cursor_pos);
694 if down {
695 let block_len = (end - start + 1) as isize;
696 self.shift_primary_cursor_lines(block_len);
697 }
698 self.history.push(Box::new(cmd));
699
700 self.finish_edit_operation();
701 self.scroll_to_cursor()
702 }
703
704 fn toggle_comment(&mut self) -> Task<Message> {
717 self.end_grouping_if_active();
718 self.cursors.remove_all_but_primary();
719
720 let Some(token) = line_comment_token(&self.syntax) else {
721 return Task::none();
722 };
723
724 let (start, end) = self.primary_line_range();
725 let pos = self.cursors.primary_position();
726 let mut cmd =
727 ToggleCommentCommand::new(&self.buffer, start, end, token, pos);
728 if cmd.is_noop() {
729 return Task::none();
730 }
731
732 let new_anchor =
734 self.cursors.primary().anchor.map(|a| cmd.adjust_position(a));
735
736 let mut cursor_pos = pos;
737 cmd.execute(&mut self.buffer, &mut cursor_pos);
738 let primary = self.cursors.primary_mut();
739 primary.position = cursor_pos;
740 primary.anchor = new_anchor;
741 self.history.push(Box::new(cmd));
742
743 self.finish_edit_operation();
744 self.scroll_to_cursor()
745 }
746
747 fn handle_backspace(&mut self) -> Task<Message> {
760 if !self.keep_vim_insert_group() {
762 self.end_grouping_if_active();
763 }
764
765 if self.delete_selection_if_present() {
767 return self.scroll_to_cursor();
768 }
769
770 self.clear_selection();
777
778 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
780 order.sort_by(|&a, &b| {
781 self.cursors.as_slice()[b]
782 .position
783 .cmp(&self.cursors.as_slice()[a].position)
784 });
785
786 for &idx in &order {
787 let pos = self.cursors.as_slice()[idx].position;
788 let edit_kind = if pos.1 > 0 {
790 EditType::DeleteCharBack
791 } else if pos.0 > 0 {
792 let prev_line_len = self.buffer.line_len(pos.0 - 1);
793 EditType::MergePrev { prev_line_len }
794 } else {
795 continue;
797 };
798 let mut cmd =
799 DeleteCharCommand::new(&self.buffer, pos.0, pos.1, pos);
800 let mut cursor_pos = pos;
801 cmd.execute(&mut self.buffer, &mut cursor_pos);
802 self.cursors.as_mut_slice()[idx].position = cursor_pos;
803 adjust_other_cursors(
804 self.cursors.as_mut_slice(),
805 idx,
806 pos.0,
807 pos.1,
808 edit_kind,
809 );
810 self.history.push(Box::new(cmd));
811 }
812
813 self.finish_edit_operation();
814 self.scroll_to_cursor()
815 }
816
817 fn handle_delete(&mut self) -> Task<Message> {
826 if !self.keep_vim_insert_group() {
828 self.end_grouping_if_active();
829 }
830
831 if self.delete_selection_if_present() {
833 return self.scroll_to_cursor();
834 }
835
836 self.clear_selection();
840
841 let mut order: Vec<usize> = (0..self.cursors.len()).collect();
843 order.sort_by(|&a, &b| {
844 self.cursors.as_slice()[b]
845 .position
846 .cmp(&self.cursors.as_slice()[a].position)
847 });
848
849 for &idx in &order {
850 let pos = self.cursors.as_slice()[idx].position;
851 let line_len = self.buffer.line_len(pos.0);
852 let edit_kind = if pos.1 < line_len {
853 EditType::DeleteCharForward
854 } else if pos.0 + 1 < self.buffer.line_count() {
855 EditType::MergeNext { edit_line_len: line_len }
856 } else {
857 continue;
859 };
860 let mut cmd =
861 DeleteForwardCommand::new(&self.buffer, pos.0, pos.1, pos);
862 let mut cursor_pos = pos;
863 cmd.execute(&mut self.buffer, &mut cursor_pos);
864 self.cursors.as_mut_slice()[idx].position = cursor_pos;
865 adjust_other_cursors(
866 self.cursors.as_mut_slice(),
867 idx,
868 pos.0,
869 pos.1,
870 edit_kind,
871 );
872 self.history.push(Box::new(cmd));
873 }
874
875 self.finish_edit_operation();
876 Task::none()
877 }
878
879 fn handle_delete_selection(&mut self) -> Task<Message> {
887 self.end_grouping_if_active();
889
890 if self.cursors.iter().any(|c| c.has_selection()) {
891 self.delete_selection();
892 self.finish_edit_operation();
893 self.scroll_to_cursor()
894 } else {
895 Task::none()
896 }
897 }
898
899 fn vim_accepts_insert_input(&self) -> bool {
904 !self.vim_enabled || self.vim_state.mode() == VimMode::Insert
905 }
906
907 fn handle_vim_key_msg(&mut self, key: char) -> Task<Message> {
908 if !self.vim_enabled {
909 return Task::none();
910 }
911
912 let previous_mode = self.vim_state.mode();
913 let action = self.vim_state.parse_key(key);
914 match action {
915 Some(VimAction::Mode(mode)) => {
916 self.handle_vim_mode(mode, previous_mode)
917 }
918 Some(VimAction::Motion { motion, count, explicit_count }) => {
919 self.handle_vim_motion(motion, count, explicit_count)
920 }
921 Some(VimAction::Insert { position, count }) => {
922 self.handle_vim_insert(position, count)
923 }
924 Some(VimAction::Operator {
925 operator,
926 motion,
927 count,
928 explicit_count,
929 }) => self.handle_vim_motion_operator(
930 operator,
931 motion,
932 count,
933 explicit_count,
934 ),
935 Some(VimAction::LineOperator { operator, count }) => {
936 let start_line = self.cursors.primary_position().0;
937 let end_line = start_line
938 .saturating_add(count.saturating_sub(1))
939 .min(self.buffer.line_count().saturating_sub(1));
940 self.handle_vim_line_operator(
941 operator, start_line, end_line, false,
942 )
943 }
944 Some(VimAction::VisualOperator(operator)) => {
945 self.handle_vim_visual_operator(operator)
946 }
947 Some(VimAction::DeleteCharacters { count }) => {
948 self.handle_vim_delete_characters(count)
949 }
950 Some(VimAction::Paste { position, count }) => {
951 self.handle_vim_paste(position, count)
952 }
953 Some(VimAction::Undo { count }) => {
954 self.handle_vim_history(false, count)
955 }
956 Some(VimAction::Redo { count }) => {
957 self.handle_vim_history(true, count)
958 }
959 Some(VimAction::RepeatSearch { reverse }) => {
960 self.handle_vim_repeat_search(reverse)
961 }
962 Some(VimAction::SubmitSearch(query)) => {
963 self.handle_vim_search(&query)
964 }
965 Some(VimAction::SubmitGotoLine(line)) => {
966 self.handle_goto_position(line.saturating_sub(1), 0)
967 }
968 Some(VimAction::WriteFile { exit_vim }) => {
969 if exit_vim {
970 self.set_vim_enabled(false);
971 }
972 Task::done(Message::WriteRequested)
973 }
974 Some(VimAction::ExitVimMode) => {
975 self.set_vim_enabled(false);
976 Task::none()
977 }
978 Some(VimAction::CommandLineChanged) => {
979 self.overlay_cache.clear();
980 Task::none()
981 }
982 None => Task::none(),
983 }
984 }
985
986 fn handle_vim_search(&mut self, query: &str) -> Task<Message> {
987 if !self.search_replace_enabled || query.is_empty() {
988 return Task::none();
989 }
990
991 self.search_state.close();
992 self.search_state.set_query(query.to_owned(), &self.buffer);
993 if self.search_state.matches.is_empty() {
994 self.overlay_cache.clear();
995 return Task::none();
996 }
997
998 let cursor = self.cursors.primary_position();
999 let next_index = self
1000 .search_state
1001 .matches
1002 .partition_point(|item| (item.line, item.col) <= cursor);
1003 self.search_state.current_match_index =
1004 Some(if next_index == self.search_state.matches.len() {
1005 0
1006 } else {
1007 next_index
1008 });
1009
1010 if let Some(search_match) = self.search_state.current_match() {
1011 self.cursors.set_single((search_match.line, search_match.col));
1012 }
1013 self.finish_navigation_operation();
1014 self.scroll_to_cursor()
1015 }
1016
1017 fn handle_vim_repeat_search(&mut self, reverse: bool) -> Task<Message> {
1018 let Some(last_search) = self.vim_state.last_search().map(str::to_owned)
1019 else {
1020 return Task::none();
1021 };
1022 if self.search_state.query != last_search {
1023 self.search_state.set_query(last_search, &self.buffer);
1024 self.search_state
1025 .select_match_near_cursor(self.cursors.primary_position());
1026 }
1027 if self.search_state.matches.is_empty() {
1028 return Task::none();
1029 }
1030
1031 if reverse {
1032 self.search_state.previous_match();
1033 } else {
1034 self.search_state.next_match();
1035 }
1036 if let Some(search_match) = self.search_state.current_match() {
1037 self.cursors.set_single((search_match.line, search_match.col));
1038 }
1039 self.finish_navigation_operation();
1040 self.scroll_to_cursor()
1041 }
1042
1043 fn handle_vim_delete_characters(&mut self, count: usize) -> Task<Message> {
1044 let start = self.vim_normal_position(self.cursors.primary_position());
1045 let line_len = self.buffer.line_len(start.0);
1046 let end = (start.0, start.1.saturating_add(count).min(line_len));
1047 self.handle_vim_character_operator(
1048 VimOperator::Delete,
1049 start,
1050 end,
1051 false,
1052 )
1053 }
1054
1055 fn handle_vim_motion_operator(
1056 &mut self,
1057 operator: VimOperator,
1058 motion: VimMotion,
1059 count: usize,
1060 explicit_count: bool,
1061 ) -> Task<Message> {
1062 let start = self.vim_normal_position(self.cursors.primary_position());
1063 if matches!(
1064 motion,
1065 VimMotion::Up
1066 | VimMotion::Down
1067 | VimMotion::DocumentStart
1068 | VimMotion::DocumentEnd
1069 ) {
1070 let target =
1071 self.vim_motion_target(start, motion, count, explicit_count);
1072 return self.handle_vim_line_operator(
1073 operator,
1074 start.0.min(target.0),
1075 start.0.max(target.0),
1076 false,
1077 );
1078 }
1079
1080 let target =
1081 self.vim_motion_target(start, motion, count, explicit_count);
1082 let (range_start, range_end) = match motion {
1083 VimMotion::Right => (
1084 start,
1085 (
1086 start.0,
1087 start
1088 .1
1089 .saturating_add(count)
1090 .min(self.buffer.line_len(start.0)),
1091 ),
1092 ),
1093 VimMotion::Left => {
1094 ((start.0, start.1.saturating_sub(count)), start)
1095 }
1096 VimMotion::WordEnd | VimMotion::LineEnd => {
1097 let end = if motion == VimMotion::LineEnd {
1098 (start.0, self.buffer.line_len(start.0))
1099 } else {
1100 (
1101 target.0,
1102 target
1103 .1
1104 .saturating_add(1)
1105 .min(self.buffer.line_len(target.0)),
1106 )
1107 };
1108 (start.min(end), start.max(end))
1109 }
1110 VimMotion::WordForward => {
1111 let end = if target > start {
1112 target
1113 } else {
1114 (start.0, self.buffer.line_len(start.0))
1115 };
1116 (start.min(end), start.max(end))
1117 }
1118 VimMotion::WordBackward
1119 | VimMotion::LineStart
1120 | VimMotion::FirstNonBlank => {
1121 (start.min(target), start.max(target))
1122 }
1123 VimMotion::Up
1124 | VimMotion::Down
1125 | VimMotion::DocumentStart
1126 | VimMotion::DocumentEnd => return Task::none(),
1127 };
1128
1129 self.handle_vim_character_operator(
1130 operator,
1131 range_start,
1132 range_end,
1133 false,
1134 )
1135 }
1136
1137 fn handle_vim_visual_operator(
1138 &mut self,
1139 operator: VimOperator,
1140 ) -> Task<Message> {
1141 if self.vim_state.mode() == VimMode::VisualLine {
1142 let (anchor, active) =
1143 self.vim_state.visual_positions().unwrap_or_else(|| {
1144 let position = self.cursors.primary_position();
1145 (position, position)
1146 });
1147 self.handle_vim_line_operator(
1148 operator,
1149 anchor.0.min(active.0),
1150 anchor.0.max(active.0),
1151 true,
1152 )
1153 } else {
1154 let Some((start, end)) = self.cursors.primary().selection_range()
1155 else {
1156 return Task::none();
1157 };
1158 self.handle_vim_character_operator(operator, start, end, true)
1159 }
1160 }
1161
1162 fn handle_vim_character_operator(
1163 &mut self,
1164 operator: VimOperator,
1165 start: (usize, usize),
1166 end: (usize, usize),
1167 from_visual: bool,
1168 ) -> Task<Message> {
1169 if start == end {
1170 return Task::none();
1171 }
1172 let register = VimRegister {
1173 text: self.extract_text_range(start, end),
1174 kind: VimRegisterKind::Characterwise,
1175 };
1176 self.apply_vim_operator(operator, start, end, register, from_visual)
1177 }
1178
1179 fn handle_vim_line_operator(
1180 &mut self,
1181 operator: VimOperator,
1182 start_line: usize,
1183 end_line: usize,
1184 from_visual: bool,
1185 ) -> Task<Message> {
1186 let last_line = self.buffer.line_count().saturating_sub(1);
1187 let start_line = start_line.min(last_line);
1188 let end_line = end_line.min(last_line).max(start_line);
1189 let mut text = String::new();
1190 for line in start_line..=end_line {
1191 text.push_str(self.buffer.line(line));
1192 text.push('\n');
1193 }
1194
1195 let (start, end) = if end_line < last_line {
1196 ((start_line, 0), (end_line + 1, 0))
1197 } else if start_line > 0 {
1198 (
1199 (start_line - 1, self.buffer.line_len(start_line - 1)),
1200 (end_line, self.buffer.line_len(end_line)),
1201 )
1202 } else {
1203 ((0, 0), (end_line, self.buffer.line_len(end_line)))
1204 };
1205 self.apply_vim_operator(
1206 operator,
1207 start,
1208 end,
1209 VimRegister { text, kind: VimRegisterKind::Linewise },
1210 from_visual,
1211 )
1212 }
1213
1214 fn apply_vim_operator(
1215 &mut self,
1216 operator: VimOperator,
1217 start: (usize, usize),
1218 end: (usize, usize),
1219 register: VimRegister,
1220 from_visual: bool,
1221 ) -> Task<Message> {
1222 self.vim_state.register = register;
1223
1224 if operator == VimOperator::Yank {
1225 if from_visual {
1226 self.cursors.set_single(self.vim_normal_position(start));
1227 } else {
1228 let position =
1229 self.vim_normal_position(self.cursors.primary_position());
1230 self.cursors.set_single(position);
1231 }
1232 self.vim_state.enter_clean_normal_mode();
1233 self.finish_navigation_operation();
1234 return self.scroll_to_cursor();
1235 }
1236
1237 self.end_grouping_if_active();
1238 if operator == VimOperator::Change {
1239 self.ensure_grouping_started("Vim change");
1240 }
1241
1242 self.pre_edit_line = start.0.min(end.0);
1243 self.pre_edit_last_line = start.0.max(end.0);
1244 self.capture_lsp_edit_snapshot(&Message::DeleteSelection);
1245
1246 let cursor_before = self.cursors.primary_position();
1247 let mut command =
1248 DeleteRangeCommand::new(&self.buffer, start, end, cursor_before);
1249 let mut cursor_after = cursor_before;
1250 command.execute(&mut self.buffer, &mut cursor_after);
1251 self.history.push(Box::new(command));
1252 self.cursors.set_single(self.vim_normal_position(cursor_after));
1253
1254 if operator == VimOperator::Change {
1255 self.vim_state.enter_insert_mode();
1256 } else {
1257 self.vim_state.enter_clean_normal_mode();
1258 }
1259 self.finish_edit_operation();
1260 self.scroll_to_cursor()
1261 }
1262
1263 fn handle_vim_paste(
1264 &mut self,
1265 position: VimPastePosition,
1266 count: usize,
1267 ) -> Task<Message> {
1268 let register = self.vim_state.register.clone();
1269 if register.text.is_empty() {
1270 return Task::none();
1271 }
1272 self.end_grouping_if_active();
1273
1274 let current = self.vim_normal_position(self.cursors.primary_position());
1275 let (insert_at, text, cursor_after) = match register.kind {
1276 VimRegisterKind::Characterwise => {
1277 let insert_at = match position {
1278 VimPastePosition::BeforeCursor => current,
1279 VimPastePosition::AfterCursor => (
1280 current.0,
1281 current
1282 .1
1283 .saturating_add(usize::from(
1284 self.buffer.line_len(current.0) > 0,
1285 ))
1286 .min(self.buffer.line_len(current.0)),
1287 ),
1288 };
1289 (insert_at, register.text.repeat(count.max(1)), insert_at)
1290 }
1291 VimRegisterKind::Linewise => {
1292 let repeated = register.text.repeat(count.max(1));
1293 match position {
1294 VimPastePosition::BeforeCursor => {
1295 ((current.0, 0), repeated, (current.0, 0))
1296 }
1297 VimPastePosition::AfterCursor
1298 if current.0 + 1 < self.buffer.line_count() =>
1299 {
1300 ((current.0 + 1, 0), repeated, (current.0 + 1, 0))
1301 }
1302 VimPastePosition::AfterCursor => {
1303 let text = format!(
1304 "\n{}",
1305 repeated.strip_suffix('\n').unwrap_or(&repeated)
1306 );
1307 (
1308 (current.0, self.buffer.line_len(current.0)),
1309 text,
1310 (current.0 + 1, 0),
1311 )
1312 }
1313 }
1314 }
1315 };
1316
1317 self.pre_edit_line = insert_at.0;
1318 self.pre_edit_last_line = insert_at.0;
1319 self.capture_lsp_edit_snapshot(&Message::Paste(text.clone()));
1320 let mut command =
1321 InsertTextCommand::new(insert_at.0, insert_at.1, text, current)
1322 .with_cursor_after(cursor_after);
1323 let mut command_cursor = current;
1324 command.execute(&mut self.buffer, &mut command_cursor);
1325 self.history.push(Box::new(command));
1326 self.cursors.set_single(self.vim_normal_position(command_cursor));
1327 self.vim_state.enter_clean_normal_mode();
1328 self.finish_edit_operation();
1329 self.scroll_to_cursor()
1330 }
1331
1332 fn handle_vim_history(
1333 &mut self,
1334 redo: bool,
1335 count: usize,
1336 ) -> Task<Message> {
1337 self.end_grouping_if_active();
1338 self.pre_edit_line = 0;
1339 self.pre_edit_last_line = usize::MAX;
1340 self.capture_lsp_edit_snapshot(if redo {
1341 &Message::Redo
1342 } else {
1343 &Message::Undo
1344 });
1345
1346 let mut cursor = self.cursors.primary_position();
1347 let mut changed = false;
1348 for _ in 0..count.max(1) {
1349 let applied = if redo {
1350 self.history.redo(&mut self.buffer, &mut cursor)
1351 } else {
1352 self.history.undo(&mut self.buffer, &mut cursor)
1353 };
1354 if !applied {
1355 break;
1356 }
1357 changed = true;
1358 }
1359 if !changed {
1360 return Task::none();
1361 }
1362
1363 self.cursors.set_single(self.vim_normal_position(cursor));
1364 self.vim_state.enter_clean_normal_mode();
1365 self.finish_edit_operation();
1366 self.scroll_to_cursor()
1367 }
1368
1369 fn handle_vim_mode(
1370 &mut self,
1371 mode: VimMode,
1372 previous_mode: VimMode,
1373 ) -> Task<Message> {
1374 self.end_grouping_if_active();
1375 match mode {
1376 VimMode::Normal => {
1377 let mut active = self
1378 .vim_state
1379 .visual_positions()
1380 .map(|(_, active)| active)
1381 .unwrap_or_else(|| self.cursors.primary_position());
1382 if previous_mode == VimMode::Insert {
1383 active.1 = active.1.saturating_sub(1);
1384 }
1385 self.vim_state.clear_visual();
1386 self.cursors.set_single(self.vim_normal_position(active));
1387 }
1388 VimMode::Visual | VimMode::VisualLine => {
1389 let position =
1390 self.vim_normal_position(self.cursors.primary_position());
1391 self.vim_state.begin_visual(position);
1392 self.apply_vim_visual_selection(
1393 position,
1394 position,
1395 mode == VimMode::VisualLine,
1396 );
1397 }
1398 VimMode::Insert => {}
1399 }
1400 self.finish_navigation_operation();
1401 self.scroll_to_cursor()
1402 }
1403
1404 fn handle_vim_motion(
1405 &mut self,
1406 motion: VimMotion,
1407 count: usize,
1408 explicit_count: bool,
1409 ) -> Task<Message> {
1410 self.end_grouping_if_active();
1411 match self.vim_state.mode() {
1412 VimMode::Visual | VimMode::VisualLine => {
1413 let (anchor, active) =
1414 self.vim_state.visual_positions().unwrap_or_else(|| {
1415 let position = self.vim_normal_position(
1416 self.cursors.primary_position(),
1417 );
1418 (position, position)
1419 });
1420 let target = self.vim_motion_target(
1421 active,
1422 motion,
1423 count,
1424 explicit_count,
1425 );
1426 self.vim_state.set_visual_active(target);
1427 self.apply_vim_visual_selection(
1428 anchor,
1429 target,
1430 self.vim_state.mode() == VimMode::VisualLine,
1431 );
1432 }
1433 VimMode::Normal => {
1434 let target = self.vim_motion_target(
1435 self.cursors.primary_position(),
1436 motion,
1437 count,
1438 explicit_count,
1439 );
1440 self.cursors.set_single(target);
1441 self.overlay_cache.clear();
1442 }
1443 VimMode::Insert => return Task::none(),
1444 }
1445 self.finish_navigation_operation();
1446 self.scroll_to_cursor()
1447 }
1448
1449 fn handle_vim_insert(
1450 &mut self,
1451 position: VimInsertPosition,
1452 count: usize,
1453 ) -> Task<Message> {
1454 self.end_grouping_if_active();
1455 let current = self
1456 .vim_state
1457 .visual_positions()
1458 .map(|(_, active)| active)
1459 .unwrap_or_else(|| self.cursors.primary_position());
1460 self.vim_state.clear_visual();
1461 let current = self.vim_normal_position(current);
1462 self.cursors.set_single(current);
1463 self.ensure_grouping_started("Vim insert");
1464
1465 match position {
1466 VimInsertPosition::BeforeCursor => {}
1467 VimInsertPosition::AfterCursor => {
1468 let line_len = self.buffer.line_len(current.0);
1469 self.cursors.primary_mut().position.1 =
1470 current.1.saturating_add(1).min(line_len);
1471 }
1472 VimInsertPosition::FirstNonBlank => {
1473 self.cursors.primary_mut().position.1 = self
1474 .buffer
1475 .line(current.0)
1476 .chars()
1477 .position(|ch| !ch.is_whitespace())
1478 .unwrap_or(0);
1479 }
1480 VimInsertPosition::EndOfLine => {
1481 self.cursors.primary_mut().position.1 =
1482 self.buffer.line_len(current.0);
1483 }
1484 VimInsertPosition::NewLineBelow => {
1485 self.cursors.primary_mut().position.1 =
1486 self.buffer.line_len(current.0);
1487 for _ in 0..count.max(1) {
1488 let _ = self.update(&Message::Enter);
1489 }
1490 }
1491 VimInsertPosition::NewLineAbove => {
1492 self.cursors.primary_mut().position.1 = 0;
1493 let line = current.0;
1494 for _ in 0..count.max(1) {
1495 let _ = self.update(&Message::Enter);
1496 self.cursors.set_single((line, 0));
1497 }
1498 }
1499 }
1500
1501 self.overlay_cache.clear();
1502 self.reset_cursor_blink();
1503 self.scroll_to_cursor()
1504 }
1505
1506 fn handle_arrow_key(
1517 &mut self,
1518 direction: ArrowDirection,
1519 shift_pressed: bool,
1520 ) -> Task<Message> {
1521 self.end_grouping_if_active();
1523
1524 if shift_pressed {
1525 for cursor in self.cursors.as_mut_slice() {
1527 if cursor.anchor.is_none() {
1528 cursor.set_anchor();
1529 }
1530 }
1531 self.move_cursor(direction);
1532 } else {
1533 self.clear_selection();
1535 self.move_cursor(direction);
1536 }
1537 self.finish_navigation_operation();
1538 self.scroll_to_cursor()
1539 }
1540
1541 fn handle_home(&mut self, shift_pressed: bool) -> Task<Message> {
1554 if shift_pressed {
1555 for cursor in self.cursors.as_mut_slice() {
1556 if cursor.anchor.is_none() {
1557 cursor.set_anchor();
1558 }
1559 cursor.position.1 = 0;
1560 }
1561 } else {
1562 self.clear_selection();
1563 for cursor in self.cursors.as_mut_slice() {
1564 cursor.position.1 = 0;
1565 }
1566 }
1567 self.cursors.sort_and_merge();
1568 self.finish_navigation_operation();
1569 self.scroll_to_cursor()
1570 }
1571
1572 fn handle_end(&mut self, shift_pressed: bool) -> Task<Message> {
1585 if shift_pressed {
1586 for cursor in self.cursors.as_mut_slice() {
1587 if cursor.anchor.is_none() {
1588 cursor.set_anchor();
1589 }
1590 cursor.position.1 = self.buffer.line_len(cursor.position.0);
1591 }
1592 } else {
1593 self.clear_selection();
1594 for cursor in self.cursors.as_mut_slice() {
1595 cursor.position.1 = self.buffer.line_len(cursor.position.0);
1596 }
1597 }
1598 self.cursors.sort_and_merge();
1599 self.finish_navigation_operation();
1600 self.scroll_to_cursor()
1601 }
1602
1603 fn handle_ctrl_home(&mut self) -> Task<Message> {
1611 self.clear_selection();
1613 self.cursors.set_single((0, 0));
1614 self.finish_navigation_operation();
1615 self.scroll_to_cursor()
1616 }
1617
1618 fn handle_ctrl_end(&mut self) -> Task<Message> {
1626 self.clear_selection();
1628 let last_line = self.buffer.line_count().saturating_sub(1);
1629 let last_col = self.buffer.line_len(last_line);
1630 self.cursors.set_single((last_line, last_col));
1631 self.finish_navigation_operation();
1632 self.scroll_to_cursor()
1633 }
1634
1635 fn handle_page_up(&mut self) -> Task<Message> {
1643 self.page_up();
1644 self.finish_navigation_operation();
1645 self.scroll_to_cursor()
1646 }
1647
1648 fn handle_page_down(&mut self) -> Task<Message> {
1656 self.page_down();
1657 self.finish_navigation_operation();
1658 self.scroll_to_cursor()
1659 }
1660
1661 fn handle_goto_position(
1672 &mut self,
1673 line: usize,
1674 col: usize,
1675 ) -> Task<Message> {
1676 self.end_grouping_if_active();
1678 self.set_cursor(line, col)
1679 }
1680
1681 fn sync_search_match_from_primary_cursor(&mut self) {
1688 if !self.search_matches_visible() || self.search_state.query.is_empty()
1689 {
1690 return;
1691 }
1692
1693 let primary = self.cursors.primary();
1694 let cursor = primary.position;
1695 let selection = primary.selection_range();
1696 if self.search_state.select_match_at_cursor(cursor, selection) {
1697 self.overlay_cache.clear();
1698 }
1699 }
1700
1701 fn handle_mouse_click_msg(&mut self, point: iced::Point) -> Task<Message> {
1713 self.request_focus();
1715
1716 self.has_canvas_focus = true;
1718
1719 self.end_grouping_if_active();
1721
1722 self.cursors.remove_all_but_primary();
1725
1726 self.handle_mouse_click(point);
1727 self.reset_cursor_blink();
1728 self.clear_selection();
1730 self.is_dragging = true;
1731 self.cursors.primary_mut().set_anchor();
1732 self.sync_search_match_from_primary_cursor();
1733
1734 self.show_cursor = true;
1736
1737 Task::none()
1738 }
1739
1740 fn handle_mouse_drag_msg(&mut self, point: iced::Point) -> Task<Message> {
1750 if self.is_dragging {
1751 let before_pos = self.cursors.primary_position();
1752 self.handle_mouse_drag(point);
1753 if self.cursors.primary_position() != before_pos {
1754 self.overlay_cache.clear();
1757 }
1758 }
1759 Task::none()
1760 }
1761
1762 fn handle_mouse_release_msg(&mut self) -> Task<Message> {
1768 self.is_dragging = false;
1769 if self.vim_enabled {
1770 if self.cursors.primary().has_selection() {
1771 let anchor = self.cursors.primary().anchor.unwrap_or_default();
1772 let position = self.cursors.primary_position();
1773 let active = if position >= anchor && position.1 > 0 {
1774 (position.0, position.1 - 1)
1775 } else {
1776 position
1777 };
1778 let anchor = self.vim_normal_position(anchor);
1779 let active = self.vim_normal_position(active);
1780 self.vim_state.set_mode_from_mouse(VimMode::Visual);
1781 self.vim_state.begin_visual(anchor);
1782 self.vim_state.set_visual_active(active);
1783 } else {
1784 let position =
1785 self.vim_normal_position(self.cursors.primary_position());
1786 self.cursors.set_single(position);
1787 }
1788 self.overlay_cache.clear();
1789 }
1790 self.sync_search_match_from_primary_cursor();
1791 Task::none()
1792 }
1793
1794 fn handle_double_click_msg(&mut self, point: iced::Point) -> Task<Message> {
1799 self.request_focus();
1800 self.has_canvas_focus = true;
1801 self.end_grouping_if_active();
1802 self.cursors.remove_all_but_primary();
1803 if let Some((line, col)) = self.calculate_cursor_from_point(point) {
1804 let line_content = self.buffer.line(line);
1805 let start = Self::word_start_in_line(line_content, col);
1806 let end = Self::word_end_in_line(line_content, col);
1807 let cursor = self.cursors.primary_mut();
1808 if start < end {
1809 cursor.anchor = Some((line, start));
1810 cursor.position = (line, end);
1811 } else {
1812 cursor.anchor = None;
1813 cursor.position = (line, col);
1814 }
1815 }
1816 self.is_dragging = false;
1817 self.show_cursor = true;
1818 self.reset_cursor_blink();
1819 self.overlay_cache.clear();
1820 self.sync_search_match_from_primary_cursor();
1821 Task::none()
1822 }
1823
1824 fn handle_triple_click_msg(&mut self, point: iced::Point) -> Task<Message> {
1826 self.request_focus();
1827 self.has_canvas_focus = true;
1828 self.end_grouping_if_active();
1829 self.cursors.remove_all_but_primary();
1830 if let Some((line, _col)) = self.calculate_cursor_from_point(point) {
1831 let line_len = self.buffer.line_len(line);
1832 let cursor = self.cursors.primary_mut();
1833 cursor.anchor = Some((line, 0));
1834 cursor.position = (line, line_len);
1835 }
1836 self.is_dragging = false;
1837 self.show_cursor = true;
1838 self.reset_cursor_blink();
1839 self.overlay_cache.clear();
1840 self.sync_search_match_from_primary_cursor();
1841 Task::none()
1842 }
1843
1844 fn handle_context_menu_requested_msg(
1850 &mut self,
1851 point: iced::Point,
1852 ) -> Task<Message> {
1853 self.request_focus();
1854 self.has_canvas_focus = true;
1855 self.focus_locked = false;
1856 self.show_cursor = true;
1857 self.is_dragging = false;
1858 self.end_grouping_if_active();
1859
1860 if let Some(position) = self.calculate_cursor_from_point(point) {
1861 let inside_selection = self.cursors.iter().any(|cursor| {
1862 cursor.selection_range().is_some_and(|(start, end)| {
1863 (start..=end).contains(&position)
1864 })
1865 });
1866
1867 if !inside_selection {
1868 self.cursors.set_single(position);
1869 self.overlay_cache.clear();
1870 }
1871 }
1872
1873 self.reset_cursor_blink();
1874 Task::none()
1875 }
1876
1877 fn handle_cut_msg(&mut self) -> Task<Message> {
1883 if !self.cursors.iter().any(|cursor| cursor.has_selection()) {
1884 return Task::none();
1885 }
1886
1887 self.end_grouping_if_active();
1888 self.ensure_grouping_started("Cut");
1889 let clipboard_task = self.copy_selection();
1890 self.delete_selection();
1891 self.end_grouping_if_active();
1892 self.finish_edit_operation();
1893
1894 Task::batch([clipboard_task, self.scroll_to_cursor()])
1895 }
1896
1897 fn handle_select_all_msg(&mut self) -> Task<Message> {
1899 self.end_grouping_if_active();
1900
1901 let last_line = self.buffer.line_count().saturating_sub(1);
1902 let end = (last_line, self.buffer.line_len(last_line));
1903 self.cursors.set_single(end);
1904 self.cursors.primary_mut().anchor = Some((0, 0));
1905 self.overlay_cache.clear();
1906 self.reset_cursor_blink();
1907
1908 self.scroll_to_cursor()
1909 }
1910
1911 fn handle_paste_msg(&mut self, text: &str) -> Task<Message> {
1924 self.end_grouping_if_active();
1926
1927 if text.is_empty() {
1929 iced::clipboard::read().and_then(|clipboard_text| {
1931 Task::done(Message::Paste(clipboard_text))
1932 })
1933 } else {
1934 self.paste_text(text);
1936 self.finish_edit_operation();
1937 self.scroll_to_cursor()
1938 }
1939 }
1940
1941 fn handle_undo_msg(&mut self) -> Task<Message> {
1951 self.end_grouping_if_active();
1953
1954 let mut cursor_pos = self.cursors.primary_position();
1955 if self.history.undo(&mut self.buffer, &mut cursor_pos) {
1956 self.cursors.primary_mut().position = cursor_pos;
1957 self.clear_selection();
1958 self.pre_edit_line = 0;
1962 self.pre_edit_last_line = usize::MAX;
1963 self.finish_edit_operation();
1964 self.scroll_to_cursor()
1965 } else {
1966 Task::none()
1967 }
1968 }
1969
1970 fn handle_redo_msg(&mut self) -> Task<Message> {
1976 let mut cursor_pos = self.cursors.primary_position();
1977 if self.history.redo(&mut self.buffer, &mut cursor_pos) {
1978 self.cursors.primary_mut().position = cursor_pos;
1979 self.clear_selection();
1980 self.pre_edit_line = 0;
1983 self.pre_edit_last_line = usize::MAX;
1984 self.finish_edit_operation();
1985 self.scroll_to_cursor()
1986 } else {
1987 Task::none()
1988 }
1989 }
1990
1991 fn handle_open_search_msg(&mut self) -> Task<Message> {
2001 self.goto_line_state.close();
2002 self.search_state.open_search();
2003 if !self.search_state.query.is_empty() {
2004 self.search_state.update_matches(&self.buffer);
2005 self.search_state
2006 .select_match_near_cursor(self.cursors.primary_position());
2007 }
2008 self.overlay_cache.clear();
2009
2010 Task::batch([
2012 focus(self.search_state.search_input_id.clone()),
2013 select_all(self.search_state.search_input_id.clone()),
2014 ])
2015 }
2016
2017 fn handle_open_search_replace_msg(&mut self) -> Task<Message> {
2023 self.goto_line_state.close();
2024 self.search_state.open_replace();
2025 if !self.search_state.query.is_empty() {
2026 self.search_state.update_matches(&self.buffer);
2027 self.search_state
2028 .select_match_near_cursor(self.cursors.primary_position());
2029 }
2030 self.overlay_cache.clear();
2031
2032 Task::batch([
2034 focus(self.search_state.search_input_id.clone()),
2035 select_all(self.search_state.search_input_id.clone()),
2036 ])
2037 }
2038
2039 fn handle_close_search_msg(&mut self) -> Task<Message> {
2045 if self.cursors.is_multi() && !self.search_state.is_open {
2047 self.cursors.remove_all_but_primary();
2048 self.overlay_cache.clear();
2049 return Task::none();
2050 }
2051 self.search_state.close();
2052 self.overlay_cache.clear();
2053 Task::none()
2054 }
2055
2056 fn handle_open_goto_line_msg(&mut self) -> Task<Message> {
2058 self.search_state.close();
2059 self.goto_line_state.open(self.cursors.primary_position().0);
2060 self.overlay_cache.clear();
2061
2062 Task::batch([
2063 focus(self.goto_line_state.input_id.clone()),
2064 select_all(self.goto_line_state.input_id.clone()),
2065 ])
2066 }
2067
2068 fn handle_close_goto_line_msg(&mut self) -> Task<Message> {
2070 self.goto_line_state.close();
2071 self.overlay_cache.clear();
2072 Task::none()
2073 }
2074
2075 fn handle_goto_line_changed_msg(&mut self, query: &str) -> Task<Message> {
2077 self.goto_line_state.query = query.to_string();
2078 Task::none()
2079 }
2080
2081 fn handle_submit_goto_line_msg(&mut self) -> Task<Message> {
2083 let Some(one_based_line) = self.goto_line_state.target_line() else {
2084 return Task::none();
2085 };
2086
2087 let target_line = one_based_line
2088 .saturating_sub(1)
2089 .min(self.buffer.line_count().saturating_sub(1));
2090 while self.hidden_lines_set().contains(&target_line) {
2091 let collapsed_count = self.collapsed_folds.len();
2092 self.unfold_at(target_line);
2093 if self.collapsed_folds.len() == collapsed_count {
2094 break;
2095 }
2096 }
2097
2098 self.goto_line_state.close();
2099 self.handle_goto_position(target_line, 0)
2100 }
2101
2102 fn handle_search_query_changed_msg(
2112 &mut self,
2113 query: &str,
2114 ) -> Task<Message> {
2115 self.search_state.set_query(query.to_string(), &self.buffer);
2116 self.overlay_cache.clear();
2117
2118 if let Some(match_pos) = self.search_state.current_match() {
2120 self.cursors.primary_mut().position =
2121 (match_pos.line, match_pos.col);
2122 self.clear_selection();
2123 return self.scroll_to_cursor();
2124 }
2125 Task::none()
2126 }
2127
2128 fn handle_replace_query_changed_msg(
2138 &mut self,
2139 replace_text: &str,
2140 ) -> Task<Message> {
2141 self.search_state.set_replace_with(replace_text.to_string());
2142 Task::none()
2143 }
2144
2145 fn handle_toggle_case_sensitive_msg(&mut self) -> Task<Message> {
2151 self.search_state.toggle_case_sensitive(&self.buffer);
2152 self.overlay_cache.clear();
2153
2154 if let Some(match_pos) = self.search_state.current_match() {
2156 self.cursors.primary_mut().position =
2157 (match_pos.line, match_pos.col);
2158 self.clear_selection();
2159 return self.scroll_to_cursor();
2160 }
2161 Task::none()
2162 }
2163
2164 fn handle_find_next_msg(&mut self) -> Task<Message> {
2170 if !self.search_state.matches.is_empty() {
2171 self.search_state.next_match();
2172 if let Some(match_pos) = self.search_state.current_match() {
2173 self.cursors.primary_mut().position =
2174 (match_pos.line, match_pos.col);
2175 self.clear_selection();
2176 self.overlay_cache.clear();
2177 return self.scroll_to_cursor();
2178 }
2179 }
2180 Task::none()
2181 }
2182
2183 fn handle_find_previous_msg(&mut self) -> Task<Message> {
2189 if !self.search_state.matches.is_empty() {
2190 self.search_state.previous_match();
2191 if let Some(match_pos) = self.search_state.current_match() {
2192 self.cursors.primary_mut().position =
2193 (match_pos.line, match_pos.col);
2194 self.clear_selection();
2195 self.overlay_cache.clear();
2196 return self.scroll_to_cursor();
2197 }
2198 }
2199 Task::none()
2200 }
2201
2202 fn handle_replace_next_msg(&mut self) -> Task<Message> {
2208 if let Some(match_pos) = self.search_state.current_match() {
2210 let query_len = self.search_state.query.chars().count();
2211 let replace_text = self.search_state.replace_with.clone();
2212
2213 let pos = self.cursors.primary_position();
2215 let mut cmd = ReplaceTextCommand::new(
2216 &self.buffer,
2217 (match_pos.line, match_pos.col),
2218 query_len,
2219 replace_text,
2220 pos,
2221 );
2222 let mut cursor_pos = pos;
2223 cmd.execute(&mut self.buffer, &mut cursor_pos);
2224 self.cursors.primary_mut().position = cursor_pos;
2225 self.history.push(Box::new(cmd));
2226
2227 self.pre_edit_line = self.pre_edit_line.min(match_pos.line);
2230 self.pre_edit_last_line =
2231 self.pre_edit_last_line.max(match_pos.line);
2232
2233 self.clear_selection();
2234 self.finish_edit_operation();
2235
2236 if !self.search_state.matches.is_empty()
2238 && let Some(next_match) = self.search_state.current_match()
2239 {
2240 self.cursors.primary_mut().position =
2241 (next_match.line, next_match.col);
2242 }
2243
2244 return self.scroll_to_cursor();
2245 }
2246 Task::none()
2247 }
2248
2249 fn handle_replace_all_msg(&mut self) -> Task<Message> {
2255 let all_matches = super::search::find_matches(
2257 &self.buffer,
2258 &self.search_state.query,
2259 self.search_state.case_sensitive,
2260 None, );
2262
2263 if !all_matches.is_empty() {
2264 let query_len = self.search_state.query.chars().count();
2265 let replace_text = self.search_state.replace_with.clone();
2266
2267 let mut composite =
2269 CompositeCommand::new("Replace All".to_string());
2270
2271 for match_pos in all_matches.iter().rev() {
2273 let pos = self.cursors.primary_position();
2274 let cmd = ReplaceTextCommand::new(
2275 &self.buffer,
2276 (match_pos.line, match_pos.col),
2277 query_len,
2278 replace_text.clone(),
2279 pos,
2280 );
2281 composite.add(Box::new(cmd));
2282 }
2283
2284 let mut cursor_pos = self.cursors.primary_position();
2286 composite.execute(&mut self.buffer, &mut cursor_pos);
2287 self.cursors.primary_mut().position = cursor_pos;
2288 self.history.push(Box::new(composite));
2289
2290 self.pre_edit_line = 0;
2293 self.pre_edit_last_line = usize::MAX;
2294
2295 self.clear_selection();
2296 self.finish_edit_operation();
2297 self.scroll_to_cursor()
2298 } else {
2299 Task::none()
2300 }
2301 }
2302
2303 fn handle_search_dialog_tab_msg(&mut self) -> Task<Message> {
2309 self.search_state.focus_next_field();
2311
2312 match self.search_state.focused_field {
2314 crate::canvas_editor::search::SearchFocusedField::Search => {
2315 focus(self.search_state.search_input_id.clone())
2316 }
2317 crate::canvas_editor::search::SearchFocusedField::Replace => {
2318 focus(self.search_state.replace_input_id.clone())
2319 }
2320 }
2321 }
2322
2323 fn handle_search_dialog_shift_tab_msg(&mut self) -> Task<Message> {
2329 self.search_state.focus_previous_field();
2331
2332 match self.search_state.focused_field {
2334 crate::canvas_editor::search::SearchFocusedField::Search => {
2335 focus(self.search_state.search_input_id.clone())
2336 }
2337 crate::canvas_editor::search::SearchFocusedField::Replace => {
2338 focus(self.search_state.replace_input_id.clone())
2339 }
2340 }
2341 }
2342
2343 fn handle_canvas_focus_gained_msg(&mut self) -> Task<Message> {
2353 self.has_canvas_focus = true;
2354 self.focus_locked = false; self.show_cursor = true;
2356 self.reset_cursor_blink();
2357 self.overlay_cache.clear();
2358 Task::none()
2359 }
2360
2361 fn handle_canvas_focus_lost_msg(&mut self) -> Task<Message> {
2367 self.has_canvas_focus = false;
2368 self.focus_locked = true; self.show_cursor = false;
2370 self.ime_preedit = None;
2371 self.overlay_cache.clear();
2372 Task::none()
2373 }
2374
2375 fn handle_ime_opened_msg(&mut self) -> Task<Message> {
2383 self.ime_preedit = None;
2384 self.overlay_cache.clear();
2385 Task::none()
2386 }
2387
2388 fn handle_ime_preedit_msg(
2401 &mut self,
2402 content: &str,
2403 selection: &Option<std::ops::Range<usize>>,
2404 ) -> Task<Message> {
2405 if content.is_empty() {
2406 self.ime_preedit = None;
2407 } else {
2408 self.ime_preedit = Some(ImePreedit {
2409 content: content.to_string(),
2410 selection: selection.clone(),
2411 });
2412 }
2413
2414 self.overlay_cache.clear();
2415 Task::none()
2416 }
2417
2418 fn handle_ime_commit_msg(&mut self, text: &str) -> Task<Message> {
2430 self.ime_preedit = None;
2431
2432 if text.is_empty() || !self.vim_accepts_insert_input() {
2433 self.overlay_cache.clear();
2434 return Task::none();
2435 }
2436
2437 self.ensure_grouping_started("Typing");
2438
2439 self.paste_text(text);
2440 self.finish_edit_operation();
2441 self.scroll_to_cursor()
2442 }
2443
2444 fn handle_ime_closed_msg(&mut self) -> Task<Message> {
2452 self.ime_preedit = None;
2453 self.overlay_cache.clear();
2454 Task::none()
2455 }
2456
2457 fn handle_tick_msg(&mut self) -> Task<Message> {
2469 if self.has_focus()
2471 && self.last_blink.elapsed() >= CURSOR_BLINK_INTERVAL
2472 {
2473 self.cursor_visible = !self.cursor_visible;
2474 self.last_blink = super::Instant::now();
2475 self.overlay_cache.clear();
2476 }
2477
2478 if !self.has_focus() {
2480 self.show_cursor = false;
2481 }
2482
2483 Task::none()
2484 }
2485
2486 fn handle_scrolled_msg(
2500 &mut self,
2501 viewport: iced::widget::scrollable::Viewport,
2502 ) -> Task<Message> {
2503 let new_scroll = viewport.absolute_offset().y;
2512 let new_height = viewport.bounds().height;
2513 let new_width = viewport.bounds().width;
2514 let scroll_changed = (self.viewport_scroll - new_scroll).abs() > 0.1;
2515 let visible_lines_count =
2516 (new_height / self.line_height).ceil() as usize + 2;
2517 let first_visible_line =
2518 (new_scroll / self.line_height).floor() as usize;
2519 let last_visible_line = first_visible_line + visible_lines_count;
2520 let margin = visible_lines_count
2521 * crate::canvas_editor::CACHE_WINDOW_MARGIN_MULTIPLIER;
2522 let window_start = first_visible_line.saturating_sub(margin);
2523 let window_end = last_visible_line + margin;
2524 let need_rewindow =
2528 if self.cache_window_end_line > self.cache_window_start_line {
2529 let lower_boundary_trigger = self.cache_window_start_line > 0
2530 && first_visible_line
2531 < self
2532 .cache_window_start_line
2533 .saturating_add(visible_lines_count / 2);
2534 let upper_boundary_trigger = last_visible_line
2535 > self
2536 .cache_window_end_line
2537 .saturating_sub(visible_lines_count / 2);
2538 lower_boundary_trigger || upper_boundary_trigger
2539 } else {
2540 true
2541 };
2542 if (self.viewport_height - new_height).abs() > 1.0
2545 || (self.viewport_width - new_width).abs() > 1.0
2546 || (scroll_changed && need_rewindow)
2547 {
2548 self.cache_window_start_line = window_start;
2549 self.cache_window_end_line = window_end;
2550 self.last_first_visible_line = first_visible_line;
2551 self.content_cache.clear();
2552 self.overlay_cache.clear();
2553 }
2554 self.viewport_scroll = new_scroll;
2555 self.viewport_height = new_height;
2556 self.viewport_width = new_width;
2557 Task::none()
2558 }
2559
2560 fn handle_horizontal_scrolled_msg(
2573 &mut self,
2574 viewport: iced::widget::scrollable::Viewport,
2575 ) -> Task<Message> {
2576 let new_x = viewport.absolute_offset().x;
2577 if (self.horizontal_scroll_offset - new_x).abs() > 0.1 {
2578 self.horizontal_scroll_offset = new_x;
2579 self.content_cache.clear();
2580 self.overlay_cache.clear();
2581 }
2582 Task::none()
2583 }
2584
2585 fn handle_alt_click_msg(&mut self, point: iced::Point) -> Task<Message> {
2600 if self.vim_enabled {
2601 return Task::none();
2602 }
2603 if let Some(pos) = self.calculate_cursor_from_point(point) {
2604 self.cursors.add_cursor(pos);
2605 self.overlay_cache.clear();
2606 self.reset_cursor_blink();
2607 }
2608 Task::none()
2609 }
2610
2611 fn handle_add_cursor_above_msg(&mut self) -> Task<Message> {
2618 if self.vim_enabled {
2619 return Task::none();
2620 }
2621 let (line, col) = self.cursors.primary_position();
2622 if line == 0 {
2623 return Task::none();
2624 }
2625 let new_line = line - 1;
2626 let new_col = col.min(self.buffer.line_len(new_line));
2627 self.cursors.add_cursor((new_line, new_col));
2628 self.overlay_cache.clear();
2629 self.reset_cursor_blink();
2630 Task::none()
2631 }
2632
2633 fn handle_add_cursor_below_msg(&mut self) -> Task<Message> {
2640 if self.vim_enabled {
2641 return Task::none();
2642 }
2643 let (line, col) = self.cursors.primary_position();
2644 let last_line = self.buffer.line_count().saturating_sub(1);
2645 if line >= last_line {
2646 return Task::none();
2647 }
2648 let new_line = line + 1;
2649 let new_col = col.min(self.buffer.line_len(new_line));
2650 self.cursors.add_cursor((new_line, new_col));
2651 self.overlay_cache.clear();
2652 self.reset_cursor_blink();
2653 Task::none()
2654 }
2655
2656 fn handle_select_next_occurrence_msg(&mut self) -> Task<Message> {
2664 if self.vim_enabled {
2665 return Task::none();
2666 }
2667 let search_text = if let Some(text) = self.get_selected_text() {
2669 text
2670 } else {
2671 let (line, col) = self.cursors.primary_position();
2673 let line_str = self.buffer.line(line).to_string();
2674 let word_start = Self::word_start_in_line(&line_str, col);
2675 let word_end = Self::word_end_in_line(&line_str, col);
2676 if word_start == word_end {
2677 return Task::none();
2678 }
2679 self.cursors.primary_mut().anchor = Some((line, word_start));
2682 self.cursors.primary_mut().position = (line, word_end);
2683 self.overlay_cache.clear();
2684 return Task::none();
2685 };
2686
2687 if search_text.is_empty() {
2688 return Task::none();
2689 }
2690
2691 let search_start = self
2693 .cursors
2694 .as_slice()
2695 .last()
2696 .map(|last| {
2697 last.selection_range()
2698 .map(|(_, end)| end)
2699 .unwrap_or(last.position)
2700 })
2701 .unwrap_or((0, 0));
2702
2703 let (start_line, start_col) = search_start;
2705 let line_count = self.buffer.line_count();
2706 let search_char_len = search_text.chars().count();
2707
2708 for line_offset in 0..=line_count {
2709 let line_idx = (start_line + line_offset) % line_count;
2710 let line_str = self.buffer.line(line_idx);
2711
2712 let search_col = if line_offset == 0 { start_col } else { 0 };
2714
2715 let prefix_bytes = char_to_byte_index(line_str, search_col);
2717 let haystack = &line_str[prefix_bytes..];
2718
2719 if let Some(byte_offset) = haystack.find(search_text.as_str()) {
2721 let char_start =
2723 search_col + haystack[..byte_offset].chars().count();
2724 let char_end = char_start + search_char_len;
2725
2726 let found_cursor = cursor_set::Cursor {
2728 position: (line_idx, char_end),
2729 anchor: Some((line_idx, char_start)),
2730 };
2731 self.cursors.add_cursor_with_selection(found_cursor);
2732 self.overlay_cache.clear();
2733 self.reset_cursor_blink();
2734 return self.scroll_to_cursor();
2735 }
2736 }
2737
2738 Task::none()
2739 }
2740
2741 pub fn update(&mut self, message: &Message) -> Task<Message> {
2754 self.pre_edit_line = self.min_active_line();
2757 self.pre_edit_last_line = self.max_active_line();
2758 self.capture_lsp_edit_snapshot(message);
2759 match message {
2760 Message::CharacterInput(ch) if self.vim_accepts_insert_input() => {
2762 self.handle_character_input_msg(*ch)
2763 }
2764 Message::CharacterInput(_) => Task::none(),
2765 Message::VimKey(ch) => self.handle_vim_key_msg(*ch),
2766 Message::ToggleVimMode => {
2767 self.set_vim_enabled(!self.vim_enabled);
2768 Task::none()
2769 }
2770 Message::Tab if self.vim_accepts_insert_input() => {
2771 self.handle_tab()
2772 }
2773 Message::Enter if self.vim_accepts_insert_input() => {
2774 self.handle_enter()
2775 }
2776 Message::Tab | Message::Enter => Task::none(),
2777
2778 Message::Backspace if self.vim_accepts_insert_input() => {
2780 self.handle_backspace()
2781 }
2782 Message::Delete if self.vim_accepts_insert_input() => {
2783 self.handle_delete()
2784 }
2785 Message::Backspace | Message::Delete => Task::none(),
2786 Message::DeleteSelection => self.handle_delete_selection(),
2787
2788 Message::ArrowKey(direction, shift) => {
2790 self.handle_arrow_key(*direction, *shift)
2791 }
2792 Message::Home(shift) => self.handle_home(*shift),
2793 Message::End(shift) => self.handle_end(*shift),
2794 Message::CtrlHome => self.handle_ctrl_home(),
2795 Message::CtrlEnd => self.handle_ctrl_end(),
2796 Message::GotoPosition(line, col) => {
2797 self.handle_goto_position(*line, *col)
2798 }
2799 Message::OpenGotoLine => self.handle_open_goto_line_msg(),
2800 Message::CloseGotoLine => self.handle_close_goto_line_msg(),
2801 Message::GotoLineChanged(query) => {
2802 self.handle_goto_line_changed_msg(query)
2803 }
2804 Message::SubmitGotoLine => self.handle_submit_goto_line_msg(),
2805 Message::PageUp => self.handle_page_up(),
2806 Message::PageDown => self.handle_page_down(),
2807
2808 Message::MouseClick(point) => self.handle_mouse_click_msg(*point),
2810 Message::MouseDrag(point) => self.handle_mouse_drag_msg(*point),
2811 Message::MouseHover(point) => self.handle_mouse_drag_msg(*point),
2812 Message::MouseRelease => self.handle_mouse_release_msg(),
2813 Message::DoubleClick(point) => self.handle_double_click_msg(*point),
2814 Message::TripleClick(point) => self.handle_triple_click_msg(*point),
2815 Message::ContextMenuRequested(point) => {
2816 self.handle_context_menu_requested_msg(*point)
2817 }
2818 Message::WriteRequested
2819 | Message::CustomContextMenuAction(_)
2820 | Message::RevealInFileManager => Task::none(),
2821
2822 Message::Cut => self.handle_cut_msg(),
2824 Message::Copy => self.copy_selection(),
2825 Message::Paste(text) => self.handle_paste_msg(text),
2826 Message::SelectAll => self.handle_select_all_msg(),
2827
2828 Message::Undo => self.handle_undo_msg(),
2830 Message::Redo => self.handle_redo_msg(),
2831
2832 Message::OpenSearch => self.handle_open_search_msg(),
2834 Message::OpenSearchReplace => self.handle_open_search_replace_msg(),
2835 Message::CloseSearch => self.handle_close_search_msg(),
2836 Message::SearchQueryChanged(query) => {
2837 self.handle_search_query_changed_msg(query)
2838 }
2839 Message::ReplaceQueryChanged(text) => {
2840 self.handle_replace_query_changed_msg(text)
2841 }
2842 Message::ToggleCaseSensitive => {
2843 self.handle_toggle_case_sensitive_msg()
2844 }
2845 Message::FindNext => self.handle_find_next_msg(),
2846 Message::FindPrevious => self.handle_find_previous_msg(),
2847 Message::ReplaceNext => self.handle_replace_next_msg(),
2848 Message::ReplaceAll => self.handle_replace_all_msg(),
2849 Message::SearchDialogTab => self.handle_search_dialog_tab_msg(),
2850 Message::SearchDialogShiftTab => {
2851 self.handle_search_dialog_shift_tab_msg()
2852 }
2853 Message::FocusNavigationTab => self.handle_focus_navigation_tab(),
2854 Message::FocusNavigationShiftTab => {
2855 self.handle_focus_navigation_shift_tab()
2856 }
2857
2858 Message::CanvasFocusGained => self.handle_canvas_focus_gained_msg(),
2860 Message::CanvasFocusLost => self.handle_canvas_focus_lost_msg(),
2861 Message::ImeOpened if self.vim_accepts_insert_input() => {
2862 self.handle_ime_opened_msg()
2863 }
2864 Message::ImeOpened => Task::none(),
2865 Message::ImePreedit(content, selection) => {
2866 if self.vim_accepts_insert_input() {
2867 self.handle_ime_preedit_msg(content, selection)
2868 } else {
2869 Task::none()
2870 }
2871 }
2872 Message::ImeCommit(text) => self.handle_ime_commit_msg(text),
2873 Message::ImeClosed => self.handle_ime_closed_msg(),
2874
2875 Message::Tick => self.handle_tick_msg(),
2877 Message::Scrolled(viewport) => self.handle_scrolled_msg(*viewport),
2878 Message::HorizontalScrolled(viewport) => {
2879 self.handle_horizontal_scrolled_msg(*viewport)
2880 }
2881
2882 Message::JumpClick(_point) => Task::none(),
2886
2887 Message::AltClick(point) => self.handle_alt_click_msg(*point),
2889 Message::AddCursorAbove => self.handle_add_cursor_above_msg(),
2890 Message::AddCursorBelow => self.handle_add_cursor_below_msg(),
2891 Message::SelectNextOccurrence => {
2892 self.handle_select_next_occurrence_msg()
2893 }
2894 Message::ToggleFold(header_line) => {
2895 self.toggle_fold(*header_line);
2896 Task::none()
2897 }
2898 Message::ToggleFoldAtCursor => {
2899 self.toggle_fold_at(self.cursors.primary_position().0);
2900 Task::none()
2901 }
2902 Message::FoldAll => {
2903 self.fold_all();
2904 Task::none()
2905 }
2906 Message::UnfoldAll => {
2907 self.unfold_all();
2908 Task::none()
2909 }
2910
2911 Message::MoveLineUp => self.move_lines(false),
2913 Message::MoveLineDown => self.move_lines(true),
2914 Message::DuplicateLineUp => self.duplicate_lines(false),
2915 Message::DuplicateLineDown => self.duplicate_lines(true),
2916 Message::ToggleComment => self.toggle_comment(),
2917 }
2918 }
2919}
2920
2921#[cfg(test)]
2922mod tests {
2923 use super::*;
2924 use crate::canvas_editor::{ArrowDirection, VimMode};
2925 use std::cell::RefCell;
2926 use std::rc::Rc;
2927
2928 fn vim_keys(editor: &mut CodeEditor, keys: &str) {
2929 for key in keys.chars() {
2930 let _ = editor.update(&Message::VimKey(key));
2931 }
2932 }
2933
2934 fn focus_editor(editor: &mut CodeEditor) {
2935 editor.request_focus();
2936 editor.has_canvas_focus = true;
2937 editor.focus_locked = false;
2938 }
2939
2940 fn assert_vim_delete(
2941 content: &str,
2942 cursor: (usize, usize),
2943 keys: &str,
2944 expected: &str,
2945 register: &str,
2946 ) {
2947 let mut editor = CodeEditor::new(content, "txt").with_vim_enabled(true);
2948 editor.cursors.set_single(cursor);
2949 vim_keys(&mut editor, keys);
2950 assert_eq!(editor.content(), expected, "keys: {keys}");
2951 assert_eq!(editor.vim_state.register.text, register, "keys: {keys}");
2952 }
2953
2954 #[derive(Default)]
2955 struct VimTestLspClient {
2956 changes: Rc<RefCell<Vec<Vec<lsp::LspTextChange>>>>,
2957 }
2958
2959 impl lsp::LspClient for VimTestLspClient {
2960 fn did_change(
2961 &mut self,
2962 _document: &lsp::LspDocument,
2963 changes: &[lsp::LspTextChange],
2964 ) {
2965 self.changes.borrow_mut().push(changes.to_vec());
2966 }
2967 }
2968
2969 #[test]
2970 fn test_vim_navigation_normal_key_does_not_insert() {
2971 let mut editor = CodeEditor::new("abc", "txt").with_vim_enabled(true);
2972
2973 vim_keys(&mut editor, "l");
2974
2975 assert_eq!(editor.content(), "abc");
2976 assert_eq!(editor.cursors.primary_position(), (0, 1));
2977
2978 let mut standard = CodeEditor::new("abc", "txt");
2979 focus_editor(&mut standard);
2980 let _ = standard.update(&Message::CharacterInput('l'));
2981 assert_eq!(standard.content(), "labc");
2982 }
2983
2984 #[test]
2985 fn test_vim_navigation_insert_and_escape_round_trip() {
2986 let mut editor = CodeEditor::new("abc", "txt").with_vim_enabled(true);
2987 focus_editor(&mut editor);
2988
2989 vim_keys(&mut editor, "i");
2990 assert_eq!(editor.vim_mode(), Some(VimMode::Insert));
2991 let _ = editor.update(&Message::CharacterInput('X'));
2992 assert_eq!(editor.content(), "Xabc");
2993
2994 let _ = editor.update(&Message::VimKey('\u{1b}'));
2995 assert_eq!(editor.vim_mode(), Some(VimMode::Normal));
2996 vim_keys(&mut editor, "l");
2997 assert_eq!(editor.content(), "Xabc");
2998 assert_eq!(editor.cursors.primary_position(), (0, 1));
2999 }
3000
3001 #[test]
3002 fn test_vim_navigation_counted_word_and_line_motions() {
3003 let mut editor =
3004 CodeEditor::new("one two\nthree four\nfive six", "txt")
3005 .with_vim_enabled(true);
3006
3007 vim_keys(&mut editor, "2w");
3008 assert_eq!(editor.cursors.primary_position(), (1, 0));
3009 vim_keys(&mut editor, "e");
3010 assert_eq!(editor.cursors.primary_position(), (1, 4));
3011 vim_keys(&mut editor, "b");
3012 assert_eq!(editor.cursors.primary_position(), (1, 0));
3013 vim_keys(&mut editor, "G");
3014 assert_eq!(editor.cursors.primary_position(), (2, 0));
3015 vim_keys(&mut editor, "gg");
3016 assert_eq!(editor.cursors.primary_position(), (0, 0));
3017 vim_keys(&mut editor, "2j");
3018 assert_eq!(editor.cursors.primary_position(), (2, 0));
3019 vim_keys(&mut editor, "k$");
3020 assert_eq!(editor.cursors.primary_position(), (1, 9));
3021 vim_keys(&mut editor, "0");
3022 assert_eq!(editor.cursors.primary_position(), (1, 0));
3023
3024 let mut folded = CodeEditor::new(
3025 "fn main() {\n let x = 1;\n if x > 0 {\n print();\n }\n}",
3026 "rs",
3027 )
3028 .with_vim_enabled(true);
3029 folded.toggle_fold(0);
3030 vim_keys(&mut folded, "j");
3031 assert_eq!(folded.cursors.primary_position(), (5, 0));
3032 }
3033
3034 #[test]
3035 fn test_vim_navigation_visual_and_visual_line_ranges() {
3036 let mut editor = CodeEditor::new("abcd\nefgh\nijkl\nmnop", "txt")
3037 .with_vim_enabled(true);
3038 editor.cursors.set_single((0, 1));
3039
3040 vim_keys(&mut editor, "vl");
3041 assert_eq!(editor.vim_mode(), Some(VimMode::Visual));
3042 assert_eq!(
3043 editor.cursors.primary().selection_range(),
3044 Some(((0, 1), (0, 3)))
3045 );
3046
3047 let _ = editor.update(&Message::VimKey('\u{1b}'));
3048 assert_eq!(editor.vim_mode(), Some(VimMode::Normal));
3049 assert!(editor.cursors.primary().anchor.is_none());
3050
3051 editor.cursors.set_single((1, 2));
3052 vim_keys(&mut editor, "Vj");
3053 assert_eq!(editor.vim_mode(), Some(VimMode::VisualLine));
3054 assert_eq!(
3055 editor.cursors.primary().selection_range(),
3056 Some(((1, 0), (3, 0)))
3057 );
3058 }
3059
3060 #[test]
3061 fn test_vim_navigation_unicode_and_empty_line_bounds() {
3062 let mut editor =
3063 CodeEditor::new("你🙂好\n\nz", "txt").with_vim_enabled(true);
3064
3065 vim_keys(&mut editor, "lll");
3066 assert_eq!(editor.cursors.primary_position(), (0, 2));
3067 vim_keys(&mut editor, "j");
3068 assert_eq!(editor.cursors.primary_position(), (1, 0));
3069 vim_keys(&mut editor, "j");
3070 assert_eq!(editor.cursors.primary_position(), (2, 0));
3071 vim_keys(&mut editor, "k");
3072 assert_eq!(editor.cursors.primary_position(), (1, 0));
3073 vim_keys(&mut editor, "k$");
3074 assert_eq!(editor.cursors.primary_position(), (0, 2));
3075 vim_keys(&mut editor, "0");
3076 assert_eq!(editor.cursors.primary_position(), (0, 0));
3077 }
3078
3079 #[test]
3080 fn test_vim_navigation_ime_only_commits_in_insert() {
3081 let mut editor = CodeEditor::new("abc", "txt").with_vim_enabled(true);
3082
3083 let _ = editor.update(&Message::ImeCommit("中".to_owned()));
3084 assert_eq!(editor.content(), "abc");
3085
3086 vim_keys(&mut editor, "i");
3087 let _ = editor.update(&Message::ImeCommit("中".to_owned()));
3088 assert_eq!(editor.content(), "中abc");
3089
3090 let mut standard = CodeEditor::new("abc", "txt");
3091 let _ = standard.update(&Message::ImeCommit("中".to_owned()));
3092 assert_eq!(standard.content(), "中abc");
3093 }
3094
3095 #[test]
3096 fn test_vim_navigation_collapses_and_blocks_extra_cursors() {
3097 let mut editor = CodeEditor::new("same\nsame\nsame", "txt");
3098 editor.cursors.add_cursor((1, 0));
3099 assert_eq!(editor.cursors.len(), 2);
3100
3101 editor.set_vim_enabled(true);
3102 assert_eq!(editor.cursors.len(), 1);
3103
3104 let _ = editor.update(&Message::AddCursorBelow);
3105 let _ = editor.update(&Message::SelectNextOccurrence);
3106 let _ = editor.update(&Message::SelectNextOccurrence);
3107 let _ = editor.update(&Message::AltClick(iced::Point::new(
3108 editor.gutter_width() + 5.0,
3109 editor.line_height,
3110 )));
3111 assert_eq!(editor.cursors.len(), 1);
3112 }
3113
3114 #[test]
3115 fn test_vim_editing_x_and_count() {
3116 let mut editor =
3117 CodeEditor::new("abcdef", "txt").with_vim_enabled(true);
3118
3119 vim_keys(&mut editor, "x");
3120 assert_eq!(editor.content(), "bcdef");
3121 assert_eq!(editor.vim_state.register.text, "a");
3122 assert_eq!(
3123 editor.vim_state.register.kind,
3124 super::super::vim::VimRegisterKind::Characterwise
3125 );
3126
3127 vim_keys(&mut editor, "2x");
3128 assert_eq!(editor.content(), "def");
3129 assert_eq!(editor.vim_state.register.text, "bc");
3130 assert_eq!(editor.cursors.primary_position(), (0, 0));
3131 }
3132
3133 #[test]
3134 fn test_vim_editing_delete_change_yank_motions() {
3135 let mut deleted =
3136 CodeEditor::new("one two three", "txt").with_vim_enabled(true);
3137 vim_keys(&mut deleted, "dw");
3138 assert_eq!(deleted.content(), "two three");
3139 assert_eq!(deleted.vim_state.register.text, "one ");
3140
3141 let mut yanked =
3142 CodeEditor::new("one two", "txt").with_vim_enabled(true);
3143 vim_keys(&mut yanked, "yw");
3144 assert_eq!(yanked.content(), "one two");
3145 assert_eq!(yanked.vim_state.register.text, "one ");
3146
3147 let mut changed =
3148 CodeEditor::new("one two", "txt").with_vim_enabled(true);
3149 focus_editor(&mut changed);
3150 vim_keys(&mut changed, "ce");
3151 assert_eq!(changed.content(), " two");
3152 assert_eq!(changed.vim_state.register.text, "one");
3153 assert_eq!(changed.vim_mode(), Some(VimMode::Insert));
3154 let _ = changed.update(&Message::CharacterInput('X'));
3155 vim_keys(&mut changed, "\u{1b}");
3156 assert_eq!(changed.content(), "X two");
3157
3158 assert_vim_delete("abc", (0, 2), "dh", "ac", "b");
3159 assert_vim_delete("abc", (0, 1), "dl", "ac", "b");
3160 assert_vim_delete("abc def", (0, 6), "db", "abc f", "de");
3161 assert_vim_delete("abcdef", (0, 3), "d0", "def", "abc");
3162 assert_vim_delete(" abc", (0, 4), "d^", " c", "ab");
3163 assert_vim_delete("abcde", (0, 2), "d$", "ab", "cde");
3164 assert_vim_delete(
3165 "one\ntwo\nthree\nfour",
3166 (2, 0),
3167 "dgg",
3168 "four",
3169 "one\ntwo\nthree\n",
3170 );
3171 assert_vim_delete(
3172 "one\ntwo\nthree\nfour",
3173 (1, 0),
3174 "dG",
3175 "one",
3176 "two\nthree\nfour\n",
3177 );
3178 }
3179
3180 #[test]
3181 fn test_vim_editing_doubled_line_operators() {
3182 let mut deleted =
3183 CodeEditor::new("one\ntwo\nthree", "txt").with_vim_enabled(true);
3184 vim_keys(&mut deleted, "dd");
3185 assert_eq!(deleted.content(), "two\nthree");
3186 assert_eq!(deleted.vim_state.register.text, "one\n");
3187 assert_eq!(
3188 deleted.vim_state.register.kind,
3189 super::super::vim::VimRegisterKind::Linewise
3190 );
3191
3192 let mut yanked =
3193 CodeEditor::new("one\ntwo\nthree", "txt").with_vim_enabled(true);
3194 yanked.cursors.set_single((1, 1));
3195 vim_keys(&mut yanked, "yy");
3196 assert_eq!(yanked.content(), "one\ntwo\nthree");
3197 assert_eq!(yanked.vim_state.register.text, "two\n");
3198 assert_eq!(
3199 yanked.vim_state.register.kind,
3200 super::super::vim::VimRegisterKind::Linewise
3201 );
3202
3203 let mut changed =
3204 CodeEditor::new("one\ntwo\nthree", "txt").with_vim_enabled(true);
3205 changed.cursors.set_single((1, 1));
3206 vim_keys(&mut changed, "cc");
3207 assert_eq!(changed.content(), "one\nthree");
3208 assert_eq!(changed.vim_state.register.text, "two\n");
3209 assert_eq!(changed.vim_mode(), Some(VimMode::Insert));
3210
3211 assert_vim_delete(
3212 "one\ntwo\nthree\nfour",
3213 (1, 0),
3214 "dk",
3215 "three\nfour",
3216 "one\ntwo\n",
3217 );
3218 assert_vim_delete(
3219 "one\ntwo\nthree\nfour",
3220 (1, 0),
3221 "dj",
3222 "one\nfour",
3223 "two\nthree\n",
3224 );
3225 assert_vim_delete(
3226 "one\ntwo\nthree",
3227 (0, 0),
3228 "2dd",
3229 "three",
3230 "one\ntwo\n",
3231 );
3232 }
3233
3234 #[test]
3235 fn test_vim_editing_visual_operators() {
3236 let mut deleted =
3237 CodeEditor::new("abcd\nefgh", "txt").with_vim_enabled(true);
3238 deleted.cursors.set_single((0, 1));
3239 vim_keys(&mut deleted, "vld");
3240 assert_eq!(deleted.content(), "ad\nefgh");
3241 assert_eq!(deleted.vim_state.register.text, "bc");
3242 assert_eq!(
3243 deleted.vim_state.register.kind,
3244 super::super::vim::VimRegisterKind::Characterwise
3245 );
3246 assert_eq!(deleted.vim_mode(), Some(VimMode::Normal));
3247
3248 let mut yanked =
3249 CodeEditor::new("one\ntwo\nthree", "txt").with_vim_enabled(true);
3250 yanked.cursors.set_single((1, 1));
3251 vim_keys(&mut yanked, "Vjy");
3252 assert_eq!(yanked.content(), "one\ntwo\nthree");
3253 assert_eq!(yanked.vim_state.register.text, "two\nthree\n");
3254 assert_eq!(
3255 yanked.vim_state.register.kind,
3256 super::super::vim::VimRegisterKind::Linewise
3257 );
3258 assert_eq!(yanked.vim_mode(), Some(VimMode::Normal));
3259
3260 let mut changed = CodeEditor::new("abcd", "txt").with_vim_enabled(true);
3261 focus_editor(&mut changed);
3262 changed.cursors.set_single((0, 1));
3263 vim_keys(&mut changed, "vlc");
3264 let _ = changed.update(&Message::CharacterInput('X'));
3265 vim_keys(&mut changed, "\u{1b}");
3266 assert_eq!(changed.content(), "aXd");
3267 assert_eq!(changed.vim_state.register.text, "bc");
3268 assert_eq!(changed.history.undo_count(), 1);
3269 }
3270
3271 #[test]
3272 fn test_vim_editing_characterwise_and_linewise_paste() {
3273 let mut characterwise =
3274 CodeEditor::new("abc", "txt").with_vim_enabled(true);
3275 vim_keys(&mut characterwise, "yl2lp");
3276 assert_eq!(characterwise.content(), "abca");
3277 assert_eq!(characterwise.cursors.primary_position(), (0, 3));
3278
3279 let mut characterwise_before =
3280 CodeEditor::new("abc", "txt").with_vim_enabled(true);
3281 vim_keys(&mut characterwise_before, "yl2lP");
3282 assert_eq!(characterwise_before.content(), "abac");
3283 assert_eq!(characterwise_before.cursors.primary_position(), (0, 2));
3284
3285 let mut linewise =
3286 CodeEditor::new("one\ntwo", "txt").with_vim_enabled(true);
3287 vim_keys(&mut linewise, "yyp");
3288 assert_eq!(linewise.content(), "one\none\ntwo");
3289 assert_eq!(linewise.cursors.primary_position(), (1, 0));
3290
3291 let mut linewise_before =
3292 CodeEditor::new("one\ntwo", "txt").with_vim_enabled(true);
3293 linewise_before.cursors.set_single((1, 0));
3294 vim_keys(&mut linewise_before, "yyP");
3295 assert_eq!(linewise_before.content(), "one\ntwo\ntwo");
3296 assert_eq!(linewise_before.cursors.primary_position(), (1, 0));
3297 }
3298
3299 #[test]
3300 fn test_vim_editing_operator_counts_multiply() {
3301 let mut editor =
3302 CodeEditor::new("one two three four five six seven", "txt")
3303 .with_vim_enabled(true);
3304
3305 vim_keys(&mut editor, "2d3w");
3306
3307 assert_eq!(editor.content(), "seven");
3308 assert_eq!(
3309 editor.vim_state.register.text,
3310 "one two three four five six "
3311 );
3312 }
3313
3314 #[test]
3315 fn test_vim_editing_undo_redo_is_one_command() {
3316 let original = "one two three";
3317 let mut editor =
3318 CodeEditor::new(original, "txt").with_vim_enabled(true);
3319 focus_editor(&mut editor);
3320
3321 vim_keys(&mut editor, "cw");
3322 let _ = editor.update(&Message::CharacterInput('X'));
3323 let _ = editor.update(&Message::CharacterInput('Y'));
3324 vim_keys(&mut editor, "\u{1b}");
3325 assert_eq!(editor.content(), "XYtwo three");
3326 assert_eq!(editor.history.undo_count(), 1);
3327
3328 vim_keys(&mut editor, "u");
3329 assert_eq!(editor.content(), original);
3330 assert_eq!(editor.history.redo_count(), 1);
3331
3332 vim_keys(&mut editor, "\u{12}");
3333 assert_eq!(editor.content(), "XYtwo three");
3334 assert_eq!(editor.history.undo_count(), 1);
3335
3336 let mut opened = CodeEditor::new("one", "txt").with_vim_enabled(true);
3337 focus_editor(&mut opened);
3338 vim_keys(&mut opened, "o");
3339 let _ = opened.update(&Message::CharacterInput('X'));
3340 vim_keys(&mut opened, "\u{1b}");
3341 assert_eq!(opened.content(), "one\nX");
3342 assert_eq!(opened.history.undo_count(), 1);
3343 vim_keys(&mut opened, "u");
3344 assert_eq!(opened.content(), "one");
3345 }
3346
3347 #[test]
3348 fn test_vim_editing_emits_incremental_lsp_change() {
3349 let changes = Rc::new(RefCell::new(Vec::new()));
3350 let client = VimTestLspClient { changes: Rc::clone(&changes) };
3351 let content = (0..10)
3352 .map(|line| format!("line{line}"))
3353 .collect::<Vec<_>>()
3354 .join("\n");
3355 let mut editor = CodeEditor::new(&content, "rs").with_vim_enabled(true);
3356 editor.attach_lsp(
3357 Box::new(client),
3358 lsp::LspDocument::new("file:///vim.rs", "rust"),
3359 );
3360 editor.cursors.set_single((5, 2));
3361
3362 vim_keys(&mut editor, "x");
3363
3364 let changes = changes.borrow();
3365 assert_eq!(changes.len(), 1);
3366 assert_eq!(changes[0].len(), 1);
3367 let change = &changes[0][0];
3368 assert_eq!(change.range.start.line, 4);
3369 assert_eq!(change.range.start.character, 0);
3370 assert_eq!(change.range.end.line, 7);
3371 assert_eq!(change.range.end.character, 0);
3372 assert_eq!(change.text, "line4\nlie5\nline6\n");
3373 }
3374
3375 #[test]
3376 fn test_horizontal_scroll_initial_state() {
3377 let editor = CodeEditor::new("short line", "rs");
3378 assert!(
3379 (editor.horizontal_scroll_offset - 0.0).abs() < f32::EPSILON,
3380 "Initial horizontal scroll offset should be 0"
3381 );
3382 }
3383
3384 #[test]
3385 fn test_set_wrap_enabled_resets_horizontal_offset() {
3386 let mut editor = CodeEditor::new("long line", "rs");
3387 editor.wrap_enabled = false;
3388 editor.horizontal_scroll_offset = 100.0;
3390
3391 editor.set_wrap_enabled(true);
3393 assert!(
3394 (editor.horizontal_scroll_offset - 0.0).abs() < f32::EPSILON,
3395 "Horizontal scroll offset should be reset when wrap is re-enabled"
3396 );
3397 }
3398
3399 #[test]
3400 fn test_canvas_focus_lost() {
3401 let mut editor = CodeEditor::new("test", "rs");
3402 editor.has_canvas_focus = true;
3403
3404 let _ = editor.update(&Message::CanvasFocusLost);
3405
3406 assert!(!editor.has_canvas_focus);
3407 assert!(!editor.show_cursor);
3408 assert!(editor.focus_locked, "Focus should be locked when lost");
3409 }
3410
3411 #[test]
3412 fn test_canvas_focus_gained_resets_lock() {
3413 let mut editor = CodeEditor::new("test", "rs");
3414 editor.has_canvas_focus = false;
3415 editor.focus_locked = true;
3416
3417 let _ = editor.update(&Message::CanvasFocusGained);
3418
3419 assert!(editor.has_canvas_focus);
3420 assert!(
3421 !editor.focus_locked,
3422 "Focus lock should be reset when focus is gained"
3423 );
3424 }
3425
3426 #[test]
3427 fn test_focus_lock_state() {
3428 let mut editor = CodeEditor::new("test", "rs");
3429
3430 assert!(!editor.focus_locked);
3432
3433 let _ = editor.update(&Message::CanvasFocusLost);
3435 assert!(editor.focus_locked, "Focus should be locked when lost");
3436
3437 editor.request_focus();
3439 let _ = editor.update(&Message::CanvasFocusGained);
3440 assert!(!editor.focus_locked, "Focus should be unlocked when regained");
3441
3442 editor.focus_locked = true;
3444 editor.reset_focus_lock();
3445 assert!(!editor.focus_locked, "Focus lock should be resetable");
3446 }
3447
3448 #[test]
3449 fn test_reset_focus_lock() {
3450 let mut editor = CodeEditor::new("test", "rs");
3451 editor.focus_locked = true;
3452
3453 editor.reset_focus_lock();
3454
3455 assert!(!editor.focus_locked);
3456 }
3457
3458 #[test]
3459 fn test_home_key() {
3460 let mut editor = CodeEditor::new("hello world", "py");
3461 editor.cursors.primary_mut().position = (0, 5); let _ = editor.update(&Message::Home(false));
3463 assert_eq!(editor.cursors.primary_position(), (0, 0));
3464 }
3465
3466 #[test]
3467 fn test_end_key() {
3468 let mut editor = CodeEditor::new("hello world", "py");
3469 editor.cursors.primary_mut().position = (0, 0);
3470 let _ = editor.update(&Message::End(false));
3471 assert_eq!(editor.cursors.primary_position(), (0, 11)); }
3473
3474 #[test]
3475 fn test_arrow_key_with_shift_creates_selection() {
3476 let mut editor = CodeEditor::new("hello world", "py");
3477 editor.cursors.primary_mut().position = (0, 0);
3478
3479 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, true));
3481 assert!(editor.cursors.primary().anchor.is_some());
3482 assert!(editor.cursors.primary().has_selection());
3483 }
3484
3485 #[test]
3486 fn test_arrow_key_without_shift_clears_selection() {
3487 let mut editor = CodeEditor::new("hello world", "py");
3488 editor.cursors.primary_mut().anchor = Some((0, 0));
3489 editor.cursors.primary_mut().position = (0, 5);
3490
3491 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Right, false));
3493 assert!(editor.cursors.primary().anchor.is_none());
3494 assert!(!editor.cursors.primary().has_selection());
3495 }
3496
3497 #[test]
3498 fn test_typing_with_selection() {
3499 let mut editor = CodeEditor::new("hello world", "py");
3500 editor.request_focus();
3502 editor.has_canvas_focus = true;
3503 editor.focus_locked = false;
3504
3505 editor.cursors.primary_mut().anchor = Some((0, 0));
3506 editor.cursors.primary_mut().position = (0, 5);
3507
3508 let _ = editor.update(&Message::CharacterInput('X'));
3509 assert_eq!(editor.buffer.line(0), "X world");
3510 assert_eq!(editor.cursors.primary_position(), (0, 1));
3511 assert!(!editor.cursors.primary().has_selection());
3512 }
3513
3514 #[test]
3515 fn test_typing_digit_with_reversed_selection_replaces_selection() {
3516 let mut editor = CodeEditor::new("hello world", "py");
3517 editor.request_focus();
3518 editor.has_canvas_focus = true;
3519 editor.focus_locked = false;
3520
3521 editor.cursors.primary_mut().anchor = Some((0, 5));
3522 editor.cursors.primary_mut().position = (0, 0);
3523
3524 let _ = editor.update(&Message::CharacterInput('7'));
3525 assert_eq!(editor.buffer.line(0), "7 world");
3526 assert_eq!(editor.cursors.primary_position(), (0, 1));
3527 assert!(!editor.cursors.primary().has_selection());
3528 }
3529
3530 #[test]
3531 fn test_typing_with_selection_undoes_as_single_edit() {
3532 let mut editor = CodeEditor::new("hello world", "py");
3533 editor.request_focus();
3534 editor.has_canvas_focus = true;
3535 editor.focus_locked = false;
3536
3537 editor.cursors.primary_mut().anchor = Some((0, 0));
3538 editor.cursors.primary_mut().position = (0, 5);
3539
3540 let _ = editor.update(&Message::CharacterInput('X'));
3541 let _ = editor.update(&Message::Undo);
3542 assert_eq!(editor.buffer.line(0), "hello world");
3543 }
3544
3545 #[test]
3546 fn test_ctrl_home() {
3547 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
3548 editor.cursors.primary_mut().position = (2, 5); let _ = editor.update(&Message::CtrlHome);
3550 assert_eq!(editor.cursors.primary_position(), (0, 0)); }
3552
3553 #[test]
3554 fn test_ctrl_end() {
3555 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
3556 editor.cursors.primary_mut().position = (0, 0); let _ = editor.update(&Message::CtrlEnd);
3558 assert_eq!(editor.cursors.primary_position(), (2, 5)); }
3560
3561 #[test]
3562 fn test_ctrl_home_clears_selection() {
3563 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
3564 editor.cursors.primary_mut().position = (2, 5);
3565 editor.cursors.primary_mut().anchor = Some((0, 0));
3566 editor.cursors.primary_mut().position = (2, 5);
3567
3568 let _ = editor.update(&Message::CtrlHome);
3569 assert_eq!(editor.cursors.primary_position(), (0, 0));
3570 assert!(editor.cursors.primary().anchor.is_none());
3571 assert!(!editor.cursors.primary().has_selection());
3572 }
3573
3574 #[test]
3575 fn test_ctrl_end_clears_selection() {
3576 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
3577 editor.cursors.primary_mut().position = (0, 0);
3578 editor.cursors.primary_mut().anchor = Some((0, 0));
3579 editor.cursors.primary_mut().position = (1, 3);
3580
3581 let _ = editor.update(&Message::CtrlEnd);
3582 assert_eq!(editor.cursors.primary_position(), (2, 5));
3583 assert!(editor.cursors.primary().anchor.is_none());
3584 assert!(!editor.cursors.primary().has_selection());
3585 }
3586
3587 #[test]
3588 fn test_goto_position_sets_cursor_and_clears_selection() {
3589 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
3590 editor.cursors.primary_mut().anchor = Some((0, 0));
3591 editor.cursors.primary_mut().position = (1, 2);
3592
3593 let _ = editor.update(&Message::GotoPosition(1, 3));
3594
3595 assert_eq!(editor.cursors.primary_position(), (1, 3));
3596 assert!(editor.cursors.primary().anchor.is_none());
3597 assert!(!editor.cursors.primary().has_selection());
3598 }
3599
3600 #[test]
3601 fn test_goto_position_clamps_out_of_range() {
3602 let mut editor = CodeEditor::new("a\nbb", "py");
3603
3604 let _ = editor.update(&Message::GotoPosition(99, 99));
3605
3606 assert_eq!(editor.cursors.primary_position(), (1, 2));
3608 }
3609
3610 #[test]
3611 fn test_scroll_sets_initial_cache_window() {
3612 let content =
3613 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
3614 let mut editor = CodeEditor::new(&content, "py");
3615
3616 let height = 400.0;
3618 let width = 800.0;
3619 let scroll = 0.0;
3620
3621 let visible_lines_count =
3623 (height / editor.line_height).ceil() as usize + 2;
3624 let first_visible_line = (scroll / editor.line_height).floor() as usize;
3625 let last_visible_line = first_visible_line + visible_lines_count;
3626 let margin = visible_lines_count * 2;
3627 let window_start = first_visible_line.saturating_sub(margin);
3628 let window_end = last_visible_line + margin;
3629
3630 editor.viewport_height = height;
3632 editor.viewport_width = width;
3633 editor.viewport_scroll = -1.0;
3634 let scroll_changed = (editor.viewport_scroll - scroll).abs() > 0.1;
3635 let need_rewindow = true;
3636 if (editor.viewport_height - height).abs() > 1.0
3637 || (editor.viewport_width - width).abs() > 1.0
3638 || (scroll_changed && need_rewindow)
3639 {
3640 editor.cache_window_start_line = window_start;
3641 editor.cache_window_end_line = window_end;
3642 editor.last_first_visible_line = first_visible_line;
3643 }
3644 editor.viewport_scroll = scroll;
3645
3646 assert_eq!(editor.last_first_visible_line, first_visible_line);
3647 assert!(editor.cache_window_end_line > editor.cache_window_start_line);
3648 assert_eq!(editor.cache_window_start_line, window_start);
3649 assert_eq!(editor.cache_window_end_line, window_end);
3650 }
3651
3652 #[test]
3653 fn test_small_scroll_keeps_window() {
3654 let content =
3655 (0..200).map(|i| format!("line{}\n", i)).collect::<String>();
3656 let mut editor = CodeEditor::new(&content, "py");
3657 let height = 400.0;
3658 let width = 800.0;
3659 let initial_scroll = 0.0;
3660 let visible_lines_count =
3661 (height / editor.line_height).ceil() as usize + 2;
3662 let first_visible_line =
3663 (initial_scroll / editor.line_height).floor() as usize;
3664 let last_visible_line = first_visible_line + visible_lines_count;
3665 let margin = visible_lines_count * 2;
3666 let window_start = first_visible_line.saturating_sub(margin);
3667 let window_end = last_visible_line + margin;
3668 editor.cache_window_start_line = window_start;
3669 editor.cache_window_end_line = window_end;
3670 editor.viewport_height = height;
3671 editor.viewport_width = width;
3672 editor.viewport_scroll = initial_scroll;
3673
3674 let small_scroll =
3676 editor.line_height * (visible_lines_count as f32 / 4.0);
3677 let first_visible_line2 =
3678 (small_scroll / editor.line_height).floor() as usize;
3679 let last_visible_line2 = first_visible_line2 + visible_lines_count;
3680 let lower_boundary_trigger = editor.cache_window_start_line > 0
3681 && first_visible_line2
3682 < editor
3683 .cache_window_start_line
3684 .saturating_add(visible_lines_count / 2);
3685 let upper_boundary_trigger = last_visible_line2
3686 > editor
3687 .cache_window_end_line
3688 .saturating_sub(visible_lines_count / 2);
3689 let need_rewindow = lower_boundary_trigger || upper_boundary_trigger;
3690
3691 assert!(!need_rewindow, "Small scroll should be inside the window");
3692 assert_eq!(editor.cache_window_start_line, window_start);
3694 assert_eq!(editor.cache_window_end_line, window_end);
3695 }
3696
3697 #[test]
3698 fn test_large_scroll_rewindows() {
3699 let content =
3700 (0..1000).map(|i| format!("line{}\n", i)).collect::<String>();
3701 let mut editor = CodeEditor::new(&content, "py");
3702 let height = 400.0;
3703 let width = 800.0;
3704 let initial_scroll = 0.0;
3705 let visible_lines_count =
3706 (height / editor.line_height).ceil() as usize + 2;
3707 let first_visible_line =
3708 (initial_scroll / editor.line_height).floor() as usize;
3709 let last_visible_line = first_visible_line + visible_lines_count;
3710 let margin = visible_lines_count * 2;
3711 editor.cache_window_start_line =
3712 first_visible_line.saturating_sub(margin);
3713 editor.cache_window_end_line = last_visible_line + margin;
3714 editor.viewport_height = height;
3715 editor.viewport_width = width;
3716 editor.viewport_scroll = initial_scroll;
3717
3718 let large_scroll =
3720 editor.line_height * ((visible_lines_count * 4) as f32);
3721 let first_visible_line2 =
3722 (large_scroll / editor.line_height).floor() as usize;
3723 let last_visible_line2 = first_visible_line2 + visible_lines_count;
3724 let window_start2 = first_visible_line2.saturating_sub(margin);
3725 let window_end2 = last_visible_line2 + margin;
3726 let need_rewindow = first_visible_line2
3727 < editor
3728 .cache_window_start_line
3729 .saturating_add(visible_lines_count / 2)
3730 || last_visible_line2
3731 > editor
3732 .cache_window_end_line
3733 .saturating_sub(visible_lines_count / 2);
3734 assert!(need_rewindow, "Large scroll should trigger window update");
3735
3736 editor.cache_window_start_line = window_start2;
3738 editor.cache_window_end_line = window_end2;
3739 editor.last_first_visible_line = first_visible_line2;
3740
3741 assert_eq!(editor.cache_window_start_line, window_start2);
3742 assert_eq!(editor.cache_window_end_line, window_end2);
3743 assert_eq!(editor.last_first_visible_line, first_visible_line2);
3744 }
3745
3746 #[test]
3747 fn test_delete_selection_message() {
3748 let mut editor = CodeEditor::new("hello world", "py");
3749 editor.cursors.primary_mut().position = (0, 0);
3750 editor.cursors.primary_mut().anchor = Some((0, 0));
3751 editor.cursors.primary_mut().position = (0, 5);
3752
3753 let _ = editor.update(&Message::DeleteSelection);
3754 assert_eq!(editor.buffer.line(0), " world");
3755 assert_eq!(editor.cursors.primary_position(), (0, 0));
3756 assert!(editor.cursors.primary().anchor.is_none());
3757 assert!(!editor.cursors.primary().has_selection());
3758 }
3759
3760 #[test]
3761 fn test_delete_selection_multiline() {
3762 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
3763 editor.cursors.primary_mut().position = (0, 2);
3764 editor.cursors.primary_mut().anchor = Some((0, 2));
3765 editor.cursors.primary_mut().position = (2, 2);
3766
3767 let _ = editor.update(&Message::DeleteSelection);
3768 assert_eq!(editor.buffer.line(0), "line3");
3769 assert_eq!(editor.cursors.primary_position(), (0, 2));
3770 assert!(editor.cursors.primary().anchor.is_none());
3771 }
3772
3773 #[test]
3774 fn test_delete_selection_no_selection() {
3775 let mut editor = CodeEditor::new("hello world", "py");
3776 editor.cursors.primary_mut().position = (0, 5);
3777
3778 let _ = editor.update(&Message::DeleteSelection);
3779 assert_eq!(editor.buffer.line(0), "hello world");
3781 assert_eq!(editor.cursors.primary_position(), (0, 5));
3782 }
3783
3784 #[test]
3785 #[allow(clippy::unwrap_used)]
3786 fn test_ime_preedit_and_commit_chinese() {
3787 let mut editor = CodeEditor::new("", "py");
3788 let _ = editor.update(&Message::ImeOpened);
3790 assert!(editor.ime_preedit.is_none());
3791
3792 let content = "安全与合规".to_string();
3794 let selection = Some(0..3); let _ = editor
3796 .update(&Message::ImePreedit(content.clone(), selection.clone()));
3797
3798 assert!(editor.ime_preedit.is_some());
3799 assert_eq!(
3800 editor.ime_preedit.as_ref().unwrap().content.clone(),
3801 content
3802 );
3803 assert_eq!(
3804 editor.ime_preedit.as_ref().unwrap().selection.clone(),
3805 selection
3806 );
3807
3808 let _ = editor.update(&Message::ImeCommit("安全与合规".to_string()));
3810 assert!(editor.ime_preedit.is_none());
3811 assert_eq!(editor.buffer.line(0), "安全与合规");
3812 assert_eq!(
3813 editor.cursors.primary_position(),
3814 (0, "安全与合规".chars().count())
3815 );
3816 }
3817
3818 #[test]
3819 fn test_undo_char_insert() {
3820 let mut editor = CodeEditor::new("hello", "py");
3821 editor.request_focus();
3823 editor.has_canvas_focus = true;
3824 editor.focus_locked = false;
3825
3826 editor.cursors.primary_mut().position = (0, 5);
3827
3828 let _ = editor.update(&Message::CharacterInput('!'));
3830 assert_eq!(editor.buffer.line(0), "hello!");
3831 assert_eq!(editor.cursors.primary_position(), (0, 6));
3832
3833 editor.history.end_group();
3835 let _ = editor.update(&Message::Undo);
3836 assert_eq!(editor.buffer.line(0), "hello");
3837 assert_eq!(editor.cursors.primary_position(), (0, 5));
3838 }
3839
3840 #[test]
3841 fn test_undo_redo_char_insert() {
3842 let mut editor = CodeEditor::new("hello", "py");
3843 editor.request_focus();
3845 editor.has_canvas_focus = true;
3846 editor.focus_locked = false;
3847
3848 editor.cursors.primary_mut().position = (0, 5);
3849
3850 let _ = editor.update(&Message::CharacterInput('!'));
3852 editor.history.end_group();
3853
3854 let _ = editor.update(&Message::Undo);
3856 assert_eq!(editor.buffer.line(0), "hello");
3857
3858 let _ = editor.update(&Message::Redo);
3860 assert_eq!(editor.buffer.line(0), "hello!");
3861 assert_eq!(editor.cursors.primary_position(), (0, 6));
3862 }
3863
3864 #[test]
3865 fn test_undo_backspace() {
3866 let mut editor = CodeEditor::new("hello", "py");
3867 editor.cursors.primary_mut().position = (0, 5);
3868
3869 let _ = editor.update(&Message::Backspace);
3871 assert_eq!(editor.buffer.line(0), "hell");
3872 assert_eq!(editor.cursors.primary_position(), (0, 4));
3873
3874 let _ = editor.update(&Message::Undo);
3876 assert_eq!(editor.buffer.line(0), "hello");
3877 assert_eq!(editor.cursors.primary_position(), (0, 5));
3878 }
3879
3880 #[test]
3881 fn test_undo_newline() {
3882 let mut editor = CodeEditor::new("hello world", "py");
3883 editor.cursors.primary_mut().position = (0, 5);
3884
3885 let _ = editor.update(&Message::Enter);
3887 assert_eq!(editor.buffer.line(0), "hello");
3888 assert_eq!(editor.buffer.line(1), " world");
3889 assert_eq!(editor.cursors.primary_position(), (1, 0));
3890
3891 let _ = editor.update(&Message::Undo);
3893 assert_eq!(editor.buffer.line(0), "hello world");
3894 assert_eq!(editor.cursors.primary_position(), (0, 5));
3895 }
3896
3897 #[test]
3898 fn test_undo_grouped_typing() {
3899 let mut editor = CodeEditor::new("hello", "py");
3900 editor.request_focus();
3902 editor.has_canvas_focus = true;
3903 editor.focus_locked = false;
3904
3905 editor.cursors.primary_mut().position = (0, 5);
3906
3907 let _ = editor.update(&Message::CharacterInput(' '));
3909 let _ = editor.update(&Message::CharacterInput('w'));
3910 let _ = editor.update(&Message::CharacterInput('o'));
3911 let _ = editor.update(&Message::CharacterInput('r'));
3912 let _ = editor.update(&Message::CharacterInput('l'));
3913 let _ = editor.update(&Message::CharacterInput('d'));
3914
3915 assert_eq!(editor.buffer.line(0), "hello world");
3916
3917 editor.history.end_group();
3919
3920 let _ = editor.update(&Message::Undo);
3922 assert_eq!(editor.buffer.line(0), "hello");
3923 assert_eq!(editor.cursors.primary_position(), (0, 5));
3924 }
3925
3926 #[test]
3927 fn test_navigation_ends_grouping() {
3928 let mut editor = CodeEditor::new("hello", "py");
3929 editor.request_focus();
3931 editor.has_canvas_focus = true;
3932 editor.focus_locked = false;
3933
3934 editor.cursors.primary_mut().position = (0, 5);
3935
3936 let _ = editor.update(&Message::CharacterInput('!'));
3938 assert!(editor.is_grouping);
3939
3940 let _ = editor.update(&Message::ArrowKey(ArrowDirection::Left, false));
3942 assert!(!editor.is_grouping);
3943
3944 let _ = editor.update(&Message::CharacterInput('?'));
3946 assert!(editor.is_grouping);
3947
3948 editor.history.end_group();
3949
3950 let _ = editor.update(&Message::Undo);
3952 assert_eq!(editor.buffer.line(0), "hello!");
3953
3954 let _ = editor.update(&Message::Undo);
3955 assert_eq!(editor.buffer.line(0), "hello");
3956 }
3957
3958 #[test]
3959 fn test_edit_increments_revision_and_clears_visual_lines_cache() {
3960 let mut editor = CodeEditor::new("hello", "rs");
3961 editor.request_focus();
3962 editor.has_canvas_focus = true;
3963 editor.focus_locked = false;
3964 editor.cursors.primary_mut().position = (0, 5);
3965
3966 let _ = editor.visual_lines_cached(800.0);
3967 assert!(
3968 editor.visual_lines_cache.borrow().is_some(),
3969 "visual_lines_cached should populate the cache"
3970 );
3971
3972 let previous_revision = editor.buffer_revision;
3973
3974 let _ = editor.update(&Message::CharacterInput('!'));
3975 assert_eq!(
3976 editor.buffer_revision,
3977 previous_revision.wrapping_add(1),
3978 "buffer_revision should change on buffer edits"
3979 );
3980 assert!(
3984 editor
3985 .visual_lines_cache
3986 .borrow()
3987 .as_ref()
3988 .is_none_or(|c| c.key.buffer_revision == editor.buffer_revision),
3989 "buffer edits should not leave stale data in the visual lines cache"
3990 );
3991 }
3992
3993 #[test]
3994 fn test_edit_refreshes_only_affected_search_matches() {
3995 let mut editor = CodeEditor::new("foo\nfoo\nfoo", "rs");
3996 editor.request_focus();
3997 editor.has_canvas_focus = true;
3998 editor.focus_locked = false;
3999 editor.search_state.open_search();
4000 editor.search_state.set_query("foo".to_owned(), &editor.buffer);
4001 editor.cursors.primary_mut().position = (1, 1);
4002
4003 let _ = editor.update(&Message::CharacterInput('x'));
4004
4005 let match_lines: Vec<usize> =
4006 editor.search_state.matches.iter().map(|item| item.line).collect();
4007 assert_eq!(match_lines, vec![0, 2]);
4008 }
4009
4010 #[test]
4011 fn test_manual_search_match_selection_updates_current_index() {
4012 let mut editor =
4013 CodeEditor::new("foo bar foo baz foo\nno result", "txt");
4014 editor.search_state.open_search();
4015 editor.search_state.set_query("foo".to_owned(), &editor.buffer);
4016 assert_eq!(editor.search_state.current_match_index, Some(0));
4017
4018 let text_start = editor.gutter_width() + 5.0;
4019 let char_width = editor.char_width;
4020 let line_y = editor.line_height / 2.0;
4021 let point_at_col = |col: usize| {
4022 iced::Point::new(text_start + char_width * col as f32, line_y)
4023 };
4024
4025 let _ = editor.update(&Message::MouseClick(point_at_col(8)));
4026 let _ = editor.update(&Message::MouseDrag(point_at_col(11)));
4027 let _ = editor.update(&Message::MouseRelease);
4028
4029 assert_eq!(
4030 editor.cursors.primary().selection_range(),
4031 Some(((0, 8), (0, 11)))
4032 );
4033 assert_eq!(editor.search_state.current_match_index, Some(1));
4034
4035 let no_match_line = iced::Point::new(
4036 text_start + char_width * 4.0,
4037 editor.line_height * 1.5,
4038 );
4039 let _ = editor.update(&Message::MouseClick(no_match_line));
4040 let _ = editor.update(&Message::MouseRelease);
4041 assert_eq!(editor.search_state.current_match_index, Some(1));
4042
4043 let _ = editor.update(&Message::FindNext);
4044 assert_eq!(editor.search_state.current_match_index, Some(2));
4045 let _ = editor.update(&Message::FindPrevious);
4046 assert_eq!(editor.search_state.current_match_index, Some(1));
4047 }
4048
4049 #[test]
4050 fn test_manual_line_selection_updates_current_search_index() {
4051 let mut editor =
4052 CodeEditor::new("foo\nprefix foo suffix\nlast foo", "txt");
4053 editor.search_state.open_search();
4054 editor.search_state.set_query("foo".to_owned(), &editor.buffer);
4055 assert_eq!(editor.search_state.current_match_index, Some(0));
4056
4057 let line_start = iced::Point::new(
4058 editor.gutter_width() + 5.0,
4059 editor.line_height * 1.5,
4060 );
4061 let _ = editor.update(&Message::MouseClick(line_start));
4062 let _ = editor.update(&Message::MouseRelease);
4063
4064 assert_eq!(editor.cursors.primary_position(), (1, 0));
4065 assert_eq!(editor.search_state.current_match_index, Some(1));
4066
4067 let _ = editor.update(&Message::FindNext);
4068 assert_eq!(editor.search_state.current_match_index, Some(2));
4069
4070 let mut keyboard_editor =
4071 CodeEditor::new("foo\nprefix foo suffix\nlast foo", "txt");
4072 keyboard_editor.search_state.open_search();
4073 keyboard_editor
4074 .search_state
4075 .set_query("foo".to_owned(), &keyboard_editor.buffer);
4076
4077 let _ = keyboard_editor
4078 .update(&Message::ArrowKey(ArrowDirection::Down, false));
4079
4080 assert_eq!(keyboard_editor.cursors.primary_position(), (1, 0));
4081 assert_eq!(keyboard_editor.search_state.current_match_index, Some(1));
4082 }
4083
4084 #[test]
4085 fn test_incremental_visual_lines_match_full_recalculation_after_newline() {
4086 use std::collections::HashSet;
4087
4088 let mut editor = CodeEditor::new("zero\nabcdefgh\nlast", "rs")
4089 .with_wrap_column(Some(4));
4090 editor.request_focus();
4091 editor.has_canvas_focus = true;
4092 editor.focus_locked = false;
4093 editor.cursors.primary_mut().position = (1, 4);
4094
4095 let _ = editor.visual_lines_cached(800.0);
4096 let _ = editor.update(&Message::Enter);
4097 let incremental = editor.visual_lines_cached(800.0);
4098
4099 let calculator = super::super::wrapping::WrappingCalculator::new(
4100 editor.wrap_enabled,
4101 editor.wrap_column,
4102 editor.full_char_width,
4103 editor.char_width,
4104 );
4105 let expected = calculator.calculate_visual_lines(
4106 &editor.buffer,
4107 800.0,
4108 editor.gutter_width(),
4109 &HashSet::new(),
4110 );
4111
4112 assert_eq!(incremental.as_ref(), &expected);
4113 }
4114
4115 #[test]
4116 fn test_multiple_undo_redo() {
4117 let mut editor = CodeEditor::new("a", "py");
4118 editor.request_focus();
4120 editor.has_canvas_focus = true;
4121 editor.focus_locked = false;
4122
4123 editor.cursors.primary_mut().position = (0, 1);
4124
4125 let _ = editor.update(&Message::CharacterInput('b'));
4127 editor.history.end_group();
4128
4129 let _ = editor.update(&Message::CharacterInput('c'));
4130 editor.history.end_group();
4131
4132 let _ = editor.update(&Message::CharacterInput('d'));
4133 editor.history.end_group();
4134
4135 assert_eq!(editor.buffer.line(0), "abcd");
4136
4137 let _ = editor.update(&Message::Undo);
4139 assert_eq!(editor.buffer.line(0), "abc");
4140
4141 let _ = editor.update(&Message::Undo);
4142 assert_eq!(editor.buffer.line(0), "ab");
4143
4144 let _ = editor.update(&Message::Undo);
4145 assert_eq!(editor.buffer.line(0), "a");
4146
4147 let _ = editor.update(&Message::Redo);
4149 assert_eq!(editor.buffer.line(0), "ab");
4150
4151 let _ = editor.update(&Message::Redo);
4152 assert_eq!(editor.buffer.line(0), "abc");
4153
4154 let _ = editor.update(&Message::Redo);
4155 assert_eq!(editor.buffer.line(0), "abcd");
4156 }
4157
4158 #[test]
4159 fn test_delete_key_with_selection() {
4160 let mut editor = CodeEditor::new("hello world", "py");
4161 editor.cursors.primary_mut().anchor = Some((0, 0));
4162 editor.cursors.primary_mut().position = (0, 5);
4163 editor.cursors.primary_mut().position = (0, 5);
4164
4165 let _ = editor.update(&Message::Delete);
4166
4167 assert_eq!(editor.buffer.line(0), " world");
4168 assert_eq!(editor.cursors.primary_position(), (0, 0));
4169 assert!(editor.cursors.primary().anchor.is_none());
4170 assert!(!editor.cursors.primary().has_selection());
4171 }
4172
4173 #[test]
4174 fn test_delete_key_without_selection() {
4175 let mut editor = CodeEditor::new("hello", "py");
4176 editor.cursors.primary_mut().position = (0, 0);
4177
4178 let _ = editor.update(&Message::Delete);
4179
4180 assert_eq!(editor.buffer.line(0), "ello");
4182 assert_eq!(editor.cursors.primary_position(), (0, 0));
4183 }
4184
4185 #[test]
4186 fn test_backspace_with_selection() {
4187 let mut editor = CodeEditor::new("hello world", "py");
4188 editor.cursors.primary_mut().anchor = Some((0, 6));
4189 editor.cursors.primary_mut().position = (0, 11);
4190 editor.cursors.primary_mut().position = (0, 11);
4191
4192 let _ = editor.update(&Message::Backspace);
4193
4194 assert_eq!(editor.buffer.line(0), "hello ");
4195 assert_eq!(editor.cursors.primary_position(), (0, 6));
4196 assert!(editor.cursors.primary().anchor.is_none());
4197 assert!(!editor.cursors.primary().has_selection());
4198 }
4199
4200 #[test]
4201 fn test_backspace_without_selection() {
4202 let mut editor = CodeEditor::new("hello", "py");
4203 editor.cursors.primary_mut().position = (0, 5);
4204
4205 let _ = editor.update(&Message::Backspace);
4206
4207 assert_eq!(editor.buffer.line(0), "hell");
4209 assert_eq!(editor.cursors.primary_position(), (0, 4));
4210 }
4211
4212 #[test]
4213 fn test_delete_multiline_selection() {
4214 let mut editor = CodeEditor::new("line1\nline2\nline3", "py");
4215 editor.cursors.primary_mut().anchor = Some((0, 2));
4216 editor.cursors.primary_mut().position = (2, 2);
4217 editor.cursors.primary_mut().position = (2, 2);
4218
4219 let _ = editor.update(&Message::Delete);
4220
4221 assert_eq!(editor.buffer.line(0), "line3");
4222 assert_eq!(editor.cursors.primary_position(), (0, 2));
4223 assert!(editor.cursors.primary().anchor.is_none());
4224 }
4225
4226 #[test]
4227 fn test_canvas_focus_gained() {
4228 let mut editor = CodeEditor::new("hello world", "py");
4229 assert!(!editor.has_canvas_focus);
4230 assert!(!editor.show_cursor);
4231
4232 let _ = editor.update(&Message::CanvasFocusGained);
4233
4234 assert!(editor.has_canvas_focus);
4235 assert!(editor.show_cursor);
4236 }
4237
4238 #[test]
4239 fn test_mouse_click_gains_focus() {
4240 let mut editor = CodeEditor::new("hello world", "py");
4241 editor.has_canvas_focus = false;
4242 editor.show_cursor = false;
4243
4244 let _ =
4245 editor.update(&Message::MouseClick(iced::Point::new(100.0, 10.0)));
4246
4247 assert!(editor.has_canvas_focus);
4248 assert!(editor.show_cursor);
4249 }
4250
4251 #[test]
4252 fn test_context_click_inside_selection_preserves_selection() {
4253 let mut editor = CodeEditor::new("hello world", "py");
4254 editor.cursors.primary_mut().anchor = Some((0, 0));
4255 editor.cursors.primary_mut().position = (0, 5);
4256 let point = iced::Point::new(
4257 editor.gutter_width() + 5.0 + editor.char_width * 2.0,
4258 editor.line_height / 2.0,
4259 );
4260
4261 let _ = editor.update(&Message::ContextMenuRequested(point));
4262
4263 assert_eq!(
4264 editor.cursors.primary().selection_range(),
4265 Some(((0, 0), (0, 5)))
4266 );
4267 }
4268
4269 #[test]
4270 fn test_context_click_outside_selection_moves_caret() {
4271 let mut editor = CodeEditor::new("hello world", "py");
4272 editor.cursors.primary_mut().anchor = Some((0, 0));
4273 editor.cursors.primary_mut().position = (0, 5);
4274 let point = iced::Point::new(
4275 editor.gutter_width() + 5.0 + editor.char_width * 8.0,
4276 editor.line_height / 2.0,
4277 );
4278
4279 let _ = editor.update(&Message::ContextMenuRequested(point));
4280
4281 assert_eq!(editor.cursors.primary_position(), (0, 8));
4282 assert!(!editor.cursors.primary().has_selection());
4283 }
4284
4285 #[test]
4286 fn test_context_menu_cut_and_select_all() {
4287 let mut editor = CodeEditor::new("hello world", "py");
4288 editor.cursors.primary_mut().anchor = Some((0, 0));
4289 editor.cursors.primary_mut().position = (0, 5);
4290
4291 let _ = editor.update(&Message::Cut);
4292 assert_eq!(editor.content(), " world");
4293
4294 let _ = editor.update(&Message::SelectAll);
4295 assert_eq!(editor.get_selected_text(), Some(" world".to_string()));
4296
4297 let _ = editor.update(&Message::Undo);
4298 assert_eq!(editor.content(), "hello world");
4299 }
4300
4301 #[test]
4302 fn test_enter_no_indent() {
4303 let mut editor = CodeEditor::new("hello", "rs");
4304 editor.cursors.primary_mut().position = (0, 5);
4305 let _ = editor.update(&Message::Enter);
4306 assert_eq!(editor.buffer.line(0), "hello");
4307 assert_eq!(editor.buffer.line(1), "");
4308 assert_eq!(editor.cursors.primary_position(), (1, 0));
4309 }
4310
4311 #[test]
4312 fn test_typing_after_enter_does_not_delete_newline_from_click_anchor() {
4313 let mut editor = CodeEditor::new("hello", "rs");
4314 editor.request_focus();
4315 editor.has_canvas_focus = true;
4316 editor.focus_locked = false;
4317
4318 editor.cursors.primary_mut().position = (0, 5);
4320 editor.cursors.primary_mut().anchor = Some((0, 5));
4321
4322 let _ = editor.update(&Message::Enter);
4323 let _ = editor.update(&Message::CharacterInput('X'));
4324
4325 assert_eq!(editor.buffer.line_count(), 2);
4326 assert_eq!(editor.buffer.line(0), "hello");
4327 assert_eq!(editor.buffer.line(1), "X");
4328 assert_eq!(editor.cursors.primary_position(), (1, 1));
4329 assert!(!editor.cursors.primary().has_selection());
4330 }
4331
4332 #[test]
4333 fn test_enter_replaces_selection_and_undo_restores_text() {
4334 let mut editor = CodeEditor::new("hello world", "rs");
4335 editor.set_auto_indent_enabled(false);
4336 editor.cursors.primary_mut().anchor = Some((0, 0));
4337 editor.cursors.primary_mut().position = (0, 5);
4338
4339 let _ = editor.update(&Message::Enter);
4340
4341 assert_eq!(editor.buffer.line(0), "");
4342 assert_eq!(editor.buffer.line(1), " world");
4343 assert_eq!(editor.cursors.primary_position(), (1, 0));
4344 assert!(!editor.cursors.primary().has_selection());
4345
4346 let _ = editor.update(&Message::Undo);
4347 assert_eq!(editor.content(), "hello world");
4348 }
4349
4350 #[test]
4351 fn test_enter_auto_indent_spaces() {
4352 let mut editor = CodeEditor::new(" hello", "rs");
4353 editor.cursors.primary_mut().position = (0, 9);
4354 let _ = editor.update(&Message::Enter);
4355 assert_eq!(editor.buffer.line(0), " hello");
4356 assert_eq!(editor.buffer.line(1), " ");
4357 assert_eq!(editor.cursors.primary_position(), (1, 4));
4358 }
4359
4360 #[test]
4361 fn test_enter_auto_indent_tab() {
4362 let mut editor = CodeEditor::new("\thello", "rs");
4363 editor.cursors.primary_mut().position = (0, 6);
4364 let _ = editor.update(&Message::Enter);
4365 assert_eq!(editor.buffer.line(0), "\thello");
4366 assert_eq!(editor.buffer.line(1), "\t");
4367 assert_eq!(editor.cursors.primary_position(), (1, 1));
4368 }
4369
4370 #[test]
4371 fn test_enter_auto_indent_undo() {
4372 let mut editor = CodeEditor::new(" hello", "rs");
4373 editor.cursors.primary_mut().position = (0, 9);
4374 let _ = editor.update(&Message::Enter);
4375 assert_eq!(editor.buffer.line_count(), 2);
4376
4377 let _ = editor.update(&Message::Undo);
4378 assert_eq!(editor.buffer.line_count(), 1);
4379 assert_eq!(editor.buffer.line(0), " hello");
4380 assert_eq!(editor.cursors.primary_position(), (0, 9));
4381 }
4382
4383 #[test]
4388 fn test_multi_cursor_char_input_different_lines() {
4389 let mut editor = CodeEditor::new("aaa\nbbb", "rs");
4390 editor.request_focus();
4391 editor.has_canvas_focus = true;
4392 editor.focus_locked = false;
4393 editor.cursors.primary_mut().position = (0, 1);
4395 editor.cursors.add_cursor((1, 1));
4396
4397 let _ = editor.update(&Message::CharacterInput('X'));
4398
4399 assert_eq!(editor.buffer.line(0), "aXaa");
4401 assert_eq!(editor.buffer.line(1), "bXbb");
4402 }
4403
4404 #[test]
4405 fn test_multi_cursor_char_input_same_line() {
4406 let mut editor = CodeEditor::new("abcd", "rs");
4407 editor.request_focus();
4408 editor.has_canvas_focus = true;
4409 editor.focus_locked = false;
4410 editor.cursors.primary_mut().position = (0, 1);
4412 editor.cursors.add_cursor((0, 3));
4413
4414 let _ = editor.update(&Message::CharacterInput('X'));
4415
4416 assert_eq!(editor.buffer.line(0), "aXbcXd");
4419 }
4420
4421 #[test]
4422 fn test_add_cursor_above() {
4423 let mut editor = CodeEditor::new("line0\nline1\nline2", "rs");
4424 editor.cursors.primary_mut().position = (1, 3);
4425
4426 let _ = editor.update(&Message::AddCursorAbove);
4427
4428 assert!(editor.cursors.is_multi());
4429 assert_eq!(editor.cursors.as_slice()[0].position, (0, 3));
4431 }
4432
4433 #[test]
4434 fn test_add_cursor_below() {
4435 let mut editor = CodeEditor::new("line0\nline1\nline2", "rs");
4436 editor.cursors.primary_mut().position = (1, 3);
4437
4438 let _ = editor.update(&Message::AddCursorBelow);
4439
4440 assert!(editor.cursors.is_multi());
4441 assert_eq!(
4443 editor
4444 .cursors
4445 .as_slice()
4446 .iter()
4447 .find(|c| c.position.0 == 2)
4448 .map(|c| c.position),
4449 Some((2, 3))
4450 );
4451 }
4452
4453 #[test]
4454 fn test_escape_collapses_multi_cursor() {
4455 let mut editor = CodeEditor::new("line0\nline1", "rs");
4456 editor.cursors.primary_mut().position = (0, 0);
4457 editor.cursors.add_cursor((1, 0));
4458 assert!(editor.cursors.is_multi());
4459
4460 let _ = editor.update(&Message::CloseSearch);
4461
4462 assert!(!editor.cursors.is_multi());
4463 }
4464
4465 #[test]
4466 fn test_select_next_occurrence_selects_word() {
4467 let mut editor = CodeEditor::new("foo bar foo", "rs");
4468 editor.cursors.primary_mut().position = (0, 1); let _ = editor.update(&Message::SelectNextOccurrence);
4471
4472 let range = editor.cursors.primary().selection_range();
4474 assert_eq!(range, Some(((0, 0), (0, 3))));
4475 }
4476
4477 #[test]
4478 fn test_select_next_occurrence_adds_cursor_for_second_occurrence() {
4479 let mut editor = CodeEditor::new("foo bar foo", "rs");
4480 editor.cursors.primary_mut().anchor = Some((0, 0));
4482 editor.cursors.primary_mut().position = (0, 3);
4483
4484 let _ = editor.update(&Message::SelectNextOccurrence);
4485
4486 assert_eq!(editor.cursors.len(), 2);
4488 }
4489
4490 #[test]
4491 fn test_multi_cursor_backspace() {
4492 let mut editor = CodeEditor::new("abc\ndef", "rs");
4493 editor.cursors.primary_mut().position = (0, 2);
4494 editor.cursors.add_cursor((1, 2));
4495
4496 let _ = editor.update(&Message::Backspace);
4497
4498 assert_eq!(editor.buffer.line(0), "ac");
4499 assert_eq!(editor.buffer.line(1), "df");
4500 }
4501
4502 #[test]
4503 fn test_toggle_comment_selection() {
4504 let mut editor = CodeEditor::new("a\nb\nc", "rs");
4505 editor.cursors.primary_mut().anchor = Some((0, 0));
4507 editor.cursors.primary_mut().position = (2, 1);
4508
4509 let _ = editor.update(&Message::ToggleComment);
4510 assert_eq!(editor.buffer.to_string(), "// a\n// b\n// c");
4511 assert_eq!(editor.cursors.primary_position(), (2, 4));
4512
4513 let _ = editor.update(&Message::ToggleComment);
4515 assert_eq!(editor.buffer.to_string(), "a\nb\nc");
4516 }
4517
4518 #[test]
4519 fn test_toggle_comment_noop_without_token() {
4520 let mut editor = CodeEditor::new("<div>", "html");
4521 let _ = editor.update(&Message::ToggleComment);
4522 assert_eq!(editor.buffer.line(0), "<div>");
4524 }
4525
4526 #[test]
4527 fn test_toggle_comment_undo() {
4528 let mut editor = CodeEditor::new(" let x = 1;", "rs");
4529 editor.cursors.primary_mut().position = (0, 8);
4530
4531 let _ = editor.update(&Message::ToggleComment);
4532 assert_eq!(editor.buffer.line(0), " // let x = 1;");
4533
4534 let _ = editor.update(&Message::Undo);
4535 assert_eq!(editor.buffer.line(0), " let x = 1;");
4536 assert_eq!(editor.cursors.primary_position(), (0, 8));
4537 }
4538
4539 #[test]
4540 fn test_open_goto_line_prefills_current_one_based_line() {
4541 let mut editor = CodeEditor::new("one\ntwo\nthree", "rs");
4542 editor.cursors.primary_mut().position = (1, 2);
4543 editor.search_state.open_search();
4544
4545 let _ = editor.update(&Message::OpenGotoLine);
4546
4547 assert!(editor.goto_line_state.is_open);
4548 assert_eq!(editor.goto_line_state.query, "2");
4549 assert!(!editor.search_state.is_open);
4550 }
4551
4552 #[test]
4553 fn test_submit_goto_line_moves_to_one_based_line_and_closes_dialog() {
4554 let mut editor = CodeEditor::new("one\ntwo\nthree", "rs");
4555 let _ = editor.update(&Message::OpenGotoLine);
4556 let _ = editor.update(&Message::GotoLineChanged("3".to_string()));
4557
4558 let _ = editor.update(&Message::SubmitGotoLine);
4559
4560 assert_eq!(editor.cursors.primary_position(), (2, 0));
4561 assert!(!editor.goto_line_state.is_open);
4562 }
4563
4564 #[test]
4565 fn test_submit_goto_line_clamps_to_last_line() {
4566 let mut editor = CodeEditor::new("one\ntwo\nthree", "rs");
4567 let _ = editor.update(&Message::OpenGotoLine);
4568 let _ = editor.update(&Message::GotoLineChanged("99".to_string()));
4569
4570 let _ = editor.update(&Message::SubmitGotoLine);
4571
4572 assert_eq!(editor.cursors.primary_position(), (2, 0));
4573 assert!(!editor.goto_line_state.is_open);
4574 }
4575
4576 #[test]
4577 fn test_submit_goto_line_reveals_folded_target() {
4578 let mut editor =
4579 CodeEditor::new("root\n child\n nested\ntail", "rs");
4580 editor.fold_all();
4581 assert!(editor.hidden_lines_set().contains(&1));
4582 let _ = editor.update(&Message::OpenGotoLine);
4583 let _ = editor.update(&Message::GotoLineChanged("2".to_string()));
4584
4585 let _ = editor.update(&Message::SubmitGotoLine);
4586
4587 assert_eq!(editor.cursors.primary_position(), (1, 0));
4588 assert!(!editor.hidden_lines_set().contains(&1));
4589 }
4590
4591 #[test]
4592 fn test_submit_goto_line_keeps_dialog_open_for_invalid_input() {
4593 let mut editor = CodeEditor::new("one\ntwo\nthree", "rs");
4594 editor.cursors.primary_mut().position = (1, 1);
4595 let _ = editor.update(&Message::OpenGotoLine);
4596 let _ = editor.update(&Message::GotoLineChanged("invalid".to_string()));
4597
4598 let _ = editor.update(&Message::SubmitGotoLine);
4599
4600 assert_eq!(editor.cursors.primary_position(), (1, 1));
4601 assert!(editor.goto_line_state.is_open);
4602 }
4603}