use super::*;use util::rel_path::RelPath; #[derive(Serialize, Deserialize, Clone, Debug)]pub struct ClipboardSelection { /// The number of bytes in this selection. pub len: usize, /// Whether this was a full-line selection. pub is_entire_line: bool, /// The indentation of the first line when this content was originally copied. pub first_line_indent: u32, #[serde(default)] pub file_path: Option<PathBuf>, #[serde(default)] pub line_range: Option<RangeInclusive<u32>>,} impl ClipboardSelection { pub fn for_buffer( len: usize, is_entire_line: bool, range: Range<Point>, buffer: &MultiBufferSnapshot, project: Option<&Entity<Project>>, cx: &App, ) -> Self { let first_line_indent = buffer .indent_size_for_line(MultiBufferRow(range.start.row)) .len; let file_path = util::maybe!({ let project = project?.read(cx); let file = buffer.file_at(range.start)?; let project_path = ProjectPath { worktree_id: file.worktree_id(cx), path: file.path().clone(), }; project.absolute_path(&project_path, cx) }); let line_range = if file_path.is_some() { buffer .range_to_buffer_range(range) .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row) } else { None }; Self { len, is_entire_line, first_line_indent, file_path, line_range, } }} impl Editor { pub fn do_paste( &mut self, text: &String, clipboard_selections: Option<Vec<ClipboardSelection>>, handle_entire_lines: bool, window: &mut Window, cx: &mut Context<Self>, ) { if self.read_only(cx) { return; } self.finalize_last_transaction(cx); let clipboard_text = Cow::Borrowed(text.as_str()); self.transact(window, cx, |this, window, cx| { let had_active_edit_prediction = this.has_active_edit_prediction(); let display_map = this.display_snapshot(cx); let old_selections = this.selections.all::<MultiBufferOffset>(&display_map); let cursor_offset = this .selections .last::<MultiBufferOffset>(&display_map) .head(); if let Some(mut clipboard_selections) = clipboard_selections { let all_selections_were_entire_line = clipboard_selections.iter().all(|s| s.is_entire_line); let first_selection_indent_column = clipboard_selections.first().map(|s| s.first_line_indent); if clipboard_selections.len() != old_selections.len() { clipboard_selections.drain(..); } let mut auto_indent_on_paste = true; this.buffer.update(cx, |buffer, cx| { let snapshot = buffer.read(cx); auto_indent_on_paste = snapshot .language_settings_at(cursor_offset, cx) .auto_indent_on_paste; let mut start_offset = 0; let mut edits = Vec::new(); let mut original_indent_columns = Vec::new(); for (ix, selection) in old_selections.iter().enumerate() { let to_insert; let entire_line; let original_indent_column; if let Some(clipboard_selection) = clipboard_selections.get(ix) { let end_offset = start_offset + clipboard_selection.len; to_insert = &clipboard_text[start_offset..end_offset]; entire_line = clipboard_selection.is_entire_line; start_offset = if entire_line { end_offset } else { end_offset + 1 }; original_indent_column = Some(clipboard_selection.first_line_indent); } else { to_insert = &*clipboard_text; entire_line = all_selections_were_entire_line; original_indent_column = first_selection_indent_column } let (range, to_insert) = if selection.is_empty() && handle_entire_lines && entire_line { // If the corresponding selection was empty when this slice of the // clipboard text was written, then the entire line containing the // selection was copied. If this selection is also currently empty, // then paste the line before the current line of the buffer. let column = selection.start.to_point(&snapshot).column as usize; let line_start = selection.start - column; (line_start..line_start, Cow::Borrowed(to_insert)) } else { let language = snapshot.language_at(selection.head()); let range = selection.range(); if let Some(language) = language && language.name() == "Markdown" { edit_for_markdown_paste( &snapshot, range, to_insert, is_standalone_url(to_insert), ) } else { (range, Cow::Borrowed(to_insert)) } }; edits.push((range, to_insert)); original_indent_columns.push(original_indent_column); } drop(snapshot); buffer.edit( edits, if auto_indent_on_paste { Some(AutoindentMode::Block { original_indent_columns, }) } else { None }, cx, ); }); let selections = this .selections .all::<MultiBufferOffset>(&this.display_snapshot(cx)); this.change_selections(Default::default(), window, cx, |s| s.select(selections)); } else { let clipboard_is_url = is_standalone_url(&clipboard_text); let auto_indent_mode = if !clipboard_text.is_empty() { Some(AutoindentMode::Block { original_indent_columns: Vec::new(), }) } else { None }; let selection_anchors = this.buffer.update(cx, |buffer, cx| { let snapshot = buffer.snapshot(cx); let anchors = old_selections .iter() .map(|s| { let anchor = snapshot.anchor_after(s.head()); s.map(|_| anchor) }) .collect::<Vec<_>>(); let mut edits = Vec::new(); // When pasting text without metadata (e.g. copied from an // external editor using multiple cursors) and the number of // lines matches the number of selections, distribute one // line per cursor instead of pasting the whole text at each. let lines: Vec<&str> = clipboard_text.split('\n').collect(); let distribute_lines = old_selections.len() > 1 && lines.len() == old_selections.len(); for (ix, selection) in old_selections.iter().enumerate() { let language = snapshot.language_at(selection.head()); let range = selection.range(); let text_for_cursor: &str = if distribute_lines { lines[ix] } else { &clipboard_text }; let (edit_range, edit_text) = if let Some(language) = language && language.name() == "Markdown" { edit_for_markdown_paste( &snapshot, range, text_for_cursor, clipboard_is_url, ) } else { (range, Cow::Borrowed(text_for_cursor)) }; edits.push((edit_range, edit_text)); } drop(snapshot); buffer.edit(edits, auto_indent_mode, cx); anchors }); this.change_selections(Default::default(), window, cx, |s| { s.select_anchors(selection_anchors); }); } // 🤔 | .. | show_in_menu | // | .. | true true // | had_edit_prediction | false true let trigger_in_words = this.show_edit_predictions_in_menu() || !had_active_edit_prediction; this.trigger_completion_on_input(text, trigger_in_words, window, cx); }); } pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) { if let Some(item) = cx.read_from_clipboard() { self.paste_item(&item, window, cx); } } pub fn paste_item( &mut self, item: &ClipboardItem, window: &mut Window, cx: &mut Context<Self>, ) { if self.read_only(cx) { return; } let clipboard_image = item.entries().iter().find_map(|entry| match entry { ClipboardEntry::Image(image) if !image.bytes.is_empty() => Some(image), _ => None, }); if let Some(image) = clipboard_image { let is_markdown = { let display_map = self.display_snapshot(cx); let selections = self.selections.all::<MultiBufferOffset>(&display_map); selections .first() .and_then(|s| display_map.buffer_snapshot().language_at(s.head())) .map(|lang| lang.name() == "Markdown") .unwrap_or(false) }; if is_markdown { let handled = maybe!({ let buffer = self.buffer().read(cx).as_singleton()?; let file = buffer.read(cx).file()?; let worktree_id = file.worktree_id(cx); let dir_rel_path = file.path().parent()?.into_arc(); let worktree = self .project .as_ref()? .read(cx) .worktree_for_id(worktree_id, cx)?; let extension = image.format.extension(); let snapshot = worktree.read(cx).snapshot(); let (filename, file_path) = unused_image_path(&dir_rel_path, extension, |path| { snapshot.entry_for_path(path).is_some() })?; let create_task = worktree.update(cx, |worktree, cx| { worktree.create_entry(file_path, false, Some(image.bytes.clone()), cx) }); cx.spawn_in(window, async move |editor, cx| { create_task.await?; editor.update_in(cx, |editor, window, cx| { editor.insert_image_snippet(&filename, window, cx) }) }) .detach_and_log_err(cx); Some(()) }); if handled.is_some() { // stop clipboard handling when the snippet is inserted return; } } } let clipboard_string = item.entries().iter().find_map(|entry| match entry { ClipboardEntry::String(s) => Some(s), _ => None, }); match clipboard_string { Some(clipboard_string) => self.do_paste( clipboard_string.text(), clipboard_string.metadata_json::<Vec<ClipboardSelection>>(), true, window, cx, ), _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx), } } fn insert_image_snippet( &mut self, filename: &str, window: &mut Window, cx: &mut Context<Self>, ) { let Some(snippet) = Snippet::parse(&format!("$0")).log_err() else { return; }; let display_map = self.display_snapshot(cx); let insertion_ranges: Vec<Range<MultiBufferOffset>> = self .selections .all::<MultiBufferOffset>(&display_map) .into_iter() .map(|selection| selection.start..selection.end) .collect(); self.insert_snippet(&insertion_ranges, snippet, window, cx) .log_err(); } pub(super) fn cut_common( &mut self, cut_no_selection_line: bool, window: &mut Window, cx: &mut Context<Self>, ) -> ClipboardItem { let mut text = String::new(); let buffer = self.buffer.read(cx).snapshot(cx); let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx)); let mut clipboard_selections = Vec::with_capacity(selections.len()); { let max_point = buffer.max_point(); let mut is_first = true; let mut prev_selection_was_entire_line = false; for selection in &mut selections { let is_entire_line = (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode(); if is_entire_line { selection.start = Point::new(selection.start.row, 0); if !selection.is_empty() && selection.end.column == 0 { selection.end = cmp::min(max_point, selection.end); } else { selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0)); } selection.goal = SelectionGoal::None; } if is_first { is_first = false; } else if !prev_selection_was_entire_line { text += "\n"; } prev_selection_was_entire_line = is_entire_line; let mut len = 0; for chunk in buffer.text_for_range(selection.start..selection.end) { text.push_str(chunk); len += chunk.len(); } clipboard_selections.push(ClipboardSelection::for_buffer( len, is_entire_line, selection.range(), &buffer, self.project.as_ref(), cx, )); } } self.transact(window, cx, |this, window, cx| { this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); }); ClipboardItem::new_string_with_json_metadata(text, clipboard_selections) } pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) { if self.read_only(cx) { return; } let item = self.cut_common(true, window, cx); cx.write_to_clipboard(item); } pub(super) fn kill_ring_cut( &mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>, ) { if self.read_only(cx) { return; } let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); let snapshot = self.buffer.read(cx).snapshot(cx); let first_selection_is_empty = first_selection.start == first_selection.end; let selection_start_point = first_selection.start.to_point(&snapshot); let selection_start_row = selection_start_point.row; let selection_start_column = selection_start_point.column; let Some((_, text_anchor)) = self .buffer .read(cx) .text_anchor_for_position(first_selection.start, cx) else { return; }; let buffer_id = text_anchor.buffer_id; self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(&mut |snapshot, sel| { if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row())); } if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0); } }); }); let item = self.cut_common(false, window, cx); let Some(item_text) = item.text() else { return; }; let entry_metadata = item.entries().first().and_then(|entry| match entry { ClipboardEntry::String(entry) => entry.metadata_json::<Vec<ClipboardSelection>>(), _ => None, }); let can_append = selection_count == 1 && first_selection_is_empty; let item = if can_append && let Some(previous_ring) = cx.try_global::<KillRing>() && previous_ring.can_append && previous_ring.buffer_id == buffer_id && previous_ring.row == selection_start_row && previous_ring.column == selection_start_column { let mut entries = previous_ring .metadata .as_ref() .map_or_else(Vec::new, Clone::clone); if let Some(metadata) = entry_metadata.as_ref() { entries.extend_from_slice(metadata); } let mut text = previous_ring.text.clone(); text.push_str(&item_text); let text_len = text.len(); KillRing { text, metadata: kill_ring_metadata_for_text(entries, text_len), row: previous_ring.row, column: previous_ring.column, buffer_id: previous_ring.buffer_id, can_append, } } else {Lines 1–500 of 763. Load more
use super::*; use util::rel_path::RelPath; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ClipboardSelection { /// The number of bytes in this selection. pub len: usize, /// Whether this was a full-line selection. pub is_entire_line: bool, /// The indentation of the first line when this content was originally copied. pub first_line_indent: u32, #[serde(default)] pub file_path: Option<PathBuf>, #[serde(default)] pub line_range: Option<RangeInclusive<u32>>, } impl ClipboardSelection { pub fn for_buffer( len: usize, is_entire_line: bool, range: Range<Point>, buffer: &MultiBufferSnapshot, project: Option<&Entity<Project>>, cx: &App, ) -> Self { let first_line_indent = buffer .indent_size_for_line(MultiBufferRow(range.start.row)) .len; let file_path = util::maybe!({ let project = project?.read(cx); let file = buffer.file_at(range.start)?; let project_path = ProjectPath { worktree_id: file.worktree_id(cx), path: file.path().clone(), }; project.absolute_path(&project_path, cx) }); let line_range = if file_path.is_some() { buffer .range_to_buffer_range(range) .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row) } else { None }; Self { len, is_entire_line, first_line_indent, file_path, line_range, } } } impl Editor { pub fn do_paste( &mut self, text: &String, clipboard_selections: Option<Vec<ClipboardSelection>>, handle_entire_lines: bool, window: &mut Window, cx: &mut Context<Self>, ) { if self.read_only(cx) { return; } self.finalize_last_transaction(cx); let clipboard_text = Cow::Borrowed(text.as_str()); self.transact(window, cx, |this, window, cx| { let had_active_edit_prediction = this.has_active_edit_prediction(); let display_map = this.display_snapshot(cx); let old_selections = this.selections.all::<MultiBufferOffset>(&display_map); let cursor_offset = this .selections .last::<MultiBufferOffset>(&display_map) .head(); if let Some(mut clipboard_selections) = clipboard_selections { let all_selections_were_entire_line = clipboard_selections.iter().all(|s| s.is_entire_line); let first_selection_indent_column = clipboard_selections.first().map(|s| s.first_line_indent); if clipboard_selections.len() != old_selections.len() { clipboard_selections.drain(..); } let mut auto_indent_on_paste = true; this.buffer.update(cx, |buffer, cx| { let snapshot = buffer.read(cx); auto_indent_on_paste = snapshot .language_settings_at(cursor_offset, cx) .auto_indent_on_paste; let mut start_offset = 0; let mut edits = Vec::new(); let mut original_indent_columns = Vec::new(); for (ix, selection) in old_selections.iter().enumerate() { let to_insert; let entire_line; let original_indent_column; if let Some(clipboard_selection) = clipboard_selections.get(ix) { let end_offset = start_offset + clipboard_selection.len; to_insert = &clipboard_text[start_offset..end_offset]; entire_line = clipboard_selection.is_entire_line; start_offset = if entire_line { end_offset } else { end_offset + 1 }; original_indent_column = Some(clipboard_selection.first_line_indent); } else { to_insert = &*clipboard_text; entire_line = all_selections_were_entire_line; original_indent_column = first_selection_indent_column } let (range, to_insert) = if selection.is_empty() && handle_entire_lines && entire_line { // If the corresponding selection was empty when this slice of the // clipboard text was written, then the entire line containing the // selection was copied. If this selection is also currently empty, // then paste the line before the current line of the buffer. let column = selection.start.to_point(&snapshot).column as usize; let line_start = selection.start - column; (line_start..line_start, Cow::Borrowed(to_insert)) } else { let language = snapshot.language_at(selection.head()); let range = selection.range(); if let Some(language) = language && language.name() == "Markdown" { edit_for_markdown_paste( &snapshot, range, to_insert, is_standalone_url(to_insert), ) } else { (range, Cow::Borrowed(to_insert)) } }; edits.push((range, to_insert)); original_indent_columns.push(original_indent_column); } drop(snapshot); buffer.edit( edits, if auto_indent_on_paste { Some(AutoindentMode::Block { original_indent_columns, }) } else { None }, cx, ); }); let selections = this .selections .all::<MultiBufferOffset>(&this.display_snapshot(cx)); this.change_selections(Default::default(), window, cx, |s| s.select(selections)); } else { let clipboard_is_url = is_standalone_url(&clipboard_text); let auto_indent_mode = if !clipboard_text.is_empty() { Some(AutoindentMode::Block { original_indent_columns: Vec::new(), }) } else { None }; let selection_anchors = this.buffer.update(cx, |buffer, cx| { let snapshot = buffer.snapshot(cx); let anchors = old_selections .iter() .map(|s| { let anchor = snapshot.anchor_after(s.head()); s.map(|_| anchor) }) .collect::<Vec<_>>(); let mut edits = Vec::new(); // When pasting text without metadata (e.g. copied from an // external editor using multiple cursors) and the number of // lines matches the number of selections, distribute one // line per cursor instead of pasting the whole text at each. let lines: Vec<&str> = clipboard_text.split('\n').collect(); let distribute_lines = old_selections.len() > 1 && lines.len() == old_selections.len(); for (ix, selection) in old_selections.iter().enumerate() { let language = snapshot.language_at(selection.head()); let range = selection.range(); let text_for_cursor: &str = if distribute_lines { lines[ix] } else { &clipboard_text }; let (edit_range, edit_text) = if let Some(language) = language && language.name() == "Markdown" { edit_for_markdown_paste( &snapshot, range, text_for_cursor, clipboard_is_url, ) } else { (range, Cow::Borrowed(text_for_cursor)) }; edits.push((edit_range, edit_text)); } drop(snapshot); buffer.edit(edits, auto_indent_mode, cx); anchors }); this.change_selections(Default::default(), window, cx, |s| { s.select_anchors(selection_anchors); }); } // 🤔 | .. | show_in_menu | // | .. | true true // | had_edit_prediction | false true let trigger_in_words = this.show_edit_predictions_in_menu() || !had_active_edit_prediction; this.trigger_completion_on_input(text, trigger_in_words, window, cx); }); } pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) { if let Some(item) = cx.read_from_clipboard() { self.paste_item(&item, window, cx); } } pub fn paste_item( &mut self, item: &ClipboardItem, window: &mut Window, cx: &mut Context<Self>, ) { if self.read_only(cx) { return; } let clipboard_image = item.entries().iter().find_map(|entry| match entry { ClipboardEntry::Image(image) if !image.bytes.is_empty() => Some(image), _ => None, }); if let Some(image) = clipboard_image { let is_markdown = { let display_map = self.display_snapshot(cx); let selections = self.selections.all::<MultiBufferOffset>(&display_map); selections .first() .and_then(|s| display_map.buffer_snapshot().language_at(s.head())) .map(|lang| lang.name() == "Markdown") .unwrap_or(false) }; if is_markdown { let handled = maybe!({ let buffer = self.buffer().read(cx).as_singleton()?; let file = buffer.read(cx).file()?; let worktree_id = file.worktree_id(cx); let dir_rel_path = file.path().parent()?.into_arc(); let worktree = self .project .as_ref()? .read(cx) .worktree_for_id(worktree_id, cx)?; let extension = image.format.extension(); let snapshot = worktree.read(cx).snapshot(); let (filename, file_path) = unused_image_path(&dir_rel_path, extension, |path| { snapshot.entry_for_path(path).is_some() })?; let create_task = worktree.update(cx, |worktree, cx| { worktree.create_entry(file_path, false, Some(image.bytes.clone()), cx) }); cx.spawn_in(window, async move |editor, cx| { create_task.await?; editor.update_in(cx, |editor, window, cx| { editor.insert_image_snippet(&filename, window, cx) }) }) .detach_and_log_err(cx); Some(()) }); if handled.is_some() { // stop clipboard handling when the snippet is inserted return; } } } let clipboard_string = item.entries().iter().find_map(|entry| match entry { ClipboardEntry::String(s) => Some(s), _ => None, }); match clipboard_string { Some(clipboard_string) => self.do_paste( clipboard_string.text(), clipboard_string.metadata_json::<Vec<ClipboardSelection>>(), true, window, cx, ), _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx), } } fn insert_image_snippet( &mut self, filename: &str, window: &mut Window, cx: &mut Context<Self>, ) { let Some(snippet) = Snippet::parse(&format!("$0")).log_err() else { return; }; let display_map = self.display_snapshot(cx); let insertion_ranges: Vec<Range<MultiBufferOffset>> = self .selections .all::<MultiBufferOffset>(&display_map) .into_iter() .map(|selection| selection.start..selection.end) .collect(); self.insert_snippet(&insertion_ranges, snippet, window, cx) .log_err(); } pub(super) fn cut_common( &mut self, cut_no_selection_line: bool, window: &mut Window, cx: &mut Context<Self>, ) -> ClipboardItem { let mut text = String::new(); let buffer = self.buffer.read(cx).snapshot(cx); let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx)); let mut clipboard_selections = Vec::with_capacity(selections.len()); { let max_point = buffer.max_point(); let mut is_first = true; let mut prev_selection_was_entire_line = false; for selection in &mut selections { let is_entire_line = (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode(); if is_entire_line { selection.start = Point::new(selection.start.row, 0); if !selection.is_empty() && selection.end.column == 0 { selection.end = cmp::min(max_point, selection.end); } else { selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0)); } selection.goal = SelectionGoal::None; } if is_first { is_first = false; } else if !prev_selection_was_entire_line { text += "\n"; } prev_selection_was_entire_line = is_entire_line; let mut len = 0; for chunk in buffer.text_for_range(selection.start..selection.end) { text.push_str(chunk); len += chunk.len(); } clipboard_selections.push(ClipboardSelection::for_buffer( len, is_entire_line, selection.range(), &buffer, self.project.as_ref(), cx, )); } } self.transact(window, cx, |this, window, cx| { this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); }); ClipboardItem::new_string_with_json_metadata(text, clipboard_selections) } pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) { if self.read_only(cx) { return; } let item = self.cut_common(true, window, cx); cx.write_to_clipboard(item); } pub(super) fn kill_ring_cut( &mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>, ) { if self.read_only(cx) { return; } let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); let snapshot = self.buffer.read(cx).snapshot(cx); let first_selection_is_empty = first_selection.start == first_selection.end; let selection_start_point = first_selection.start.to_point(&snapshot); let selection_start_row = selection_start_point.row; let selection_start_column = selection_start_point.column; let Some((_, text_anchor)) = self .buffer .read(cx) .text_anchor_for_position(first_selection.start, cx) else { return; }; let buffer_id = text_anchor.buffer_id; self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(&mut |snapshot, sel| { if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row())); } if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0); } }); }); let item = self.cut_common(false, window, cx); let Some(item_text) = item.text() else { return; }; let entry_metadata = item.entries().first().and_then(|entry| match entry { ClipboardEntry::String(entry) => entry.metadata_json::<Vec<ClipboardSelection>>(), _ => None, }); let can_append = selection_count == 1 && first_selection_is_empty; let item = if can_append && let Some(previous_ring) = cx.try_global::<KillRing>() && previous_ring.can_append && previous_ring.buffer_id == buffer_id && previous_ring.row == selection_start_row && previous_ring.column == selection_start_column { let mut entries = previous_ring .metadata .as_ref() .map_or_else(Vec::new, Clone::clone); if let Some(metadata) = entry_metadata.as_ref() { entries.extend_from_slice(metadata); } let mut text = previous_ring.text.clone(); text.push_str(&item_text); let text_len = text.len(); KillRing { text, metadata: kill_ring_metadata_for_text(entries, text_len), row: previous_ring.row, column: previous_ring.column, buffer_id: previous_ring.buffer_id, can_append, } } else {