change.rs

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