1use rust_i18n::t;
9
10use crate::input::multi_cursor::{
11 add_cursor_above, add_cursor_at_next_match, add_cursor_below, AddCursorResult,
12};
13use crate::model::buffer_position::byte_to_2d;
14use crate::model::event::{CursorId, Event};
15use crate::primitives::word_navigation::{
16 find_vi_word_end, find_word_start_left, find_word_start_right,
17};
18
19use super::Editor;
20
21impl Editor {
33 pub fn copy_selection(&mut self) {
38 let has_block_selection = self
40 .active_cursors()
41 .iter()
42 .any(|(_, cursor)| cursor.has_block_selection());
43
44 if has_block_selection {
45 let text = self.copy_block_selection_text();
47 if !text.is_empty() {
48 self.clipboard.copy(text);
49 self.status_message = Some(t!("clipboard.copied").to_string());
50 }
51 return;
52 }
53
54 let has_selection = self
56 .active_cursors()
57 .iter()
58 .any(|(_, cursor)| cursor.selection_range().is_some());
59
60 if has_selection {
61 let ranges: Vec<_> = self
63 .active_cursors()
64 .iter()
65 .filter_map(|(_, cursor)| cursor.selection_range())
66 .collect();
67
68 let mut text = String::new();
69 let state = self.active_state_mut();
70 for range in ranges {
71 if !text.is_empty() {
72 text.push('\n');
73 }
74 let range_text = state.get_text_range(range.start, range.end);
75 text.push_str(&range_text);
76 }
77
78 if !text.is_empty() {
79 self.clipboard.copy(text);
80 self.status_message = Some(t!("clipboard.copied").to_string());
81 }
82 } else {
83 let estimated_line_length = 80;
85 let mut text = String::new();
86
87 let positions: Vec<_> = self
89 .active_cursors()
90 .iter()
91 .map(|(_, c)| c.position)
92 .collect();
93 let state = self.active_state_mut();
94
95 for pos in positions {
96 let mut iter = state.buffer.line_iterator(pos, estimated_line_length);
97 if let Some((_start, content)) = iter.next_line() {
98 if !text.is_empty() {
99 text.push('\n');
100 }
101 text.push_str(&content);
102 }
103 }
104
105 if !text.is_empty() {
106 self.clipboard.copy(text);
107 self.status_message = Some(t!("clipboard.copied_line").to_string());
108 }
109 }
110 }
111
112 fn copy_block_selection_text(&mut self) -> String {
121 let estimated_line_length = 120;
122
123 let block_infos: Vec<_> = self
125 .active_cursors()
126 .iter()
127 .filter_map(|(_, cursor)| {
128 if !cursor.has_block_selection() {
129 return None;
130 }
131 let block_anchor = cursor.block_anchor?;
132 let anchor_byte = cursor.anchor?; let cursor_byte = cursor.position;
134 Some((block_anchor, anchor_byte, cursor_byte))
135 })
136 .collect();
137
138 let mut result = String::new();
139
140 for (block_anchor, anchor_byte, cursor_byte) in block_infos {
141 let cursor_2d = {
143 let state = self.active_state();
144 byte_to_2d(&state.buffer, cursor_byte)
145 };
146
147 let min_col = block_anchor.column.min(cursor_2d.column);
149 let max_col = block_anchor.column.max(cursor_2d.column);
150
151 let start_byte = anchor_byte.min(cursor_byte);
153 let end_byte = anchor_byte.max(cursor_byte);
154
155 let state = self.active_state_mut();
157 let mut iter = state
158 .buffer
159 .line_iterator(start_byte, estimated_line_length);
160
161 let mut lines_text = Vec::new();
163 loop {
164 let line_start = iter.current_position();
165
166 if line_start > end_byte {
168 break;
169 }
170
171 if let Some((_offset, line_content)) = iter.next_line() {
172 let content_without_newline = line_content.trim_end_matches(&['\n', '\r'][..]);
175 let chars: Vec<char> = content_without_newline.chars().collect();
176
177 let extracted: String = chars
179 .iter()
180 .skip(min_col)
181 .take(max_col.saturating_sub(min_col))
182 .collect();
183
184 lines_text.push(extracted);
185
186 if line_start + line_content.len() > end_byte {
188 break;
189 }
190 } else {
191 break;
192 }
193 }
194
195 if !result.is_empty() && !lines_text.is_empty() {
197 result.push('\n');
198 }
199 result.push_str(&lines_text.join("\n"));
200 }
201
202 result
203 }
204
205 pub fn copy_selection_with_theme(&mut self, theme_name: &str) {
210 let has_selection = self
212 .active_cursors()
213 .iter()
214 .any(|(_, cursor)| cursor.selection_range().is_some());
215
216 if !has_selection {
217 self.status_message = Some(t!("clipboard.no_selection").to_string());
218 return;
219 }
220
221 if theme_name.is_empty() {
223 self.start_copy_with_formatting_prompt();
224 return;
225 }
226 use crate::services::styled_html::render_styled_html;
227
228 let theme = match self.theme_registry.get_cloned(theme_name) {
230 Some(t) => t,
231 None => {
232 self.status_message = Some(format!("Theme '{}' not found", theme_name));
233 return;
234 }
235 };
236
237 let ranges: Vec<_> = self
239 .active_cursors()
240 .iter()
241 .filter_map(|(_, cursor)| cursor.selection_range())
242 .collect();
243
244 if ranges.is_empty() {
245 self.status_message = Some(t!("clipboard.no_selection").to_string());
246 return;
247 }
248
249 let min_offset = ranges.iter().map(|r| r.start).min().unwrap_or(0);
251 let max_offset = ranges.iter().map(|r| r.end).max().unwrap_or(0);
252
253 let (text, highlight_spans) = {
255 let state = self.active_state_mut();
256
257 let mut text = String::new();
259 for range in &ranges {
260 if !text.is_empty() {
261 text.push('\n');
262 }
263 let range_text = state.get_text_range(range.start, range.end);
264 text.push_str(&range_text);
265 }
266
267 if text.is_empty() {
268 (text, Vec::new())
269 } else {
270 let highlight_spans = state.highlighter.highlight_viewport(
272 &state.buffer,
273 min_offset,
274 max_offset,
275 &theme,
276 0, );
278 (text, highlight_spans)
279 }
280 };
281
282 if text.is_empty() {
283 self.status_message = Some(t!("clipboard.no_text").to_string());
284 return;
285 }
286
287 let adjusted_spans: Vec<_> = if ranges.len() == 1 {
289 let base_offset = ranges[0].start;
290 highlight_spans
291 .into_iter()
292 .filter_map(|span| {
293 if span.range.end <= base_offset || span.range.start >= ranges[0].end {
294 return None;
295 }
296 let start = span.range.start.saturating_sub(base_offset);
297 let end = (span.range.end - base_offset).min(text.len());
298 if start < end {
299 Some(crate::primitives::highlighter::HighlightSpan {
300 range: start..end,
301 color: span.color,
302 category: span.category,
303 })
304 } else {
305 None
306 }
307 })
308 .collect()
309 } else {
310 Vec::new()
311 };
312
313 let html = render_styled_html(&text, &adjusted_spans, &theme);
315
316 if self.clipboard.copy_html(&html, &text) {
318 self.status_message =
319 Some(t!("clipboard.copied_with_theme", theme = theme_name).to_string());
320 } else {
321 self.clipboard.copy(text);
322 self.status_message = Some(t!("clipboard.copied_plain").to_string());
323 }
324 }
325
326 fn start_copy_with_formatting_prompt(&mut self) {
328 use crate::view::prompt::PromptType;
329
330 let available_themes = self.theme_registry.list();
331 let resolved_current = self
334 .theme_registry
335 .resolve_key(&self.config.theme.0)
336 .unwrap_or_else(|| self.config.theme.0.clone());
337 let current_theme_key = resolved_current.as_str();
338
339 let current_index = available_themes
341 .iter()
342 .position(|info| info.key == *current_theme_key)
343 .or_else(|| {
344 let normalized = crate::view::theme::normalize_theme_name(current_theme_key);
345 available_themes.iter().position(|info| {
346 crate::view::theme::normalize_theme_name(&info.name) == normalized
347 })
348 })
349 .unwrap_or(0);
350
351 let suggestions: Vec<crate::input::commands::Suggestion> = available_themes
352 .iter()
353 .map(|info| {
354 let is_current = Some(info) == available_themes.get(current_index);
355 let description = if is_current {
356 Some(format!("{} (current)", info.key))
357 } else {
358 Some(info.key.clone())
359 };
360 crate::input::commands::Suggestion {
361 text: info.name.clone(),
362 description,
363 value: Some(info.key.clone()),
364 disabled: false,
365 keybinding: None,
366 source: None,
367 }
368 })
369 .collect();
370
371 self.prompt = Some(crate::view::prompt::Prompt::with_suggestions(
372 "Copy with theme: ".to_string(),
373 PromptType::CopyWithFormattingTheme,
374 suggestions,
375 ));
376
377 if let Some(prompt) = self.prompt.as_mut() {
378 if !prompt.suggestions.is_empty() {
379 prompt.selected_suggestion = Some(current_index);
380 prompt.input = current_theme_key.to_string();
381 prompt.cursor_pos = prompt.input.len();
382 }
383 }
384 }
385
386 pub fn cut_selection(&mut self) {
390 let has_selection = self
392 .active_cursors()
393 .iter()
394 .any(|(_, cursor)| cursor.selection_range().is_some());
395
396 self.copy_selection();
398
399 if has_selection {
400 let mut deletions: Vec<_> = self
403 .active_cursors()
404 .iter()
405 .filter_map(|(_, c)| c.selection_range())
406 .collect();
407 deletions.sort_by_key(|r| r.start);
409
410 let primary_id = self.active_cursors().primary_id();
411 let state = self.active_state_mut();
412 let events: Vec<_> = deletions
413 .iter()
414 .rev()
415 .map(|range| {
416 let deleted_text = state.get_text_range(range.start, range.end);
417 Event::Delete {
418 range: range.clone(),
419 deleted_text,
420 cursor_id: primary_id,
421 }
422 })
423 .collect();
424
425 if events.len() > 1 {
427 if let Some(bulk_edit) = self.apply_events_as_bulk_edit(events, "Cut".to_string()) {
429 self.active_event_log_mut().append(bulk_edit);
430 }
431 } else if let Some(event) = events.into_iter().next() {
432 self.log_and_apply_event(&event);
433 }
434
435 if !deletions.is_empty() {
436 self.status_message = Some(t!("clipboard.cut").to_string());
437 }
438 } else {
439 let estimated_line_length = 80;
441
442 let positions: Vec<_> = self
445 .active_cursors()
446 .iter()
447 .map(|(_, c)| c.position)
448 .collect();
449 let mut deletions: Vec<_> = {
450 let state = self.active_state_mut();
451 positions
452 .into_iter()
453 .filter_map(|pos| {
454 let mut iter = state.buffer.line_iterator(pos, estimated_line_length);
455 let line_start = iter.current_position();
456 iter.next_line().map(|(_start, content)| {
457 let line_end = line_start + content.len();
458 line_start..line_end
459 })
460 })
461 .collect()
462 };
463 deletions.sort_by_key(|r| r.start);
465
466 let primary_id = self.active_cursors().primary_id();
467 let state = self.active_state_mut();
468 let events: Vec<_> = deletions
469 .iter()
470 .rev()
471 .map(|range| {
472 let deleted_text = state.get_text_range(range.start, range.end);
473 Event::Delete {
474 range: range.clone(),
475 deleted_text,
476 cursor_id: primary_id,
477 }
478 })
479 .collect();
480
481 if events.len() > 1 {
483 if let Some(bulk_edit) =
485 self.apply_events_as_bulk_edit(events, "Cut line".to_string())
486 {
487 self.active_event_log_mut().append(bulk_edit);
488 }
489 } else if let Some(event) = events.into_iter().next() {
490 self.log_and_apply_event(&event);
491 }
492
493 if !deletions.is_empty() {
494 self.status_message = Some(t!("clipboard.cut_line").to_string());
495 }
496 }
497 }
498
499 pub fn paste(&mut self) {
507 let text = match self.clipboard.paste() {
509 Some(text) => text,
510 None => return,
511 };
512
513 self.paste_text(text);
515 }
516
517 pub fn paste_text(&mut self, paste_text: String) {
531 if paste_text.is_empty() {
532 return;
533 }
534
535 let normalized = paste_text.replace("\r\n", "\n").replace('\r', "\n");
538
539 if let Some(prompt) = self.prompt.as_mut() {
541 prompt.insert_str(&normalized);
542 self.update_prompt_suggestions();
543 self.status_message = Some(t!("clipboard.pasted").to_string());
544 return;
545 }
546
547 if self.terminal_mode {
549 self.send_terminal_input(normalized.as_bytes());
550 return;
551 }
552
553 let mut cursor_data: Vec<_> = self
555 .active_cursors()
556 .iter()
557 .map(|(cursor_id, cursor)| {
558 let selection = cursor.selection_range();
559 let insert_position = selection
560 .as_ref()
561 .map(|r| r.start)
562 .unwrap_or(cursor.position);
563 (cursor_id, selection, insert_position)
564 })
565 .collect();
566 cursor_data.sort_by_key(|(_, _, pos)| std::cmp::Reverse(*pos));
567
568 let mut lines_for_distribution: Vec<&str> = normalized.split('\n').collect();
573 if lines_for_distribution.len() > 1 && lines_for_distribution.last() == Some(&"") {
574 lines_for_distribution.pop();
575 }
576 let use_column_paste = cursor_data.len() > 1
577 && lines_for_distribution.len() > 1
578 && lines_for_distribution.len() == cursor_data.len();
579
580 let paste_text_full = match self.active_state().buffer.line_ending() {
583 crate::model::buffer::LineEnding::LF => normalized.clone(),
584 crate::model::buffer::LineEnding::CRLF => normalized.replace('\n', "\r\n"),
585 crate::model::buffer::LineEnding::CR => normalized.replace('\n', "\r"),
586 };
587
588 let cursor_data_with_text: Vec<_> = {
590 let state = self.active_state_mut();
591 cursor_data
592 .into_iter()
593 .map(|(cursor_id, selection, insert_position)| {
594 let deleted_text = selection
595 .as_ref()
596 .map(|r| state.get_text_range(r.start, r.end));
597 (cursor_id, selection, insert_position, deleted_text)
598 })
599 .collect()
600 };
601
602 let total = cursor_data_with_text.len();
610 let mut events = Vec::new();
611 for (i, (cursor_id, selection, insert_position, deleted_text)) in
612 cursor_data_with_text.into_iter().enumerate()
613 {
614 if let (Some(range), Some(text)) = (selection, deleted_text) {
615 events.push(Event::Delete {
616 range,
617 deleted_text: text,
618 cursor_id,
619 });
620 }
621 let text = if use_column_paste {
622 lines_for_distribution[total - 1 - i].to_string()
623 } else {
624 paste_text_full.clone()
625 };
626 events.push(Event::Insert {
627 position: insert_position,
628 text,
629 cursor_id,
630 });
631 }
632
633 if events.len() > 1 {
635 if let Some(bulk_edit) = self.apply_events_as_bulk_edit(events, "Paste".to_string()) {
637 self.active_event_log_mut().append(bulk_edit);
638 }
639 } else if let Some(event) = events.into_iter().next() {
640 self.log_and_apply_event(&event);
641 }
642
643 self.status_message = Some(t!("clipboard.pasted").to_string());
644 }
645
646 #[doc(hidden)]
650 pub fn set_clipboard_for_test(&mut self, text: String) {
651 self.clipboard.set_internal(text);
652 self.clipboard.set_internal_only(true);
653 }
654
655 #[doc(hidden)]
658 pub fn paste_for_test(&mut self) {
659 let paste_text = match self.clipboard.paste_internal() {
661 Some(text) => text,
662 None => return,
663 };
664
665 self.paste_text(paste_text);
667 }
668
669 #[doc(hidden)]
672 pub fn clipboard_content_for_test(&self) -> String {
673 self.clipboard.get_internal().to_string()
674 }
675
676 pub fn copy_buffer_path(&mut self, buffer_id: crate::model::event::BufferId, relative: bool) {
687 let path = self
688 .buffers
689 .get(&buffer_id)
690 .and_then(|state| state.buffer.file_path().map(|p| p.to_path_buf()));
691 let Some(path) = path else {
692 self.status_message = Some(t!("clipboard.no_file_path").to_string());
693 return;
694 };
695
696 let path_str = if relative {
697 path.strip_prefix(&self.working_dir)
698 .unwrap_or(&path)
699 .to_string_lossy()
700 .into_owned()
701 } else {
702 path.to_string_lossy().into_owned()
703 };
704
705 self.clipboard.copy(path_str.clone());
706 self.status_message = Some(t!("clipboard.copied_path", path = &path_str).to_string());
707 }
708
709 pub fn copy_active_buffer_path(&mut self, relative: bool) {
711 let buffer_id = self.active_buffer();
712 self.copy_buffer_path(buffer_id, relative);
713 }
714
715 pub fn add_cursor_at_next_match(&mut self) {
724 if let Some(range) = self.search_match_at_primary_cursor() {
725 let primary_id = self.active_cursors().primary_id();
726 let primary = self.active_cursors().primary();
727 let event = Event::MoveCursor {
728 cursor_id: primary_id,
729 old_position: primary.position,
730 new_position: range.end,
731 old_anchor: primary.anchor,
732 new_anchor: Some(range.start),
733 old_sticky_column: primary.sticky_column,
734 new_sticky_column: 0,
735 };
736 self.active_event_log_mut().append(event.clone());
737 self.apply_event_to_active_buffer(&event);
738 return;
739 }
740
741 let cursors = self.active_cursors().clone();
742 let state = self.active_state_mut();
743 match add_cursor_at_next_match(state, &cursors) {
744 AddCursorResult::Success {
745 cursor,
746 total_cursors,
747 } => {
748 let next_id = CursorId(self.active_cursors().count());
750 let event = Event::AddCursor {
751 cursor_id: next_id,
752 position: cursor.position,
753 anchor: cursor.anchor,
754 };
755
756 self.active_event_log_mut().append(event.clone());
758 self.apply_event_to_active_buffer(&event);
759
760 self.status_message =
761 Some(t!("clipboard.added_cursor_match", count = total_cursors).to_string());
762 }
763 AddCursorResult::WordSelected {
764 word_start,
765 word_end,
766 } => {
767 let primary_id = self.active_cursors().primary_id();
769 let primary = self.active_cursors().primary();
770 let event = Event::MoveCursor {
771 cursor_id: primary_id,
772 old_position: primary.position,
773 new_position: word_end,
774 old_anchor: primary.anchor,
775 new_anchor: Some(word_start),
776 old_sticky_column: primary.sticky_column,
777 new_sticky_column: 0,
778 };
779
780 self.active_event_log_mut().append(event.clone());
782 self.apply_event_to_active_buffer(&event);
783 }
784 AddCursorResult::Failed { message } => {
785 self.status_message = Some(message);
786 }
787 }
788 }
789
790 pub fn add_cursor_above(&mut self) {
792 let cursors = self.active_cursors().clone();
793 let state = self.active_state_mut();
794 match add_cursor_above(state, &cursors) {
795 AddCursorResult::Success {
796 cursor,
797 total_cursors,
798 } => {
799 let next_id = CursorId(self.active_cursors().count());
801 let event = Event::AddCursor {
802 cursor_id: next_id,
803 position: cursor.position,
804 anchor: cursor.anchor,
805 };
806
807 self.active_event_log_mut().append(event.clone());
809 self.apply_event_to_active_buffer(&event);
810
811 self.status_message =
812 Some(t!("clipboard.added_cursor_above", count = total_cursors).to_string());
813 }
814 AddCursorResult::Failed { message } => {
815 self.status_message = Some(message);
816 }
817 AddCursorResult::WordSelected { .. } => unreachable!(),
818 }
819 }
820
821 pub fn add_cursor_below(&mut self) {
823 let cursors = self.active_cursors().clone();
824 let state = self.active_state_mut();
825 match add_cursor_below(state, &cursors) {
826 AddCursorResult::Success {
827 cursor,
828 total_cursors,
829 } => {
830 let next_id = CursorId(self.active_cursors().count());
832 let event = Event::AddCursor {
833 cursor_id: next_id,
834 position: cursor.position,
835 anchor: cursor.anchor,
836 };
837
838 self.active_event_log_mut().append(event.clone());
840 self.apply_event_to_active_buffer(&event);
841
842 self.status_message =
843 Some(t!("clipboard.added_cursor_below", count = total_cursors).to_string());
844 }
845 AddCursorResult::Failed { message } => {
846 self.status_message = Some(message);
847 }
848 AddCursorResult::WordSelected { .. } => unreachable!(),
849 }
850 }
851
852 pub fn yank_word_forward(&mut self) {
858 let cursor_positions: Vec<_> = self
859 .active_cursors()
860 .iter()
861 .map(|(_, c)| c.position)
862 .collect();
863 let ranges: Vec<_> = {
864 let state = self.active_state();
865 cursor_positions
866 .into_iter()
867 .filter_map(|start| {
868 let end = find_word_start_right(&state.buffer, start);
869 if end > start {
870 Some(start..end)
871 } else {
872 None
873 }
874 })
875 .collect()
876 };
877
878 if ranges.is_empty() {
879 return;
880 }
881
882 let mut text = String::new();
884 let state = self.active_state_mut();
885 for range in ranges {
886 if !text.is_empty() {
887 text.push('\n');
888 }
889 let range_text = state.get_text_range(range.start, range.end);
890 text.push_str(&range_text);
891 }
892
893 if !text.is_empty() {
894 let len = text.len();
895 self.clipboard.copy(text);
896 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
897 }
898 }
899
900 pub fn yank_vi_word_end(&mut self) {
902 let cursor_positions: Vec<_> = self
903 .active_cursors()
904 .iter()
905 .map(|(_, c)| c.position)
906 .collect();
907 let ranges: Vec<_> = {
908 let state = self.active_state();
909 cursor_positions
910 .into_iter()
911 .filter_map(|start| {
912 let word_end = find_vi_word_end(&state.buffer, start);
913 let end = (word_end + 1).min(state.buffer.len());
914 if end > start {
915 Some(start..end)
916 } else {
917 None
918 }
919 })
920 .collect()
921 };
922
923 if ranges.is_empty() {
924 return;
925 }
926
927 let mut text = String::new();
928 let state = self.active_state_mut();
929 for range in ranges {
930 if !text.is_empty() {
931 text.push('\n');
932 }
933 let range_text = state.get_text_range(range.start, range.end);
934 text.push_str(&range_text);
935 }
936
937 if !text.is_empty() {
938 let len = text.len();
939 self.clipboard.copy(text);
940 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
941 }
942 }
943
944 pub fn yank_word_backward(&mut self) {
946 let cursor_positions: Vec<_> = self
947 .active_cursors()
948 .iter()
949 .map(|(_, c)| c.position)
950 .collect();
951 let ranges: Vec<_> = {
952 let state = self.active_state();
953 cursor_positions
954 .into_iter()
955 .filter_map(|end| {
956 let start = find_word_start_left(&state.buffer, end);
957 if start < end {
958 Some(start..end)
959 } else {
960 None
961 }
962 })
963 .collect()
964 };
965
966 if ranges.is_empty() {
967 return;
968 }
969
970 let mut text = String::new();
971 let state = self.active_state_mut();
972 for range in ranges {
973 if !text.is_empty() {
974 text.push('\n');
975 }
976 let range_text = state.get_text_range(range.start, range.end);
977 text.push_str(&range_text);
978 }
979
980 if !text.is_empty() {
981 let len = text.len();
982 self.clipboard.copy(text);
983 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
984 }
985 }
986
987 pub fn yank_to_line_end(&mut self) {
989 let estimated_line_length = 80;
990
991 let cursor_positions: Vec<_> = self
993 .active_cursors()
994 .iter()
995 .map(|(_, cursor)| cursor.position)
996 .collect();
997
998 let state = self.active_state_mut();
1000 let mut ranges = Vec::new();
1001 for pos in cursor_positions {
1002 let mut iter = state.buffer.line_iterator(pos, estimated_line_length);
1003 let line_start = iter.current_position();
1004 if let Some((_start, content)) = iter.next_line() {
1005 let content_len = content.trim_end_matches(&['\n', '\r'][..]).len();
1007 let line_end = line_start + content_len;
1008 if pos < line_end {
1009 ranges.push(pos..line_end);
1010 }
1011 }
1012 }
1013
1014 if ranges.is_empty() {
1015 return;
1016 }
1017
1018 let mut text = String::new();
1019 for range in ranges {
1020 if !text.is_empty() {
1021 text.push('\n');
1022 }
1023 let range_text = state.get_text_range(range.start, range.end);
1024 text.push_str(&range_text);
1025 }
1026
1027 if !text.is_empty() {
1028 let len = text.len();
1029 self.clipboard.copy(text);
1030 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
1031 }
1032 }
1033
1034 pub fn yank_to_line_start(&mut self) {
1036 let estimated_line_length = 80;
1037
1038 let cursor_positions: Vec<_> = self
1040 .active_cursors()
1041 .iter()
1042 .map(|(_, cursor)| cursor.position)
1043 .collect();
1044
1045 let state = self.active_state_mut();
1047 let mut ranges = Vec::new();
1048 for pos in cursor_positions {
1049 let iter = state.buffer.line_iterator(pos, estimated_line_length);
1050 let line_start = iter.current_position();
1051 if pos > line_start {
1052 ranges.push(line_start..pos);
1053 }
1054 }
1055
1056 if ranges.is_empty() {
1057 return;
1058 }
1059
1060 let mut text = String::new();
1061 for range in ranges {
1062 if !text.is_empty() {
1063 text.push('\n');
1064 }
1065 let range_text = state.get_text_range(range.start, range.end);
1066 text.push_str(&range_text);
1067 }
1068
1069 if !text.is_empty() {
1070 let len = text.len();
1071 self.clipboard.copy(text);
1072 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
1073 }
1074 }
1075}