1mod change;
2mod delete;
3
4use crate::{motion::Motion, state::Operator, Vim};
5use change::init as change_init;
6use gpui::{actions, MutableAppContext};
7
8use self::{change::change_over, delete::delete_over};
9
10actions!(vim, [InsertLineAbove, InsertLineBelow, InsertAfter]);
11
12pub fn init(cx: &mut MutableAppContext) {
13 change_init(cx);
14}
15
16pub fn normal_motion(motion: Motion, cx: &mut MutableAppContext) {
17 Vim::update(cx, |vim, cx| {
18 match vim.state.operator_stack.pop() {
19 None => move_cursor(vim, motion, cx),
20 Some(Operator::Change) => change_over(vim, motion, cx),
21 Some(Operator::Delete) => delete_over(vim, motion, cx),
22 Some(Operator::Namespace(_)) => {
23 // Can't do anything for a namespace operator. Ignoring
24 }
25 }
26 vim.clear_operator(cx);
27 });
28}
29
30fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
31 vim.update_active_editor(cx, |editor, cx| {
32 editor.move_cursors(cx, |map, cursor, goal| motion.move_point(map, cursor, goal))
33 });
34}
35
36#[cfg(test)]
37mod test {
38 use indoc::indoc;
39 use util::test::marked_text;
40
41 use crate::{
42 state::{
43 Mode::{self, *},
44 Namespace, Operator,
45 },
46 vim_test_context::VimTestContext,
47 };
48
49 #[gpui::test]
50 async fn test_h(cx: &mut gpui::TestAppContext) {
51 let cx = VimTestContext::new(cx, true).await;
52 let mut cx = cx.binding(["h"]);
53 cx.assert("The q|uick", "The |quick");
54 cx.assert("|The quick", "|The quick");
55 cx.assert(
56 indoc! {"
57 The quick
58 |brown"},
59 indoc! {"
60 The quick
61 |brown"},
62 );
63 }
64
65 #[gpui::test]
66 async fn test_l(cx: &mut gpui::TestAppContext) {
67 let cx = VimTestContext::new(cx, true).await;
68 let mut cx = cx.binding(["l"]);
69 cx.assert("The q|uick", "The qu|ick");
70 cx.assert("The quic|k", "The quic|k");
71 cx.assert(
72 indoc! {"
73 The quic|k
74 brown"},
75 indoc! {"
76 The quic|k
77 brown"},
78 );
79 }
80
81 #[gpui::test]
82 async fn test_j(cx: &mut gpui::TestAppContext) {
83 let cx = VimTestContext::new(cx, true).await;
84 let mut cx = cx.binding(["j"]);
85 cx.assert(
86 indoc! {"
87 The |quick
88 brown fox"},
89 indoc! {"
90 The quick
91 brow|n fox"},
92 );
93 cx.assert(
94 indoc! {"
95 The quick
96 brow|n fox"},
97 indoc! {"
98 The quick
99 brow|n fox"},
100 );
101 cx.assert(
102 indoc! {"
103 The quic|k
104 brown"},
105 indoc! {"
106 The quick
107 brow|n"},
108 );
109 cx.assert(
110 indoc! {"
111 The quick
112 |brown"},
113 indoc! {"
114 The quick
115 |brown"},
116 );
117 }
118
119 #[gpui::test]
120 async fn test_k(cx: &mut gpui::TestAppContext) {
121 let cx = VimTestContext::new(cx, true).await;
122 let mut cx = cx.binding(["k"]);
123 cx.assert(
124 indoc! {"
125 The |quick
126 brown fox"},
127 indoc! {"
128 The |quick
129 brown fox"},
130 );
131 cx.assert(
132 indoc! {"
133 The quick
134 brow|n fox"},
135 indoc! {"
136 The |quick
137 brown fox"},
138 );
139 cx.assert(
140 indoc! {"
141 The
142 quic|k"},
143 indoc! {"
144 Th|e
145 quick"},
146 );
147 }
148
149 #[gpui::test]
150 async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
151 let cx = VimTestContext::new(cx, true).await;
152 let mut cx = cx.binding(["shift-$"]);
153 cx.assert("T|est test", "Test tes|t");
154 cx.assert("Test tes|t", "Test tes|t");
155 cx.assert(
156 indoc! {"
157 The |quick
158 brown"},
159 indoc! {"
160 The quic|k
161 brown"},
162 );
163 cx.assert(
164 indoc! {"
165 The quic|k
166 brown"},
167 indoc! {"
168 The quic|k
169 brown"},
170 );
171
172 let mut cx = cx.binding(["0"]);
173 cx.assert("Test |test", "|Test test");
174 cx.assert("|Test test", "|Test test");
175 cx.assert(
176 indoc! {"
177 The |quick
178 brown"},
179 indoc! {"
180 |The quick
181 brown"},
182 );
183 cx.assert(
184 indoc! {"
185 |The quick
186 brown"},
187 indoc! {"
188 |The quick
189 brown"},
190 );
191 }
192
193 #[gpui::test]
194 async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
195 let cx = VimTestContext::new(cx, true).await;
196 let mut cx = cx.binding(["shift-G"]);
197
198 cx.assert(
199 indoc! {"
200 The |quick
201
202 brown fox jumps
203 over the lazy dog"},
204 indoc! {"
205 The quick
206
207 brown fox jumps
208 over| the lazy dog"},
209 );
210 cx.assert(
211 indoc! {"
212 The quick
213
214 brown fox jumps
215 over| the lazy dog"},
216 indoc! {"
217 The quick
218
219 brown fox jumps
220 over| the lazy dog"},
221 );
222 cx.assert(
223 indoc! {"
224 The qui|ck
225
226 brown"},
227 indoc! {"
228 The quick
229
230 brow|n"},
231 );
232 cx.assert(
233 indoc! {"
234 The qui|ck
235
236 "},
237 indoc! {"
238 The quick
239
240 |"},
241 );
242 }
243
244 #[gpui::test]
245 async fn test_next_word_start(cx: &mut gpui::TestAppContext) {
246 let mut cx = VimTestContext::new(cx, true).await;
247 let (_, cursor_offsets) = marked_text(indoc! {"
248 The |quick|-|brown
249 |
250 |
251 |fox_jumps |over
252 |th||e"});
253 cx.set_state(
254 indoc! {"
255 |The quick-brown
256
257
258 fox_jumps over
259 the"},
260 Mode::Normal,
261 );
262
263 for cursor_offset in cursor_offsets {
264 cx.simulate_keystroke("w");
265 cx.assert_newest_selection_head_offset(cursor_offset);
266 }
267
268 // Reset and test ignoring punctuation
269 let (_, cursor_offsets) = marked_text(indoc! {"
270 The |quick-brown
271 |
272 |
273 |fox_jumps |over
274 |th||e"});
275 cx.set_state(
276 indoc! {"
277 |The quick-brown
278
279
280 fox_jumps over
281 the"},
282 Mode::Normal,
283 );
284
285 for cursor_offset in cursor_offsets {
286 cx.simulate_keystroke("shift-W");
287 cx.assert_newest_selection_head_offset(cursor_offset);
288 }
289 }
290
291 #[gpui::test]
292 async fn test_next_word_end(cx: &mut gpui::TestAppContext) {
293 let mut cx = VimTestContext::new(cx, true).await;
294 let (_, cursor_offsets) = marked_text(indoc! {"
295 Th|e quic|k|-brow|n
296
297
298 fox_jump|s ove|r
299 th|e"});
300 cx.set_state(
301 indoc! {"
302 |The quick-brown
303
304
305 fox_jumps over
306 the"},
307 Mode::Normal,
308 );
309
310 for cursor_offset in cursor_offsets {
311 cx.simulate_keystroke("e");
312 cx.assert_newest_selection_head_offset(cursor_offset);
313 }
314
315 // Reset and test ignoring punctuation
316 let (_, cursor_offsets) = marked_text(indoc! {"
317 Th|e quick-brow|n
318
319
320 fox_jump|s ove|r
321 th||e"});
322 cx.set_state(
323 indoc! {"
324 |The quick-brown
325
326
327 fox_jumps over
328 the"},
329 Mode::Normal,
330 );
331 for cursor_offset in cursor_offsets {
332 cx.simulate_keystroke("shift-E");
333 cx.assert_newest_selection_head_offset(cursor_offset);
334 }
335 }
336
337 #[gpui::test]
338 async fn test_previous_word_start(cx: &mut gpui::TestAppContext) {
339 let mut cx = VimTestContext::new(cx, true).await;
340 let (_, cursor_offsets) = marked_text(indoc! {"
341 ||The |quick|-|brown
342 |
343 |
344 |fox_jumps |over
345 |the"});
346 cx.set_state(
347 indoc! {"
348 The quick-brown
349
350
351 fox_jumps over
352 th|e"},
353 Mode::Normal,
354 );
355
356 for cursor_offset in cursor_offsets.into_iter().rev() {
357 cx.simulate_keystroke("b");
358 cx.assert_newest_selection_head_offset(cursor_offset);
359 }
360
361 // Reset and test ignoring punctuation
362 let (_, cursor_offsets) = marked_text(indoc! {"
363 ||The |quick-brown
364 |
365 |
366 |fox_jumps |over
367 |the"});
368 cx.set_state(
369 indoc! {"
370 The quick-brown
371
372
373 fox_jumps over
374 th|e"},
375 Mode::Normal,
376 );
377 for cursor_offset in cursor_offsets.into_iter().rev() {
378 cx.simulate_keystroke("shift-B");
379 cx.assert_newest_selection_head_offset(cursor_offset);
380 }
381 }
382
383 #[gpui::test]
384 async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) {
385 let mut cx = VimTestContext::new(cx, true).await;
386
387 // Can abort with escape to get back to normal mode
388 cx.simulate_keystroke("g");
389 assert_eq!(cx.mode(), Normal);
390 assert_eq!(
391 cx.active_operator(),
392 Some(Operator::Namespace(Namespace::G))
393 );
394 cx.simulate_keystroke("escape");
395 assert_eq!(cx.mode(), Normal);
396 assert_eq!(cx.active_operator(), None);
397 }
398
399 #[gpui::test]
400 async fn test_move_to_start(cx: &mut gpui::TestAppContext) {
401 let cx = VimTestContext::new(cx, true).await;
402 let mut cx = cx.binding(["g", "g"]);
403 cx.assert(
404 indoc! {"
405 The quick
406
407 brown fox jumps
408 over |the lazy dog"},
409 indoc! {"
410 The q|uick
411
412 brown fox jumps
413 over the lazy dog"},
414 );
415 cx.assert(
416 indoc! {"
417 The q|uick
418
419 brown fox jumps
420 over the lazy dog"},
421 indoc! {"
422 The q|uick
423
424 brown fox jumps
425 over the lazy dog"},
426 );
427 cx.assert(
428 indoc! {"
429 The quick
430
431 brown fox jumps
432 over the la|zy dog"},
433 indoc! {"
434 The quic|k
435
436 brown fox jumps
437 over the lazy dog"},
438 );
439 cx.assert(
440 indoc! {"
441
442
443 brown fox jumps
444 over the la|zy dog"},
445 indoc! {"
446 |
447
448 brown fox jumps
449 over the lazy dog"},
450 );
451 }
452}