use crate::{motion::Motion, utils::copy_selections_content, Vim}; use collections::HashMap; use editor::{Autoscroll, Bias}; use gpui::MutableAppContext; pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_columns: HashMap<_, _> = Default::default(); editor.change_selections(Some(Autoscroll::Fit), cx, |s| { s.move_with(|map, selection| { let original_head = selection.head(); motion.expand_selection(map, selection, true); original_columns.insert(selection.id, original_head.column()); }); }); copy_selections_content(editor, motion.linewise(), cx); editor.insert(&"", cx); // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); editor.change_selections(Some(Autoscroll::Fit), cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if motion.linewise() { if let Some(column) = original_columns.get(&selection.id) { *cursor.column_mut() = *column } } cursor = map.clip_point(cursor, Bias::Left); selection.collapse_to(cursor, selection.goal) }); }); }); }); } #[cfg(test)] mod test { use indoc::indoc; use crate::{state::Mode, vim_test_context::VimTestContext}; #[gpui::test] async fn test_delete_h(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "h"]); cx.assert("Teˇst", "Tˇst"); cx.assert("Tˇest", "ˇest"); cx.assert("ˇTest", "ˇTest"); cx.assert( indoc! {" Test ˇtest"}, indoc! {" Test ˇtest"}, ); } #[gpui::test] async fn test_delete_l(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "l"]); cx.assert("ˇTest", "ˇest"); cx.assert("Teˇst", "Teˇt"); cx.assert("Tesˇt", "Teˇs"); cx.assert( indoc! {" Tesˇt test"}, indoc! {" Teˇs test"}, ); } #[gpui::test] async fn test_delete_w(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "w"]); cx.assert("Teˇst", "Tˇe"); cx.assert("Tˇest test", "Tˇtest"); cx.assert( indoc! {" Test teˇst test"}, indoc! {" Test tˇe test"}, ); cx.assert( indoc! {" Test tesˇt test"}, indoc! {" Test teˇs test"}, ); cx.assert( indoc! {" Test test ˇ test"}, indoc! {" Test test ˇ test"}, ); let mut cx = cx.binding(["d", "shift-w"]); cx.assert("Test teˇst-test test", "Test teˇtest"); } #[gpui::test] async fn test_delete_e(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "e"]); cx.assert("Teˇst Test", "Teˇ Test"); cx.assert("Tˇest test", "Tˇ test"); cx.assert( indoc! {" Test teˇst test"}, indoc! {" Test tˇe test"}, ); cx.assert( indoc! {" Test tesˇt test"}, "Test teˇs", ); cx.assert( indoc! {" Test test ˇ test"}, indoc! {" Test test ˇ test"}, ); let mut cx = cx.binding(["d", "shift-e"]); cx.assert("Test teˇst-test test", "Test teˇ test"); } #[gpui::test] async fn test_delete_b(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "b"]); cx.assert("Teˇst Test", "ˇst Test"); cx.assert("Test ˇtest", "ˇtest"); cx.assert("Test1 test2 ˇtest3", "Test1 ˇtest3"); cx.assert( indoc! {" Test test ˇtest"}, // Trailing whitespace after cursor indoc! {" Testˇ test"}, ); cx.assert( indoc! {" Test test ˇ test"}, // Trailing whitespace after cursor indoc! {" Testˇ test"}, ); let mut cx = cx.binding(["d", "shift-b"]); cx.assert("Test test-test ˇtest", "Test ˇtest"); } #[gpui::test] async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "$"]); cx.assert( indoc! {" The qˇuick brown fox"}, indoc! {" The ˇq brown fox"}, ); cx.assert( indoc! {" The quick ˇ brown fox"}, indoc! {" The quick ˇ brown fox"}, ); } #[gpui::test] async fn test_delete_0(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "0"]); cx.assert( indoc! {" The qˇuick brown fox"}, indoc! {" ˇuick brown fox"}, ); cx.assert( indoc! {" The quick ˇ brown fox"}, indoc! {" The quick ˇ brown fox"}, ); } #[gpui::test] async fn test_delete_k(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "k"]); cx.assert( indoc! {" The quick brown ˇfox jumps over"}, "jumps ˇover", ); cx.assert( indoc! {" The quick brown fox jumps ˇover"}, "The quˇick", ); cx.assert( indoc! {" The qˇuick brown fox jumps over"}, indoc! {" brownˇ fox jumps over"}, ); cx.assert( indoc! {" ˇbrown fox jumps over"}, "ˇjumps over", ); } #[gpui::test] async fn test_delete_j(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "j"]); cx.assert( indoc! {" The quick brown ˇfox jumps over"}, "The quˇick", ); cx.assert( indoc! {" The quick brown fox jumps ˇover"}, indoc! {" The quick brown ˇfox"}, ); cx.assert( indoc! {" The qˇuick brown fox jumps over"}, "jumpsˇ over", ); cx.assert( indoc! {" The quick brown fox ˇ"}, indoc! {" The quick ˇbrown fox"}, ); } #[gpui::test] async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "shift-g"]); cx.assert( indoc! {" The quick brownˇ fox jumps over the lazy"}, "The qˇuick", ); cx.assert( indoc! {" The quick brownˇ fox jumps over the lazy"}, "The qˇuick", ); cx.assert( indoc! {" The quick brown fox jumps over the lˇazy"}, indoc! {" The quick brown fox jumpsˇ over"}, ); cx.assert( indoc! {" The quick brown fox jumps over ˇ"}, indoc! {" The quick brown fox ˇjumps over"}, ); } #[gpui::test] async fn test_delete_gg(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; let mut cx = cx.binding(["d", "g", "g"]); cx.assert( indoc! {" The quick brownˇ fox jumps over the lazy"}, indoc! {" jumpsˇ over the lazy"}, ); cx.assert( indoc! {" The quick brown fox jumps over the lˇazy"}, "ˇ", ); cx.assert( indoc! {" The qˇuick brown fox jumps over the lazy"}, indoc! {" brownˇ fox jumps over the lazy"}, ); cx.assert( indoc! {" ˇ brown fox jumps over the lazy"}, indoc! {" ˇbrown fox jumps over the lazy"}, ); } #[gpui::test] async fn test_cancel_delete_operator(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state( indoc! {" The quick brown fox juˇmps over the lazy dog"}, Mode::Normal, ); // Canceling operator twice reverts to normal mode with no active operator cx.simulate_keystrokes(["d", "escape", "k"]); assert_eq!(cx.active_operator(), None); assert_eq!(cx.mode(), Mode::Normal); cx.assert_editor_state(indoc! {" The quˇick brown fox jumps over the lazy dog"}); } #[gpui::test] async fn test_unbound_command_cancels_pending_operator(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; cx.set_state( indoc! {" The quick brown fox juˇmps over the lazy dog"}, Mode::Normal, ); // Canceling operator twice reverts to normal mode with no active operator cx.simulate_keystrokes(["d", "y"]); assert_eq!(cx.active_operator(), None); assert_eq!(cx.mode(), Mode::Normal); } }