Conrad IrwinUse consistent naming55dd0b13 years agoHistory
260 lines · 8.5 KB · Rust
RawDownload
use editor::movement;use gpui::{actions, AppContext, WindowContext};use language::Point;use workspace::Workspace; use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim}; actions!(vim, [Substitute, SubstituteLine]); pub(crate) fn init(cx: &mut AppContext) {    cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {        Vim::update(cx, |vim, cx| {            let count = vim.pop_number_operator(cx);            substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);        })    });     cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| {        Vim::update(cx, |vim, cx| {            if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {                vim.switch_mode(Mode::VisualLine, false, cx)            }            let count = vim.pop_number_operator(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, |editor, cx| {        editor.set_clip_at_line_ends(false, cx);        editor.transact(cx, |editor, 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);                    }                    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);                        if let Some((point, _)) = (Motion::FirstNonWhitespace {                            display_lines: false,                        })                        .move_point(                            map,                            selection.start,                            selection.goal,                            None,                        ) {                            selection.start = point;                        }                    }                })            });            copy_selections_content(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.assert_shared_state("The quick ˇ").await;         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.assert_shared_state(indoc! {"            The ˇver            the lazy dog"})            .await;         let cases = cx.each_marked_position(indoc! {"            The ˇquick brown            fox jumps ˇover            the ˇlazy dog"});        for initial_state in cases {            cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])                .await;            cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])                .await;        }    }     #[gpui::test]    async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {        let mut cx = NeovimBackedTestContext::new(cx)            .await            .binding(["shift-v", "c"]);        cx.assert(indoc! {"            The quˇick brown            fox jumps over            the lazy dog"})            .await;        // Test pasting code copied on change        cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;        cx.assert_state_matches().await;         cx.assert_all(indoc! {"            The quick brown            fox juˇmps over            the laˇzy dog"})            .await;        let mut cx = cx.binding(["shift-v", "j", "c"]);        cx.assert(indoc! {"            The quˇick brown            fox jumps over            the lazy dog"})            .await;        // Test pasting code copied on delete        cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;        cx.assert_state_matches().await;         cx.assert_all(indoc! {"            The quick brown            fox juˇmps over            the laˇzy dog"})            .await;    }     #[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.assert_shared_state(indoc! {"            The quick brown            oˇ            the lazy dog            "})            .await;         // visual mode        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"])            .await;        cx.assert_shared_state(indoc! {"            oˇ            the lazy dog            "})            .await;         // visual block mode        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"])            .await;        cx.assert_shared_state(indoc! {"            The quick brown            oˇ            "})            .await;         // visual mode including newline        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"])            .await;        cx.assert_shared_state(indoc! {"            The quick brown            oˇ            the lazy dog            "})            .await;         // indentation        cx.set_neovim_option("shiftwidth=4").await;        cx.set_shared_state(initial_state).await;        cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"])            .await;        cx.assert_shared_state(indoc! {"            The quick brown                oˇ            the lazy dog            "})            .await;    }}