1use editor::movement;
2use gpui::{actions, AppContext, WindowContext};
3use language::Point;
4use workspace::Workspace;
5
6use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
7
8actions!(Substitute, SubstituteLine);
9
10pub(crate) fn init(cx: &mut AppContext) {
11 // todo!()
12 // cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
13 // Vim::update(cx, |vim, cx| {
14 // vim.start_recording(cx);
15 // let count = vim.take_count(cx);
16 // substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);
17 // })
18 // });
19
20 // cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
21 // Vim::update(cx, |vim, cx| {
22 // vim.start_recording(cx);
23 // if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
24 // vim.switch_mode(Mode::VisualLine, false, cx)
25 // }
26 // let count = vim.take_count(cx);
27 // substitute(vim, count, true, cx)
28 // })
29 // });
30}
31
32pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
33 vim.update_active_editor(cx, |editor, cx| {
34 editor.set_clip_at_line_ends(false, cx);
35 editor.transact(cx, |editor, cx| {
36 let text_layout_details = editor.text_layout_details(cx);
37 editor.change_selections(None, cx, |s| {
38 s.move_with(|map, selection| {
39 if selection.start == selection.end {
40 Motion::Right.expand_selection(
41 map,
42 selection,
43 count,
44 true,
45 &text_layout_details,
46 );
47 }
48 if line_mode {
49 // in Visual mode when the selection contains the newline at the end
50 // of the line, we should exclude it.
51 if !selection.is_empty() && selection.end.column() == 0 {
52 selection.end = movement::left(map, selection.end);
53 }
54 Motion::CurrentLine.expand_selection(
55 map,
56 selection,
57 None,
58 false,
59 &text_layout_details,
60 );
61 if let Some((point, _)) = (Motion::FirstNonWhitespace {
62 display_lines: false,
63 })
64 .move_point(
65 map,
66 selection.start,
67 selection.goal,
68 None,
69 &text_layout_details,
70 ) {
71 selection.start = point;
72 }
73 }
74 })
75 });
76 copy_selections_content(editor, line_mode, cx);
77 let selections = editor.selections.all::<Point>(cx).into_iter();
78 let edits = selections.map(|selection| (selection.start..selection.end, ""));
79 editor.edit(edits, cx);
80 });
81 });
82 vim.switch_mode(Mode::Insert, true, cx);
83}
84
85// #[cfg(test)]
86// mod test {
87// use crate::{
88// state::Mode,
89// test::{NeovimBackedTestContext, VimTestContext},
90// };
91// use indoc::indoc;
92
93// #[gpui::test]
94// async fn test_substitute(cx: &mut gpui::TestAppContext) {
95// let mut cx = VimTestContext::new(cx, true).await;
96
97// // supports a single cursor
98// cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
99// cx.simulate_keystrokes(["s", "x"]);
100// cx.assert_editor_state("xˇbc\n");
101
102// // supports a selection
103// cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
104// cx.assert_editor_state("a«bcˇ»\n");
105// cx.simulate_keystrokes(["s", "x"]);
106// cx.assert_editor_state("axˇ\n");
107
108// // supports counts
109// cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
110// cx.simulate_keystrokes(["2", "s", "x"]);
111// cx.assert_editor_state("xˇc\n");
112
113// // supports multiple cursors
114// cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
115// cx.simulate_keystrokes(["2", "s", "x"]);
116// cx.assert_editor_state("axˇdexˇg\n");
117
118// // does not read beyond end of line
119// cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
120// cx.simulate_keystrokes(["5", "s", "x"]);
121// cx.assert_editor_state("xˇ\n");
122
123// // it handles multibyte characters
124// cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal);
125// cx.simulate_keystrokes(["4", "s"]);
126// cx.assert_editor_state("ˇ\n");
127
128// // should transactionally undo selection changes
129// cx.simulate_keystrokes(["escape", "u"]);
130// cx.assert_editor_state("ˇcàfé\n");
131
132// // it handles visual line mode
133// cx.set_state(
134// indoc! {"
135// alpha
136// beˇta
137// gamma"},
138// Mode::Normal,
139// );
140// cx.simulate_keystrokes(["shift-v", "s"]);
141// cx.assert_editor_state(indoc! {"
142// alpha
143// ˇ
144// gamma"});
145// }
146
147// #[gpui::test]
148// async fn test_visual_change(cx: &mut gpui::TestAppContext) {
149// let mut cx = NeovimBackedTestContext::new(cx).await;
150
151// cx.set_shared_state("The quick ˇbrown").await;
152// cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
153// cx.assert_shared_state("The quick ˇ").await;
154
155// cx.set_shared_state(indoc! {"
156// The ˇquick brown
157// fox jumps over
158// the lazy dog"})
159// .await;
160// cx.simulate_shared_keystrokes(["v", "w", "j", "c"]).await;
161// cx.assert_shared_state(indoc! {"
162// The ˇver
163// the lazy dog"})
164// .await;
165
166// let cases = cx.each_marked_position(indoc! {"
167// The ˇquick brown
168// fox jumps ˇover
169// the ˇlazy dog"});
170// for initial_state in cases {
171// cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
172// .await;
173// cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
174// .await;
175// }
176// }
177
178// #[gpui::test]
179// async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
180// let mut cx = NeovimBackedTestContext::new(cx)
181// .await
182// .binding(["shift-v", "c"]);
183// cx.assert(indoc! {"
184// The quˇick brown
185// fox jumps over
186// the lazy dog"})
187// .await;
188// // Test pasting code copied on change
189// cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
190// cx.assert_state_matches().await;
191
192// cx.assert_all(indoc! {"
193// The quick brown
194// fox juˇmps over
195// the laˇzy dog"})
196// .await;
197// let mut cx = cx.binding(["shift-v", "j", "c"]);
198// cx.assert(indoc! {"
199// The quˇick brown
200// fox jumps over
201// the lazy dog"})
202// .await;
203// // Test pasting code copied on delete
204// cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
205// cx.assert_state_matches().await;
206
207// cx.assert_all(indoc! {"
208// The quick brown
209// fox juˇmps over
210// the laˇzy dog"})
211// .await;
212// }
213
214// #[gpui::test]
215// async fn test_substitute_line(cx: &mut gpui::TestAppContext) {
216// let mut cx = NeovimBackedTestContext::new(cx).await;
217
218// let initial_state = indoc! {"
219// The quick brown
220// fox juˇmps over
221// the lazy dog
222// "};
223
224// // normal mode
225// cx.set_shared_state(initial_state).await;
226// cx.simulate_shared_keystrokes(["shift-s", "o"]).await;
227// cx.assert_shared_state(indoc! {"
228// The quick brown
229// oˇ
230// the lazy dog
231// "})
232// .await;
233
234// // visual mode
235// cx.set_shared_state(initial_state).await;
236// cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"])
237// .await;
238// cx.assert_shared_state(indoc! {"
239// oˇ
240// the lazy dog
241// "})
242// .await;
243
244// // visual block mode
245// cx.set_shared_state(initial_state).await;
246// cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"])
247// .await;
248// cx.assert_shared_state(indoc! {"
249// The quick brown
250// oˇ
251// "})
252// .await;
253
254// // visual mode including newline
255// cx.set_shared_state(initial_state).await;
256// cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"])
257// .await;
258// cx.assert_shared_state(indoc! {"
259// The quick brown
260// oˇ
261// the lazy dog
262// "})
263// .await;
264
265// // indentation
266// cx.set_neovim_option("shiftwidth=4").await;
267// cx.set_shared_state(initial_state).await;
268// cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"])
269// .await;
270// cx.assert_shared_state(indoc! {"
271// The quick brown
272// oˇ
273// the lazy dog
274// "})
275// .await;
276// }
277// }