diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 739b40124181044326144c85897cf7e1d7536d5c..8b4aefcaac371383dd3114c2b12abd166ef9aa72 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -965,8 +965,17 @@ impl Vim { window: &mut Window, cx: &mut Context, ) { + // We need to use `text.chars().count()` instead of `text.len()` here as + // `len()` counts bytes, not characters. + let char_count = text.chars().count(); + let count = Vim::take_count(cx).unwrap_or(char_count); let is_return_char = text == "\n".into() || text == "\r".into(); - let count = Vim::take_count(cx).unwrap_or(1); + let repeat_count = match (is_return_char, char_count) { + (true, _) => 0, + (_, 1) => count, + (_, _) => 1, + }; + Vim::take_forced_motion(cx); self.stop_recording(cx); self.update_editor(cx, |_, editor, cx| { @@ -989,7 +998,7 @@ impl Vim { edits.push(( range.start.to_offset(&display_map, Bias::Left) ..range.end.to_offset(&display_map, Bias::Left), - text.repeat(if is_return_char { 0 } else { count }), + text.repeat(repeat_count), )); } diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index c9a9fbdb9ee3428ce80c934a686a73a63ddee714..93c30141daeac21805e8ea1aab610988a09a9635 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -1,5 +1,5 @@ use crate::{ - Vim, + Operator, Vim, motion::{self, Motion}, object::Object, state::Mode, @@ -8,7 +8,7 @@ use editor::{ Anchor, Bias, Editor, EditorSnapshot, SelectionEffects, ToOffset, ToPoint, display_map::ToDisplayPoint, }; -use gpui::{Context, Window, actions}; +use gpui::{ClipboardEntry, Context, Window, actions}; use language::{Point, SelectionGoal}; use std::ops::Range; use std::sync::Arc; @@ -278,10 +278,27 @@ impl Vim { ); } } + + /// Pastes the clipboard contents, replacing the same number of characters + /// as the clipboard's contents. + pub fn paste_replace(&mut self, window: &mut Window, cx: &mut Context) { + let clipboard_text = + cx.read_from_clipboard() + .and_then(|item| match item.entries().first() { + Some(ClipboardEntry::String(text)) => Some(text.text().to_string()), + _ => None, + }); + + if let Some(text) = clipboard_text { + self.push_operator(Operator::Replace, window, cx); + self.normal_replace(Arc::from(text), window, cx); + } + } } #[cfg(test)] mod test { + use gpui::ClipboardItem; use indoc::indoc; use crate::{ @@ -521,4 +538,22 @@ mod test { assert_eq!(0, highlights.len()); }); } + + #[gpui::test] + async fn test_paste_replace(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state(indoc! {"ˇ123"}, Mode::Replace); + cx.write_to_clipboard(ClipboardItem::new_string("456".to_string())); + cx.dispatch_action(editor::actions::Paste); + cx.assert_state(indoc! {"45ˇ6"}, Mode::Replace); + + // If the clipboard's contents length is greater than the remaining text + // length, nothing sould be replace and cursor should remain in the same + // position. + cx.set_state(indoc! {"ˇ123"}, Mode::Replace); + cx.write_to_clipboard(ClipboardItem::new_string("4567".to_string())); + cx.dispatch_action(editor::actions::Paste); + cx.assert_state(indoc! {"ˇ123"}, Mode::Replace); + } } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index e0b9bfd6e06c3528bd81b81e98d5cb65abb35aa3..cb553b64e91eadbb5e529d56bb1e1a5a7da2c7be 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -23,6 +23,7 @@ use collections::HashMap; use editor::{ Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, SelectionEffects, ToPoint, + actions::Paste, movement::{self, FindRange}, }; use gpui::{ @@ -919,6 +920,17 @@ impl Vim { ); }); + Vim::action( + editor, + cx, + |vim, _: &editor::actions::Paste, window, cx| match vim.mode { + Mode::Replace => vim.paste_replace(window, cx), + _ => { + vim.update_editor(cx, |_, editor, cx| editor.paste(&Paste, window, cx)); + } + }, + ); + normal::register(editor, cx); insert::register(editor, cx); helix::register(editor, cx);