1use gpui::WindowContext;
2use language::Point;
3
4use crate::{motion::Motion, Mode, Vim};
5
6pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
7 vim.update_active_editor(cx, |editor, cx| {
8 editor.set_clip_at_line_ends(false, cx);
9 editor.transact(cx, |editor, cx| {
10 editor.change_selections(None, cx, |s| {
11 s.move_with(|map, selection| {
12 if selection.start == selection.end {
13 Motion::Right.expand_selection(map, selection, count, true);
14 }
15 })
16 });
17 let selections = editor.selections.all::<Point>(cx);
18 for selection in selections.into_iter().rev() {
19 editor.buffer().update(cx, |buffer, cx| {
20 buffer.edit([(selection.start..selection.end, "")], None, cx)
21 })
22 }
23 });
24 editor.set_clip_at_line_ends(true, cx);
25 });
26 vim.switch_mode(Mode::Insert, true, cx)
27}
28
29#[cfg(test)]
30mod test {
31 use crate::{state::Mode, test::VimTestContext};
32 use indoc::indoc;
33
34 #[gpui::test]
35 async fn test_substitute(cx: &mut gpui::TestAppContext) {
36 let mut cx = VimTestContext::new(cx, true).await;
37
38 // supports a single cursor
39 cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
40 cx.simulate_keystrokes(["s", "x"]);
41 cx.assert_editor_state("xˇbc\n");
42
43 // supports a selection
44 cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual { line: false });
45 cx.assert_editor_state("a«bcˇ»\n");
46 cx.simulate_keystrokes(["s", "x"]);
47 cx.assert_editor_state("axˇ\n");
48
49 // supports counts
50 cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
51 cx.simulate_keystrokes(["2", "s", "x"]);
52 cx.assert_editor_state("xˇc\n");
53
54 // supports multiple cursors
55 cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
56 cx.simulate_keystrokes(["2", "s", "x"]);
57 cx.assert_editor_state("axˇdexˇg\n");
58
59 // does not read beyond end of line
60 cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
61 cx.simulate_keystrokes(["5", "s", "x"]);
62 cx.assert_editor_state("xˇ\n");
63
64 // it handles multibyte characters
65 cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal);
66 cx.simulate_keystrokes(["4", "s"]);
67 cx.assert_editor_state("ˇ\n");
68
69 // should transactionally undo selection changes
70 cx.simulate_keystrokes(["escape", "u"]);
71 cx.assert_editor_state("ˇcàfé\n");
72 }
73}