vim: add */#/g*/g# for jumping to next word

As in vim, this toggles the normal search experience.
This commit is contained in:
Conrad Irwin 2023-06-28 13:13:02 -06:00
parent 96ce0bb783
commit dbec2ed1f1
4 changed files with 152 additions and 4 deletions

View File

@ -101,6 +101,8 @@
"vim::SwitchMode",
"Normal"
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
"1": [
"vim::Number",
@ -240,7 +242,19 @@
"vim::SwitchMode",
"Normal"
],
"d": "editor::GoToDefinition"
"d": "editor::GoToDefinition",
"*": [
"vim::MoveToNext",
{
"partialWord": true
}
],
"#": [
"vim::MoveToPrev",
{
"partialWord": true
}
]
}
},
{

View File

@ -65,6 +65,7 @@ pub struct BufferSearchBar {
pub query_editor: ViewHandle<Editor>,
active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
active_match_index: Option<usize>,
pending_match_direction: Option<Direction>,
active_searchable_item_subscription: Option<Subscription>,
seachable_items_with_matches:
HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
@ -252,6 +253,7 @@ impl BufferSearchBar {
default_options: SearchOptions::NONE,
search_options: SearchOptions::NONE,
pending_search: None,
pending_match_direction: None,
query_contains_error: false,
dismissed: true,
}
@ -285,10 +287,10 @@ impl BufferSearchBar {
&mut self,
focus: bool,
suggest_query: bool,
search_option: SearchOptions,
search_options: SearchOptions,
cx: &mut ViewContext<Self>,
) -> bool {
self.search_options = search_option;
self.search_options = search_options;
let searchable_item = if let Some(searchable_item) = &self.active_searchable_item {
SearchableItemHandle::boxed_clone(searchable_item.as_ref())
} else {
@ -486,6 +488,17 @@ impl BufferSearchBar {
self.select_match(Direction::Prev, cx);
}
pub fn select_word_under_cursor(
&mut self,
direction: Direction,
options: SearchOptions,
cx: &mut ViewContext<Self>,
) {
self.active_match_index = None;
self.pending_match_direction = Some(direction);
self.show_with_options(false, true, options, cx);
}
pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
if let Some(index) = self.active_match_index {
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
@ -567,6 +580,7 @@ impl BufferSearchBar {
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() {
self.active_match_index.take();
self.pending_match_direction.take();
active_searchable_item.clear_matches(cx);
} else {
let query = if self.search_options.contains(SearchOptions::REGEX) {
@ -614,7 +628,15 @@ impl BufferSearchBar {
.unwrap();
active_searchable_item.update_matches(matches, cx);
if select_closest_match {
if let Some(match_ix) = this.active_match_index {
if let Some(mut match_ix) = this.active_match_index {
if let Some(direction) = this.pending_match_direction.take()
{
match_ix += match direction {
Direction::Next => 1,
Direction::Prev => matches.len() - 1,
};
match_ix = match_ix % matches.len();
}
active_searchable_item
.activate_match(match_ix, matches, cx);
}

View File

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

View File

@ -0,0 +1,108 @@
use gpui::{impl_actions, ViewContext};
use search::{BufferSearchBar, SearchOptions};
use serde_derive::Deserialize;
use workspace::{searchable::Direction, Workspace};
use crate::Vim;
#[derive(Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MoveToNext {
#[serde(default)]
partial_word: bool,
}
#[derive(Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MoveToPrev {
#[serde(default)]
partial_word: bool,
}
impl_actions!(vim, [MoveToNext, MoveToPrev]);
pub(crate) fn move_to_next(
workspace: &mut Workspace,
action: &MoveToNext,
cx: &mut ViewContext<Workspace>,
) {
move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
}
pub(crate) fn move_to_prev(
workspace: &mut Workspace,
action: &MoveToPrev,
cx: &mut ViewContext<Workspace>,
) {
move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
}
fn move_to_internal(
workspace: &mut Workspace,
direction: Direction,
whole_word: bool,
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 options = SearchOptions::CASE_SENSITIVE;
options.set(SearchOptions::WHOLE_WORD, whole_word);
search_bar.select_word_under_cursor(direction, options, cx);
});
}
});
vim.clear_operator(cx);
});
}
#[cfg(test)]
mod test {
use search::BufferSearchBar;
use crate::{state::Mode, test::VimTestContext};
#[gpui::test]
async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
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(["*"]);
search_bar.next_notification(&cx).await;
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["*"]);
search_bar.next_notification(&cx).await;
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["#"]);
search_bar.next_notification(&cx).await;
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["#"]);
search_bar.next_notification(&cx).await;
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["g", "*"]);
search_bar.next_notification(&cx).await;
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["n"]);
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["g", "#"]);
search_bar.next_notification(&cx).await;
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
}
}