change.rs

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