substitute.rs

  1use gpui::WindowContext;
  2use language::Point;
  3
  4use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
  5
  6pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
  7    let line_mode = vim.state().mode == Mode::VisualLine;
  8    vim.update_active_editor(cx, |editor, cx| {
  9        editor.set_clip_at_line_ends(false, cx);
 10        editor.transact(cx, |editor, cx| {
 11            editor.change_selections(None, cx, |s| {
 12                s.move_with(|map, selection| {
 13                    if selection.start == selection.end {
 14                        Motion::Right.expand_selection(map, selection, count, true);
 15                    }
 16                    if line_mode {
 17                        Motion::CurrentLine.expand_selection(map, selection, None, false);
 18                        if let Some((point, _)) = (Motion::FirstNonWhitespace {
 19                            display_lines: false,
 20                        })
 21                        .move_point(
 22                            map,
 23                            selection.start,
 24                            selection.goal,
 25                            None,
 26                        ) {
 27                            selection.start = point;
 28                        }
 29                    }
 30                })
 31            });
 32            copy_selections_content(editor, line_mode, cx);
 33            let selections = editor.selections.all::<Point>(cx).into_iter();
 34            let edits = selections.map(|selection| (selection.start..selection.end, ""));
 35            editor.edit(edits, cx);
 36        });
 37    });
 38    vim.switch_mode(Mode::Insert, true, cx);
 39}
 40
 41#[cfg(test)]
 42mod test {
 43    use crate::{
 44        state::Mode,
 45        test::{NeovimBackedTestContext, VimTestContext},
 46    };
 47    use indoc::indoc;
 48
 49    #[gpui::test]
 50    async fn test_substitute(cx: &mut gpui::TestAppContext) {
 51        let mut cx = VimTestContext::new(cx, true).await;
 52
 53        // supports a single cursor
 54        cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
 55        cx.simulate_keystrokes(["s", "x"]);
 56        cx.assert_editor_state("xˇbc\n");
 57
 58        // supports a selection
 59        cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
 60        cx.assert_editor_state("a«bcˇ»\n");
 61        cx.simulate_keystrokes(["s", "x"]);
 62        cx.assert_editor_state("axˇ\n");
 63
 64        // supports counts
 65        cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
 66        cx.simulate_keystrokes(["2", "s", "x"]);
 67        cx.assert_editor_state("xˇc\n");
 68
 69        // supports multiple cursors
 70        cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
 71        cx.simulate_keystrokes(["2", "s", "x"]);
 72        cx.assert_editor_state("axˇdexˇg\n");
 73
 74        // does not read beyond end of line
 75        cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
 76        cx.simulate_keystrokes(["5", "s", "x"]);
 77        cx.assert_editor_state("\n");
 78
 79        // it handles multibyte characters
 80        cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal);
 81        cx.simulate_keystrokes(["4", "s"]);
 82        cx.assert_editor_state("ˇ\n");
 83
 84        // should transactionally undo selection changes
 85        cx.simulate_keystrokes(["escape", "u"]);
 86        cx.assert_editor_state("ˇcàfé\n");
 87
 88        // it handles visual line mode
 89        cx.set_state(
 90            indoc! {"
 91            alpha
 92              beˇta
 93            gamma"},
 94            Mode::Normal,
 95        );
 96        cx.simulate_keystrokes(["shift-v", "s"]);
 97        cx.assert_editor_state(indoc! {"
 98            alpha
 99              ˇ
100            gamma"});
101    }
102
103    #[gpui::test]
104    async fn test_visual_change(cx: &mut gpui::TestAppContext) {
105        let mut cx = NeovimBackedTestContext::new(cx).await;
106
107        cx.set_shared_state("The quick ˇbrown").await;
108        cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
109        cx.assert_shared_state("The quick ˇ").await;
110
111        cx.set_shared_state(indoc! {"
112            The ˇquick brown
113            fox jumps over
114            the lazy dog"})
115            .await;
116        cx.simulate_shared_keystrokes(["v", "w", "j", "c"]).await;
117        cx.assert_shared_state(indoc! {"
118            The ˇver
119            the lazy dog"})
120            .await;
121
122        let cases = cx.each_marked_position(indoc! {"
123            The ˇquick brown
124            fox jumps ˇover
125            the ˇlazy dog"});
126        for initial_state in cases {
127            cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
128                .await;
129            cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
130                .await;
131        }
132    }
133
134    #[gpui::test]
135    async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
136        let mut cx = NeovimBackedTestContext::new(cx)
137            .await
138            .binding(["shift-v", "c"]);
139        cx.assert(indoc! {"
140            The quˇick brown
141            fox jumps over
142            the lazy dog"})
143            .await;
144        // Test pasting code copied on change
145        cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
146        cx.assert_state_matches().await;
147
148        cx.assert_all(indoc! {"
149            The quick brown
150            fox juˇmps over
151            the laˇzy dog"})
152            .await;
153        let mut cx = cx.binding(["shift-v", "j", "c"]);
154        cx.assert(indoc! {"
155            The quˇick brown
156            fox jumps over
157            the lazy dog"})
158            .await;
159        // Test pasting code copied on delete
160        cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
161        cx.assert_state_matches().await;
162
163        cx.assert_all(indoc! {"
164            The quick brown
165            fox juˇmps over
166            the laˇzy dog"})
167            .await;
168    }
169}