diff --git a/crates/vim/src/helix/paste.rs b/crates/vim/src/helix/paste.rs index 32f636b41046a5f8c8ade054594218890e23758f..c43281421462ee66e75d226b8769367f4db417b9 100644 --- a/crates/vim/src/helix/paste.rs +++ b/crates/vim/src/helix/paste.rs @@ -33,16 +33,14 @@ impl Vim { let selected_register = vim.selected_register.take(); - let Some((text, clipboard_selections)) = Vim::update_globals(cx, |globals, cx| { + let Some(register) = Vim::update_globals(cx, |globals, cx| { globals.read_register(selected_register, Some(editor), cx) }) - .and_then(|reg| { - (!reg.text.is_empty()) - .then_some(reg.text) - .zip(reg.clipboard_selections) - }) else { + .filter(|reg| !reg.text.is_empty()) else { return; }; + let text = register.text; + let clipboard_selections = register.clipboard_selections; let display_map = editor.display_snapshot(cx); let current_selections = editor.selections.all_adjusted_display(&display_map); @@ -63,7 +61,9 @@ impl Vim { let mut replacement_texts: Vec = Vec::new(); for ix in 0..current_selections.len() { - let to_insert = if let Some(clip_sel) = clipboard_selections.get(ix) { + let to_insert = if let Some(clip_sel) = + clipboard_selections.as_ref().and_then(|s| s.get(ix)) + { let end_offset = start_offset + clip_sel.len; let text = text[start_offset..end_offset].to_string(); start_offset = if clip_sel.is_entire_line { @@ -102,13 +102,16 @@ impl Vim { } else if action.before { sel.start } else if sel.start == sel.end { - // Helix and Zed differ in how they understand - // single-point cursors. In Helix, a single-point cursor - // is "on top" of some character, and pasting after that - // cursor means that the pasted content should go after - // that character. (If the cursor is at the end of a - // line, the pasted content goes on the next line.) - movement::right(&display_map, sel.end) + // In Helix, a single-point cursor is "on top" of a + // character, and pasting after means after that character. + // At line end this means the next line. But on an empty + // line there is no character, so paste at the cursor. + let right = movement::right(&display_map, sel.end); + if right.row() != sel.end.row() && sel.end.column() == 0 { + sel.end + } else { + right + } } else { sel.end }; @@ -146,8 +149,58 @@ impl Vim { mod test { use indoc::indoc; + use gpui::ClipboardItem; + use crate::{state::Mode, test::VimTestContext}; + #[gpui::test] + async fn test_system_clipboard_paste(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + cx.set_state( + indoc! {" + The quiˇck brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.write_to_clipboard(ClipboardItem::new_string("clipboard".to_string())); + cx.simulate_keystrokes("p"); + cx.assert_state( + indoc! {" + The quic«clipboardˇ»k brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Multiple cursors with system clipboard (no metadata) pastes + // the same text at each cursor. + cx.set_state( + indoc! {" + ˇThe quick brown + fox ˇjumps over + the lazy dog."}, + Mode::HelixNormal, + ); + cx.write_to_clipboard(ClipboardItem::new_string("hi".to_string())); + cx.simulate_keystrokes("p"); + cx.assert_state( + indoc! {" + T«hiˇ»he quick brown + fox j«hiˇ»umps over + the lazy dog."}, + Mode::HelixNormal, + ); + + // Multiple cursors on empty lines should paste on those same lines. + cx.set_state("ˇ\nˇ\nˇ\nend", Mode::HelixNormal); + cx.write_to_clipboard(ClipboardItem::new_string("X".to_string())); + cx.simulate_keystrokes("p"); + cx.assert_state("«Xˇ»\n«Xˇ»\n«Xˇ»\nend", Mode::HelixNormal); + } + #[gpui::test] async fn test_paste(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;