Detailed changes
@@ -539,6 +539,7 @@
"bindings": {
"d": "vim::CurrentLine",
"s": "vim::PushDeleteSurrounds",
+ "v": "vim::PushForcedMotion", // "d v"
"o": "editor::ToggleSelectedDiffHunks", // "d o"
"shift-o": "git::ToggleStaged",
"p": "git::Restore", // "d p"
@@ -587,6 +588,7 @@
"context": "vim_operator == y",
"bindings": {
"y": "vim::CurrentLine",
+ "v": "vim::PushForcedMotion",
"s": ["vim::PushAddSurrounds", {}]
}
},
@@ -24,6 +24,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
if self.change_list.is_empty() {
return;
}
@@ -234,6 +234,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
return;
};
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
let n = if count > 1 {
format!(".,.+{}", count.saturating_sub(1))
} else {
@@ -1323,6 +1324,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Vim>,
) {
@@ -1335,7 +1337,13 @@ impl Vim {
let start = editor.selections.newest_display(cx);
let text_layout_details = editor.text_layout_details(window);
let (mut range, _) = motion
- .range(&snapshot, start.clone(), times, &text_layout_details)
+ .range(
+ &snapshot,
+ start.clone(),
+ times,
+ &text_layout_details,
+ forced_motion,
+ )
.unwrap_or((start.range(), MotionKind::Exclusive));
if range.start != start.start {
editor.change_selections(None, window, cx, |s| {
@@ -18,6 +18,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &Indent, window, cx| {
vim.record_current_action(cx);
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
vim.store_visual_marks(window, cx);
vim.update_editor(window, cx, |vim, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
@@ -36,6 +37,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &Outdent, window, cx| {
vim.record_current_action(cx);
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
vim.store_visual_marks(window, cx);
vim.update_editor(window, cx, |vim, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
@@ -54,6 +56,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &AutoIndent, window, cx| {
vim.record_current_action(cx);
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
vim.store_visual_marks(window, cx);
vim.update_editor(window, cx, |vim, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
@@ -75,6 +78,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
dir: IndentDirection,
window: &mut Window,
cx: &mut Context<Self>,
@@ -88,7 +92,13 @@ impl Vim {
s.move_with(|map, selection| {
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, &text_layout_details);
+ motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
});
});
match dir {
@@ -23,6 +23,7 @@ impl Vim {
return;
}
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
self.stop_recording_immediately(action.boxed_clone(), cx);
if count <= 1 || Vim::globals(cx).dot_replaying {
self.create_mark("^".into(), window, cx);
@@ -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);
+ }
}
@@ -86,12 +86,14 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &DeleteLeft, window, cx| {
vim.record_current_action(cx);
let times = Vim::take_count(cx);
- vim.delete_motion(Motion::Left, times, window, cx);
+ let forced_motion = Vim::take_forced_motion(cx);
+ vim.delete_motion(Motion::Left, times, forced_motion, window, cx);
});
Vim::action(editor, cx, |vim, _: &DeleteRight, window, cx| {
vim.record_current_action(cx);
let times = Vim::take_count(cx);
- vim.delete_motion(Motion::Right, times, window, cx);
+ let forced_motion = Vim::take_forced_motion(cx);
+ vim.delete_motion(Motion::Right, times, forced_motion, window, cx);
});
Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| {
@@ -111,11 +113,13 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, window, cx| {
vim.start_recording(cx);
let times = Vim::take_count(cx);
+ let forced_motion = Vim::take_forced_motion(cx);
vim.change_motion(
Motion::EndOfLine {
display_lines: false,
},
times,
+ forced_motion,
window,
cx,
);
@@ -123,11 +127,13 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &DeleteToEndOfLine, window, cx| {
vim.record_current_action(cx);
let times = Vim::take_count(cx);
+ let forced_motion = Vim::take_forced_motion(cx);
vim.delete_motion(
Motion::EndOfLine {
display_lines: false,
},
times,
+ forced_motion,
window,
cx,
);
@@ -142,6 +148,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &Undo, window, cx| {
let times = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
vim.update_editor(window, cx, |_, editor, window, cx| {
for _ in 0..times.unwrap_or(1) {
editor.undo(&editor::actions::Undo, window, cx);
@@ -150,6 +157,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
});
Vim::action(editor, cx, |vim, _: &Redo, window, cx| {
let times = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
vim.update_editor(window, cx, |_, editor, window, cx| {
for _ in 0..times.unwrap_or(1) {
editor.redo(&editor::actions::Redo, window, cx);
@@ -170,48 +178,93 @@ impl Vim {
motion: Motion,
operator: Option<Operator>,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
match operator {
None => self.move_cursor(motion, times, window, cx),
- Some(Operator::Change) => self.change_motion(motion, times, window, cx),
- Some(Operator::Delete) => self.delete_motion(motion, times, window, cx),
- Some(Operator::Yank) => self.yank_motion(motion, times, window, cx),
+ Some(Operator::Change) => self.change_motion(motion, times, forced_motion, window, cx),
+ Some(Operator::Delete) => self.delete_motion(motion, times, forced_motion, window, cx),
+ Some(Operator::Yank) => self.yank_motion(motion, times, forced_motion, window, cx),
Some(Operator::AddSurrounds { target: None }) => {}
- Some(Operator::Indent) => {
- self.indent_motion(motion, times, IndentDirection::In, window, cx)
- }
- Some(Operator::Rewrap) => self.rewrap_motion(motion, times, window, cx),
- Some(Operator::Outdent) => {
- self.indent_motion(motion, times, IndentDirection::Out, window, cx)
- }
- Some(Operator::AutoIndent) => {
- self.indent_motion(motion, times, IndentDirection::Auto, window, cx)
- }
- Some(Operator::ShellCommand) => self.shell_command_motion(motion, times, window, cx),
- Some(Operator::Lowercase) => {
- self.convert_motion(motion, times, ConvertTarget::LowerCase, window, cx)
- }
- Some(Operator::Uppercase) => {
- self.convert_motion(motion, times, ConvertTarget::UpperCase, window, cx)
- }
- Some(Operator::OppositeCase) => {
- self.convert_motion(motion, times, ConvertTarget::OppositeCase, window, cx)
- }
- Some(Operator::Rot13) => {
- self.convert_motion(motion, times, ConvertTarget::Rot13, window, cx)
- }
- Some(Operator::Rot47) => {
- self.convert_motion(motion, times, ConvertTarget::Rot47, window, cx)
+ Some(Operator::Indent) => self.indent_motion(
+ motion,
+ times,
+ forced_motion,
+ IndentDirection::In,
+ window,
+ cx,
+ ),
+ Some(Operator::Rewrap) => self.rewrap_motion(motion, times, forced_motion, window, cx),
+ Some(Operator::Outdent) => self.indent_motion(
+ motion,
+ times,
+ forced_motion,
+ IndentDirection::Out,
+ window,
+ cx,
+ ),
+ Some(Operator::AutoIndent) => self.indent_motion(
+ motion,
+ times,
+ forced_motion,
+ IndentDirection::Auto,
+ window,
+ cx,
+ ),
+ Some(Operator::ShellCommand) => {
+ self.shell_command_motion(motion, times, forced_motion, window, cx)
}
+ Some(Operator::Lowercase) => self.convert_motion(
+ motion,
+ times,
+ forced_motion,
+ ConvertTarget::LowerCase,
+ window,
+ cx,
+ ),
+ Some(Operator::Uppercase) => self.convert_motion(
+ motion,
+ times,
+ forced_motion,
+ ConvertTarget::UpperCase,
+ window,
+ cx,
+ ),
+ Some(Operator::OppositeCase) => self.convert_motion(
+ motion,
+ times,
+ forced_motion,
+ ConvertTarget::OppositeCase,
+ window,
+ cx,
+ ),
+ Some(Operator::Rot13) => self.convert_motion(
+ motion,
+ times,
+ forced_motion,
+ ConvertTarget::Rot13,
+ window,
+ cx,
+ ),
+ Some(Operator::Rot47) => self.convert_motion(
+ motion,
+ times,
+ forced_motion,
+ ConvertTarget::Rot47,
+ window,
+ cx,
+ ),
Some(Operator::ToggleComments) => {
- self.toggle_comments_motion(motion, times, window, cx)
+ self.toggle_comments_motion(motion, times, forced_motion, window, cx)
}
Some(Operator::ReplaceWithRegister) => {
- self.replace_with_register_motion(motion, times, window, cx)
+ self.replace_with_register_motion(motion, times, forced_motion, window, cx)
+ }
+ Some(Operator::Exchange) => {
+ self.exchange_motion(motion, times, forced_motion, window, cx)
}
- Some(Operator::Exchange) => self.exchange_motion(motion, times, window, cx),
Some(operator) => {
// Can't do anything for text objects, Ignoring
error!("Unexpected normal mode motion operator: {:?}", operator)
@@ -492,6 +545,7 @@ impl Vim {
) {
self.record_current_action(cx);
let mut times = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
if self.mode.is_visual() {
times = 1;
} else if times > 1 {
@@ -513,11 +567,19 @@ impl Vim {
fn yank_line(&mut self, _: &YankLine, window: &mut Window, cx: &mut Context<Self>) {
let count = Vim::take_count(cx);
- self.yank_motion(motion::Motion::CurrentLine, count, window, cx)
+ let forced_motion = Vim::take_forced_motion(cx);
+ self.yank_motion(
+ motion::Motion::CurrentLine,
+ count,
+ forced_motion,
+ window,
+ cx,
+ )
}
fn show_location(&mut self, _: &ShowLocation, window: &mut Window, cx: &mut Context<Self>) {
let count = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
self.update_editor(window, cx, |vim, editor, _window, cx| {
let selection = editor.selections.newest_anchor();
if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
@@ -577,6 +639,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
self.stop_recording(cx);
self.update_editor(window, cx, |_, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
@@ -18,6 +18,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -59,6 +60,7 @@ impl Vim {
selection,
times,
&text_layout_details,
+ forced_motion,
);
if let Motion::CurrentLine = motion {
let mut start_offset =
@@ -181,7 +183,7 @@ fn expand_changed_word_selection(
} else {
Motion::NextWordStart { ignore_punctuation }
};
- motion.expand_selection(map, selection, times, text_layout_details)
+ motion.expand_selection(map, selection, times, text_layout_details, false)
}
}
@@ -25,6 +25,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
mode: ConvertTarget,
window: &mut Window,
cx: &mut Context<Self>,
@@ -39,7 +40,13 @@ impl Vim {
s.move_with(|map, selection| {
let anchor = map.display_point_to_anchor(selection.head(), Bias::Left);
selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, &text_layout_details);
+ motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
});
});
match mode {
@@ -185,6 +192,7 @@ impl Vim {
self.record_current_action(cx);
self.store_visual_marks(window, cx);
let count = Vim::take_count(cx).unwrap_or(1) as u32;
+ Vim::take_forced_motion(cx);
self.update_editor(window, cx, |vim, editor, window, cx| {
let mut ranges = Vec::new();
@@ -18,6 +18,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -33,9 +34,13 @@ impl Vim {
s.move_with(|map, selection| {
let original_head = selection.head();
original_columns.insert(selection.id, original_head.column());
- let kind =
- motion.expand_selection(map, selection, times, &text_layout_details);
-
+ let kind = motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
ranges_to_copy
.push(selection.start.to_point(map)..selection.end.to_point(map));
@@ -29,12 +29,14 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, action: &Increment, window, cx| {
vim.record_current_action(cx);
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
let step = if action.step { count as i32 } else { 0 };
vim.increment(count as i64, step, window, cx)
});
Vim::action(editor, cx, |vim, action: &Decrement, window, cx| {
vim.record_current_action(cx);
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
let step = if action.step { -1 * (count as i32) } else { 0 };
vim.increment(-(count as i64), step, window, cx)
});
@@ -28,6 +28,7 @@ impl Vim {
self.record_current_action(cx);
self.store_visual_marks(window, cx);
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
self.update_editor(window, cx, |vim, editor, window, cx| {
let text_layout_details = editor.text_layout_details(window);
@@ -247,6 +248,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -258,7 +260,13 @@ impl Vim {
editor.set_clip_at_line_ends(false, cx);
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
- motion.expand_selection(map, selection, times, &text_layout_details);
+ motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
});
});
@@ -170,6 +170,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
let mut count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
self.clear_operator(window, cx);
let globals = Vim::globals(cx);
@@ -201,6 +202,7 @@ impl Vim {
cx: &mut Context<Self>,
) {
let count = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
let Some((mut actions, selection, mode)) = Vim::update_globals(cx, |globals, _| {
let actions = globals.recorded_actions.clone();
@@ -55,6 +55,7 @@ impl Vim {
by: fn(c: Option<f32>) -> ScrollAmount,
) {
let amount = by(Vim::take_count(cx).map(|c| c as f32));
+ Vim::take_forced_motion(cx);
self.update_editor(window, cx, |_, editor, window, cx| {
scroll_editor(editor, move_cursor, &amount, window, cx)
});
@@ -138,6 +138,7 @@ impl Vim {
Direction::Next
};
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
let prior_selections = self.editor_selections(window, cx);
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
@@ -261,6 +262,7 @@ impl Vim {
return;
};
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
let prior_selections = self.editor_selections(window, cx);
let success = pane.update(cx, |pane, cx| {
@@ -303,6 +305,7 @@ impl Vim {
return;
};
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
let prior_selections = self.editor_selections(window, cx);
let cursor_word = self.editor_cursor_word(window, cx);
let vim = cx.entity().clone();
@@ -13,6 +13,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &Substitute, window, cx| {
vim.start_recording(cx);
let count = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
vim.substitute(count, vim.mode == Mode::VisualLine, window, cx);
});
@@ -22,6 +23,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
vim.switch_mode(Mode::VisualLine, false, window, cx)
}
let count = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
vim.substitute(count, true, window, cx)
});
}
@@ -47,6 +49,7 @@ impl Vim {
selection,
count,
&text_layout_details,
+ false,
);
}
if line_mode {
@@ -60,6 +63,7 @@ impl Vim {
selection,
None,
&text_layout_details,
+ false,
);
if let Some((point, _)) = (Motion::FirstNonWhitespace {
display_lines: false,
@@ -9,6 +9,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -21,7 +22,13 @@ impl Vim {
s.move_with(|map, selection| {
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, &text_layout_details);
+ motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
});
});
editor.toggle_comments(&Default::default(), window, cx);
@@ -21,6 +21,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -33,8 +34,19 @@ impl Vim {
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
let original_position = (selection.head(), selection.goal);
- original_positions.insert(selection.id, original_position);
- kind = motion.expand_selection(map, selection, times, &text_layout_details);
+ kind = motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
+ if kind == Some(MotionKind::Exclusive) {
+ original_positions
+ .insert(selection.id, (selection.start, selection.goal));
+ } else {
+ original_positions.insert(selection.id, original_position);
+ }
})
});
let Some(kind) = kind else { return };
@@ -27,6 +27,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
return;
}
let count = Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
vim.undo_replace(count, window, cx)
});
}
@@ -179,6 +180,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -188,7 +190,13 @@ impl Vim {
let text_layout_details = editor.text_layout_details(window);
let mut selection = editor.selections.newest_display(cx);
let snapshot = editor.snapshot(window, cx);
- motion.expand_selection(&snapshot, &mut selection, times, &text_layout_details);
+ motion.expand_selection(
+ &snapshot,
+ &mut selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
let start = snapshot
.buffer_snapshot
.anchor_before(selection.start.to_point(&snapshot));
@@ -10,6 +10,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &Rewrap, window, cx| {
vim.record_current_action(cx);
Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
vim.store_visual_marks(window, cx);
vim.update_editor(window, cx, |vim, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
@@ -43,6 +44,7 @@ impl Vim {
&mut self,
motion: Motion,
times: Option<usize>,
+ forced_motion: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -55,7 +57,13 @@ impl Vim {
s.move_with(|map, selection| {
let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
selection_starts.insert(selection.id, anchor);
- motion.expand_selection(map, selection, times, &text_layout_details);
+ motion.expand_selection(
+ map,
+ selection,
+ times,
+ &text_layout_details,
+ forced_motion,
+ );
});
});
editor.rewrap_impl(
@@ -202,7 +202,7 @@ pub struct VimGlobals {
pub pre_count: Option<usize>,
/// post_count is the number after an operator is specified (2 in 3d2d)
pub post_count: Option<usize>,
-
+ pub forced_motion: bool,
pub stop_recording_after_next_action: bool,
pub ignore_current_insertion: bool,
pub recorded_count: Option<usize>,
@@ -27,6 +27,7 @@ impl Vim {
) {
self.stop_recording(cx);
let count = Vim::take_count(cx);
+ let forced_motion = Vim::take_forced_motion(cx);
let mode = self.mode;
self.update_editor(window, cx, |_, editor, window, cx| {
let text_layout_details = editor.text_layout_details(window);
@@ -55,7 +56,13 @@ impl Vim {
}
SurroundsType::Motion(motion) => {
motion
- .range(&display_map, selection.clone(), count, &text_layout_details)
+ .range(
+ &display_map,
+ selection.clone(),
+ count,
+ &text_layout_details,
+ forced_motion,
+ )
.map(|(mut range, _)| {
// The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace
if let Motion::CurrentLine = motion {
@@ -13,7 +13,7 @@ use super::{VimTestContext, neovim_connection::NeovimConnection};
use crate::state::{Mode, VimGlobals};
pub struct NeovimBackedTestContext {
- cx: VimTestContext,
+ pub(crate) cx: VimTestContext,
pub(crate) neovim: NeovimConnection,
last_set_state: Option<String>,
@@ -142,6 +142,10 @@ impl VimTestContext {
self.update_editor(|editor, _, cx| editor.addon::<VimAddon>().unwrap().entity.read(cx).mode)
}
+ pub fn forced_motion(&mut self) -> bool {
+ self.update_editor(|_, _, cx| cx.global::<VimGlobals>().forced_motion)
+ }
+
pub fn active_operator(&mut self) -> Option<Operator> {
self.update_editor(|editor, _, cx| {
editor
@@ -145,6 +145,7 @@ actions!(
PushDeleteSurrounds,
PushMark,
ToggleMarksView,
+ PushForcedMotion,
PushIndent,
PushOutdent,
PushAutoIndent,
@@ -233,6 +234,7 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
+ Vim::take_forced_motion(cx);
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
return;
@@ -248,6 +250,7 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
+ Vim::take_forced_motion(cx);
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
return;
@@ -263,6 +266,7 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
+ Vim::take_forced_motion(cx);
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, height * count, window, cx);
@@ -270,6 +274,7 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
+ Vim::take_forced_motion(cx);
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
@@ -472,7 +477,9 @@ impl Vim {
vim.switch_mode(Mode::HelixNormal, false, window, cx)
},
);
-
+ Vim::action(editor, cx, |_, _: &PushForcedMotion, _, cx| {
+ Vim::globals(cx).forced_motion = true;
+ });
Vim::action(editor, cx, |vim, action: &PushObject, window, cx| {
vim.push_operator(
Operator::Object {
@@ -907,6 +914,7 @@ impl Vim {
self.current_tx.take();
self.current_anchor.take();
}
+ Vim::take_forced_motion(cx);
if mode != Mode::Insert && mode != Mode::Replace {
Vim::take_count(cx);
}
@@ -1011,6 +1019,13 @@ impl Vim {
count
}
+ pub fn take_forced_motion(cx: &mut App) -> bool {
+ let global_state = cx.global_mut::<VimGlobals>();
+ let forced_motion = global_state.forced_motion;
+ global_state.forced_motion = false;
+ forced_motion
+ }
+
pub fn cursor_shape(&self, cx: &mut App) -> CursorShape {
match self.mode {
Mode::Normal => {
@@ -1372,6 +1387,7 @@ impl Vim {
fn clear_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) {
Vim::take_count(cx);
+ Vim::take_forced_motion(cx);
self.selected_register.take();
self.operator_stack.clear();
self.sync_vim_settings(window, cx);
@@ -85,6 +85,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, _: &SelectLargerSyntaxNode, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
for _ in 0..count {
vim.update_editor(window, cx, |_, editor, window, cx| {
editor.select_larger_syntax_node(&Default::default(), window, cx);
@@ -97,6 +98,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
cx,
|vim, _: &SelectSmallerSyntaxNode, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1);
+ Vim::take_forced_motion(cx);
for _ in 0..count {
vim.update_editor(window, cx, |_, editor, window, cx| {
editor.select_smaller_syntax_node(&Default::default(), window, cx);
@@ -682,6 +684,7 @@ impl Vim {
}
pub fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) {
+ Vim::take_forced_motion(cx);
let count =
Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
self.update_editor(window, cx, |_, editor, window, cx| {
@@ -704,6 +707,7 @@ impl Vim {
window: &mut Window,
cx: &mut Context<Self>,
) {
+ Vim::take_forced_motion(cx);
let count =
Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
self.update_editor(window, cx, |_, editor, window, cx| {
@@ -725,6 +729,7 @@ impl Vim {
window: &mut Window,
cx: &mut Context<Self>,
) {
+ Vim::take_forced_motion(cx);
let count = Vim::take_count(cx).unwrap_or(1);
let Some(pane) = self.pane(window, cx) else {
return;
@@ -0,0 +1,10 @@
+{"Put":{"state":"the quick brown foˇx\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"$"}
+{"Get":{"state":"the quick brown foˇx\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"$"}
+{"Get":{"state":"ˇx\njumped over the lazy dog","mode":"Normal"}}
@@ -0,0 +1,15 @@
+{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"0"}
+{"Get":{"state":"ˇhe quick brown fox\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick bˇrown fox\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"0"}
+{"Get":{"state":"ˇown fox\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick brown foˇx\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"0"}
+{"Get":{"state":"ˇ\njumped over the lazy dog","mode":"Normal"}}
@@ -0,0 +1,24 @@
+{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
+{"Key":"y"}
+{"Key":"v"}
+{"Key":"j"}
+{"Key":"p"}
+{"Get":{"state":"the quick brown fox\nˇthe quick brown fox\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick bˇrown fox\njumped over the lazy dog"}}
+{"Key":"y"}
+{"Key":"v"}
+{"Key":"j"}
+{"Key":"p"}
+{"Get":{"state":"the quick brˇrown fox\njumped overown fox\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick brown foˇx\njumped over the lazy dog"}}
+{"Key":"y"}
+{"Key":"v"}
+{"Key":"j"}
+{"Key":"p"}
+{"Get":{"state":"the quick brown foxˇx\njumped over the la\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick brown fox\njˇumped over the lazy dog"}}
+{"Key":"y"}
+{"Key":"v"}
+{"Key":"k"}
+{"Key":"p"}
+{"Get":{"state":"thˇhe quick brown fox\nje quick brown fox\njumped over the lazy dog","mode":"Normal"}}
@@ -0,0 +1,15 @@
+{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"e"}
+{"Get":{"state":"ˇe quick brown fox\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick bˇrown fox\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"e"}
+{"Get":{"state":"the quick bˇn fox\njumped over the lazy dog","mode":"Normal"}}
+{"Put":{"state":"the quick brown foˇx\njumped over the lazy dog"}}
+{"Key":"d"}
+{"Key":"v"}
+{"Key":"e"}
+{"Get":{"state":"the quick brown foˇd over the lazy dog","mode":"Normal"}}