use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement};use gpui::{Action, Context, Window};use language::{Bias, SelectionGoal};use schemars::JsonSchema;use serde::Deserialize;use settings::Settings;use std::cmp;use vim_mode_setting::HelixModeSetting; use crate::{ Vim, motion::{Motion, MotionKind}, object::Object, state::{Mode, Register},}; /// Pastes text from the specified register at the cursor position.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]pub struct Paste { #[serde(default)] before: bool, #[serde(default)] preserve_clipboard: bool,} impl Vim { pub fn paste(&mut self, action: &Paste, window: &mut Window, cx: &mut Context<Self>) { self.record_current_action(cx); self.store_visual_marks(window, cx); let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); self.update_editor(window, cx, |vim, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let selected_register = vim.selected_register.take(); let Some(Register { text, clipboard_selections, }) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) .filter(|reg| !reg.text.is_empty()) else { return; }; let clipboard_selections = clipboard_selections .filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine); if !action.preserve_clipboard && vim.mode.is_visual() { vim.copy_selections_content(editor, MotionKind::for_mode(vim.mode), window, cx); } let (display_map, current_selections) = editor.selections.all_adjusted_display(cx); // unlike zed, if you have a multi-cursor selection from vim block mode, // pasting it will paste it on subsequent lines, even if you don't yet // have a cursor there. let mut selections_to_process = Vec::new(); let mut i = 0; while i < current_selections.len() { selections_to_process .push((current_selections[i].start..current_selections[i].end, true)); i += 1; } if let Some(clipboard_selections) = clipboard_selections.as_ref() { let left = current_selections .iter() .map(|selection| cmp::min(selection.start.column(), selection.end.column())) .min() .unwrap(); let mut row = current_selections.last().unwrap().end.row().next_row(); while i < clipboard_selections.len() { let cursor = display_map.clip_point(DisplayPoint::new(row, left), Bias::Left); selections_to_process.push((cursor..cursor, false)); i += 1; row.0 += 1; } } let first_selection_indent_column = clipboard_selections.as_ref().and_then(|zed_selections| { zed_selections .first() .map(|selection| selection.first_line_indent) }); let before = action.before || vim.mode == Mode::VisualLine; let mut edits = Vec::new(); let mut new_selections = Vec::new(); let mut original_indent_columns = Vec::new(); let mut start_offset = 0; for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() { let (mut to_insert, original_indent_column) = if let Some(clipboard_selections) = &clipboard_selections { if let Some(clipboard_selection) = clipboard_selections.get(ix) { let end_offset = start_offset + clipboard_selection.len; let text = text[start_offset..end_offset].to_string(); start_offset = end_offset + 1; (text, Some(clipboard_selection.first_line_indent)) } else { ("".to_string(), first_selection_indent_column) } } else { (text.to_string(), first_selection_indent_column) }; let line_mode = to_insert.ends_with('\n'); let is_multiline = to_insert.contains('\n'); if line_mode && !before { if selection.is_empty() { to_insert = "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()]; } else { to_insert = "\n".to_owned() + &to_insert; } } else if line_mode && vim.mode == Mode::VisualLine { to_insert.pop(); } let display_range = if !selection.is_empty() { // If vim is in VISUAL LINE mode and the column for the // selection's end point is 0, that means that the // cursor is at the newline character (\n) at the end of // the line. In this situation we'll want to move one // position to the left, ensuring we don't join the last // line of the selection with the line directly below. let end_point = if vim.mode == Mode::VisualLine && selection.end.column() == 0 { movement::left(&display_map, selection.end) } else { selection.end }; selection.start..end_point } else if line_mode { let point = if before { movement::line_beginning(&display_map, selection.start, false) } else { movement::line_end(&display_map, selection.start, false) }; point..point } else { let point = if before { selection.start } else { movement::saturating_right(&display_map, selection.start) }; point..point }; let point_range = display_range.start.to_point(&display_map) ..display_range.end.to_point(&display_map); let anchor = if is_multiline || vim.mode == Mode::VisualLine { display_map.buffer_snapshot.anchor_before(point_range.start) } else { display_map.buffer_snapshot.anchor_after(point_range.end) }; if *preserve { new_selections.push((anchor, line_mode, is_multiline)); } edits.push((point_range, to_insert.repeat(count))); original_indent_columns.push(original_indent_column); } let cursor_offset = editor.selections.last::<usize>(cx).head(); if editor .buffer() .read(cx) .snapshot(cx) .language_settings_at(cursor_offset, cx) .auto_indent_on_paste { editor.edit_with_block_indent(edits, original_indent_columns, cx); } else { editor.edit(edits, cx); } // in line_mode vim will insert the new text on the next (or previous if before) line // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank). // otherwise vim will insert the next text at (or before) the current cursor position, // the cursor will go to the last (or first, if is_multiline) inserted character. editor.change_selections(Default::default(), window, cx, |s| { s.replace_cursors_with(|map| { let mut cursors = Vec::new(); for (anchor, line_mode, is_multiline) in &new_selections { let mut cursor = anchor.to_display_point(map); if *line_mode { if !before { cursor = movement::down( map, cursor, SelectionGoal::None, false, &text_layout_details, ) .0; } cursor = movement::indented_line_beginning(map, cursor, true, true); } else if !is_multiline && !vim.temp_mode { cursor = movement::saturating_left(map, cursor) } cursors.push(cursor); if vim.mode == Mode::VisualBlock { break; } } cursors }); }) }); }); if HelixModeSetting::get_global(cx).0 { self.switch_mode(Mode::HelixNormal, true, window, cx); } else { self.switch_mode(Mode::Normal, true, window, cx); } } pub fn replace_with_register_object( &mut self, object: Object, around: bool, window: &mut Window, cx: &mut Context<Self>, ) { self.stop_recording(cx); let selected_register = self.selected_register.take(); self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around, None); }); }); let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) .filter(|reg| !reg.text.is_empty()) else { return; }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start }) }) }); }); } pub fn replace_with_register_motion( &mut self, motion: Motion, times: Option<usize>, forced_motion: bool, window: &mut Window, cx: &mut Context<Self>, ) { self.stop_recording(cx); let selected_register = self.selected_register.take(); self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { motion.expand_selection( map, selection, times, &text_layout_details, forced_motion, ); }); }); let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) .filter(|reg| !reg.text.is_empty()) else { return; }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start }) }) }); }); }} #[cfg(test)]mod test { use crate::{ UseSystemClipboard, VimSettings, state::{Mode, Register}, test::{NeovimBackedTestContext, VimTestContext}, }; use gpui::ClipboardItem; use indoc::indoc; use language::{ LanguageName, language_settings::{AllLanguageSettings, LanguageSettingsContent}, }; use settings::SettingsStore; #[gpui::test] async fn test_paste(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; // single line cx.set_shared_state(indoc! {" The quick brown fox ˇjumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("v w y").await; cx.shared_clipboard().await.assert_eq("jumps o"); cx.set_shared_state(indoc! {" The quick brown fox jumps oveˇr the lazy dog"}) .await; cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps overjumps ˇo the lazy dog"}); cx.set_shared_state(indoc! {" The quick brown fox jumps oveˇr the lazy dog"}) .await; cx.simulate_shared_keystrokes("shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps ovejumps ˇor the lazy dog"}); // line mode cx.set_shared_state(indoc! {" The quick brown fox juˇmps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("d d").await; cx.shared_clipboard().await.assert_eq("fox jumps over\n"); cx.shared_state().await.assert_eq(indoc! {" The quick brown the laˇzy dog"}); cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown the lazy dog ˇfox jumps over"}); cx.simulate_shared_keystrokes("k shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown ˇfox jumps over the lazy dog fox jumps over"}); // multiline, cursor to first character of pasted text. cx.set_shared_state(indoc! {" The quick brown fox jumps ˇover the lazy dog"}) .await; cx.simulate_shared_keystrokes("v j y").await; cx.shared_clipboard().await.assert_eq("over\nthe lazy do"); cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps oˇover the lazy dover the lazy dog"}); cx.simulate_shared_keystrokes("u shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps ˇover the lazy doover the lazy dog"}); } #[gpui::test] async fn test_yank_system_clipboard_never(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::<VimSettings>(cx, |s| { s.use_system_clipboard = Some(UseSystemClipboard::Never) }); }); cx.set_state( indoc! {" The quick brown fox jˇumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("v i w y"); cx.assert_state( indoc! {" The quick brown fox ˇjumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("p"); cx.assert_state( indoc! {" The quick brown fox jjumpˇsumps over the lazy dog"}, Mode::Normal, ); assert_eq!(cx.read_from_clipboard(), None); } #[gpui::test] async fn test_yank_system_clipboard_on_yank(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::<VimSettings>(cx, |s| { s.use_system_clipboard = Some(UseSystemClipboard::OnYank) }); }); // copy in visual mode cx.set_state( indoc! {" The quick brown fox jˇumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("v i w y"); cx.assert_state( indoc! {" The quick brown fox ˇjumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("p"); cx.assert_state( indoc! {" The quick brown fox jjumpˇsumps over the lazy dog"}, Mode::Normal, ); assert_eq!( cx.read_from_clipboard() .map(|item| item.text().unwrap().to_string()), Some("jumps".into()) ); cx.simulate_keystrokes("d d p"); cx.assert_state( indoc! {" The quick brown the lazy dog ˇfox jjumpsumps over"}, Mode::Normal, ); assert_eq!( cx.read_from_clipboard() .map(|item| item.text().unwrap().to_string()), Some("jumps".into()) ); cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string())); cx.simulate_keystrokes("shift-p"); cx.assert_state( indoc! {" The quick brown the lazy dog test-copˇyfox jjumpsumps over"},Lines 1–500 of 1036. Load more
use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement}; use gpui::{Action, Context, Window}; use language::{Bias, SelectionGoal}; use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; use std::cmp; use vim_mode_setting::HelixModeSetting; use crate::{ Vim, motion::{Motion, MotionKind}, object::Object, state::{Mode, Register}, }; /// Pastes text from the specified register at the cursor position. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] pub struct Paste { #[serde(default)] before: bool, #[serde(default)] preserve_clipboard: bool, } impl Vim { pub fn paste(&mut self, action: &Paste, window: &mut Window, cx: &mut Context<Self>) { self.record_current_action(cx); self.store_visual_marks(window, cx); let count = Vim::take_count(cx).unwrap_or(1); Vim::take_forced_motion(cx); self.update_editor(window, cx, |vim, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let selected_register = vim.selected_register.take(); let Some(Register { text, clipboard_selections, }) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) .filter(|reg| !reg.text.is_empty()) else { return; }; let clipboard_selections = clipboard_selections .filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine); if !action.preserve_clipboard && vim.mode.is_visual() { vim.copy_selections_content(editor, MotionKind::for_mode(vim.mode), window, cx); } let (display_map, current_selections) = editor.selections.all_adjusted_display(cx); // unlike zed, if you have a multi-cursor selection from vim block mode, // pasting it will paste it on subsequent lines, even if you don't yet // have a cursor there. let mut selections_to_process = Vec::new(); let mut i = 0; while i < current_selections.len() { selections_to_process .push((current_selections[i].start..current_selections[i].end, true)); i += 1; } if let Some(clipboard_selections) = clipboard_selections.as_ref() { let left = current_selections .iter() .map(|selection| cmp::min(selection.start.column(), selection.end.column())) .min() .unwrap(); let mut row = current_selections.last().unwrap().end.row().next_row(); while i < clipboard_selections.len() { let cursor = display_map.clip_point(DisplayPoint::new(row, left), Bias::Left); selections_to_process.push((cursor..cursor, false)); i += 1; row.0 += 1; } } let first_selection_indent_column = clipboard_selections.as_ref().and_then(|zed_selections| { zed_selections .first() .map(|selection| selection.first_line_indent) }); let before = action.before || vim.mode == Mode::VisualLine; let mut edits = Vec::new(); let mut new_selections = Vec::new(); let mut original_indent_columns = Vec::new(); let mut start_offset = 0; for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() { let (mut to_insert, original_indent_column) = if let Some(clipboard_selections) = &clipboard_selections { if let Some(clipboard_selection) = clipboard_selections.get(ix) { let end_offset = start_offset + clipboard_selection.len; let text = text[start_offset..end_offset].to_string(); start_offset = end_offset + 1; (text, Some(clipboard_selection.first_line_indent)) } else { ("".to_string(), first_selection_indent_column) } } else { (text.to_string(), first_selection_indent_column) }; let line_mode = to_insert.ends_with('\n'); let is_multiline = to_insert.contains('\n'); if line_mode && !before { if selection.is_empty() { to_insert = "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()]; } else { to_insert = "\n".to_owned() + &to_insert; } } else if line_mode && vim.mode == Mode::VisualLine { to_insert.pop(); } let display_range = if !selection.is_empty() { // If vim is in VISUAL LINE mode and the column for the // selection's end point is 0, that means that the // cursor is at the newline character (\n) at the end of // the line. In this situation we'll want to move one // position to the left, ensuring we don't join the last // line of the selection with the line directly below. let end_point = if vim.mode == Mode::VisualLine && selection.end.column() == 0 { movement::left(&display_map, selection.end) } else { selection.end }; selection.start..end_point } else if line_mode { let point = if before { movement::line_beginning(&display_map, selection.start, false) } else { movement::line_end(&display_map, selection.start, false) }; point..point } else { let point = if before { selection.start } else { movement::saturating_right(&display_map, selection.start) }; point..point }; let point_range = display_range.start.to_point(&display_map) ..display_range.end.to_point(&display_map); let anchor = if is_multiline || vim.mode == Mode::VisualLine { display_map.buffer_snapshot.anchor_before(point_range.start) } else { display_map.buffer_snapshot.anchor_after(point_range.end) }; if *preserve { new_selections.push((anchor, line_mode, is_multiline)); } edits.push((point_range, to_insert.repeat(count))); original_indent_columns.push(original_indent_column); } let cursor_offset = editor.selections.last::<usize>(cx).head(); if editor .buffer() .read(cx) .snapshot(cx) .language_settings_at(cursor_offset, cx) .auto_indent_on_paste { editor.edit_with_block_indent(edits, original_indent_columns, cx); } else { editor.edit(edits, cx); } // in line_mode vim will insert the new text on the next (or previous if before) line // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank). // otherwise vim will insert the next text at (or before) the current cursor position, // the cursor will go to the last (or first, if is_multiline) inserted character. editor.change_selections(Default::default(), window, cx, |s| { s.replace_cursors_with(|map| { let mut cursors = Vec::new(); for (anchor, line_mode, is_multiline) in &new_selections { let mut cursor = anchor.to_display_point(map); if *line_mode { if !before { cursor = movement::down( map, cursor, SelectionGoal::None, false, &text_layout_details, ) .0; } cursor = movement::indented_line_beginning(map, cursor, true, true); } else if !is_multiline && !vim.temp_mode { cursor = movement::saturating_left(map, cursor) } cursors.push(cursor); if vim.mode == Mode::VisualBlock { break; } } cursors }); }) }); }); if HelixModeSetting::get_global(cx).0 { self.switch_mode(Mode::HelixNormal, true, window, cx); } else { self.switch_mode(Mode::Normal, true, window, cx); } } pub fn replace_with_register_object( &mut self, object: Object, around: bool, window: &mut Window, cx: &mut Context<Self>, ) { self.stop_recording(cx); let selected_register = self.selected_register.take(); self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around, None); }); }); let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) .filter(|reg| !reg.text.is_empty()) else { return; }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start }) }) }); }); } pub fn replace_with_register_motion( &mut self, motion: Motion, times: Option<usize>, forced_motion: bool, window: &mut Window, cx: &mut Context<Self>, ) { self.stop_recording(cx); let selected_register = self.selected_register.take(); self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { motion.expand_selection( map, selection, times, &text_layout_details, forced_motion, ); }); }); let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) .filter(|reg| !reg.text.is_empty()) else { return; }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start }) }) }); }); } } #[cfg(test)] mod test { use crate::{ UseSystemClipboard, VimSettings, state::{Mode, Register}, test::{NeovimBackedTestContext, VimTestContext}, }; use gpui::ClipboardItem; use indoc::indoc; use language::{ LanguageName, language_settings::{AllLanguageSettings, LanguageSettingsContent}, }; use settings::SettingsStore; #[gpui::test] async fn test_paste(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; // single line cx.set_shared_state(indoc! {" The quick brown fox ˇjumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("v w y").await; cx.shared_clipboard().await.assert_eq("jumps o"); cx.set_shared_state(indoc! {" The quick brown fox jumps oveˇr the lazy dog"}) .await; cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps overjumps ˇo the lazy dog"}); cx.set_shared_state(indoc! {" The quick brown fox jumps oveˇr the lazy dog"}) .await; cx.simulate_shared_keystrokes("shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps ovejumps ˇor the lazy dog"}); // line mode cx.set_shared_state(indoc! {" The quick brown fox juˇmps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("d d").await; cx.shared_clipboard().await.assert_eq("fox jumps over\n"); cx.shared_state().await.assert_eq(indoc! {" The quick brown the laˇzy dog"}); cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown the lazy dog ˇfox jumps over"}); cx.simulate_shared_keystrokes("k shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown ˇfox jumps over the lazy dog fox jumps over"}); // multiline, cursor to first character of pasted text. cx.set_shared_state(indoc! {" The quick brown fox jumps ˇover the lazy dog"}) .await; cx.simulate_shared_keystrokes("v j y").await; cx.shared_clipboard().await.assert_eq("over\nthe lazy do"); cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps oˇover the lazy dover the lazy dog"}); cx.simulate_shared_keystrokes("u shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps ˇover the lazy doover the lazy dog"}); } #[gpui::test] async fn test_yank_system_clipboard_never(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::<VimSettings>(cx, |s| { s.use_system_clipboard = Some(UseSystemClipboard::Never) }); }); cx.set_state( indoc! {" The quick brown fox jˇumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("v i w y"); cx.assert_state( indoc! {" The quick brown fox ˇjumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("p"); cx.assert_state( indoc! {" The quick brown fox jjumpˇsumps over the lazy dog"}, Mode::Normal, ); assert_eq!(cx.read_from_clipboard(), None); } #[gpui::test] async fn test_yank_system_clipboard_on_yank(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::<VimSettings>(cx, |s| { s.use_system_clipboard = Some(UseSystemClipboard::OnYank) }); }); // copy in visual mode cx.set_state( indoc! {" The quick brown fox jˇumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("v i w y"); cx.assert_state( indoc! {" The quick brown fox ˇjumps over the lazy dog"}, Mode::Normal, ); cx.simulate_keystrokes("p"); cx.assert_state( indoc! {" The quick brown fox jjumpˇsumps over the lazy dog"}, Mode::Normal, ); assert_eq!( cx.read_from_clipboard() .map(|item| item.text().unwrap().to_string()), Some("jumps".into()) ); cx.simulate_keystrokes("d d p"); cx.assert_state( indoc! {" The quick brown the lazy dog ˇfox jjumpsumps over"}, Mode::Normal, ); assert_eq!( cx.read_from_clipboard() .map(|item| item.text().unwrap().to_string()), Some("jumps".into()) ); cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string())); cx.simulate_keystrokes("shift-p"); cx.assert_state( indoc! {" The quick brown the lazy dog test-copˇyfox jjumpsumps over"},