substitute.rs

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