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