change.rs

  1use crate::{motion::Motion, state::Mode, utils::copy_selections_content, Vim};
  2use editor::{char_kind, movement, Autoscroll};
  3use gpui::{impl_actions, MutableAppContext, ViewContext};
  4use serde::Deserialize;
  5use workspace::Workspace;
  6
  7#[derive(Clone, Deserialize, PartialEq)]
  8#[serde(rename_all = "camelCase")]
  9struct ChangeWord {
 10    #[serde(default)]
 11    ignore_punctuation: bool,
 12}
 13
 14impl_actions!(vim, [ChangeWord]);
 15
 16pub fn init(cx: &mut MutableAppContext) {
 17    cx.add_action(change_word);
 18}
 19
 20pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
 21    vim.update_active_editor(cx, |editor, cx| {
 22        editor.transact(cx, |editor, cx| {
 23            // We are swapping to insert mode anyway. Just set the line end clipping behavior now
 24            editor.set_clip_at_line_ends(false, cx);
 25            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 26                s.move_with(|map, selection| {
 27                    motion.expand_selection(map, selection, false);
 28                });
 29            });
 30            copy_selections_content(editor, motion.linewise(), cx);
 31            editor.insert("", cx);
 32        });
 33    });
 34    vim.switch_mode(Mode::Insert, false, cx)
 35}
 36
 37// From the docs https://vimhelp.org/change.txt.html#cw
 38// Special case: When the cursor is in a word, "cw" and "cW" do not include the
 39// white space after a word, they only change up to the end of the word. This is
 40// because Vim interprets "cw" as change-word, and a word does not include the
 41// following white space.
 42fn change_word(
 43    _: &mut Workspace,
 44    &ChangeWord { ignore_punctuation }: &ChangeWord,
 45    cx: &mut ViewContext<Workspace>,
 46) {
 47    Vim::update(cx, |vim, cx| {
 48        vim.update_active_editor(cx, |editor, cx| {
 49            editor.transact(cx, |editor, cx| {
 50                // We are swapping to insert mode anyway. Just set the line end clipping behavior now
 51                editor.set_clip_at_line_ends(false, cx);
 52                editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 53                    s.move_with(|map, selection| {
 54                        if selection.end.column() == map.line_len(selection.end.row()) {
 55                            return;
 56                        }
 57
 58                        selection.end =
 59                            movement::find_boundary(map, selection.end, |left, right| {
 60                                let left_kind =
 61                                    char_kind(left).coerce_punctuation(ignore_punctuation);
 62                                let right_kind =
 63                                    char_kind(right).coerce_punctuation(ignore_punctuation);
 64
 65                                left_kind != right_kind || left == '\n' || right == '\n'
 66                            });
 67                    });
 68                });
 69                copy_selections_content(editor, false, cx);
 70                editor.insert("", cx);
 71            });
 72        });
 73        vim.switch_mode(Mode::Insert, false, cx);
 74    });
 75}
 76
 77#[cfg(test)]
 78mod test {
 79    use indoc::indoc;
 80
 81    use crate::{state::Mode, vim_test_context::VimTestContext};
 82
 83    #[gpui::test]
 84    async fn test_change_h(cx: &mut gpui::TestAppContext) {
 85        let cx = VimTestContext::new(cx, true).await;
 86        let mut cx = cx.binding(["c", "h"]).mode_after(Mode::Insert);
 87        cx.assert("Teˇst", "Tˇst");
 88        cx.assert("Tˇest", "ˇest");
 89        cx.assert("ˇTest", "ˇTest");
 90        cx.assert(
 91            indoc! {"
 92                Test
 93                ˇtest"},
 94            indoc! {"
 95                Test
 96                ˇtest"},
 97        );
 98    }
 99
100    #[gpui::test]
101    async fn test_change_l(cx: &mut gpui::TestAppContext) {
102        let cx = VimTestContext::new(cx, true).await;
103        let mut cx = cx.binding(["c", "l"]).mode_after(Mode::Insert);
104        cx.assert("Teˇst", "Teˇt");
105        cx.assert("Tesˇt", "Tesˇ");
106    }
107
108    #[gpui::test]
109    async fn test_change_w(cx: &mut gpui::TestAppContext) {
110        let cx = VimTestContext::new(cx, true).await;
111        let mut cx = cx.binding(["c", "w"]).mode_after(Mode::Insert);
112        cx.assert("Teˇst", "Teˇ");
113        cx.assert("Tˇest test", "Tˇ test");
114        cx.assert("Testˇ  test", "Testˇtest");
115        cx.assert(
116            indoc! {"
117                Test teˇst
118                test"},
119            indoc! {"
120                Test teˇ
121                test"},
122        );
123        cx.assert(
124            indoc! {"
125                Test tesˇt
126                test"},
127            indoc! {"
128                Test tesˇ
129                test"},
130        );
131        cx.assert(
132            indoc! {"
133                Test test
134                ˇ
135                test"},
136            indoc! {"
137                Test test
138                ˇ
139                test"},
140        );
141
142        let mut cx = cx.binding(["c", "shift-w"]);
143        cx.assert("Test teˇst-test test", "Test teˇ test");
144    }
145
146    #[gpui::test]
147    async fn test_change_e(cx: &mut gpui::TestAppContext) {
148        let cx = VimTestContext::new(cx, true).await;
149        let mut cx = cx.binding(["c", "e"]).mode_after(Mode::Insert);
150        cx.assert("Teˇst Test", "Teˇ Test");
151        cx.assert("Tˇest test", "Tˇ test");
152        cx.assert(
153            indoc! {"
154                Test teˇst
155                test"},
156            indoc! {"
157                Test teˇ
158                test"},
159        );
160        cx.assert(
161            indoc! {"
162                Test tesˇt
163                test"},
164            "Test tesˇ",
165        );
166        cx.assert(
167            indoc! {"
168                Test test
169                ˇ
170                test"},
171            indoc! {"
172                Test test
173                ˇ
174                test"},
175        );
176
177        let mut cx = cx.binding(["c", "shift-e"]);
178        cx.assert("Test teˇst-test test", "Test teˇ test");
179    }
180
181    #[gpui::test]
182    async fn test_change_b(cx: &mut gpui::TestAppContext) {
183        let cx = VimTestContext::new(cx, true).await;
184        let mut cx = cx.binding(["c", "b"]).mode_after(Mode::Insert);
185        cx.assert("Teˇst Test", "ˇst Test");
186        cx.assert("Test ˇtest", "ˇtest");
187        cx.assert("Test1 test2 ˇtest3", "Test1 ˇtest3");
188        cx.assert(
189            indoc! {"
190                Test test
191                ˇtest"},
192            indoc! {"
193                Test ˇ
194                test"},
195        );
196        cx.assert(
197            indoc! {"
198                Test test
199                ˇ
200                test"},
201            indoc! {"
202                Test ˇ
203                
204                test"},
205        );
206
207        let mut cx = cx.binding(["c", "shift-b"]);
208        cx.assert("Test test-test ˇtest", "Test ˇtest");
209    }
210
211    #[gpui::test]
212    async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
213        let cx = VimTestContext::new(cx, true).await;
214        let mut cx = cx.binding(["c", "$"]).mode_after(Mode::Insert);
215        cx.assert(
216            indoc! {"
217                The qˇuick
218                brown fox"},
219            indoc! {"
220                The qˇ
221                brown fox"},
222        );
223        cx.assert(
224            indoc! {"
225                The quick
226                ˇ
227                brown fox"},
228            indoc! {"
229                The quick
230                ˇ
231                brown fox"},
232        );
233    }
234
235    #[gpui::test]
236    async fn test_change_0(cx: &mut gpui::TestAppContext) {
237        let cx = VimTestContext::new(cx, true).await;
238        let mut cx = cx.binding(["c", "0"]).mode_after(Mode::Insert);
239        cx.assert(
240            indoc! {"
241                The qˇuick
242                brown fox"},
243            indoc! {"
244                ˇuick
245                brown fox"},
246        );
247        cx.assert(
248            indoc! {"
249                The quick
250                ˇ
251                brown fox"},
252            indoc! {"
253                The quick
254                ˇ
255                brown fox"},
256        );
257    }
258
259    #[gpui::test]
260    async fn test_change_k(cx: &mut gpui::TestAppContext) {
261        let cx = VimTestContext::new(cx, true).await;
262        let mut cx = cx.binding(["c", "k"]).mode_after(Mode::Insert);
263        cx.assert(
264            indoc! {"
265                The quick
266                brown ˇfox
267                jumps over"},
268            indoc! {"
269                ˇ
270                jumps over"},
271        );
272        cx.assert(
273            indoc! {"
274                The quick
275                brown fox
276                jumps ˇover"},
277            indoc! {"
278                The quick
279                ˇ"},
280        );
281        cx.assert(
282            indoc! {"
283                The qˇuick
284                brown fox
285                jumps over"},
286            indoc! {"
287                ˇ
288                brown fox
289                jumps over"},
290        );
291        cx.assert(
292            indoc! {"
293                ˇ
294                brown fox
295                jumps over"},
296            indoc! {"
297                ˇ
298                brown fox
299                jumps over"},
300        );
301    }
302
303    #[gpui::test]
304    async fn test_change_j(cx: &mut gpui::TestAppContext) {
305        let cx = VimTestContext::new(cx, true).await;
306        let mut cx = cx.binding(["c", "j"]).mode_after(Mode::Insert);
307        cx.assert(
308            indoc! {"
309                The quick
310                brown ˇfox
311                jumps over"},
312            indoc! {"
313                The quick
314                ˇ"},
315        );
316        cx.assert(
317            indoc! {"
318                The quick
319                brown fox
320                jumps ˇover"},
321            indoc! {"
322                The quick
323                brown fox
324                ˇ"},
325        );
326        cx.assert(
327            indoc! {"
328                The qˇuick
329                brown fox
330                jumps over"},
331            indoc! {"
332                ˇ
333                jumps over"},
334        );
335        cx.assert(
336            indoc! {"
337                The quick
338                brown fox
339                ˇ"},
340            indoc! {"
341                The quick
342                brown fox
343                ˇ"},
344        );
345    }
346
347    #[gpui::test]
348    async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
349        let cx = VimTestContext::new(cx, true).await;
350        let mut cx = cx.binding(["c", "shift-g"]).mode_after(Mode::Insert);
351        cx.assert(
352            indoc! {"
353                The quick
354                brownˇ fox
355                jumps over
356                the lazy"},
357            indoc! {"
358                The quick
359                ˇ"},
360        );
361        cx.assert(
362            indoc! {"
363                The quick
364                brownˇ fox
365                jumps over
366                the lazy"},
367            indoc! {"
368                The quick
369                ˇ"},
370        );
371        cx.assert(
372            indoc! {"
373                The quick
374                brown fox
375                jumps over
376                the lˇazy"},
377            indoc! {"
378                The quick
379                brown fox
380                jumps over
381                ˇ"},
382        );
383        cx.assert(
384            indoc! {"
385                The quick
386                brown fox
387                jumps over
388                ˇ"},
389            indoc! {"
390                The quick
391                brown fox
392                jumps over
393                ˇ"},
394        );
395    }
396
397    #[gpui::test]
398    async fn test_change_gg(cx: &mut gpui::TestAppContext) {
399        let cx = VimTestContext::new(cx, true).await;
400        let mut cx = cx.binding(["c", "g", "g"]).mode_after(Mode::Insert);
401        cx.assert(
402            indoc! {"
403                The quick
404                brownˇ fox
405                jumps over
406                the lazy"},
407            indoc! {"
408                ˇ
409                jumps over
410                the lazy"},
411        );
412        cx.assert(
413            indoc! {"
414                The quick
415                brown fox
416                jumps over
417                the lˇazy"},
418            "ˇ",
419        );
420        cx.assert(
421            indoc! {"
422                The qˇuick
423                brown fox
424                jumps over
425                the lazy"},
426            indoc! {"
427                ˇ
428                brown fox
429                jumps over
430                the lazy"},
431        );
432        cx.assert(
433            indoc! {"
434                ˇ
435                brown fox
436                jumps over
437                the lazy"},
438            indoc! {"
439                ˇ
440                brown fox
441                jumps over
442                the lazy"},
443        );
444    }
445}