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