vim: Add g M motion to go to the middle of a line (#30227)

Adds the "g M" vim motion to go to the middle of the line.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Alex Shen 2025-05-16 14:21:30 -07:00 committed by GitHub
parent c7725e31d9
commit d791c6cdb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 146 additions and 0 deletions

View file

@ -152,6 +152,7 @@
"g end": ["vim::EndOfLine", { "display_lines": true }],
"g 0": ["vim::StartOfLine", { "display_lines": true }],
"g home": ["vim::StartOfLine", { "display_lines": true }],
"g shift-m": ["vim::MiddleOfLine", { "display_lines": true }],
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
"g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic",

View file

@ -84,6 +84,9 @@ pub enum Motion {
StartOfLine {
display_lines: bool,
},
MiddleOfLine {
display_lines: bool,
},
EndOfLine {
display_lines: bool,
},
@ -265,6 +268,13 @@ pub struct StartOfLine {
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct MiddleOfLine {
#[serde(default)]
display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct UnmatchedForward {
@ -283,6 +293,7 @@ impl_actions!(
vim,
[
StartOfLine,
MiddleOfLine,
EndOfLine,
FirstNonWhitespace,
Down,
@ -409,6 +420,15 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
cx,
)
});
Vim::action(editor, cx, |vim, action: &MiddleOfLine, window, cx| {
vim.motion(
Motion::MiddleOfLine {
display_lines: action.display_lines,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &EndOfLine, window, cx| {
vim.motion(
Motion::EndOfLine {
@ -737,6 +757,7 @@ impl Motion {
| SentenceBackward
| SentenceForward
| GoToColumn
| MiddleOfLine { .. }
| UnmatchedForward { .. }
| UnmatchedBackward { .. }
| NextWordStart { .. }
@ -769,6 +790,7 @@ impl Motion {
Down { .. }
| Up { .. }
| EndOfLine { .. }
| MiddleOfLine { .. }
| Matching
| UnmatchedForward { .. }
| UnmatchedBackward { .. }
@ -894,6 +916,10 @@ impl Motion {
start_of_line(map, *display_lines, point),
SelectionGoal::None,
),
MiddleOfLine { display_lines } => (
middle_of_line(map, *display_lines, point, maybe_times),
SelectionGoal::None,
),
EndOfLine { display_lines } => (
end_of_line(map, *display_lines, point, times),
SelectionGoal::None,
@ -1944,6 +1970,36 @@ pub(crate) fn start_of_line(
}
}
pub(crate) fn middle_of_line(
map: &DisplaySnapshot,
display_lines: bool,
point: DisplayPoint,
times: Option<usize>,
) -> DisplayPoint {
let percent = if let Some(times) = times.filter(|&t| t <= 100) {
times as f64 / 100.
} else {
0.5
};
if display_lines {
map.clip_point(
DisplayPoint::new(
point.row(),
(map.line_len(point.row()) as f64 * percent) as u32,
),
Bias::Left,
)
} else {
let mut buffer_point = point.to_point(map);
buffer_point.column = (map
.buffer_snapshot
.line_len(MultiBufferRow(buffer_point.row)) as f64
* percent) as u32;
map.clip_point(buffer_point.to_display_point(map), Bias::Left)
}
}
pub(crate) fn end_of_line(
map: &DisplaySnapshot,
display_lines: bool,
@ -3906,6 +3962,61 @@ mod test {
assert_eq!(cx.cx.forced_motion(), false);
}
#[gpui::test]
async fn test_forced_motion_delete_to_middle_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 g shift-m").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇ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 g shift-m").await;
cx.shared_state().await.assert_eq(indoc! {"
the quickˇ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 g shift-m").await;
cx.shared_state().await.assert_eq(indoc! {"
the quicˇk
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 7 5 g shift-m").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇ fox
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 2 3 g shift-m").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇuick brown fox
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;

View file

@ -0,0 +1,34 @@
{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
{"Key":"d"}
{"Key":"v"}
{"Key":"g"}
{"Key":"shift-m"}
{"Get":{"state":"ˇ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":"g"}
{"Key":"shift-m"}
{"Get":{"state":"the quickˇ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":"g"}
{"Key":"shift-m"}
{"Get":{"state":"the quicˇk\njumped over the lazy dog","mode":"Normal"}}
{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
{"Key":"d"}
{"Key":"v"}
{"Key":"7"}
{"Key":"5"}
{"Key":"g"}
{"Key":"shift-m"}
{"Get":{"state":"ˇ fox\njumped over the lazy dog","mode":"Normal"}}
{"Put":{"state":"ˇthe quick brown fox\njumped over the lazy dog"}}
{"Key":"d"}
{"Key":"v"}
{"Key":"2"}
{"Key":"3"}
{"Key":"g"}
{"Key":"shift-m"}
{"Get":{"state":"ˇuick brown fox\njumped over the lazy dog","mode":"Normal"}}