vim: ? to search backwards, and /<enter> to repeat search

This commit is contained in:
Conrad Irwin 2023-07-06 13:21:01 -06:00
parent d70f415e8e
commit 6cf13c62d1
5 changed files with 106 additions and 21 deletions

View File

@ -197,10 +197,11 @@
"p": "vim::Paste", "p": "vim::Paste",
"u": "editor::Undo", "u": "editor::Undo",
"ctrl-r": "editor::Redo", "ctrl-r": "editor::Redo",
"/": [ "/": "vim::Search",
"buffer_search::Deploy", "?": [
"vim::Search",
{ {
"focus": true "backwards": true,
} }
], ],
"ctrl-f": "vim::PageDown", "ctrl-f": "vim::PageDown",
@ -356,7 +357,8 @@
{ {
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"enter": "buffer_search::FocusEditor" "enter": "buffer_search::FocusEditor",
"escape": "buffer_search::Dismiss"
} }
} }
] ]

View File

@ -28,7 +28,6 @@ use self::{
case::change_case, case::change_case,
change::{change_motion, change_object}, change::{change_motion, change_object},
delete::{delete_motion, delete_object}, delete::{delete_motion, delete_object},
search::{move_to_next, move_to_prev},
substitute::substitute, substitute::substitute,
yank::{yank_motion, yank_object}, yank::{yank_motion, yank_object},
}; };
@ -59,8 +58,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(insert_line_above); cx.add_action(insert_line_above);
cx.add_action(insert_line_below); cx.add_action(insert_line_below);
cx.add_action(change_case); cx.add_action(change_case);
cx.add_action(move_to_next); search::init(cx);
cx.add_action(move_to_prev);
cx.add_action(|_: &mut Workspace, _: &Substitute, cx| { cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
Vim::update(cx, |vim, cx| { Vim::update(cx, |vim, cx| {
let times = vim.pop_number_operator(cx); let times = vim.pop_number_operator(cx);

View File

@ -1,4 +1,4 @@
use gpui::{impl_actions, ViewContext}; use gpui::{impl_actions, AppContext, ViewContext};
use search::{BufferSearchBar, SearchOptions}; use search::{BufferSearchBar, SearchOptions};
use serde_derive::Deserialize; use serde_derive::Deserialize;
use workspace::{searchable::Direction, Workspace}; use workspace::{searchable::Direction, Workspace};
@ -19,25 +19,47 @@ pub(crate) struct MoveToPrev {
partial_word: bool, partial_word: bool,
} }
impl_actions!(vim, [MoveToNext, MoveToPrev]); #[derive(Clone, Deserialize, PartialEq)]
pub(crate) struct Search {
#[serde(default)]
backwards: bool,
}
pub(crate) fn move_to_next( impl_actions!(vim, [MoveToNext, MoveToPrev, Search]);
workspace: &mut Workspace,
action: &MoveToNext, pub(crate) fn init(cx: &mut AppContext) {
cx: &mut ViewContext<Workspace>, cx.add_action(move_to_next);
) { cx.add_action(move_to_prev);
cx.add_action(search);
}
fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
move_to_internal(workspace, Direction::Next, !action.partial_word, cx) move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
} }
pub(crate) fn move_to_prev( fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {
workspace: &mut Workspace,
action: &MoveToPrev,
cx: &mut ViewContext<Workspace>,
) {
move_to_internal(workspace, Direction::Prev, !action.partial_word, cx) move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
} }
fn move_to_internal( fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
let pane = workspace.active_pane().clone();
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| {
let options = SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX;
let direction = if action.backwards {
Direction::Prev
} else {
Direction::Next
};
search_bar.select_match(direction, cx);
search_bar.show_with_options(true, false, options, cx);
})
}
})
}
pub fn move_to_internal(
workspace: &mut Workspace, workspace: &mut Workspace,
direction: Direction, direction: Direction,
whole_word: bool, whole_word: bool,
@ -60,6 +82,7 @@ fn move_to_internal(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use editor::DisplayPoint;
use search::BufferSearchBar; use search::BufferSearchBar;
use crate::{state::Mode, test::VimTestContext}; use crate::{state::Mode, test::VimTestContext};
@ -105,4 +128,65 @@ mod test {
search_bar.next_notification(&cx).await; search_bar.next_notification(&cx).await;
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal); cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
} }
#[gpui::test]
async fn test_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
cx.simulate_keystrokes(["/", "c", "c"]);
let search_bar = cx.workspace(|workspace, cx| {
workspace
.active_pane()
.read(cx)
.toolbar()
.read(cx)
.item_of_type::<BufferSearchBar>()
.expect("Buffer search bar should be deployed")
});
search_bar.read_with(cx.cx, |bar, cx| {
assert_eq!(bar.query_editor.read(cx).text(cx), "cc");
});
// wait for the query editor change event to fire.
search_bar.next_notification(&cx).await;
cx.update_editor(|editor, cx| {
let highlights = editor.all_background_highlights(cx);
assert_eq!(3, highlights.len());
assert_eq!(
DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
highlights[0].0
)
});
cx.simulate_keystrokes(["enter"]);
// n to go to next/N to go to previous
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
cx.simulate_keystrokes(["n"]);
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
cx.simulate_keystrokes(["shift-n"]);
// ?<enter> to go to previous
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
cx.simulate_keystrokes(["?", "enter"]);
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
cx.simulate_keystrokes(["?", "enter"]);
// /<enter> to go to next
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
cx.simulate_keystrokes(["/", "enter"]);
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
// ?{search}<enter> to search backwards
cx.simulate_keystrokes(["?", "b", "enter"]);
// wait for the query editor change event to fire.
search_bar.next_notification(&cx).await;
cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
}
} }

View File

@ -97,7 +97,7 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
}); });
search_bar.read_with(cx.cx, |bar, cx| { search_bar.read_with(cx.cx, |bar, cx| {
assert_eq!(bar.query_editor.read(cx).text(cx), "jumps"); assert_eq!(bar.query_editor.read(cx).text(cx), "");
}) })
} }

View File

@ -90,6 +90,7 @@ impl<'a> VimTestContext<'a> {
self.cx.set_state(text) self.cx.set_state(text)
} }
#[track_caller]
pub fn assert_state(&mut self, text: &str, mode: Mode) { pub fn assert_state(&mut self, text: &str, mode: Mode) {
self.assert_editor_state(text); self.assert_editor_state(text);
assert_eq!(self.mode(), mode, "{}", self.assertion_context()); assert_eq!(self.mode(), mode, "{}", self.assertion_context());