From 926acd6033b1d10758c48d541737adb937c96bca Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 22 Jun 2023 13:22:19 -0600 Subject: [PATCH] vim: substitute handles multibyte characters And is now in its own file --- crates/vim/src/normal.rs | 21 +-------- crates/vim/src/normal/substitute.rs | 69 +++++++++++++++++++++++++++++ crates/vim/src/test.rs | 24 ---------- 3 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 crates/vim/src/normal/substitute.rs diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index ecec232104..702f056b75 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -1,5 +1,6 @@ mod change; mod delete; +mod substitute; mod yank; use std::{borrow::Cow, cmp::Ordering, sync::Arc}; @@ -25,6 +26,7 @@ use workspace::Workspace; use self::{ change::{change_motion, change_object}, delete::{delete_motion, delete_object}, + substitute::substitute, yank::{yank_motion, yank_object}, }; @@ -478,25 +480,6 @@ pub(crate) fn normal_replace(text: Arc, cx: &mut WindowContext) { }); } -pub fn substitute(vim: &mut Vim, count: usize, cx: &mut WindowContext) { - vim.update_active_editor(cx, |editor, cx| { - editor.transact(cx, |editor, cx| { - let selections = editor.selections.all::(cx); - for selection in selections.into_iter().rev() { - let end = if selection.start == selection.end { - selection.start + Point::new(0, count as u32) - } else { - selection.end - }; - editor.buffer().update(cx, |buffer, cx| { - buffer.edit([(selection.start..end, "")], None, cx) - }) - } - }) - }); - vim.switch_mode(Mode::Insert, true, cx) -} - #[cfg(test)] mod test { use gpui::TestAppContext; diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs new file mode 100644 index 0000000000..e208983b11 --- /dev/null +++ b/crates/vim/src/normal/substitute.rs @@ -0,0 +1,69 @@ +use gpui::WindowContext; +use language::Point; + +use crate::{motion::Motion, Mode, Vim}; + +pub fn substitute(vim: &mut Vim, count: usize, cx: &mut WindowContext) { + vim.update_active_editor(cx, |editor, cx| { + editor.set_clip_at_line_ends(false, cx); + editor.change_selections(None, cx, |s| { + s.move_with(|map, selection| { + if selection.start == selection.end { + Motion::Right.expand_selection(map, selection, count, true); + } + }) + }); + editor.transact(cx, |editor, cx| { + let selections = editor.selections.all::(cx); + for selection in selections.into_iter().rev() { + editor.buffer().update(cx, |buffer, cx| { + buffer.edit([(selection.start..selection.end, "")], None, cx) + }) + } + }); + editor.set_clip_at_line_ends(true, cx); + }); + vim.switch_mode(Mode::Insert, true, cx) +} + +#[cfg(test)] +mod test { + use crate::{state::Mode, test::VimTestContext}; + use indoc::indoc; + + #[gpui::test] + async fn test_substitute(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + // supports a single cursor + cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal); + cx.simulate_keystrokes(["s", "x"]); + cx.assert_editor_state("xˇbc\n"); + + // supports a selection + cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual { line: false }); + cx.assert_editor_state("a«bcˇ»\n"); + cx.simulate_keystrokes(["s", "x"]); + cx.assert_editor_state("axˇ\n"); + + // supports counts + cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal); + cx.simulate_keystrokes(["2", "s", "x"]); + cx.assert_editor_state("xˇc\n"); + + // supports multiple cursors + cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal); + cx.simulate_keystrokes(["2", "s", "x"]); + cx.assert_editor_state("axˇdexˇg\n"); + + // does not read beyond end of line + cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal); + cx.simulate_keystrokes(["5", "s", "x"]); + cx.assert_editor_state("xˇ\n"); + + // it handles multibyte characters + cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal); + cx.simulate_keystrokes(["4", "s", "x"]); + cx.assert_editor_state("xˇ\n"); + } +} diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index c7d944bd2a..0214806e11 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -98,27 +98,3 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) { assert_eq!(bar.query_editor.read(cx).text(cx), "jumps"); }) } - -#[gpui::test] -async fn test_substitute(cx: &mut gpui::TestAppContext) { - let mut cx = VimTestContext::new(cx, true).await; - - // supports a single cursor - cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal); - cx.simulate_keystrokes(["s", "x"]); - cx.assert_editor_state("xˇbc\n"); - - // supports a selection - cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual { line: false }); - cx.simulate_keystrokes(["s", "x"]); - cx.assert_editor_state("axˇ\n"); - - // supports multiple cursors - cx.set_state(indoc! {"a«bcˇ»deˇfg\n"}, Mode::Normal); - cx.simulate_keystrokes(["s", "x"]); - cx.assert_editor_state("axˇdexˇg\n"); - - cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal); - cx.simulate_keystrokes(["2", "s", "x"]); - cx.assert_editor_state("xˇc\n"); -}