1use crate::{motion::Motion, utils::copy_selections_content, Vim};
2use collections::HashMap;
3use editor::{Autoscroll, Bias};
4use gpui::MutableAppContext;
5
6pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
7 vim.update_active_editor(cx, |editor, cx| {
8 editor.transact(cx, |editor, cx| {
9 editor.set_clip_at_line_ends(false, cx);
10 let mut original_columns: HashMap<_, _> = Default::default();
11 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
12 s.move_with(|map, selection| {
13 let original_head = selection.head();
14 motion.expand_selection(map, selection, true);
15 original_columns.insert(selection.id, original_head.column());
16 });
17 });
18 copy_selections_content(editor, motion.linewise(), cx);
19 editor.insert("", cx);
20
21 // Fixup cursor position after the deletion
22 editor.set_clip_at_line_ends(true, cx);
23 editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
24 s.move_with(|map, selection| {
25 let mut cursor = selection.head();
26 if motion.linewise() {
27 if let Some(column) = original_columns.get(&selection.id) {
28 *cursor.column_mut() = *column
29 }
30 }
31 cursor = map.clip_point(cursor, Bias::Left);
32 selection.collapse_to(cursor, selection.goal)
33 });
34 });
35 });
36 });
37}
38
39#[cfg(test)]
40mod test {
41 use indoc::indoc;
42
43 use crate::{state::Mode, vim_test_context::VimTestContext};
44
45 #[gpui::test]
46 async fn test_delete_h(cx: &mut gpui::TestAppContext) {
47 let cx = VimTestContext::new(cx, true).await;
48 let mut cx = cx.binding(["d", "h"]);
49 cx.assert("Teˇst", "Tˇst");
50 cx.assert("Tˇest", "ˇest");
51 cx.assert("ˇTest", "ˇTest");
52 cx.assert(
53 indoc! {"
54 Test
55 ˇtest"},
56 indoc! {"
57 Test
58 ˇtest"},
59 );
60 }
61
62 #[gpui::test]
63 async fn test_delete_l(cx: &mut gpui::TestAppContext) {
64 let cx = VimTestContext::new(cx, true).await;
65 let mut cx = cx.binding(["d", "l"]);
66 cx.assert("ˇTest", "ˇest");
67 cx.assert("Teˇst", "Teˇt");
68 cx.assert("Tesˇt", "Teˇs");
69 cx.assert(
70 indoc! {"
71 Tesˇt
72 test"},
73 indoc! {"
74 Teˇs
75 test"},
76 );
77 }
78
79 #[gpui::test]
80 async fn test_delete_w(cx: &mut gpui::TestAppContext) {
81 let cx = VimTestContext::new(cx, true).await;
82 let mut cx = cx.binding(["d", "w"]);
83 cx.assert("Teˇst", "Tˇe");
84 cx.assert("Tˇest test", "Tˇtest");
85 cx.assert(
86 indoc! {"
87 Test teˇst
88 test"},
89 indoc! {"
90 Test tˇe
91 test"},
92 );
93 cx.assert(
94 indoc! {"
95 Test tesˇt
96 test"},
97 indoc! {"
98 Test teˇs
99 test"},
100 );
101 cx.assert(
102 indoc! {"
103 Test test
104 ˇ
105 test"},
106 indoc! {"
107 Test test
108 ˇ
109 test"},
110 );
111
112 let mut cx = cx.binding(["d", "shift-w"]);
113 cx.assert("Test teˇst-test test", "Test teˇtest");
114 }
115
116 #[gpui::test]
117 async fn test_delete_e(cx: &mut gpui::TestAppContext) {
118 let cx = VimTestContext::new(cx, true).await;
119 let mut cx = cx.binding(["d", "e"]);
120 cx.assert("Teˇst Test", "Teˇ Test");
121 cx.assert("Tˇest test", "Tˇ test");
122 cx.assert(
123 indoc! {"
124 Test teˇst
125 test"},
126 indoc! {"
127 Test tˇe
128 test"},
129 );
130 cx.assert(
131 indoc! {"
132 Test tesˇt
133 test"},
134 "Test teˇs",
135 );
136 cx.assert(
137 indoc! {"
138 Test test
139 ˇ
140 test"},
141 indoc! {"
142 Test test
143 ˇ
144 test"},
145 );
146
147 let mut cx = cx.binding(["d", "shift-e"]);
148 cx.assert("Test teˇst-test test", "Test teˇ test");
149 }
150
151 #[gpui::test]
152 async fn test_delete_b(cx: &mut gpui::TestAppContext) {
153 let cx = VimTestContext::new(cx, true).await;
154 let mut cx = cx.binding(["d", "b"]);
155 cx.assert("Teˇst Test", "ˇst Test");
156 cx.assert("Test ˇtest", "ˇtest");
157 cx.assert("Test1 test2 ˇtest3", "Test1 ˇtest3");
158 cx.assert(
159 indoc! {"
160 Test test
161 ˇtest"},
162 // Trailing whitespace after cursor
163 indoc! {"
164 Testˇ
165 test"},
166 );
167 cx.assert(
168 indoc! {"
169 Test test
170 ˇ
171 test"},
172 // Trailing whitespace after cursor
173 indoc! {"
174 Testˇ
175
176 test"},
177 );
178
179 let mut cx = cx.binding(["d", "shift-b"]);
180 cx.assert("Test test-test ˇtest", "Test ˇtest");
181 }
182
183 #[gpui::test]
184 async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
185 let cx = VimTestContext::new(cx, true).await;
186 let mut cx = cx.binding(["d", "$"]);
187 cx.assert(
188 indoc! {"
189 The qˇuick
190 brown fox"},
191 indoc! {"
192 The ˇq
193 brown fox"},
194 );
195 cx.assert(
196 indoc! {"
197 The quick
198 ˇ
199 brown fox"},
200 indoc! {"
201 The quick
202 ˇ
203 brown fox"},
204 );
205 }
206
207 #[gpui::test]
208 async fn test_delete_0(cx: &mut gpui::TestAppContext) {
209 let cx = VimTestContext::new(cx, true).await;
210 let mut cx = cx.binding(["d", "0"]);
211 cx.assert(
212 indoc! {"
213 The qˇuick
214 brown fox"},
215 indoc! {"
216 ˇuick
217 brown fox"},
218 );
219 cx.assert(
220 indoc! {"
221 The quick
222 ˇ
223 brown fox"},
224 indoc! {"
225 The quick
226 ˇ
227 brown fox"},
228 );
229 }
230
231 #[gpui::test]
232 async fn test_delete_k(cx: &mut gpui::TestAppContext) {
233 let cx = VimTestContext::new(cx, true).await;
234 let mut cx = cx.binding(["d", "k"]);
235 cx.assert(
236 indoc! {"
237 The quick
238 brown ˇfox
239 jumps over"},
240 "jumps ˇover",
241 );
242 cx.assert(
243 indoc! {"
244 The quick
245 brown fox
246 jumps ˇover"},
247 "The quˇick",
248 );
249 cx.assert(
250 indoc! {"
251 The qˇuick
252 brown fox
253 jumps over"},
254 indoc! {"
255 brownˇ fox
256 jumps over"},
257 );
258 cx.assert(
259 indoc! {"
260 ˇbrown fox
261 jumps over"},
262 "ˇjumps over",
263 );
264 }
265
266 #[gpui::test]
267 async fn test_delete_j(cx: &mut gpui::TestAppContext) {
268 let cx = VimTestContext::new(cx, true).await;
269 let mut cx = cx.binding(["d", "j"]);
270 cx.assert(
271 indoc! {"
272 The quick
273 brown ˇfox
274 jumps over"},
275 "The quˇick",
276 );
277 cx.assert(
278 indoc! {"
279 The quick
280 brown fox
281 jumps ˇover"},
282 indoc! {"
283 The quick
284 brown ˇfox"},
285 );
286 cx.assert(
287 indoc! {"
288 The qˇuick
289 brown fox
290 jumps over"},
291 "jumpsˇ over",
292 );
293 cx.assert(
294 indoc! {"
295 The quick
296 brown fox
297 ˇ"},
298 indoc! {"
299 The quick
300 ˇbrown fox"},
301 );
302 }
303
304 #[gpui::test]
305 async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
306 let cx = VimTestContext::new(cx, true).await;
307 let mut cx = cx.binding(["d", "shift-g"]);
308 cx.assert(
309 indoc! {"
310 The quick
311 brownˇ fox
312 jumps over
313 the lazy"},
314 "The qˇuick",
315 );
316 cx.assert(
317 indoc! {"
318 The quick
319 brownˇ fox
320 jumps over
321 the lazy"},
322 "The qˇuick",
323 );
324 cx.assert(
325 indoc! {"
326 The quick
327 brown fox
328 jumps over
329 the lˇazy"},
330 indoc! {"
331 The quick
332 brown fox
333 jumpsˇ over"},
334 );
335 cx.assert(
336 indoc! {"
337 The quick
338 brown fox
339 jumps over
340 ˇ"},
341 indoc! {"
342 The quick
343 brown fox
344 ˇjumps over"},
345 );
346 }
347
348 #[gpui::test]
349 async fn test_delete_gg(cx: &mut gpui::TestAppContext) {
350 let cx = VimTestContext::new(cx, true).await;
351 let mut cx = cx.binding(["d", "g", "g"]);
352 cx.assert(
353 indoc! {"
354 The quick
355 brownˇ fox
356 jumps over
357 the lazy"},
358 indoc! {"
359 jumpsˇ over
360 the lazy"},
361 );
362 cx.assert(
363 indoc! {"
364 The quick
365 brown fox
366 jumps over
367 the lˇazy"},
368 "ˇ",
369 );
370 cx.assert(
371 indoc! {"
372 The qˇuick
373 brown fox
374 jumps over
375 the lazy"},
376 indoc! {"
377 brownˇ fox
378 jumps over
379 the lazy"},
380 );
381 cx.assert(
382 indoc! {"
383 ˇ
384 brown fox
385 jumps over
386 the lazy"},
387 indoc! {"
388 ˇbrown fox
389 jumps over
390 the lazy"},
391 );
392 }
393
394 #[gpui::test]
395 async fn test_cancel_delete_operator(cx: &mut gpui::TestAppContext) {
396 let mut cx = VimTestContext::new(cx, true).await;
397 cx.set_state(
398 indoc! {"
399 The quick brown
400 fox juˇmps over
401 the lazy dog"},
402 Mode::Normal,
403 );
404
405 // Canceling operator twice reverts to normal mode with no active operator
406 cx.simulate_keystrokes(["d", "escape", "k"]);
407 assert_eq!(cx.active_operator(), None);
408 assert_eq!(cx.mode(), Mode::Normal);
409 cx.assert_editor_state(indoc! {"
410 The quˇick brown
411 fox jumps over
412 the lazy dog"});
413 }
414
415 #[gpui::test]
416 async fn test_unbound_command_cancels_pending_operator(cx: &mut gpui::TestAppContext) {
417 let mut cx = VimTestContext::new(cx, true).await;
418 cx.set_state(
419 indoc! {"
420 The quick brown
421 fox juˇmps over
422 the lazy dog"},
423 Mode::Normal,
424 );
425
426 // Canceling operator twice reverts to normal mode with no active operator
427 cx.simulate_keystrokes(["d", "y"]);
428 assert_eq!(cx.active_operator(), None);
429 assert_eq!(cx.mode(), Mode::Normal);
430 }
431}