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.active_window_mut().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.active_window_mut().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.active_window_mut().status_message =
108 Some(t!("clipboard.copied_line").to_string());
109 }
110 }
111 }
112
113 fn copy_block_selection_text(&mut self) -> String {
122 let estimated_line_length = 120;
123
124 let block_infos: Vec<_> = self
126 .active_cursors()
127 .iter()
128 .filter_map(|(_, cursor)| {
129 if !cursor.has_block_selection() {
130 return None;
131 }
132 let block_anchor = cursor.block_anchor?;
133 let anchor_byte = cursor.anchor?; let cursor_byte = cursor.position;
135 Some((block_anchor, anchor_byte, cursor_byte))
136 })
137 .collect();
138
139 let mut result = String::new();
140
141 for (block_anchor, anchor_byte, cursor_byte) in block_infos {
142 let cursor_2d = {
144 let state = self.active_state();
145 byte_to_2d(&state.buffer, cursor_byte)
146 };
147
148 let min_col = block_anchor.column.min(cursor_2d.column);
150 let max_col = block_anchor.column.max(cursor_2d.column);
151
152 let start_byte = anchor_byte.min(cursor_byte);
154 let end_byte = anchor_byte.max(cursor_byte);
155
156 let state = self.active_state_mut();
158 let mut iter = state
159 .buffer
160 .line_iterator(start_byte, estimated_line_length);
161
162 let mut lines_text = Vec::new();
164 loop {
165 let line_start = iter.current_position();
166
167 if line_start > end_byte {
169 break;
170 }
171
172 if let Some((_offset, line_content)) = iter.next_line() {
173 let content_without_newline = line_content.trim_end_matches(&['\n', '\r'][..]);
176 let chars: Vec<char> = content_without_newline.chars().collect();
177
178 let extracted: String = chars
180 .iter()
181 .skip(min_col)
182 .take(max_col.saturating_sub(min_col))
183 .collect();
184
185 lines_text.push(extracted);
186
187 if line_start + line_content.len() > end_byte {
189 break;
190 }
191 } else {
192 break;
193 }
194 }
195
196 if !result.is_empty() && !lines_text.is_empty() {
198 result.push('\n');
199 }
200 result.push_str(&lines_text.join("\n"));
201 }
202
203 result
204 }
205
206 pub fn copy_selection_with_theme(&mut self, theme_name: &str) {
211 let has_selection = self
213 .active_cursors()
214 .iter()
215 .any(|(_, cursor)| cursor.selection_range().is_some());
216
217 if !has_selection {
218 self.active_window_mut().status_message =
219 Some(t!("clipboard.no_selection").to_string());
220 return;
221 }
222
223 if theme_name.is_empty() {
225 self.start_copy_with_formatting_prompt();
226 return;
227 }
228 use crate::services::styled_html::render_styled_html;
229
230 let theme = match self.theme_registry.get_cloned(theme_name) {
232 Some(t) => t,
233 None => {
234 self.active_window_mut().status_message =
235 Some(format!("Theme '{}' not found", theme_name));
236 return;
237 }
238 };
239
240 let ranges: Vec<_> = self
242 .active_cursors()
243 .iter()
244 .filter_map(|(_, cursor)| cursor.selection_range())
245 .collect();
246
247 if ranges.is_empty() {
248 self.active_window_mut().status_message =
249 Some(t!("clipboard.no_selection").to_string());
250 return;
251 }
252
253 let min_offset = ranges.iter().map(|r| r.start).min().unwrap_or(0);
255 let max_offset = ranges.iter().map(|r| r.end).max().unwrap_or(0);
256
257 let (text, highlight_spans) = {
259 let state = self.active_state_mut();
260
261 let mut text = String::new();
263 for range in &ranges {
264 if !text.is_empty() {
265 text.push('\n');
266 }
267 let range_text = state.get_text_range(range.start, range.end);
268 text.push_str(&range_text);
269 }
270
271 if text.is_empty() {
272 (text, Vec::new())
273 } else {
274 let highlight_spans = state.highlighter.highlight_viewport(
276 &state.buffer,
277 min_offset,
278 max_offset,
279 &theme,
280 0, );
282 (text, highlight_spans)
283 }
284 };
285
286 if text.is_empty() {
287 self.active_window_mut().status_message = Some(t!("clipboard.no_text").to_string());
288 return;
289 }
290
291 let adjusted_spans: Vec<_> = if ranges.len() == 1 {
293 let base_offset = ranges[0].start;
294 highlight_spans
295 .into_iter()
296 .filter_map(|span| {
297 if span.range.end <= base_offset || span.range.start >= ranges[0].end {
298 return None;
299 }
300 let start = span.range.start.saturating_sub(base_offset);
301 let end = (span.range.end - base_offset).min(text.len());
302 if start < end {
303 Some(crate::primitives::highlighter::HighlightSpan {
304 range: start..end,
305 color: span.color,
306 category: span.category,
307 })
308 } else {
309 None
310 }
311 })
312 .collect()
313 } else {
314 Vec::new()
315 };
316
317 let html = render_styled_html(&text, &adjusted_spans, &theme);
319
320 if self.clipboard.copy_html(&html, &text) {
322 self.active_window_mut().status_message =
323 Some(t!("clipboard.copied_with_theme", theme = theme_name).to_string());
324 } else {
325 self.clipboard.copy(text);
326 self.active_window_mut().status_message =
327 Some(t!("clipboard.copied_plain").to_string());
328 }
329 }
330
331 fn start_copy_with_formatting_prompt(&mut self) {
333 use crate::view::prompt::PromptType;
334
335 let available_themes = self.theme_registry.list();
336 let resolved_current = self
339 .theme_registry
340 .resolve_key(&self.config.theme.0)
341 .unwrap_or_else(|| self.config.theme.0.clone());
342 let current_theme_key = resolved_current.as_str();
343
344 let current_index = available_themes
346 .iter()
347 .position(|info| info.key == *current_theme_key)
348 .or_else(|| {
349 let normalized = crate::view::theme::normalize_theme_name(current_theme_key);
350 available_themes.iter().position(|info| {
351 crate::view::theme::normalize_theme_name(&info.name) == normalized
352 })
353 })
354 .unwrap_or(0);
355
356 let suggestions: Vec<crate::input::commands::Suggestion> = available_themes
357 .iter()
358 .map(|info| {
359 let is_current = Some(info) == available_themes.get(current_index);
360 let description = if is_current {
361 Some(format!("{} (current)", info.key))
362 } else {
363 Some(info.key.clone())
364 };
365 crate::input::commands::Suggestion {
366 text: info.name.clone(),
367 description,
368 value: Some(info.key.clone()),
369 disabled: false,
370 keybinding: None,
371 source: None,
372 }
373 })
374 .collect();
375
376 self.active_window_mut().prompt = Some(crate::view::prompt::Prompt::with_suggestions(
377 "Copy with theme: ".to_string(),
378 PromptType::CopyWithFormattingTheme,
379 suggestions,
380 ));
381
382 if let Some(prompt) = self.active_window_mut().prompt.as_mut() {
383 if !prompt.suggestions.is_empty() {
384 prompt.selected_suggestion = Some(current_index);
385 prompt.input = current_theme_key.to_string();
386 prompt.cursor_pos = prompt.input.len();
387 }
388 }
389 }
390
391 pub fn cut_selection(&mut self) {
395 let has_selection = self
397 .active_cursors()
398 .iter()
399 .any(|(_, cursor)| cursor.selection_range().is_some());
400
401 self.copy_selection();
403
404 if has_selection {
405 let mut deletions: Vec<_> = self
408 .active_cursors()
409 .iter()
410 .filter_map(|(_, c)| c.selection_range())
411 .collect();
412 deletions.sort_by_key(|r| r.start);
414
415 let primary_id = self.active_cursors().primary_id();
416 let state = self.active_state_mut();
417 let events: Vec<_> = deletions
418 .iter()
419 .rev()
420 .map(|range| {
421 let deleted_text = state.get_text_range(range.start, range.end);
422 Event::Delete {
423 range: range.clone(),
424 deleted_text,
425 cursor_id: primary_id,
426 }
427 })
428 .collect();
429
430 if events.len() > 1 {
432 if let Some(bulk_edit) = self.apply_events_as_bulk_edit(events, "Cut".to_string()) {
434 self.active_event_log_mut().append(bulk_edit);
435 }
436 } else if let Some(event) = events.into_iter().next() {
437 self.log_and_apply_event(&event);
438 }
439
440 if !deletions.is_empty() {
441 self.active_window_mut().status_message = Some(t!("clipboard.cut").to_string());
442 }
443 } else {
444 let estimated_line_length = 80;
446
447 let positions: Vec<_> = self
450 .active_cursors()
451 .iter()
452 .map(|(_, c)| c.position)
453 .collect();
454 let mut deletions: Vec<_> = {
455 let state = self.active_state_mut();
456 positions
457 .into_iter()
458 .filter_map(|pos| {
459 let mut iter = state.buffer.line_iterator(pos, estimated_line_length);
460 let line_start = iter.current_position();
461 iter.next_line().map(|(_start, content)| {
462 let line_end = line_start + content.len();
463 line_start..line_end
464 })
465 })
466 .collect()
467 };
468 deletions.sort_by_key(|r| r.start);
470
471 let primary_id = self.active_cursors().primary_id();
472 let state = self.active_state_mut();
473 let events: Vec<_> = deletions
474 .iter()
475 .rev()
476 .map(|range| {
477 let deleted_text = state.get_text_range(range.start, range.end);
478 Event::Delete {
479 range: range.clone(),
480 deleted_text,
481 cursor_id: primary_id,
482 }
483 })
484 .collect();
485
486 if events.len() > 1 {
488 if let Some(bulk_edit) =
490 self.apply_events_as_bulk_edit(events, "Cut line".to_string())
491 {
492 self.active_event_log_mut().append(bulk_edit);
493 }
494 } else if let Some(event) = events.into_iter().next() {
495 self.log_and_apply_event(&event);
496 }
497
498 if !deletions.is_empty() {
499 self.active_window_mut().status_message =
500 Some(t!("clipboard.cut_line").to_string());
501 }
502 }
503 }
504
505 pub fn paste(&mut self) {
513 let text = match self.clipboard.paste() {
515 Some(text) => text,
516 None => return,
517 };
518
519 self.paste_text(text);
521 }
522
523 pub fn paste_text(&mut self, paste_text: String) {
537 if paste_text.is_empty() {
538 return;
539 }
540
541 let normalized = paste_text.replace("\r\n", "\n").replace('\r', "\n");
544
545 if let Some(prompt) = self.active_window_mut().prompt.as_mut() {
547 prompt.insert_str(&normalized);
548 self.update_prompt_suggestions();
549 self.active_window_mut().status_message = Some(t!("clipboard.pasted").to_string());
550 return;
551 }
552
553 if self.active_window().terminal_mode {
555 self.active_window_mut()
556 .send_terminal_input(normalized.as_bytes());
557 return;
558 }
559
560 let mut cursor_data: Vec<_> = self
562 .active_cursors()
563 .iter()
564 .map(|(cursor_id, cursor)| {
565 let selection = cursor.selection_range();
566 let insert_position = selection
567 .as_ref()
568 .map(|r| r.start)
569 .unwrap_or(cursor.position);
570 (cursor_id, selection, insert_position)
571 })
572 .collect();
573 cursor_data.sort_by_key(|(_, _, pos)| std::cmp::Reverse(*pos));
574
575 let mut lines_for_distribution: Vec<&str> = normalized.split('\n').collect();
580 if lines_for_distribution.len() > 1 && lines_for_distribution.last() == Some(&"") {
581 lines_for_distribution.pop();
582 }
583 let use_column_paste = cursor_data.len() > 1
584 && lines_for_distribution.len() > 1
585 && lines_for_distribution.len() == cursor_data.len();
586
587 let paste_text_full = match self.active_state().buffer.line_ending() {
590 crate::model::buffer::LineEnding::LF => normalized.clone(),
591 crate::model::buffer::LineEnding::CRLF => normalized.replace('\n', "\r\n"),
592 crate::model::buffer::LineEnding::CR => normalized.replace('\n', "\r"),
593 };
594
595 let cursor_data_with_text: Vec<_> = {
597 let state = self.active_state_mut();
598 cursor_data
599 .into_iter()
600 .map(|(cursor_id, selection, insert_position)| {
601 let deleted_text = selection
602 .as_ref()
603 .map(|r| state.get_text_range(r.start, r.end));
604 (cursor_id, selection, insert_position, deleted_text)
605 })
606 .collect()
607 };
608
609 let total = cursor_data_with_text.len();
617 let mut events = Vec::new();
618 for (i, (cursor_id, selection, insert_position, deleted_text)) in
619 cursor_data_with_text.into_iter().enumerate()
620 {
621 if let (Some(range), Some(text)) = (selection, deleted_text) {
622 events.push(Event::Delete {
623 range,
624 deleted_text: text,
625 cursor_id,
626 });
627 }
628 let text = if use_column_paste {
629 lines_for_distribution[total - 1 - i].to_string()
630 } else {
631 paste_text_full.clone()
632 };
633 events.push(Event::Insert {
634 position: insert_position,
635 text,
636 cursor_id,
637 });
638 }
639
640 if events.len() > 1 {
642 if let Some(bulk_edit) = self.apply_events_as_bulk_edit(events, "Paste".to_string()) {
644 self.active_event_log_mut().append(bulk_edit);
645 }
646 } else if let Some(event) = events.into_iter().next() {
647 self.log_and_apply_event(&event);
648 }
649
650 self.active_window_mut().status_message = Some(t!("clipboard.pasted").to_string());
651 }
652
653 #[doc(hidden)]
657 pub fn set_clipboard_for_test(&mut self, text: String) {
658 self.clipboard.set_internal(text);
659 self.clipboard.set_internal_only(true);
660 }
661
662 #[doc(hidden)]
665 pub fn paste_for_test(&mut self) {
666 let paste_text = match self.clipboard.paste_internal() {
668 Some(text) => text,
669 None => return,
670 };
671
672 self.paste_text(paste_text);
674 }
675
676 #[doc(hidden)]
679 pub fn clipboard_content_for_test(&self) -> String {
680 self.clipboard.get_internal().to_string()
681 }
682
683 pub fn copy_buffer_path(&mut self, buffer_id: crate::model::event::BufferId, relative: bool) {
694 let path = self
695 .buffers()
696 .get(&buffer_id)
697 .and_then(|state| state.buffer.file_path().map(|p| p.to_path_buf()));
698 let Some(path) = path else {
699 self.active_window_mut().status_message =
700 Some(t!("clipboard.no_file_path").to_string());
701 return;
702 };
703
704 let path_str = if relative {
705 path.strip_prefix(&self.working_dir)
706 .unwrap_or(&path)
707 .to_string_lossy()
708 .into_owned()
709 } else {
710 path.to_string_lossy().into_owned()
711 };
712
713 self.clipboard.copy(path_str.clone());
714 self.active_window_mut().status_message =
715 Some(t!("clipboard.copied_path", path = &path_str).to_string());
716 }
717
718 pub fn copy_active_buffer_path(&mut self, relative: bool) {
720 let buffer_id = self.active_buffer();
721 self.copy_buffer_path(buffer_id, relative);
722 }
723
724 pub fn add_cursor_at_next_match(&mut self) {
733 if let Some(range) = self.active_window().search_match_at_primary_cursor() {
734 let primary_id = self.active_cursors().primary_id();
735 let primary = self.active_cursors().primary();
736 let event = Event::MoveCursor {
737 cursor_id: primary_id,
738 old_position: primary.position,
739 new_position: range.end,
740 old_anchor: primary.anchor,
741 new_anchor: Some(range.start),
742 old_sticky_column: primary.sticky_column,
743 new_sticky_column: 0,
744 };
745 self.active_event_log_mut().append(event.clone());
746 self.apply_event_to_active_buffer(&event);
747 return;
748 }
749
750 let cursors = self.active_cursors().clone();
751 let state = self.active_state_mut();
752 match add_cursor_at_next_match(state, &cursors) {
753 AddCursorResult::Success {
754 cursor,
755 total_cursors,
756 } => {
757 let next_id = CursorId(self.active_cursors().count());
759 let event = Event::AddCursor {
760 cursor_id: next_id,
761 position: cursor.position,
762 anchor: cursor.anchor,
763 };
764
765 self.active_event_log_mut().append(event.clone());
767 self.apply_event_to_active_buffer(&event);
768
769 self.active_window_mut().status_message =
770 Some(t!("clipboard.added_cursor_match", count = total_cursors).to_string());
771 }
772 AddCursorResult::WordSelected {
773 word_start,
774 word_end,
775 } => {
776 let primary_id = self.active_cursors().primary_id();
778 let primary = self.active_cursors().primary();
779 let event = Event::MoveCursor {
780 cursor_id: primary_id,
781 old_position: primary.position,
782 new_position: word_end,
783 old_anchor: primary.anchor,
784 new_anchor: Some(word_start),
785 old_sticky_column: primary.sticky_column,
786 new_sticky_column: 0,
787 };
788
789 self.active_event_log_mut().append(event.clone());
791 self.apply_event_to_active_buffer(&event);
792 }
793 AddCursorResult::Failed { message } => {
794 self.active_window_mut().status_message = Some(message);
795 }
796 }
797 }
798
799 pub fn add_cursor_above(&mut self) {
801 let cursors = self.active_cursors().clone();
802 let state = self.active_state_mut();
803 match add_cursor_above(state, &cursors) {
804 AddCursorResult::Success {
805 cursor,
806 total_cursors,
807 } => {
808 let next_id = CursorId(self.active_cursors().count());
810 let event = Event::AddCursor {
811 cursor_id: next_id,
812 position: cursor.position,
813 anchor: cursor.anchor,
814 };
815
816 self.active_event_log_mut().append(event.clone());
818 self.apply_event_to_active_buffer(&event);
819
820 self.active_window_mut().status_message =
821 Some(t!("clipboard.added_cursor_above", count = total_cursors).to_string());
822 }
823 AddCursorResult::Failed { message } => {
824 self.active_window_mut().status_message = Some(message);
825 }
826 AddCursorResult::WordSelected { .. } => unreachable!(),
827 }
828 }
829
830 pub fn add_cursor_below(&mut self) {
832 let cursors = self.active_cursors().clone();
833 let state = self.active_state_mut();
834 match add_cursor_below(state, &cursors) {
835 AddCursorResult::Success {
836 cursor,
837 total_cursors,
838 } => {
839 let next_id = CursorId(self.active_cursors().count());
841 let event = Event::AddCursor {
842 cursor_id: next_id,
843 position: cursor.position,
844 anchor: cursor.anchor,
845 };
846
847 self.active_event_log_mut().append(event.clone());
849 self.apply_event_to_active_buffer(&event);
850
851 self.active_window_mut().status_message =
852 Some(t!("clipboard.added_cursor_below", count = total_cursors).to_string());
853 }
854 AddCursorResult::Failed { message } => {
855 self.active_window_mut().status_message = Some(message);
856 }
857 AddCursorResult::WordSelected { .. } => unreachable!(),
858 }
859 }
860
861 pub fn yank_word_forward(&mut self) {
867 let cursor_positions: Vec<_> = self
868 .active_cursors()
869 .iter()
870 .map(|(_, c)| c.position)
871 .collect();
872 let ranges: Vec<_> = {
873 let state = self.active_state();
874 cursor_positions
875 .into_iter()
876 .filter_map(|start| {
877 let end = find_word_start_right(&state.buffer, start);
878 if end > start {
879 Some(start..end)
880 } else {
881 None
882 }
883 })
884 .collect()
885 };
886
887 if ranges.is_empty() {
888 return;
889 }
890
891 let mut text = String::new();
893 let state = self.active_state_mut();
894 for range in ranges {
895 if !text.is_empty() {
896 text.push('\n');
897 }
898 let range_text = state.get_text_range(range.start, range.end);
899 text.push_str(&range_text);
900 }
901
902 if !text.is_empty() {
903 let len = text.len();
904 self.clipboard.copy(text);
905 self.active_window_mut().status_message =
906 Some(t!("clipboard.yanked", count = len).to_string());
907 }
908 }
909
910 pub fn yank_vi_word_end(&mut self) {
912 let cursor_positions: Vec<_> = self
913 .active_cursors()
914 .iter()
915 .map(|(_, c)| c.position)
916 .collect();
917 let ranges: Vec<_> = {
918 let state = self.active_state();
919 cursor_positions
920 .into_iter()
921 .filter_map(|start| {
922 let word_end = find_vi_word_end(&state.buffer, start);
923 let end = (word_end + 1).min(state.buffer.len());
924 if end > start {
925 Some(start..end)
926 } else {
927 None
928 }
929 })
930 .collect()
931 };
932
933 if ranges.is_empty() {
934 return;
935 }
936
937 let mut text = String::new();
938 let state = self.active_state_mut();
939 for range in ranges {
940 if !text.is_empty() {
941 text.push('\n');
942 }
943 let range_text = state.get_text_range(range.start, range.end);
944 text.push_str(&range_text);
945 }
946
947 if !text.is_empty() {
948 let len = text.len();
949 self.clipboard.copy(text);
950 self.active_window_mut().status_message =
951 Some(t!("clipboard.yanked", count = len).to_string());
952 }
953 }
954
955 pub fn yank_word_backward(&mut self) {
957 let cursor_positions: Vec<_> = self
958 .active_cursors()
959 .iter()
960 .map(|(_, c)| c.position)
961 .collect();
962 let ranges: Vec<_> = {
963 let state = self.active_state();
964 cursor_positions
965 .into_iter()
966 .filter_map(|end| {
967 let start = find_word_start_left(&state.buffer, end);
968 if start < end {
969 Some(start..end)
970 } else {
971 None
972 }
973 })
974 .collect()
975 };
976
977 if ranges.is_empty() {
978 return;
979 }
980
981 let mut text = String::new();
982 let state = self.active_state_mut();
983 for range in ranges {
984 if !text.is_empty() {
985 text.push('\n');
986 }
987 let range_text = state.get_text_range(range.start, range.end);
988 text.push_str(&range_text);
989 }
990
991 if !text.is_empty() {
992 let len = text.len();
993 self.clipboard.copy(text);
994 self.active_window_mut().status_message =
995 Some(t!("clipboard.yanked", count = len).to_string());
996 }
997 }
998
999 pub fn yank_to_line_end(&mut self) {
1001 let estimated_line_length = 80;
1002
1003 let cursor_positions: Vec<_> = self
1005 .active_cursors()
1006 .iter()
1007 .map(|(_, cursor)| cursor.position)
1008 .collect();
1009
1010 let state = self.active_state_mut();
1012 let mut ranges = Vec::new();
1013 for pos in cursor_positions {
1014 let mut iter = state.buffer.line_iterator(pos, estimated_line_length);
1015 let line_start = iter.current_position();
1016 if let Some((_start, content)) = iter.next_line() {
1017 let content_len = content.trim_end_matches(&['\n', '\r'][..]).len();
1019 let line_end = line_start + content_len;
1020 if pos < line_end {
1021 ranges.push(pos..line_end);
1022 }
1023 }
1024 }
1025
1026 if ranges.is_empty() {
1027 return;
1028 }
1029
1030 let mut text = String::new();
1031 for range in ranges {
1032 if !text.is_empty() {
1033 text.push('\n');
1034 }
1035 let range_text = state.get_text_range(range.start, range.end);
1036 text.push_str(&range_text);
1037 }
1038
1039 if !text.is_empty() {
1040 let len = text.len();
1041 self.clipboard.copy(text);
1042 self.active_window_mut().status_message =
1043 Some(t!("clipboard.yanked", count = len).to_string());
1044 }
1045 }
1046
1047 pub fn yank_to_line_start(&mut self) {
1049 let estimated_line_length = 80;
1050
1051 let cursor_positions: Vec<_> = self
1053 .active_cursors()
1054 .iter()
1055 .map(|(_, cursor)| cursor.position)
1056 .collect();
1057
1058 let state = self.active_state_mut();
1060 let mut ranges = Vec::new();
1061 for pos in cursor_positions {
1062 let iter = state.buffer.line_iterator(pos, estimated_line_length);
1063 let line_start = iter.current_position();
1064 if pos > line_start {
1065 ranges.push(line_start..pos);
1066 }
1067 }
1068
1069 if ranges.is_empty() {
1070 return;
1071 }
1072
1073 let mut text = String::new();
1074 for range in ranges {
1075 if !text.is_empty() {
1076 text.push('\n');
1077 }
1078 let range_text = state.get_text_range(range.start, range.end);
1079 text.push_str(&range_text);
1080 }
1081
1082 if !text.is_empty() {
1083 let len = text.len();
1084 self.clipboard.copy(text);
1085 self.active_window_mut().status_message =
1086 Some(t!("clipboard.yanked", count = len).to_string());
1087 }
1088 }
1089}