mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 22:53:14 +03:00
vim: Allow search with operators & visual mode (#10226)
Fixes: #4346 Release Notes: - vim: Add search motions (`/,?,n,N,*,#`) in visual modes and as targets for operators like `d`,`c`,`y` ([#4346](https://github.com/zed-industries/zed/issues/4346)).
This commit is contained in:
parent
f9bf60f017
commit
f327118e06
@ -73,8 +73,17 @@
|
|||||||
],
|
],
|
||||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||||
|
|
||||||
"n": "search::SelectNextMatch",
|
"/": "vim::Search",
|
||||||
"shift-n": "search::SelectPrevMatch",
|
"?": [
|
||||||
|
"vim::Search",
|
||||||
|
{
|
||||||
|
"backwards": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"*": "vim::MoveToNext",
|
||||||
|
"#": "vim::MoveToPrev",
|
||||||
|
"n": "vim::MoveToNextMatch",
|
||||||
|
"shift-n": "vim::MoveToPrevMatch",
|
||||||
"%": "vim::Matching",
|
"%": "vim::Matching",
|
||||||
"f": [
|
"f": [
|
||||||
"vim::PushOperator",
|
"vim::PushOperator",
|
||||||
@ -351,15 +360,6 @@
|
|||||||
],
|
],
|
||||||
"u": "editor::Undo",
|
"u": "editor::Undo",
|
||||||
"ctrl-r": "editor::Redo",
|
"ctrl-r": "editor::Redo",
|
||||||
"/": "vim::Search",
|
|
||||||
"?": [
|
|
||||||
"vim::Search",
|
|
||||||
{
|
|
||||||
"backwards": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"*": "vim::MoveToNext",
|
|
||||||
"#": "vim::MoveToPrev",
|
|
||||||
"r": ["vim::PushOperator", "Replace"],
|
"r": ["vim::PushOperator", "Replace"],
|
||||||
"s": "vim::Substitute",
|
"s": "vim::Substitute",
|
||||||
"shift-s": "vim::SubstituteLine",
|
"shift-s": "vim::SubstituteLine",
|
||||||
|
@ -48,7 +48,6 @@ fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
|
|||||||
.upgrade()
|
.upgrade()
|
||||||
.is_some_and(|previous| previous == editor.clone())
|
.is_some_and(|previous| previous == editor.clone())
|
||||||
{
|
{
|
||||||
vim.sync_vim_settings(cx);
|
|
||||||
vim.clear_operator(cx);
|
vim.clear_operator(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ use editor::{
|
|||||||
movement::{
|
movement::{
|
||||||
self, find_boundary, find_preceding_boundary_display_point, FindRange, TextLayoutDetails,
|
self, find_boundary, find_preceding_boundary_display_point, FindRange, TextLayoutDetails,
|
||||||
},
|
},
|
||||||
Bias, DisplayPoint, ToOffset,
|
scroll::Autoscroll,
|
||||||
|
Anchor, Bias, DisplayPoint, ToOffset,
|
||||||
};
|
};
|
||||||
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
|
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
|
||||||
use language::{char_kind, CharKind, Point, Selection, SelectionGoal};
|
use language::{char_kind, CharKind, Point, Selection, SelectionGoal};
|
||||||
@ -20,7 +21,7 @@ use crate::{
|
|||||||
Vim,
|
Vim,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Motion {
|
pub enum Motion {
|
||||||
Left,
|
Left,
|
||||||
Backspace,
|
Backspace,
|
||||||
@ -96,6 +97,14 @@ pub enum Motion {
|
|||||||
WindowTop,
|
WindowTop,
|
||||||
WindowMiddle,
|
WindowMiddle,
|
||||||
WindowBottom,
|
WindowBottom,
|
||||||
|
|
||||||
|
// we don't have a good way to run a search syncronously, so
|
||||||
|
// we handle search motions by running the search async and then
|
||||||
|
// calling back into motion with this
|
||||||
|
ZedSearchResult {
|
||||||
|
prior_selections: Vec<Range<Anchor>>,
|
||||||
|
new_selections: Vec<Range<Anchor>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
@ -379,6 +388,34 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn search_motion(m: Motion, cx: &mut WindowContext) {
|
||||||
|
if let Motion::ZedSearchResult {
|
||||||
|
prior_selections, ..
|
||||||
|
} = &m
|
||||||
|
{
|
||||||
|
match Vim::read(cx).state().mode {
|
||||||
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||||
|
if !prior_selections.is_empty() {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
vim.update_active_editor(cx, |_, editor, cx| {
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.select_ranges(prior_selections.iter().cloned())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::Normal | Mode::Replace | Mode::Insert => {
|
||||||
|
if Vim::read(cx).active_operator().is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
motion(m, cx)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
|
pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
|
||||||
if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
|
if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
|
||||||
Vim::read(cx).active_operator()
|
Vim::read(cx).active_operator()
|
||||||
@ -453,7 +490,8 @@ impl Motion {
|
|||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. }
|
| FindBackward { .. }
|
||||||
| RepeatFind { .. }
|
| RepeatFind { .. }
|
||||||
| RepeatFindReversed { .. } => false,
|
| RepeatFindReversed { .. }
|
||||||
|
| ZedSearchResult { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,7 +529,8 @@ impl Motion {
|
|||||||
| WindowTop
|
| WindowTop
|
||||||
| WindowMiddle
|
| WindowMiddle
|
||||||
| WindowBottom
|
| WindowBottom
|
||||||
| NextLineStart => false,
|
| NextLineStart
|
||||||
|
| ZedSearchResult { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,7 +568,8 @@ impl Motion {
|
|||||||
| NextSubwordStart { .. }
|
| NextSubwordStart { .. }
|
||||||
| PreviousSubwordStart { .. }
|
| PreviousSubwordStart { .. }
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. } => false,
|
| FindBackward { .. }
|
||||||
|
| ZedSearchResult { .. } => false,
|
||||||
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
||||||
motion.inclusive()
|
motion.inclusive()
|
||||||
}
|
}
|
||||||
@ -720,6 +760,18 @@ impl Motion {
|
|||||||
WindowTop => window_top(map, point, &text_layout_details, times - 1),
|
WindowTop => window_top(map, point, &text_layout_details, times - 1),
|
||||||
WindowMiddle => window_middle(map, point, &text_layout_details),
|
WindowMiddle => window_middle(map, point, &text_layout_details),
|
||||||
WindowBottom => window_bottom(map, point, &text_layout_details, times - 1),
|
WindowBottom => window_bottom(map, point, &text_layout_details, times - 1),
|
||||||
|
ZedSearchResult { new_selections, .. } => {
|
||||||
|
// There will be only one selection, as
|
||||||
|
// Search::SelectNextMatch selects a single match.
|
||||||
|
if let Some(new_selection) = new_selections.first() {
|
||||||
|
(
|
||||||
|
new_selection.start.to_display_point(map),
|
||||||
|
SelectionGoal::None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(new_point != point || infallible).then_some((new_point, goal))
|
(new_point != point || infallible).then_some((new_point, goal))
|
||||||
@ -734,6 +786,33 @@ impl Motion {
|
|||||||
expand_to_surrounding_newline: bool,
|
expand_to_surrounding_newline: bool,
|
||||||
text_layout_details: &TextLayoutDetails,
|
text_layout_details: &TextLayoutDetails,
|
||||||
) -> Option<Range<DisplayPoint>> {
|
) -> Option<Range<DisplayPoint>> {
|
||||||
|
if let Motion::ZedSearchResult {
|
||||||
|
prior_selections,
|
||||||
|
new_selections,
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
if let Some((prior_selection, new_selection)) =
|
||||||
|
prior_selections.first().zip(new_selections.first())
|
||||||
|
{
|
||||||
|
let start = prior_selection
|
||||||
|
.start
|
||||||
|
.to_display_point(map)
|
||||||
|
.min(new_selection.start.to_display_point(map));
|
||||||
|
let end = new_selection
|
||||||
|
.end
|
||||||
|
.to_display_point(map)
|
||||||
|
.max(prior_selection.end.to_display_point(map));
|
||||||
|
|
||||||
|
if start < end {
|
||||||
|
return Some(start..end);
|
||||||
|
} else {
|
||||||
|
return Some(end..start);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((new_head, goal)) = self.move_point(
|
if let Some((new_head, goal)) = self.move_point(
|
||||||
map,
|
map,
|
||||||
selection.head(),
|
selection.head(),
|
||||||
|
@ -4,7 +4,7 @@ use serde_derive::Deserialize;
|
|||||||
use workspace::{searchable::Direction, Workspace};
|
use workspace::{searchable::Direction, Workspace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::Motion,
|
motion::{search_motion, Motion},
|
||||||
normal::move_cursor,
|
normal::move_cursor,
|
||||||
state::{Mode, SearchState},
|
state::{Mode, SearchState},
|
||||||
Vim,
|
Vim,
|
||||||
@ -49,7 +49,7 @@ struct Replacement {
|
|||||||
is_case_sensitive: bool,
|
is_case_sensitive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(vim, [SearchSubmit]);
|
actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPrevMatch]);
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
vim,
|
vim,
|
||||||
[FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
|
[FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
|
||||||
@ -58,6 +58,8 @@ impl_actions!(
|
|||||||
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||||
workspace.register_action(move_to_next);
|
workspace.register_action(move_to_next);
|
||||||
workspace.register_action(move_to_prev);
|
workspace.register_action(move_to_prev);
|
||||||
|
workspace.register_action(move_to_next_match);
|
||||||
|
workspace.register_action(move_to_prev_match);
|
||||||
workspace.register_action(search);
|
workspace.register_action(search);
|
||||||
workspace.register_action(search_submit);
|
workspace.register_action(search_submit);
|
||||||
workspace.register_action(search_deploy);
|
workspace.register_action(search_deploy);
|
||||||
@ -74,6 +76,22 @@ fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewCon
|
|||||||
move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
|
move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_to_next_match(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_: &MoveToNextMatch,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
move_to_match_internal(workspace, Direction::Next, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_to_prev_match(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_: &MoveToPrevMatch,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
move_to_match_internal(workspace, Direction::Prev, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
|
fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
|
||||||
let pane = workspace.active_pane().clone();
|
let pane = workspace.active_pane().clone();
|
||||||
let direction = if action.backwards {
|
let direction = if action.backwards {
|
||||||
@ -83,6 +101,7 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
|
|||||||
};
|
};
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let count = vim.take_count(cx).unwrap_or(1);
|
let count = vim.take_count(cx).unwrap_or(1);
|
||||||
|
let prior_selections = vim.editor_selections(cx);
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||||
search_bar.update(cx, |search_bar, cx| {
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
@ -102,6 +121,9 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
|
|||||||
direction,
|
direction,
|
||||||
count,
|
count,
|
||||||
initial_query: query.clone(),
|
initial_query: query.clone(),
|
||||||
|
prior_selections,
|
||||||
|
prior_operator: vim.active_operator(),
|
||||||
|
prior_mode: vim.state().mode,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -116,6 +138,7 @@ fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
|
fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
|
||||||
|
let mut motion = None;
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let pane = workspace.active_pane().clone();
|
let pane = workspace.active_pane().clone();
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
@ -135,10 +158,60 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
|
|||||||
state.count = 1;
|
state.count = 1;
|
||||||
search_bar.select_match(direction, count, cx);
|
search_bar.select_match(direction, count, cx);
|
||||||
search_bar.focus_editor(&Default::default(), cx);
|
search_bar.focus_editor(&Default::default(), cx);
|
||||||
|
|
||||||
|
let prior_selections = state.prior_selections.drain(..).collect();
|
||||||
|
let prior_mode = state.prior_mode;
|
||||||
|
let prior_operator = state.prior_operator.take();
|
||||||
|
let new_selections = vim.editor_selections(cx);
|
||||||
|
|
||||||
|
if prior_mode != vim.state().mode {
|
||||||
|
vim.switch_mode(prior_mode, true, cx);
|
||||||
|
}
|
||||||
|
if let Some(operator) = prior_operator {
|
||||||
|
vim.push_operator(operator, cx);
|
||||||
|
};
|
||||||
|
motion = Some(Motion::ZedSearchResult {
|
||||||
|
prior_selections,
|
||||||
|
new_selections,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if let Some(motion) = motion {
|
||||||
|
search_motion(motion, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_to_match_internal(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
direction: Direction,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
let mut motion = None;
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
let pane = workspace.active_pane().clone();
|
||||||
|
let count = vim.take_count(cx).unwrap_or(1);
|
||||||
|
let prior_selections = vim.editor_selections(cx);
|
||||||
|
|
||||||
|
pane.update(cx, |pane, cx| {
|
||||||
|
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||||
|
search_bar.update(cx, |search_bar, cx| {
|
||||||
|
search_bar.select_match(direction, count, cx);
|
||||||
|
|
||||||
|
let new_selections = vim.editor_selections(cx);
|
||||||
|
motion = Some(Motion::ZedSearchResult {
|
||||||
|
prior_selections,
|
||||||
|
new_selections,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if let Some(motion) = motion {
|
||||||
|
search_motion(motion, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_to_internal(
|
pub fn move_to_internal(
|
||||||
@ -150,6 +223,7 @@ pub fn move_to_internal(
|
|||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let pane = workspace.active_pane().clone();
|
let pane = workspace.active_pane().clone();
|
||||||
let count = vim.take_count(cx).unwrap_or(1);
|
let count = vim.take_count(cx).unwrap_or(1);
|
||||||
|
let prior_selections = vim.editor_selections(cx);
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||||
@ -159,6 +233,8 @@ pub fn move_to_internal(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Some(query) = search_bar.query_suggestion(cx) else {
|
let Some(query) = search_bar.query_suggestion(cx) else {
|
||||||
|
vim.clear_operator(cx);
|
||||||
|
let _ = search_bar.search("", None, cx);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let mut query = regex::escape(&query);
|
let mut query = regex::escape(&query);
|
||||||
@ -174,7 +250,17 @@ pub fn move_to_internal(
|
|||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
search.await?;
|
search.await?;
|
||||||
search_bar.update(&mut cx, |search_bar, cx| {
|
search_bar.update(&mut cx, |search_bar, cx| {
|
||||||
search_bar.select_match(direction, count, cx)
|
search_bar.select_match(direction, count, cx);
|
||||||
|
|
||||||
|
let new_selections =
|
||||||
|
Vim::update(cx, |vim, cx| vim.editor_selections(cx));
|
||||||
|
search_motion(
|
||||||
|
Motion::ZedSearchResult {
|
||||||
|
prior_selections,
|
||||||
|
new_selections,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
@ -186,8 +272,6 @@ pub fn move_to_internal(
|
|||||||
if vim.state().mode.is_visual() {
|
if vim.state().mode.is_visual() {
|
||||||
vim.switch_mode(Mode::Normal, false, cx)
|
vim.switch_mode(Mode::Normal, false, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
vim.clear_operator(cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +446,7 @@ fn parse_replace_all(query: &str) -> Replacement {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use editor::DisplayPoint;
|
use editor::DisplayPoint;
|
||||||
|
use indoc::indoc;
|
||||||
use search::BufferSearchBar;
|
use search::BufferSearchBar;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -508,4 +593,62 @@ mod test {
|
|||||||
cx.assert_shared_state("a.c. abcd ˇa.c. abcd").await;
|
cx.assert_shared_state("a.c. abcd ˇa.c. abcd").await;
|
||||||
cx.assert_shared_mode(Mode::Normal).await;
|
cx.assert_shared_mode(Mode::Normal).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_d_search(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇa.c. abcd a.c. abcd").await;
|
||||||
|
cx.simulate_shared_keystrokes(["d", "/", "c", "d"]).await;
|
||||||
|
cx.simulate_shared_keystrokes(["enter"]).await;
|
||||||
|
cx.assert_shared_state("ˇcd a.c. abcd").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_v_search(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇa.c. abcd a.c. abcd").await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "/", "c", "d"]).await;
|
||||||
|
cx.simulate_shared_keystrokes(["enter"]).await;
|
||||||
|
cx.assert_shared_state("«a.c. abcˇ»d a.c. abcd").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("a a aˇ a a a").await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "/", "a"]).await;
|
||||||
|
cx.simulate_shared_keystrokes(["enter"]).await;
|
||||||
|
cx.assert_shared_state("a a a« aˇ» a a").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "enter"]).await;
|
||||||
|
cx.assert_shared_state("a a a« a aˇ» a").await;
|
||||||
|
cx.simulate_shared_keystrokes(["?", "enter"]).await;
|
||||||
|
cx.assert_shared_state("a a a« aˇ» a a").await;
|
||||||
|
cx.simulate_shared_keystrokes(["?", "enter"]).await;
|
||||||
|
cx.assert_shared_state("a a «ˇa »a a a").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "enter"]).await;
|
||||||
|
cx.assert_shared_state("a a a« aˇ» a a").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "enter"]).await;
|
||||||
|
cx.assert_shared_state("a a a« a aˇ» a").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_visual_block_search(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"ˇone two
|
||||||
|
three four
|
||||||
|
five six
|
||||||
|
"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["ctrl-v", "j", "/", "f"])
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["enter"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {
|
||||||
|
"«one twoˇ»
|
||||||
|
«three fˇ»our
|
||||||
|
five six
|
||||||
|
"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,21 +138,15 @@ impl Clone for ReplayableAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct SearchState {
|
pub struct SearchState {
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
pub initial_query: String,
|
pub initial_query: String,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SearchState {
|
pub prior_selections: Vec<Range<Anchor>>,
|
||||||
fn default() -> Self {
|
pub prior_operator: Option<Operator>,
|
||||||
Self {
|
pub prior_mode: Mode,
|
||||||
direction: Direction::Next,
|
|
||||||
count: 1,
|
|
||||||
initial_query: "".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorState {
|
impl EditorState {
|
||||||
|
@ -4,12 +4,22 @@ use gpui::WindowContext;
|
|||||||
use language::BracketPair;
|
use language::BracketPair;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum SurroundsType {
|
pub enum SurroundsType {
|
||||||
Motion(Motion),
|
Motion(Motion),
|
||||||
Object(Object),
|
Object(Object),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This exists so that we can have Deserialize on Operators, but not on Motions.
|
||||||
|
impl<'de> Deserialize<'de> for SurroundsType {
|
||||||
|
fn deserialize<D>(_: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Err(serde::de::Error::custom("Cannot deserialize SurroundsType"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowContext) {
|
pub fn add_surrounds(text: Arc<str>, target: SurroundsType, cx: &mut WindowContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.stop_recording();
|
vim.stop_recording();
|
||||||
|
@ -21,7 +21,7 @@ use collections::HashMap;
|
|||||||
use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
|
use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
|
||||||
use editor::{
|
use editor::{
|
||||||
movement::{self, FindRange},
|
movement::{self, FindRange},
|
||||||
Editor, EditorEvent, EditorMode,
|
Anchor, Editor, EditorEvent, EditorMode,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent,
|
actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent,
|
||||||
@ -295,6 +295,18 @@ impl Vim {
|
|||||||
Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
|
Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn editor_selections(&mut self, cx: &mut WindowContext) -> Vec<Range<Anchor>> {
|
||||||
|
self.update_active_editor(cx, |_, editor, _| {
|
||||||
|
editor
|
||||||
|
.selections
|
||||||
|
.disjoint_anchors()
|
||||||
|
.iter()
|
||||||
|
.map(|selection| selection.tail()..selection.head())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// When doing an action that modifies the buffer, we start recording so that `.`
|
/// When doing an action that modifies the buffer, we start recording so that `.`
|
||||||
/// will replay the action.
|
/// will replay the action.
|
||||||
pub fn start_recording(&mut self, cx: &mut WindowContext) {
|
pub fn start_recording(&mut self, cx: &mut WindowContext) {
|
||||||
|
7
crates/vim/test_data/test_d_search.json
Normal file
7
crates/vim/test_data/test_d_search.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{"Put":{"state":"ˇa.c. abcd a.c. abcd"}}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"c"}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"ˇcd a.c. abcd","mode":"Normal"}}
|
28
crates/vim/test_data/test_v_search.json
Normal file
28
crates/vim/test_data/test_v_search.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{"Put":{"state":"ˇa.c. abcd a.c. abcd"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"c"}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"«a.c. abcˇ»d a.c. abcd","mode":"Visual"}}
|
||||||
|
{"Put":{"state":"a a aˇ a a a"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"a a a« aˇ» a a","mode":"Visual"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"a a a« a aˇ» a","mode":"Visual"}}
|
||||||
|
{"Key":"?"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"a a a« aˇ» a a","mode":"Visual"}}
|
||||||
|
{"Key":"?"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"a a «ˇa »a a a","mode":"Visual"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"a a a« aˇ» a a","mode":"Visual"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"a a a« a aˇ» a","mode":"Visual"}}
|
7
crates/vim/test_data/test_visual_block_search.json
Normal file
7
crates/vim/test_data/test_visual_block_search.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{"Put":{"state":"ˇone two\nthree four\nfive six\n"}}
|
||||||
|
{"Key":"ctrl-v"}
|
||||||
|
{"Key":"j"}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"f"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"«one twoˇ»\n«three fˇ»our\nfive six\n","mode":"VisualBlock"}}
|
@ -18,9 +18,10 @@ pub enum SearchEvent {
|
|||||||
ActiveMatchChanged,
|
ActiveMatchChanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Prev,
|
Prev,
|
||||||
|
#[default]
|
||||||
Next,
|
Next,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user