vim: Support gn command and remap gn to gl (#9982)
Release Notes: - Resolves #4273 @algora-pbc /claim #4273 This is a work-in-progress. The process for `gn` command is: - maintain updated vim.workspace_state.search.initial_query - modify editor.select_next_state with vim.workspace_state.search.initial_query - use editor.select_next() - merge selections - set editor.select_next_state to previous state To make this possible, several private members and editor structures are made public. `gN` is not yet implemented and the cursor still does not jump to the next selection in the first use. Maybe there is an better way to do this? --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
fe4b345603
commit
bf9b443b4a
8 changed files with 311 additions and 89 deletions
|
@ -137,8 +137,10 @@
|
||||||
"g d": "editor::GoToDefinition",
|
"g d": "editor::GoToDefinition",
|
||||||
"g shift-d": "editor::GoToTypeDefinition",
|
"g shift-d": "editor::GoToTypeDefinition",
|
||||||
"g x": "editor::OpenUrl",
|
"g x": "editor::OpenUrl",
|
||||||
"g n": "vim::SelectNext",
|
"g n": "vim::SelectNextMatch",
|
||||||
"g shift-n": "vim::SelectPrevious",
|
"g shift-n": "vim::SelectPreviousMatch",
|
||||||
|
"g l": "vim::SelectNext",
|
||||||
|
"g shift-l": "vim::SelectPrevious",
|
||||||
"g >": [
|
"g >": [
|
||||||
"editor::SelectNext",
|
"editor::SelectNext",
|
||||||
{
|
{
|
||||||
|
|
|
@ -963,7 +963,7 @@ impl BufferSearchBar {
|
||||||
done_rx
|
done_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
|
pub fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let new_index = self
|
let new_index = self
|
||||||
.active_searchable_item
|
.active_searchable_item
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -39,6 +39,7 @@ ui.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
command_palette.workspace = true
|
command_palette.workspace = true
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use anyhow::Result;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -10,10 +9,13 @@ use editor::{
|
||||||
};
|
};
|
||||||
use gpui::{actions, ViewContext, WindowContext};
|
use gpui::{actions, ViewContext, WindowContext};
|
||||||
use language::{Point, Selection, SelectionGoal};
|
use language::{Point, Selection, SelectionGoal};
|
||||||
use workspace::Workspace;
|
use search::BufferSearchBar;
|
||||||
|
use util::ResultExt;
|
||||||
|
use workspace::{searchable::Direction, Workspace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::{start_of_line, Motion},
|
motion::{start_of_line, Motion},
|
||||||
|
normal::substitute::substitute,
|
||||||
object::Object,
|
object::Object,
|
||||||
state::{Mode, Operator},
|
state::{Mode, Operator},
|
||||||
utils::{copy_selections_content, yank_selections_content},
|
utils::{copy_selections_content, yank_selections_content},
|
||||||
|
@ -31,6 +33,8 @@ actions!(
|
||||||
OtherEnd,
|
OtherEnd,
|
||||||
SelectNext,
|
SelectNext,
|
||||||
SelectPrevious,
|
SelectPrevious,
|
||||||
|
SelectNextMatch,
|
||||||
|
SelectPreviousMatch,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -47,14 +51,29 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
workspace.register_action(other_end);
|
workspace.register_action(other_end);
|
||||||
workspace.register_action(delete);
|
workspace.register_action(|_, _: &VisualDelete, cx| {
|
||||||
workspace.register_action(yank);
|
Vim::update(cx, |vim, cx| {
|
||||||
|
vim.record_current_action(cx);
|
||||||
workspace.register_action(|workspace, action, cx| {
|
delete(vim, cx);
|
||||||
select_next(workspace, action, cx).ok();
|
});
|
||||||
});
|
});
|
||||||
workspace.register_action(|workspace, action, cx| {
|
workspace.register_action(|_, _: &VisualYank, cx| {
|
||||||
select_previous(workspace, action, cx).ok();
|
Vim::update(cx, |vim, cx| {
|
||||||
|
yank(vim, cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.register_action(select_next);
|
||||||
|
workspace.register_action(select_previous);
|
||||||
|
workspace.register_action(|workspace, _: &SelectNextMatch, cx| {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
select_match(workspace, vim, Direction::Next, cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
workspace.register_action(|workspace, _: &SelectPreviousMatch, cx| {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
select_match(workspace, vim, Direction::Prev, cx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,70 +352,65 @@ pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
|
pub fn delete(vim: &mut Vim, cx: &mut WindowContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
vim.update_active_editor(cx, |vim, editor, cx| {
|
||||||
vim.record_current_action(cx);
|
let mut original_columns: HashMap<_, _> = Default::default();
|
||||||
vim.update_active_editor(cx, |vim, editor, cx| {
|
let line_mode = editor.selections.line_mode;
|
||||||
let mut original_columns: HashMap<_, _> = Default::default();
|
|
||||||
let line_mode = editor.selections.line_mode;
|
|
||||||
|
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
|
||||||
if line_mode {
|
|
||||||
let mut position = selection.head();
|
|
||||||
if !selection.reversed {
|
|
||||||
position = movement::left(map, position);
|
|
||||||
}
|
|
||||||
original_columns.insert(selection.id, position.to_point(map).column);
|
|
||||||
}
|
|
||||||
selection.goal = SelectionGoal::None;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
copy_selections_content(vim, editor, line_mode, cx);
|
|
||||||
editor.insert("", cx);
|
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
|
||||||
s.move_with(|map, selection| {
|
|
||||||
let mut cursor = selection.head().to_point(map);
|
|
||||||
|
|
||||||
if let Some(column) = original_columns.get(&selection.id) {
|
|
||||||
cursor.column = *column
|
|
||||||
}
|
|
||||||
let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
|
|
||||||
selection.collapse_to(cursor, selection.goal)
|
|
||||||
});
|
|
||||||
if vim.state().mode == Mode::VisualBlock {
|
|
||||||
s.select_anchors(vec![s.first_anchor()])
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
vim.switch_mode(Mode::Normal, true, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
|
|
||||||
Vim::update(cx, |vim, cx| {
|
|
||||||
vim.update_active_editor(cx, |vim, editor, cx| {
|
|
||||||
let line_mode = editor.selections.line_mode;
|
|
||||||
yank_selections_content(vim, editor, line_mode, cx);
|
|
||||||
editor.change_selections(None, cx, |s| {
|
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if line_mode {
|
if line_mode {
|
||||||
selection.start = start_of_line(map, false, selection.start);
|
let mut position = selection.head();
|
||||||
};
|
if !selection.reversed {
|
||||||
selection.collapse_to(selection.start, SelectionGoal::None)
|
position = movement::left(map, position);
|
||||||
|
}
|
||||||
|
original_columns.insert(selection.id, position.to_point(map).column);
|
||||||
|
}
|
||||||
|
selection.goal = SelectionGoal::None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
copy_selections_content(vim, editor, line_mode, cx);
|
||||||
|
editor.insert("", cx);
|
||||||
|
|
||||||
|
// Fixup cursor position after the deletion
|
||||||
|
editor.set_clip_at_line_ends(true, cx);
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
let mut cursor = selection.head().to_point(map);
|
||||||
|
|
||||||
|
if let Some(column) = original_columns.get(&selection.id) {
|
||||||
|
cursor.column = *column
|
||||||
|
}
|
||||||
|
let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
|
||||||
|
selection.collapse_to(cursor, selection.goal)
|
||||||
});
|
});
|
||||||
if vim.state().mode == Mode::VisualBlock {
|
if vim.state().mode == Mode::VisualBlock {
|
||||||
s.select_anchors(vec![s.first_anchor()])
|
s.select_anchors(vec![s.first_anchor()])
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
vim.switch_mode(Mode::Normal, true, cx);
|
|
||||||
});
|
});
|
||||||
|
vim.switch_mode(Mode::Normal, true, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn yank(vim: &mut Vim, cx: &mut WindowContext) {
|
||||||
|
vim.update_active_editor(cx, |vim, editor, cx| {
|
||||||
|
let line_mode = editor.selections.line_mode;
|
||||||
|
yank_selections_content(vim, editor, line_mode, cx);
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
if line_mode {
|
||||||
|
selection.start = start_of_line(map, false, selection.start);
|
||||||
|
};
|
||||||
|
selection.collapse_to(selection.start, SelectionGoal::None)
|
||||||
|
});
|
||||||
|
if vim.state().mode == Mode::VisualBlock {
|
||||||
|
s.select_anchors(vec![s.first_anchor()])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
vim.switch_mode(Mode::Normal, true, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
|
pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
|
||||||
|
@ -442,48 +456,112 @@ pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_next(
|
pub fn select_next(_: &mut Workspace, _: &SelectNext, cx: &mut ViewContext<Workspace>) {
|
||||||
_: &mut Workspace,
|
|
||||||
_: &SelectNext,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) -> Result<()> {
|
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let count =
|
let count =
|
||||||
vim.take_count(cx)
|
vim.take_count(cx)
|
||||||
.unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
|
.unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
|
||||||
vim.update_active_editor(cx, |_, editor, cx| {
|
vim.update_active_editor(cx, |_, editor, cx| {
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
match editor.select_next(&Default::default(), cx) {
|
if editor
|
||||||
Err(a) => return Err(a),
|
.select_next(&Default::default(), cx)
|
||||||
_ => {}
|
.log_err()
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
.unwrap_or(Ok(()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_previous(
|
pub fn select_previous(_: &mut Workspace, _: &SelectPrevious, cx: &mut ViewContext<Workspace>) {
|
||||||
_: &mut Workspace,
|
|
||||||
_: &SelectPrevious,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) -> Result<()> {
|
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let count =
|
let count =
|
||||||
vim.take_count(cx)
|
vim.take_count(cx)
|
||||||
.unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
|
.unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
|
||||||
vim.update_active_editor(cx, |_, editor, cx| {
|
vim.update_active_editor(cx, |_, editor, cx| {
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
match editor.select_previous(&Default::default(), cx) {
|
if editor
|
||||||
Err(a) => return Err(a),
|
.select_previous(&Default::default(), cx)
|
||||||
_ => {}
|
.log_err()
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
.unwrap_or(Ok(()))
|
}
|
||||||
|
|
||||||
|
pub fn select_match(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
vim: &mut Vim,
|
||||||
|
direction: Direction,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) {
|
||||||
|
let count = vim.take_count(cx).unwrap_or(1);
|
||||||
|
let pane = workspace.active_pane().clone();
|
||||||
|
let vim_is_normal = vim.state().mode == Mode::Normal;
|
||||||
|
let mut start_selection = 0usize;
|
||||||
|
let mut end_selection = 0usize;
|
||||||
|
|
||||||
|
vim.update_active_editor(cx, |_, editor, _| {
|
||||||
|
editor.set_collapse_matches(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
if vim_is_normal {
|
||||||
|
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| {
|
||||||
|
// without update_match_index there is a bug when the cursor is before the first match
|
||||||
|
search_bar.update_match_index(cx);
|
||||||
|
search_bar.select_match(direction.opposite(), 1, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.update_active_editor(cx, |_, editor, cx| {
|
||||||
|
let latest = editor.selections.newest::<usize>(cx);
|
||||||
|
start_selection = latest.start;
|
||||||
|
end_selection = latest.end;
|
||||||
|
});
|
||||||
|
|
||||||
|
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.update_match_index(cx);
|
||||||
|
search_bar.select_match(direction, count, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
vim.update_active_editor(cx, |_, editor, cx| {
|
||||||
|
let latest = editor.selections.newest::<usize>(cx);
|
||||||
|
if vim_is_normal {
|
||||||
|
start_selection = latest.start;
|
||||||
|
end_selection = latest.end;
|
||||||
|
} else {
|
||||||
|
start_selection = start_selection.min(latest.start);
|
||||||
|
end_selection = end_selection.max(latest.end);
|
||||||
|
}
|
||||||
|
if direction == Direction::Prev {
|
||||||
|
std::mem::swap(&mut start_selection, &mut end_selection);
|
||||||
|
}
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.select_ranges([start_selection..end_selection]);
|
||||||
|
});
|
||||||
|
editor.set_collapse_matches(true);
|
||||||
|
});
|
||||||
|
match vim.maybe_pop_operator() {
|
||||||
|
Some(Operator::Change) => substitute(vim, None, false, cx),
|
||||||
|
Some(Operator::Delete) => {
|
||||||
|
vim.stop_recording();
|
||||||
|
delete(vim, cx)
|
||||||
|
}
|
||||||
|
Some(Operator::Yank) => yank(vim, cx),
|
||||||
|
_ => {} // Ignoring other operators
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1052,4 +1130,69 @@ mod test {
|
||||||
cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
|
cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
|
||||||
assert_eq!(cx.mode(), Mode::VisualBlock);
|
assert_eq!(cx.mode(), Mode::VisualBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_gn(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("aaˇ aa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "a", "a", "enter"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa ˇaa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "n"]).await;
|
||||||
|
cx.assert_shared_state("aa «aaˇ» aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "n"]).await;
|
||||||
|
cx.assert_shared_state("aa «aa aaˇ» aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["escape", "d", "g", "n"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa aa ˇ aa aa").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("aaˇ aa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "a", "a", "enter"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa ˇaa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["3", "g", "n"]).await;
|
||||||
|
cx.assert_shared_state("aa aa aa «aaˇ» aa").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("aaˇ aa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "a", "a", "enter"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa ˇaa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "shift-n"]).await;
|
||||||
|
cx.assert_shared_state("aa «ˇaa» aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "shift-n"]).await;
|
||||||
|
cx.assert_shared_state("«ˇaa aa» aa aa aa").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_dgn_repeat(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("aaˇ aa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "a", "a", "enter"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa ˇaa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["d", "g", "n"]).await;
|
||||||
|
|
||||||
|
cx.assert_shared_state("aa ˇ aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["."]).await;
|
||||||
|
cx.assert_shared_state("aa ˇ aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["."]).await;
|
||||||
|
cx.assert_shared_state("aa ˇ aa").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_cgn_repeat(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("aaˇ aa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["/", "a", "a", "enter"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa ˇaa aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["c", "g", "n", "x", "escape"])
|
||||||
|
.await;
|
||||||
|
cx.assert_shared_state("aa ˇx aa aa aa").await;
|
||||||
|
cx.simulate_shared_keystrokes(["."]).await;
|
||||||
|
cx.assert_shared_state("aa x ˇx aa aa").await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
crates/vim/test_data/test_cgn_repeat.json
Normal file
14
crates/vim/test_data/test_cgn_repeat.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{"Put":{"state":"aaˇ aa aa aa aa"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"aa ˇaa aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"c"}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"n"}
|
||||||
|
{"Key":"x"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"aa ˇx aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"aa x ˇx aa aa","mode":"Normal"}}
|
14
crates/vim/test_data/test_dgn_repeat.json
Normal file
14
crates/vim/test_data/test_dgn_repeat.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{"Put":{"state":"aaˇ aa aa aa aa"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"aa ˇaa aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"n"}
|
||||||
|
{"Get":{"state":"aa ˇ aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"aa ˇ aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"aa ˇ aa","mode":"Normal"}}
|
39
crates/vim/test_data/test_gn.json
Normal file
39
crates/vim/test_data/test_gn.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{"Put":{"state":"aaˇ aa aa aa aa"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"aa ˇaa aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"n"}
|
||||||
|
{"Get":{"state":"aa «aaˇ» aa aa aa","mode":"Visual"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"n"}
|
||||||
|
{"Get":{"state":"aa «aa aaˇ» aa aa","mode":"Visual"}}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Key":"d"}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"n"}
|
||||||
|
{"Get":{"state":"aa aa ˇ aa aa","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"aaˇ aa aa aa aa"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"aa ˇaa aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"3"}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"n"}
|
||||||
|
{"Get":{"state":"aa aa aa «aaˇ» aa","mode":"Visual"}}
|
||||||
|
{"Put":{"state":"aaˇ aa aa aa aa"}}
|
||||||
|
{"Key":"/"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"enter"}
|
||||||
|
{"Get":{"state":"aa ˇaa aa aa aa","mode":"Normal"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"shift-n"}
|
||||||
|
{"Get":{"state":"aa «ˇaa» aa aa aa","mode":"Visual"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"shift-n"}
|
||||||
|
{"Get":{"state":"«ˇaa aa» aa aa aa","mode":"Visual"}}
|
|
@ -24,6 +24,15 @@ pub enum Direction {
|
||||||
Next,
|
Next,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Direction {
|
||||||
|
pub fn opposite(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Direction::Prev => Direction::Next,
|
||||||
|
Direction::Next => Direction::Prev,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct SearchOptions {
|
pub struct SearchOptions {
|
||||||
pub case: bool,
|
pub case: bool,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue