287 lines · 9.2 KB · Rust
RawDownload
use editor::movement;use gpui::{actions, ViewContext, WindowContext};use language::Point;use workspace::Workspace; use crate::{motion::Motion, normal::yank::copy_selections_content, Mode, Vim}; actions!(vim, [Substitute, SubstituteLine]); pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {    workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {        Vim::update(cx, |vim, cx| {            vim.start_recording(cx);            let count = vim.take_count(cx);            substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);        })    });     workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {        Vim::update(cx, |vim, cx| {            vim.start_recording(cx);            if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {                vim.switch_mode(Mode::VisualLine, false, cx)            }            let count = vim.take_count(cx);            substitute(vim, count, true, cx)        })    });} pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {    vim.update_active_editor(cx, |vim, editor, cx| {        editor.set_clip_at_line_ends(false, cx);        editor.transact(cx, |editor, cx| {            let text_layout_details = editor.text_layout_details(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,                            &text_layout_details,                        );                    }                    if line_mode {                        // in Visual mode when the selection contains the newline at the end                        // of the line, we should exclude it.                        if !selection.is_empty() && selection.end.column() == 0 {                            selection.end = movement::left(map, selection.end);                        }                        Motion::CurrentLine.expand_selection(                            map,                            selection,                            None,                            false,                            &text_layout_details,                        );                        if let Some((point, _)) = (Motion::FirstNonWhitespace {                            display_lines: false,                        })                        .move_point(                            map,                            selection.start,                            selection.goal,                            None,                            &text_layout_details,                        ) {                            selection.start = point;                        }                    }                })            });            copy_selections_content(vim, editor, line_mode, cx);            let selections = editor.selections.all::<Point>(cx).into_iter();            let edits = selections.map(|selection| (selection.start..selection.end, ""));            editor.edit(edits, cx);        });    });    vim.switch_mode(Mode::Insert, true, cx);} #[cfg(test)]mod test {    use crate::{        state::Mode,        test::{NeovimBackedTestContext, 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);        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");        cx.assert_editor_state("ˇ\n");         // should transactionally undo selection changes        cx.simulate_keystrokes("escape u");        cx.assert_editor_state("ˇcàfé\n");         // it handles visual line mode        cx.set_state(            indoc! {"            alpha              beˇta            gamma"},            Mode::Normal,        );        cx.simulate_keystrokes("shift-v s");        cx.assert_editor_state(indoc! {"            alpha              ˇ            gamma"});    }     #[gpui::test]    async fn test_visual_change(cx: &mut gpui::TestAppContext) {        let mut cx = NeovimBackedTestContext::new(cx).await;         cx.set_shared_state("The quick ˇbrown").await;        cx.simulate_shared_keystrokes("v w c").await;        cx.shared_state().await.assert_eq("The quick ˇ");         cx.set_shared_state(indoc! {"            The ˇquick brown            fox jumps over            the lazy dog"})            .await;        cx.simulate_shared_keystrokes("v w j c").await;        cx.shared_state().await.assert_eq(indoc! {"            The ˇver            the lazy dog"});         cx.simulate_at_each_offset(            "v w j c",            indoc! {"                    The ˇquick brown                    fox jumps ˇover                    the ˇlazy dog"},        )        .await        .assert_matches();        cx.simulate_at_each_offset(            "v w k c",            indoc! {"                    The ˇquick brown                    fox jumps ˇover                    the ˇlazy dog"},        )        .await        .assert_matches();    }     #[gpui::test]    async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {        let mut cx = NeovimBackedTestContext::new(cx).await;        cx.simulate(            "shift-v c",            indoc! {"            The quˇick brown            fox jumps over            the lazy dog"},        )        .await        .assert_matches();        // Test pasting code copied on change        cx.simulate_shared_keystrokes("escape j p").await;        cx.shared_state().await.assert_matches();         cx.simulate_at_each_offset(            "shift-v c",            indoc! {"            The quick brown            fox juˇmps over            the laˇzy dog"},        )        .await        .assert_matches();        cx.simulate(            "shift-v j c",            indoc! {"            The quˇick brown            fox jumps over            the lazy dog"},        )        .await        .assert_matches();        // Test pasting code copied on delete        cx.simulate_shared_keystrokes("escape j p").await;        cx.shared_state().await.assert_matches();         cx.simulate_at_each_offset(            "shift-v j c",            indoc! {"            The quick brown            fox juˇmps over            the laˇzy dog"},        )        .await        .assert_matches();    }     #[gpui::test]    async fn test_substitute_line(cx: &mut gpui::TestAppContext) {        let mut cx = NeovimBackedTestContext::new(cx).await;         let initial_state = indoc! {"                    The quick brown                    fox juˇmps over                    the lazy dog                    "};         // normal mode        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes("shift-s o").await;        cx.shared_state().await.assert_eq(indoc! {"            The quick brown            oˇ            the lazy dog            "});         // visual mode        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes("v k shift-s o").await;        cx.shared_state().await.assert_eq(indoc! {"            oˇ            the lazy dog            "});         // visual block mode        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes("ctrl-v j shift-s o").await;        cx.shared_state().await.assert_eq(indoc! {"            The quick brown            oˇ            "});         // visual mode including newline        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes("v $ shift-s o").await;        cx.shared_state().await.assert_eq(indoc! {"            The quick brown            oˇ            the lazy dog            "});         // indentation        cx.set_neovim_option("shiftwidth=4").await;        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes("> > shift-s o").await;        cx.shared_state().await.assert_eq(indoc! {"            The quick brown                oˇ            the lazy dog            "});    }}