Release Notes:

- vim: Fix `gi` when the insert ended at the end of a line (#12162)
- vim: Add `gv` to restore previous visual selection (#12888)
- vim: Fix `gl` when the first match is at the end of a line
This commit is contained in:
Conrad Irwin 2024-06-14 10:16:59 -06:00 committed by GitHub
parent 3539a7c04a
commit 3b84b106e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 239 additions and 49 deletions

View file

@ -5,7 +5,7 @@ use editor::{
display_map::{DisplaySnapshot, ToDisplayPoint},
movement,
scroll::Autoscroll,
Bias, DisplayPoint, Editor,
Bias, DisplayPoint, Editor, ToOffset,
};
use gpui::{actions, ViewContext, WindowContext};
use language::{Point, Selection, SelectionGoal};
@ -16,8 +16,8 @@ use workspace::{searchable::Direction, Workspace};
use crate::{
motion::{start_of_line, Motion},
normal::substitute::substitute,
normal::yank::{copy_selections_content, yank_selections_content},
normal::{mark::create_visual_marks, substitute::substitute},
object::Object,
state::{Mode, Operator},
Vim,
@ -37,6 +37,7 @@ actions!(
SelectPrevious,
SelectNextMatch,
SelectPreviousMatch,
RestoreVisualSelection,
]
);
@ -83,6 +84,52 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
select_match(workspace, vim, Direction::Prev, cx);
});
});
workspace.register_action(|_, _: &RestoreVisualSelection, cx| {
Vim::update(cx, |vim, cx| {
let Some((stored_mode, reversed)) =
vim.update_state(|state| state.stored_visual_mode.take())
else {
return;
};
let Some((start, end)) = vim.state().marks.get("<").zip(vim.state().marks.get(">"))
else {
return;
};
let ranges = start
.into_iter()
.zip(end)
.zip(reversed)
.map(|((start, end), reversed)| (*start, *end, reversed))
.collect::<Vec<_>>();
if vim.state().mode.is_visual() {
create_visual_marks(vim, vim.state().mode, cx);
}
vim.update_active_editor(cx, |_, editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
let map = s.display_map();
let ranges = ranges
.into_iter()
.map(|(start, end, reversed)| {
let new_end =
movement::saturating_right(&map, end.to_display_point(&map));
Selection {
id: s.new_selection_id(),
start: start.to_offset(&map.buffer_snapshot),
end: new_end.to_offset(&map, Bias::Left),
reversed,
goal: SelectionGoal::None,
}
})
.collect();
s.select(ranges);
})
});
vim.switch_mode(stored_mode, true, cx)
});
});
}
pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
@ -483,6 +530,7 @@ pub fn select_next(_: &mut Workspace, _: &SelectNext, cx: &mut ViewContext<Works
vim.take_count(cx)
.unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
vim.update_active_editor(cx, |_, editor, cx| {
editor.set_clip_at_line_ends(false, cx);
for _ in 0..count {
if editor
.select_next(&Default::default(), cx)
@ -1166,6 +1214,17 @@ mod test {
cx.shared_state().await.assert_eq("«ˇaa aa» aa aa aa");
}
#[gpui::test]
async fn test_gl(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("aaˇ aa\naa", Mode::Normal);
cx.simulate_keystrokes("g l");
cx.assert_state("«aaˇ» «aaˇ»\naa", Mode::Visual);
cx.simulate_keystrokes("g >");
cx.assert_state("«aaˇ» aa\n«aaˇ»", Mode::Visual);
}
#[gpui::test]
async fn test_dgn_repeat(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
@ -1247,4 +1306,56 @@ mod test {
"
});
}
#[gpui::test]
async fn test_gv(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
"The ˇquick brown"
})
.await;
cx.simulate_shared_keystrokes("v i w escape g v").await;
cx.shared_state().await.assert_eq(indoc! {
"The «quickˇ» brown"
});
cx.simulate_shared_keystrokes("o escape g v").await;
cx.shared_state().await.assert_eq(indoc! {
"The «ˇquick» brown"
});
cx.simulate_shared_keystrokes("escape ^ ctrl-v l").await;
cx.shared_state().await.assert_eq(indoc! {
"«Thˇ»e quick brown"
});
cx.simulate_shared_keystrokes("g v").await;
cx.shared_state().await.assert_eq(indoc! {
"The «ˇquick» brown"
});
cx.simulate_shared_keystrokes("g v").await;
cx.shared_state().await.assert_eq(indoc! {
"«Thˇ»e quick brown"
});
cx.set_state(
indoc! {"
fiˇsh one
fish two
fish red
fish blue
"},
Mode::Normal,
);
cx.simulate_keystrokes("4 g l escape escape g v");
cx.assert_state(
indoc! {"
«fishˇ» one
«fishˇ» two
«fishˇ» red
«fishˇ» blue
"},
Mode::Visual,
);
}
}