vim: Add some forced motion support (#27991)
Closes https://github.com/zed-industries/zed/issues/20971 Added `v` input to yank and delete to override default motion. The global vim state tracking if the forced motion flag was passed handled the same way that the count is. [The main chunk of code maps the motion kind from the default to the overridden kind](https://github.com/zed-industries/zed/pull/27991/files#diff-2dca6b7d1673c912d14e4edc74e415abbe3a4e6d6b37e0e2006d30828bf4bb9cR1249-R1254). To handle the case of deleting a single character (dv0) at the start of a row I had to modify the control flow [here](https://github.com/zed-industries/zed/pull/27991/files#diff-2dca6b7d1673c912d14e4edc74e415abbe3a4e6d6b37e0e2006d30828bf4bb9cR1240-R1244). Then to handle an exclusive delete till the end of the row (dv$) I [saturated the endpoint with a left bias](https://github.com/zed-industries/zed/pull/27991/files#diff-2dca6b7d1673c912d14e4edc74e415abbe3a4e6d6b37e0e2006d30828bf4bb9cR1281-R1286). Test case: dv0 https://github.com/user-attachments/assets/613cf9fb-9732-425c-9179-025f3e107584 Test case: yvjp https://github.com/user-attachments/assets/550b7c77-1eb8-41c3-894b-117eb50b7a5d Release Notes: - Added some forced motion support for delete and yank
This commit is contained in:
parent
1df01eabfe
commit
08ce230bae
30 changed files with 485 additions and 58 deletions
|
@ -650,6 +650,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
let count = Vim::take_count(cx);
|
||||
let forced_motion = Vim::take_forced_motion(cx);
|
||||
let active_operator = self.active_operator();
|
||||
let mut waiting_operator: Option<Operator> = None;
|
||||
match self.mode {
|
||||
|
@ -659,7 +660,14 @@ impl Vim {
|
|||
target: Some(SurroundsType::Motion(motion)),
|
||||
});
|
||||
} else {
|
||||
self.normal_motion(motion.clone(), active_operator.clone(), count, window, cx)
|
||||
self.normal_motion(
|
||||
motion.clone(),
|
||||
active_operator.clone(),
|
||||
count,
|
||||
forced_motion,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
|
@ -1183,7 +1191,6 @@ impl Motion {
|
|||
SelectionGoal::None,
|
||||
),
|
||||
};
|
||||
|
||||
(new_point != point || infallible).then_some((new_point, goal))
|
||||
}
|
||||
|
||||
|
@ -1194,6 +1201,7 @@ impl Motion {
|
|||
selection: Selection<DisplayPoint>,
|
||||
times: Option<usize>,
|
||||
text_layout_details: &TextLayoutDetails,
|
||||
forced_motion: bool,
|
||||
) -> Option<(Range<DisplayPoint>, MotionKind)> {
|
||||
if let Motion::ZedSearchResult {
|
||||
prior_selections,
|
||||
|
@ -1221,18 +1229,29 @@ impl Motion {
|
|||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let (new_head, goal) = self.move_point(
|
||||
let maybe_new_point = self.move_point(
|
||||
map,
|
||||
selection.head(),
|
||||
selection.goal,
|
||||
times,
|
||||
text_layout_details,
|
||||
)?;
|
||||
);
|
||||
|
||||
let (new_head, goal) = match (maybe_new_point, forced_motion) {
|
||||
(Some((p, g)), _) => Some((p, g)),
|
||||
(None, false) => None,
|
||||
(None, true) => Some((selection.head(), selection.goal)),
|
||||
}?;
|
||||
|
||||
let mut selection = selection.clone();
|
||||
selection.set_head(new_head, goal);
|
||||
|
||||
let mut kind = self.default_kind();
|
||||
let mut kind = match (self.default_kind(), forced_motion) {
|
||||
(MotionKind::Linewise, true) => MotionKind::Exclusive,
|
||||
(MotionKind::Exclusive, true) => MotionKind::Inclusive,
|
||||
(MotionKind::Inclusive, true) => MotionKind::Exclusive,
|
||||
(kind, false) => kind,
|
||||
};
|
||||
|
||||
if let Motion::NextWordStart {
|
||||
ignore_punctuation: _,
|
||||
|
@ -1259,6 +1278,12 @@ impl Motion {
|
|||
} else if kind == MotionKind::Exclusive && !self.skip_exclusive_special_case() {
|
||||
let start_point = selection.start.to_point(map);
|
||||
let mut end_point = selection.end.to_point(map);
|
||||
let mut next_point = selection.end;
|
||||
*next_point.column_mut() += 1;
|
||||
next_point = map.clip_point(next_point, Bias::Right);
|
||||
if next_point.to_point(map) == end_point && forced_motion {
|
||||
selection.end = movement::saturating_left(map, selection.end);
|
||||
}
|
||||
|
||||
if end_point.row > start_point.row {
|
||||
let first_non_blank_of_start_row = map
|
||||
|
@ -1304,8 +1329,15 @@ impl Motion {
|
|||
selection: &mut Selection<DisplayPoint>,
|
||||
times: Option<usize>,
|
||||
text_layout_details: &TextLayoutDetails,
|
||||
forced_motion: bool,
|
||||
) -> Option<MotionKind> {
|
||||
let (range, kind) = self.range(map, selection.clone(), times, text_layout_details)?;
|
||||
let (range, kind) = self.range(
|
||||
map,
|
||||
selection.clone(),
|
||||
times,
|
||||
text_layout_details,
|
||||
forced_motion,
|
||||
)?;
|
||||
selection.start = range.start;
|
||||
selection.end = range.end;
|
||||
Some(kind)
|
||||
|
@ -3816,6 +3848,7 @@ mod test {
|
|||
Mode::Normal,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_key_can_remove_last_character(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
@ -3823,4 +3856,147 @@ mod test {
|
|||
cx.simulate_shared_keystrokes("delete").await;
|
||||
cx.shared_state().await.assert_eq("aˇb");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_forced_motion_delete_to_start_of_line(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
ˇthe quick brown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v 0").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
ˇhe quick brown fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick bˇrown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v 0").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
ˇown fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick brown foˇx
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v 0").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
ˇ
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_forced_motion_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick brown foˇx
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v $").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
the quick brown foˇx
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
ˇthe quick brown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v $").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
ˇx
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_forced_motion_yank(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
ˇthe quick brown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("y v j p").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
the quick brown fox
|
||||
ˇthe quick brown fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick bˇrown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("y v j p").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
the quick brˇrown fox
|
||||
jumped overown fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick brown foˇx
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("y v j p").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
the quick brown foxˇx
|
||||
jumped over the la
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick brown fox
|
||||
jˇumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("y v k p").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
thˇhe quick brown fox
|
||||
je quick brown fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_inclusive_to_exclusive_delete(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
ˇthe quick brown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v e").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
ˇe quick brown fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick bˇrown fox
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v e").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
the quick bˇn fox
|
||||
jumped over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
the quick brown foˇx
|
||||
jumped over the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("d v e").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
the quick brown foˇd over the lazy dog"});
|
||||
assert_eq!(cx.cx.forced_motion(), false);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue