Rebuild vim search experience on refactored code
This commit is contained in:
parent
232d14a3ae
commit
b4b0f622de
8 changed files with 226 additions and 116 deletions
|
@ -1,9 +1,9 @@
|
|||
use gpui::{impl_actions, AppContext, ViewContext};
|
||||
use search::{BufferSearchBar, SearchOptions};
|
||||
use gpui::{actions, impl_actions, AppContext, ViewContext};
|
||||
use search::{buffer_search, BufferSearchBar, SearchOptions};
|
||||
use serde_derive::Deserialize;
|
||||
use workspace::{searchable::Direction, Workspace};
|
||||
use workspace::{searchable::Direction, Pane, Workspace};
|
||||
|
||||
use crate::Vim;
|
||||
use crate::{state::SearchState, Vim};
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -26,11 +26,14 @@ pub(crate) struct Search {
|
|||
}
|
||||
|
||||
impl_actions!(vim, [MoveToNext, MoveToPrev, Search]);
|
||||
actions!(vim, [SearchSubmit]);
|
||||
|
||||
pub(crate) fn init(cx: &mut AppContext) {
|
||||
cx.add_action(move_to_next);
|
||||
cx.add_action(move_to_prev);
|
||||
cx.add_action(search);
|
||||
cx.add_action(search_submit);
|
||||
cx.add_action(search_deploy);
|
||||
}
|
||||
|
||||
fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
|
||||
|
@ -43,19 +46,68 @@ fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewCon
|
|||
|
||||
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);
|
||||
})
|
||||
}
|
||||
let direction = if action.backwards {
|
||||
Direction::Prev
|
||||
} else {
|
||||
Direction::Next
|
||||
};
|
||||
Vim::update(cx, |vim, cx| {
|
||||
let count = vim.pop_number_operator(cx).unwrap_or(1);
|
||||
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| {
|
||||
if !search_bar.show(cx) {
|
||||
return;
|
||||
}
|
||||
let query = search_bar.query(cx);
|
||||
|
||||
search_bar.select_query(cx);
|
||||
cx.focus_self();
|
||||
|
||||
if query.is_empty() {
|
||||
search_bar.set_search_options(
|
||||
SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
vim.state.search = SearchState {
|
||||
direction,
|
||||
count,
|
||||
initial_query: query,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext<Pane>) {
|
||||
Vim::update(cx, |vim, _| vim.state.search = Default::default());
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
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 mut state = &mut vim.state.search;
|
||||
let mut count = state.count;
|
||||
|
||||
// in the case that the query has changed, the search bar
|
||||
// will have selected the next match already.
|
||||
if (search_bar.query(cx) != state.initial_query)
|
||||
&& state.direction == Direction::Next
|
||||
{
|
||||
count = count.saturating_sub(1);
|
||||
}
|
||||
search_bar.select_match(state.direction, Some(count), cx);
|
||||
state.count = 1;
|
||||
search_bar.focus_editor(&Default::default(), cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -67,18 +119,32 @@ pub fn move_to_internal(
|
|||
) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
let pane = workspace.active_pane().clone();
|
||||
let count = vim.pop_number_operator(cx).unwrap_or(1);
|
||||
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 mut options = SearchOptions::CASE_SENSITIVE;
|
||||
// options.set(SearchOptions::WHOLE_WORD, whole_word);
|
||||
// search_bar.show(false, false, cx);
|
||||
// let word = search_bar.query_suggestion();
|
||||
// search_bar.show()
|
||||
// search_bar.search(word, options)
|
||||
|
||||
// search_bar.select_word_under_cursor(direction, options, cx);
|
||||
let search = search_bar.update(cx, |search_bar, cx| {
|
||||
let mut options = SearchOptions::CASE_SENSITIVE;
|
||||
options.set(SearchOptions::WHOLE_WORD, whole_word);
|
||||
if search_bar.show(cx) {
|
||||
search_bar
|
||||
.query_suggestion(cx)
|
||||
.map(|query| search_bar.search(&query, Some(options), cx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(search) = search {
|
||||
let search_bar = search_bar.downgrade();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
search.await?;
|
||||
search_bar.update(&mut cx, |search_bar, cx| {
|
||||
search_bar.select_match(direction, Some(count), cx)
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
vim.clear_operator(cx);
|
||||
|
@ -100,15 +166,6 @@ mod test {
|
|||
deterministic: Arc<gpui::executor::Deterministic>,
|
||||
) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
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")
|
||||
});
|
||||
cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
|
||||
|
||||
cx.simulate_keystrokes(["*"]);
|
||||
|
@ -127,6 +184,10 @@ mod test {
|
|||
deterministic.run_until_parked();
|
||||
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
|
||||
|
||||
cx.simulate_keystrokes(["2", "*"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
|
||||
|
||||
cx.simulate_keystrokes(["g", "*"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
|
||||
|
@ -140,7 +201,10 @@ mod test {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_search(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
deterministic: Arc<gpui::executor::Deterministic>,
|
||||
) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
|
||||
|
@ -160,8 +224,7 @@ mod test {
|
|||
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;
|
||||
deterministic.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
let highlights = editor.all_background_highlights(cx);
|
||||
|
@ -173,30 +236,49 @@ mod test {
|
|||
});
|
||||
|
||||
cx.simulate_keystrokes(["enter"]);
|
||||
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
|
||||
|
||||
// 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"]);
|
||||
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
|
||||
|
||||
// ?<enter> to go to previous
|
||||
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
|
||||
cx.simulate_keystrokes(["?", "enter"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
|
||||
cx.simulate_keystrokes(["?", "enter"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
|
||||
|
||||
// /<enter> to go to next
|
||||
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
|
||||
cx.simulate_keystrokes(["/", "enter"]);
|
||||
deterministic.run_until_parked();
|
||||
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;
|
||||
|
||||
deterministic.run_until_parked();
|
||||
cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
|
||||
|
||||
// works with counts
|
||||
cx.simulate_keystrokes(["4", "/", "c"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.simulate_keystrokes(["enter"]);
|
||||
cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
|
||||
|
||||
// check that searching resumes from cursor, not previous match
|
||||
cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
|
||||
cx.simulate_keystrokes(["/", "d"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.simulate_keystrokes(["enter"]);
|
||||
cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
|
||||
cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
|
||||
cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
|
||||
cx.simulate_keystrokes(["/", "b"]);
|
||||
deterministic.run_until_parked();
|
||||
cx.simulate_keystrokes(["enter"]);
|
||||
cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use gpui::keymap_matcher::KeymapContext;
|
||||
use language::CursorShape;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use workspace::searchable::Direction;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Mode {
|
||||
|
@ -38,6 +39,23 @@ pub enum Operator {
|
|||
pub struct VimState {
|
||||
pub mode: Mode,
|
||||
pub operator_stack: Vec<Operator>,
|
||||
pub search: SearchState,
|
||||
}
|
||||
|
||||
pub struct SearchState {
|
||||
pub direction: Direction,
|
||||
pub count: usize,
|
||||
pub initial_query: String,
|
||||
}
|
||||
|
||||
impl Default for SearchState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
direction: Direction::Next,
|
||||
count: 1,
|
||||
initial_query: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VimState {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue