Something went wrong. Try again.
fork of gpui
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372use editor::{ DisplayPoint, MultiBufferOffset, RowExt, SelectionEffects, ToOffset, 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 text::LineEnding;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, Default, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]pub struct Paste { #[serde(default)] before: bool, #[serde(default)] pub(crate) 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(cx, |vim, editor, cx| { if editor.read_only(cx) { return; }
let text_layout_details = editor.text_layout_details(window, cx); 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 { vim.set_status_label( format!("Nothing in register {}", selected_register.unwrap_or('"')), cx, ); 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 = editor.display_snapshot(cx); let current_selections = editor.selections.all_adjusted_display(&display_map);
// 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; let mut mark_start_adjustments = Vec::new();
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 = if clipboard_selection.is_entire_line { end_offset } else { 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) }; LineEnding::normalize(&mut to_insert); 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()]; mark_start_adjustments.push(1usize); } else { to_insert = "\n".to_owned() + &to_insert; mark_start_adjustments.push(1usize); } } else if line_mode && vim.mode == Mode::VisualLine { to_insert.pop(); mark_start_adjustments.push(0usize); } else { mark_start_adjustments.push(0usize); }
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); }
// Record anchors before applying edits to track pasted text start. // anchor_before(start) stays before inserted text (for `[` mark). let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); let mark_start_anchors: Vec<_> = edits .iter() .map(|(range, _)| buffer_snapshot.anchor_before(range.start)) .collect(); let paste_text_last_character_offsets: Vec<_> = edits .iter() .map(|(_, text)| { text.strip_suffix('\n') .unwrap_or(text.as_str()) .char_indices() .next_back() .map_or(0, |(offset, _)| offset) }) .collect();
let cursor_offset = editor .selections .last::<MultiBufferOffset>(&display_map) .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); }
// Set `[` and `]` marks to the pasted text range. let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); let start_anchors: Vec<_> = mark_start_anchors .iter() .zip(mark_start_adjustments.iter()) .map(|(anchor, &adj)| { let offset = anchor.to_offset(&buffer_snapshot) + adj; buffer_snapshot.anchor_before(offset) }) .collect(); let end_anchors: Vec<_> = mark_start_anchors .iter() .zip(paste_text_last_character_offsets.iter()) .map(|(anchor, &last_character_offset)| { let start = anchor.to_offset(&buffer_snapshot); let end = start + last_character_offset; buffer_snapshot.anchor_after(end) }) .collect(); vim.set_mark("[".to_string(), start_anchors, editor.buffer(), window, cx); vim.set_mark("]".to_string(), end_anchors, editor.buffer(), window, 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(cx, |vim, editor, 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(&mut |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 { vim.set_status_label( format!("Nothing in register {}", selected_register.unwrap_or('"')), cx, ); 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(&mut |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(cx, |vim, editor, cx| { let text_layout_details = editor.text_layout_details(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(&mut |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 { vim.set_status_label( format!("Nothing in register {}", selected_register.unwrap_or('"')), cx, ); 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(&mut |map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start }) }) }); }); }}
#[cfg(test)]mod test { use crate::{ state::{Mode, Register}, test::{NeovimBackedTestContext, VimTestContext}, }; use gpui::ClipboardItem; use indoc::indoc; use language::{LanguageName, language_settings::LanguageSettingsContent}; use settings::{SettingsStore, UseSystemClipboard};
#[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(cx, |s| { s.vim.get_or_insert_default().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(cx, |s| { s.vim.get_or_insert_default().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()), 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()), 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"}, Mode::Normal, ); }
#[gpui::test] async fn test_paste_visual(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;
// copy in visual mode cx.set_shared_state(indoc! {" The quick brown fox jˇumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("v i w y").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox ˇjumps over the lazy dog"}); // paste in visual mode cx.simulate_shared_keystrokes("w v i w p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps jumpˇs the lazy dog"}); cx.shared_clipboard().await.assert_eq("over"); // paste in visual line mode cx.simulate_shared_keystrokes("up shift-v shift-p").await; cx.shared_state().await.assert_eq(indoc! {" ˇover fox jumps jumps the lazy dog"}); cx.shared_clipboard().await.assert_eq("over"); // paste in visual block mode cx.simulate_shared_keystrokes("ctrl-v down down p").await; cx.shared_state().await.assert_eq(indoc! {" oveˇrver overox jumps jumps overhe lazy dog"});
// copy in visual line mode cx.set_shared_state(indoc! {" The quick brown fox juˇmps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("shift-v d").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown the laˇzy dog"}); // paste in visual mode cx.simulate_shared_keystrokes("v i w p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown the• ˇfox jumps over dog"}); cx.shared_clipboard().await.assert_eq("lazy"); cx.set_shared_state(indoc! {" The quick brown fox juˇmps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("shift-v d").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown the laˇzy dog"}); cx.shared_clipboard().await.assert_eq("fox jumps over\n"); // paste in visual line mode cx.simulate_shared_keystrokes("k shift-v p").await; cx.shared_state().await.assert_eq(indoc! {" ˇfox jumps over the lazy dog"}); cx.shared_clipboard().await.assert_eq("The quick brown\n");
// Copy line and paste in visual mode, with cursor on newline character. cx.set_shared_state(indoc! {" ˇThe quick brown fox jumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("y y shift-v j $ p").await; cx.shared_state().await.assert_eq(indoc! {" ˇThe quick brown the lazy dog"}); }
#[gpui::test] async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; // copy in visual block mode cx.set_shared_state(indoc! {" The ˇquick brown fox jumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("ctrl-v 2 j y").await; cx.shared_clipboard().await.assert_eq("q\nj\nl"); cx.simulate_shared_keystrokes("p").await; cx.shared_state().await.assert_eq(indoc! {" The qˇquick brown fox jjumps over the llazy dog"}); cx.simulate_shared_keystrokes("v i w shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The ˇq brown fox jjjumps over the lllazy dog"}); cx.simulate_shared_keystrokes("v i w shift-p").await;
cx.set_shared_state(indoc! {" The ˇquick brown fox jumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("ctrl-v j y").await; cx.shared_clipboard().await.assert_eq("q\nj"); cx.simulate_shared_keystrokes("l ctrl-v 2 j shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The qˇqick brown fox jjmps over the lzy dog"});
cx.simulate_shared_keystrokes("shift-v p").await; cx.shared_state().await.assert_eq(indoc! {" ˇq j fox jjmps over the lzy dog"}); }
#[gpui::test] async fn test_paste_indent(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new_typescript(cx).await;
cx.set_state( indoc! {" class A {ˇ } "}, Mode::Normal, ); cx.simulate_keystrokes("o a ( ) { escape"); cx.assert_state( indoc! {" class A { a()ˇ{} } "}, Mode::Normal, ); // cursor goes to the first non-blank character in the line; cx.simulate_keystrokes("y y p"); cx.assert_state( indoc! {" class A { a(){} ˇa(){} } "}, Mode::Normal, ); // indentation is preserved when pasting cx.simulate_keystrokes("u shift-v up y shift-p"); cx.assert_state( indoc! {" ˇclass A { a(){} class A { a(){} } "}, Mode::Normal, ); }
#[gpui::test] async fn test_paste_auto_indent(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
cx.set_state( indoc! {" mod some_module { ˇfn main() { } } "}, Mode::Normal, ); // default auto indentation cx.simulate_keystrokes("y y p"); cx.assert_state( indoc! {" mod some_module { fn main() { ˇfn main() { } } "}, Mode::Normal, ); // back to previous state cx.simulate_keystrokes("u u"); cx.assert_state( indoc! {" mod some_module { ˇfn main() { } } "}, Mode::Normal, ); cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings(cx, |settings| { settings.project.all_languages.languages.0.insert( LanguageName::new_static("Rust").0.to_string(), LanguageSettingsContent { auto_indent_on_paste: Some(false), ..Default::default() }, ); }); }); // auto indentation turned off cx.simulate_keystrokes("y y p"); cx.assert_state( indoc! {" mod some_module { fn main() { ˇfn main() { } } "}, Mode::Normal, ); }
#[gpui::test] async fn test_paste_count(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {" onˇe two three "}) .await; cx.simulate_shared_keystrokes("y y 3 p").await; cx.shared_state().await.assert_eq(indoc! {" one ˇone one one two three "});
cx.set_shared_state(indoc! {" one ˇtwo three "}) .await; cx.simulate_shared_keystrokes("y $ $ 3 p").await; cx.shared_state().await.assert_eq(indoc! {" one twotwotwotwˇo three "}); }
#[gpui::test] async fn test_paste_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(cx, |s| { s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); });
cx.set_state( indoc! {" ˇThe quick brown fox jumps over the lazy dog"}, Mode::Normal, );
cx.write_to_clipboard(ClipboardItem::new_string("something else".to_string()));
cx.simulate_keystrokes("d d"); cx.assert_state( indoc! {" ˇfox jumps over the lazy dog"}, Mode::Normal, );
cx.simulate_keystrokes("shift-v p"); cx.assert_state( indoc! {" ˇThe quick brown the lazy dog"}, Mode::Normal, );
cx.simulate_keystrokes("shift-v"); cx.dispatch_action(editor::actions::Paste); cx.assert_state( indoc! {" ˇsomething else the lazy dog"}, Mode::Normal, ); }
#[gpui::test] async fn test_editor_paste_visual_preserves_system_clipboard(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
cx.set_state( indoc! {" The quick brown fox ˇjumps over the lazy dog"}, Mode::Normal, );
// Put known content on the system clipboard cx.write_to_clipboard(ClipboardItem::new_string("from clipboard".to_string()));
// Select "jumps" in visual mode, then editor::Paste (Cmd-V / Ctrl-V) cx.simulate_keystrokes("v i w"); cx.dispatch_action(editor::actions::Paste);
// The selected text should be replaced with clipboard content cx.assert_state( indoc! {" The quick brown fox from clipboarˇd over the lazy dog"}, Mode::Normal, );
// System clipboard must still hold the original value, not "jumps" assert_eq!( cx.read_from_clipboard().map(|item| item.text().unwrap()), Some("from clipboard".into()), ); }
#[gpui::test] async fn test_numbered_registers(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings(cx, |s| { s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); });
cx.set_shared_state(indoc! {" The quick brown fox jˇumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("y y \" 0 p").await; cx.shared_register('0').await.assert_eq("fox jumps over\n"); cx.shared_register('"').await.assert_eq("fox jumps over\n");
cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps over ˇfox jumps over the lazy dog"}); cx.simulate_shared_keystrokes("k k d d").await; cx.shared_register('0').await.assert_eq("fox jumps over\n"); cx.shared_register('1').await.assert_eq("The quick brown\n"); cx.shared_register('"').await.assert_eq("The quick brown\n");
cx.simulate_shared_keystrokes("d d shift-g d d").await; cx.shared_register('0').await.assert_eq("fox jumps over\n"); cx.shared_register('3').await.assert_eq("The quick brown\n"); cx.shared_register('2').await.assert_eq("fox jumps over\n"); cx.shared_register('1').await.assert_eq("the lazy dog\n");
cx.shared_state().await.assert_eq(indoc! {" ˇfox jumps over"});
cx.simulate_shared_keystrokes("d d \" 3 p p \" 1 p").await; cx.set_shared_state(indoc! {" The quick brown fox jumps over ˇthe lazy dog"}) .await; }
#[gpui::test] async fn test_named_registers(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings(cx, |s| { s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); });
cx.set_shared_state(indoc! {" The quick brown fox jˇumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("\" a d a w").await; cx.shared_register('a').await.assert_eq("jumps "); cx.simulate_shared_keystrokes("\" shift-a d i w").await; cx.shared_register('a').await.assert_eq("jumps over"); cx.shared_register('"').await.assert_eq("jumps over"); cx.simulate_shared_keystrokes("\" a p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown fox jumps oveˇr the lazy dog"}); cx.simulate_shared_keystrokes("\" a d a w").await; cx.shared_register('a').await.assert_eq(" over"); }
#[gpui::test] async fn test_special_registers(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings(cx, |s| { s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); });
cx.set_shared_state(indoc! {" The quick brown fox jˇumps over the lazy dog"}) .await; cx.simulate_shared_keystrokes("d i w").await; cx.shared_register('-').await.assert_eq("jumps"); cx.simulate_shared_keystrokes("\" _ d d").await; cx.shared_register('_').await.assert_eq("");
cx.simulate_shared_keystrokes("shift-v \" _ y w").await; cx.shared_register('"').await.assert_eq("jumps");
cx.shared_state().await.assert_eq(indoc! {" The quick brown the ˇlazy dog"}); cx.simulate_shared_keystrokes("\" \" d ^").await; cx.shared_register('0').await.assert_eq("the "); cx.shared_register('"').await.assert_eq("the ");
cx.simulate_shared_keystrokes("^ \" + d $").await; cx.shared_clipboard().await.assert_eq("lazy dog"); cx.shared_register('"').await.assert_eq("lazy dog");
cx.simulate_shared_keystrokes("/ d o g enter").await; cx.shared_register('/').await.assert_eq("dog"); cx.simulate_shared_keystrokes("\" / shift-p").await; cx.shared_state().await.assert_eq(indoc! {" The quick brown doˇg"});
// not testing nvim as it doesn't have a filename cx.simulate_keystrokes("\" % p"); #[cfg(not(target_os = "windows"))] cx.assert_state( indoc! {" The quick brown dogdir/file.rˇs"}, Mode::Normal, ); #[cfg(target_os = "windows")] cx.assert_state( indoc! {" The quick brown dogdir\\file.rˇs"}, Mode::Normal, ); }
#[gpui::test] async fn test_multicursor_paste(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings(cx, |s| { s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never) }); });
cx.set_state( indoc! {" ˇfish one fish two fish red fish blue "}, Mode::Normal, ); cx.simulate_keystrokes("4 g l w escape d i w 0 shift-p"); cx.assert_state( indoc! {" onˇefish• twˇofish• reˇdfish• bluˇefish• "}, Mode::Normal, ); }
#[gpui::test] async fn test_replace_with_register(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
cx.set_state( indoc! {" ˇfish one two three "}, Mode::Normal, ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("w"); cx.simulate_keystrokes("g shift-r i w"); cx.assert_state( indoc! {" fish fisˇh two three "}, Mode::Normal, ); cx.simulate_keystrokes("j b g shift-r e"); cx.assert_state( indoc! {" fish fish two fisˇh "}, Mode::Normal, ); let clipboard: Register = cx.read_from_clipboard().unwrap().into(); assert_eq!(clipboard.text, "fish");
cx.set_state( indoc! {" ˇfish one two three "}, Mode::Normal, ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("w"); cx.simulate_keystrokes("v i w g shift-r"); cx.assert_state( indoc! {" fish fisˇh two three "}, Mode::Normal, ); cx.simulate_keystrokes("g shift-r r"); cx.assert_state( indoc! {" fisˇh two three "}, Mode::Normal, ); cx.simulate_keystrokes("j w g shift-r $"); cx.assert_state( indoc! {" fish two fisˇh "}, Mode::Normal, ); let clipboard: Register = cx.read_from_clipboard().unwrap().into(); assert_eq!(clipboard.text, "fish"); }
#[gpui::test] async fn test_replace_with_register_dot_repeat(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
cx.set_state( indoc! {" ˇfish one two three "}, Mode::Normal, ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("w"); cx.simulate_keystrokes("g shift-r i w"); cx.assert_state( indoc! {" fish fisˇh two three "}, Mode::Normal, ); cx.simulate_keystrokes("j ."); cx.assert_state( indoc! {" fish fish two fisˇh "}, Mode::Normal, ); }
#[gpui::test] async fn test_paste_entire_line_from_editor_copy(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
cx.set_state( indoc! {" ˇline one line two line three"}, Mode::Normal, );
// Simulate what the editor's do_copy produces for two entire-line selections: // entire-line selections are NOT separated by an extra newline in the clipboard text. let clipboard_text = "line one\nline two\n".to_string(); let clipboard_selections = vec![ editor::ClipboardSelection { len: "line one\n".len(), is_entire_line: true, first_line_indent: 0, file_path: None, line_range: None, }, editor::ClipboardSelection { len: "line two\n".len(), is_entire_line: true, first_line_indent: 0, file_path: None, line_range: None, }, ]; cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata( clipboard_text, clipboard_selections, ));
cx.simulate_keystrokes("p"); cx.assert_state( indoc! {" line one ˇline one line two line two line three"}, Mode::Normal, ); }
#[gpui::test] async fn test_paste_marks(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;
// Yank a word, paste it elsewhere, then verify `[ and `] point to pasted text. cx.set_state( indoc! {" ˇhello world foo bar"}, Mode::Normal, ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("j w"); cx.simulate_keystrokes("p"); cx.assert_state( indoc! {" hello world foo bhellˇoar"}, Mode::Normal, ); // `[ should go to start of pasted text cx.simulate_keystrokes("` ["); cx.assert_state( indoc! {" hello world foo bˇhelloar"}, Mode::Normal, ); // `] should go to end of pasted text cx.simulate_keystrokes("` ]"); cx.assert_state( indoc! {" hello world foo bhellˇoar"}, Mode::Normal, );
// Line-mode paste: yank a line, paste below, verify marks. cx.set_state( indoc! {" ˇfirst line second line third line"}, Mode::Normal, ); cx.simulate_keystrokes("y y j p"); cx.assert_state( indoc! {" first line second line ˇfirst line third line"}, Mode::Normal, ); cx.simulate_keystrokes("` ["); cx.assert_state( indoc! {" first line second line ˇfirst line third line"}, Mode::Normal, ); cx.simulate_keystrokes("` ]"); cx.assert_state( indoc! {" first line second line first linˇe third line"}, Mode::Normal, ); }
#[gpui::test] async fn test_paste_marks_unicode(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state("ˇxy", Mode::Normal); cx.write_to_clipboard(ClipboardItem::new_string("é".to_string()));
cx.simulate_keystrokes("p"); cx.simulate_keystrokes("` ]");
cx.assert_state("xˇéy", Mode::Normal); }
#[gpui::test] async fn test_paste_marks_normalize_line_endings(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state("ˇxy", Mode::Normal); cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb".to_string()));
cx.simulate_keystrokes("p"); cx.simulate_keystrokes("` ]");
cx.assert_state("xa\nˇby", Mode::Normal); }
#[gpui::test] async fn test_paste_marks_read_only(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state("ˇx", Mode::Normal); cx.write_to_clipboard(ClipboardItem::new_string("long text".to_string())); cx.update_editor(|editor, _window, _cx| editor.set_read_only(true));
cx.simulate_keystrokes("p");
cx.assert_state("ˇx", Mode::Normal); }
#[gpui::test] async fn test_paste_marks_linewise_before(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state( indoc! {" ˇfirst line second line third line"}, Mode::Normal, );
cx.simulate_keystrokes("y y j shift-p"); cx.simulate_keystrokes("` [ v ` ]");
cx.assert_state( indoc! {" first line «first lineˇ» second line third line"}, Mode::Visual, ); }}