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) {
527 if paste_text.is_empty() {
528 return;
529 }
530
531 let normalized = paste_text.replace("\r\n", "\n").replace('\r', "\n");
534
535 if let Some(prompt) = self.prompt.as_mut() {
537 prompt.insert_str(&normalized);
538 self.update_prompt_suggestions();
539 self.status_message = Some(t!("clipboard.pasted").to_string());
540 return;
541 }
542
543 if self.terminal_mode {
545 self.send_terminal_input(normalized.as_bytes());
546 return;
547 }
548
549 let buffer_line_ending = self.active_state().buffer.line_ending();
551 let paste_text = match buffer_line_ending {
552 crate::model::buffer::LineEnding::LF => normalized,
553 crate::model::buffer::LineEnding::CRLF => normalized.replace('\n', "\r\n"),
554 crate::model::buffer::LineEnding::CR => normalized.replace('\n', "\r"),
555 };
556
557 let mut events = Vec::new();
558
559 let mut cursor_data: Vec<_> = self
561 .active_cursors()
562 .iter()
563 .map(|(cursor_id, cursor)| {
564 let selection = cursor.selection_range();
565 let insert_position = selection
566 .as_ref()
567 .map(|r| r.start)
568 .unwrap_or(cursor.position);
569 (cursor_id, selection, insert_position)
570 })
571 .collect();
572 cursor_data.sort_by_key(|(_, _, pos)| std::cmp::Reverse(*pos));
573
574 let cursor_data_with_text: Vec<_> = {
576 let state = self.active_state_mut();
577 cursor_data
578 .into_iter()
579 .map(|(cursor_id, selection, insert_position)| {
580 let deleted_text = selection
581 .as_ref()
582 .map(|r| state.get_text_range(r.start, r.end));
583 (cursor_id, selection, insert_position, deleted_text)
584 })
585 .collect()
586 };
587
588 for (cursor_id, selection, insert_position, deleted_text) in cursor_data_with_text {
590 if let (Some(range), Some(text)) = (selection, deleted_text) {
591 events.push(Event::Delete {
592 range,
593 deleted_text: text,
594 cursor_id,
595 });
596 }
597 events.push(Event::Insert {
598 position: insert_position,
599 text: paste_text.clone(),
600 cursor_id,
601 });
602 }
603
604 if events.len() > 1 {
606 if let Some(bulk_edit) = self.apply_events_as_bulk_edit(events, "Paste".to_string()) {
608 self.active_event_log_mut().append(bulk_edit);
609 }
610 } else if let Some(event) = events.into_iter().next() {
611 self.log_and_apply_event(&event);
612 }
613
614 self.status_message = Some(t!("clipboard.pasted").to_string());
615 }
616
617 #[doc(hidden)]
621 pub fn set_clipboard_for_test(&mut self, text: String) {
622 self.clipboard.set_internal(text);
623 self.clipboard.set_internal_only(true);
624 }
625
626 #[doc(hidden)]
629 pub fn paste_for_test(&mut self) {
630 let paste_text = match self.clipboard.paste_internal() {
632 Some(text) => text,
633 None => return,
634 };
635
636 self.paste_text(paste_text);
638 }
639
640 #[doc(hidden)]
643 pub fn clipboard_content_for_test(&self) -> String {
644 self.clipboard.get_internal().to_string()
645 }
646
647 pub fn copy_buffer_path(&mut self, buffer_id: crate::model::event::BufferId, relative: bool) {
658 let path = self
659 .buffers
660 .get(&buffer_id)
661 .and_then(|state| state.buffer.file_path().map(|p| p.to_path_buf()));
662 let Some(path) = path else {
663 self.status_message = Some(t!("clipboard.no_file_path").to_string());
664 return;
665 };
666
667 let path_str = if relative {
668 path.strip_prefix(&self.working_dir)
669 .unwrap_or(&path)
670 .to_string_lossy()
671 .into_owned()
672 } else {
673 path.to_string_lossy().into_owned()
674 };
675
676 self.clipboard.copy(path_str.clone());
677 self.status_message = Some(t!("clipboard.copied_path", path = &path_str).to_string());
678 }
679
680 pub fn copy_active_buffer_path(&mut self, relative: bool) {
682 let buffer_id = self.active_buffer();
683 self.copy_buffer_path(buffer_id, relative);
684 }
685
686 pub fn add_cursor_at_next_match(&mut self) {
689 let cursors = self.active_cursors().clone();
690 let state = self.active_state_mut();
691 match add_cursor_at_next_match(state, &cursors) {
692 AddCursorResult::Success {
693 cursor,
694 total_cursors,
695 } => {
696 let next_id = CursorId(self.active_cursors().count());
698 let event = Event::AddCursor {
699 cursor_id: next_id,
700 position: cursor.position,
701 anchor: cursor.anchor,
702 };
703
704 self.active_event_log_mut().append(event.clone());
706 self.apply_event_to_active_buffer(&event);
707
708 self.status_message =
709 Some(t!("clipboard.added_cursor_match", count = total_cursors).to_string());
710 }
711 AddCursorResult::WordSelected {
712 word_start,
713 word_end,
714 } => {
715 let primary_id = self.active_cursors().primary_id();
717 let primary = self.active_cursors().primary();
718 let event = Event::MoveCursor {
719 cursor_id: primary_id,
720 old_position: primary.position,
721 new_position: word_end,
722 old_anchor: primary.anchor,
723 new_anchor: Some(word_start),
724 old_sticky_column: primary.sticky_column,
725 new_sticky_column: 0,
726 };
727
728 self.active_event_log_mut().append(event.clone());
730 self.apply_event_to_active_buffer(&event);
731 }
732 AddCursorResult::Failed { message } => {
733 self.status_message = Some(message);
734 }
735 }
736 }
737
738 pub fn add_cursor_above(&mut self) {
740 let cursors = self.active_cursors().clone();
741 let state = self.active_state_mut();
742 match add_cursor_above(state, &cursors) {
743 AddCursorResult::Success {
744 cursor,
745 total_cursors,
746 } => {
747 let next_id = CursorId(self.active_cursors().count());
749 let event = Event::AddCursor {
750 cursor_id: next_id,
751 position: cursor.position,
752 anchor: cursor.anchor,
753 };
754
755 self.active_event_log_mut().append(event.clone());
757 self.apply_event_to_active_buffer(&event);
758
759 self.status_message =
760 Some(t!("clipboard.added_cursor_above", count = total_cursors).to_string());
761 }
762 AddCursorResult::Failed { message } => {
763 self.status_message = Some(message);
764 }
765 AddCursorResult::WordSelected { .. } => unreachable!(),
766 }
767 }
768
769 pub fn add_cursor_below(&mut self) {
771 let cursors = self.active_cursors().clone();
772 let state = self.active_state_mut();
773 match add_cursor_below(state, &cursors) {
774 AddCursorResult::Success {
775 cursor,
776 total_cursors,
777 } => {
778 let next_id = CursorId(self.active_cursors().count());
780 let event = Event::AddCursor {
781 cursor_id: next_id,
782 position: cursor.position,
783 anchor: cursor.anchor,
784 };
785
786 self.active_event_log_mut().append(event.clone());
788 self.apply_event_to_active_buffer(&event);
789
790 self.status_message =
791 Some(t!("clipboard.added_cursor_below", count = total_cursors).to_string());
792 }
793 AddCursorResult::Failed { message } => {
794 self.status_message = Some(message);
795 }
796 AddCursorResult::WordSelected { .. } => unreachable!(),
797 }
798 }
799
800 pub fn yank_word_forward(&mut self) {
806 let cursor_positions: Vec<_> = self
807 .active_cursors()
808 .iter()
809 .map(|(_, c)| c.position)
810 .collect();
811 let ranges: Vec<_> = {
812 let state = self.active_state();
813 cursor_positions
814 .into_iter()
815 .filter_map(|start| {
816 let end = find_word_start_right(&state.buffer, start);
817 if end > start {
818 Some(start..end)
819 } else {
820 None
821 }
822 })
823 .collect()
824 };
825
826 if ranges.is_empty() {
827 return;
828 }
829
830 let mut text = String::new();
832 let state = self.active_state_mut();
833 for range in ranges {
834 if !text.is_empty() {
835 text.push('\n');
836 }
837 let range_text = state.get_text_range(range.start, range.end);
838 text.push_str(&range_text);
839 }
840
841 if !text.is_empty() {
842 let len = text.len();
843 self.clipboard.copy(text);
844 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
845 }
846 }
847
848 pub fn yank_vi_word_end(&mut self) {
850 let cursor_positions: Vec<_> = self
851 .active_cursors()
852 .iter()
853 .map(|(_, c)| c.position)
854 .collect();
855 let ranges: Vec<_> = {
856 let state = self.active_state();
857 cursor_positions
858 .into_iter()
859 .filter_map(|start| {
860 let word_end = find_vi_word_end(&state.buffer, start);
861 let end = (word_end + 1).min(state.buffer.len());
862 if end > start {
863 Some(start..end)
864 } else {
865 None
866 }
867 })
868 .collect()
869 };
870
871 if ranges.is_empty() {
872 return;
873 }
874
875 let mut text = String::new();
876 let state = self.active_state_mut();
877 for range in ranges {
878 if !text.is_empty() {
879 text.push('\n');
880 }
881 let range_text = state.get_text_range(range.start, range.end);
882 text.push_str(&range_text);
883 }
884
885 if !text.is_empty() {
886 let len = text.len();
887 self.clipboard.copy(text);
888 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
889 }
890 }
891
892 pub fn yank_word_backward(&mut self) {
894 let cursor_positions: Vec<_> = self
895 .active_cursors()
896 .iter()
897 .map(|(_, c)| c.position)
898 .collect();
899 let ranges: Vec<_> = {
900 let state = self.active_state();
901 cursor_positions
902 .into_iter()
903 .filter_map(|end| {
904 let start = find_word_start_left(&state.buffer, end);
905 if start < end {
906 Some(start..end)
907 } else {
908 None
909 }
910 })
911 .collect()
912 };
913
914 if ranges.is_empty() {
915 return;
916 }
917
918 let mut text = String::new();
919 let state = self.active_state_mut();
920 for range in ranges {
921 if !text.is_empty() {
922 text.push('\n');
923 }
924 let range_text = state.get_text_range(range.start, range.end);
925 text.push_str(&range_text);
926 }
927
928 if !text.is_empty() {
929 let len = text.len();
930 self.clipboard.copy(text);
931 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
932 }
933 }
934
935 pub fn yank_to_line_end(&mut self) {
937 let estimated_line_length = 80;
938
939 let cursor_positions: Vec<_> = self
941 .active_cursors()
942 .iter()
943 .map(|(_, cursor)| cursor.position)
944 .collect();
945
946 let state = self.active_state_mut();
948 let mut ranges = Vec::new();
949 for pos in cursor_positions {
950 let mut iter = state.buffer.line_iterator(pos, estimated_line_length);
951 let line_start = iter.current_position();
952 if let Some((_start, content)) = iter.next_line() {
953 let content_len = content.trim_end_matches(&['\n', '\r'][..]).len();
955 let line_end = line_start + content_len;
956 if pos < line_end {
957 ranges.push(pos..line_end);
958 }
959 }
960 }
961
962 if ranges.is_empty() {
963 return;
964 }
965
966 let mut text = String::new();
967 for range in ranges {
968 if !text.is_empty() {
969 text.push('\n');
970 }
971 let range_text = state.get_text_range(range.start, range.end);
972 text.push_str(&range_text);
973 }
974
975 if !text.is_empty() {
976 let len = text.len();
977 self.clipboard.copy(text);
978 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
979 }
980 }
981
982 pub fn yank_to_line_start(&mut self) {
984 let estimated_line_length = 80;
985
986 let cursor_positions: Vec<_> = self
988 .active_cursors()
989 .iter()
990 .map(|(_, cursor)| cursor.position)
991 .collect();
992
993 let state = self.active_state_mut();
995 let mut ranges = Vec::new();
996 for pos in cursor_positions {
997 let iter = state.buffer.line_iterator(pos, estimated_line_length);
998 let line_start = iter.current_position();
999 if pos > line_start {
1000 ranges.push(line_start..pos);
1001 }
1002 }
1003
1004 if ranges.is_empty() {
1005 return;
1006 }
1007
1008 let mut text = String::new();
1009 for range in ranges {
1010 if !text.is_empty() {
1011 text.push('\n');
1012 }
1013 let range_text = state.get_text_range(range.start, range.end);
1014 text.push_str(&range_text);
1015 }
1016
1017 if !text.is_empty() {
1018 let len = text.len();
1019 self.clipboard.copy(text);
1020 self.status_message = Some(t!("clipboard.yanked", count = len).to_string());
1021 }
1022 }
1023}