mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 02:17:35 +03:00
vim: ALlow counts on insert actions
This re-uses the existing repeat infrastructure.
This commit is contained in:
parent
cee549e1ef
commit
d868d00985
@ -533,7 +533,7 @@
|
|||||||
// TODO: Move this to a dock open action
|
// TODO: Move this to a dock open action
|
||||||
"cmd-shift-c": "collab_panel::ToggleFocus",
|
"cmd-shift-c": "collab_panel::ToggleFocus",
|
||||||
"cmd-alt-i": "zed::DebugElements",
|
"cmd-alt-i": "zed::DebugElements",
|
||||||
"ctrl-:": "editor::ToggleInlayHints",
|
"ctrl-:": "editor::ToggleInlayHints"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -499,7 +499,7 @@
|
|||||||
"around": true
|
"around": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{state::Mode, Vim};
|
use crate::{normal::repeat, state::Mode, Vim};
|
||||||
use editor::{scroll::autoscroll::Autoscroll, Bias};
|
use editor::{scroll::autoscroll::Autoscroll, Bias};
|
||||||
use gpui::{actions, AppContext, ViewContext};
|
use gpui::{actions, Action, AppContext, ViewContext};
|
||||||
use language::SelectionGoal;
|
use language::SelectionGoal;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@ -10,24 +10,41 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
cx.add_action(normal_before);
|
cx.add_action(normal_before);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext<Workspace>) {
|
fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |vim, cx| {
|
let should_repeat = Vim::update(cx, |vim, cx| {
|
||||||
vim.stop_recording();
|
let count = vim.take_count().unwrap_or(1);
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.stop_recording_immediately(action.boxed_clone());
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
if count <= 1 || vim.workspace_state.replaying {
|
||||||
s.move_cursors_with(|map, mut cursor, _| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
*cursor.column_mut() = cursor.column().saturating_sub(1);
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
s.move_cursors_with(|map, mut cursor, _| {
|
||||||
|
*cursor.column_mut() = cursor.column().saturating_sub(1);
|
||||||
|
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
vim.switch_mode(Mode::Normal, false, cx);
|
||||||
vim.switch_mode(Mode::Normal, false, cx);
|
false
|
||||||
})
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if should_repeat {
|
||||||
|
repeat::repeat(cx, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{state::Mode, test::VimTestContext};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use gpui::executor::Deterministic;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
state::Mode,
|
||||||
|
test::{NeovimBackedTestContext, VimTestContext},
|
||||||
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
|
async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
|
||||||
@ -40,4 +57,78 @@ mod test {
|
|||||||
assert_eq!(cx.mode(), Mode::Normal);
|
assert_eq!(cx.mode(), Mode::Normal);
|
||||||
cx.assert_editor_state("Tesˇt");
|
cx.assert_editor_state("Tesˇt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_insert_with_counts(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx: &mut gpui::TestAppContext,
|
||||||
|
) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇhello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("----ˇ-hello\n").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇhello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("h----ˇ-ello\n").await;
|
||||||
|
|
||||||
|
cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("---ˇ-h-----ello\n").await;
|
||||||
|
|
||||||
|
cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("----h-----ello--ˇ-\n").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇhello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇhello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_insert_with_repeat(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx: &mut gpui::TestAppContext,
|
||||||
|
) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇhello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("--ˇ-hello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["."]).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("----ˇ--hello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["2", "."]).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("-----ˇ---hello\n").await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇhello\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("hello\nkk\nkˇk\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["."]).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
|
||||||
|
cx.simulate_shared_keystrokes(["1", "."]).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ mod case;
|
|||||||
mod change;
|
mod change;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod paste;
|
mod paste;
|
||||||
mod repeat;
|
pub(crate) mod repeat;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod search;
|
mod search;
|
||||||
pub mod substitute;
|
pub mod substitute;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
insert::NormalBefore,
|
||||||
motion::Motion,
|
motion::Motion,
|
||||||
state::{Mode, RecordedSelection, ReplayableAction},
|
state::{Mode, RecordedSelection, ReplayableAction},
|
||||||
visual::visual_motion,
|
visual::visual_motion,
|
||||||
Vim,
|
Vim,
|
||||||
};
|
};
|
||||||
use gpui::{actions, Action, AppContext};
|
use gpui::{actions, Action, AppContext, WindowContext};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
actions!(vim, [Repeat, EndRepeat,]);
|
actions!(vim, [Repeat, EndRepeat,]);
|
||||||
@ -17,6 +18,27 @@ fn should_replay(action: &Box<dyn Action>) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
|
||||||
|
match action {
|
||||||
|
ReplayableAction::Action(action) => {
|
||||||
|
if super::InsertBefore.id() == action.id()
|
||||||
|
|| super::InsertAfter.id() == action.id()
|
||||||
|
|| super::InsertFirstNonWhitespace.id() == action.id()
|
||||||
|
|| super::InsertEndOfLine.id() == action.id()
|
||||||
|
{
|
||||||
|
Some(super::InsertBefore.boxed_clone())
|
||||||
|
} else if super::InsertLineAbove.id() == action.id()
|
||||||
|
|| super::InsertLineBelow.id() == action.id()
|
||||||
|
{
|
||||||
|
Some(super::InsertLineBelow.boxed_clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplayableAction::Insertion { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn init(cx: &mut AppContext) {
|
pub(crate) fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(|_: &mut Workspace, _: &EndRepeat, cx| {
|
cx.add_action(|_: &mut Workspace, _: &EndRepeat, cx| {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
@ -28,127 +50,156 @@ pub(crate) fn init(cx: &mut AppContext) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.add_action(|_: &mut Workspace, _: &Repeat, cx| {
|
cx.add_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
|
||||||
let Some((actions, editor, selection)) = Vim::update(cx, |vim, cx| {
|
}
|
||||||
let actions = vim.workspace_state.recorded_actions.clone();
|
|
||||||
let Some(editor) = vim.active_editor.clone() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let count = vim.take_count();
|
|
||||||
|
|
||||||
vim.workspace_state.replaying = true;
|
pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
|
||||||
|
let Some((mut actions, editor, selection)) = Vim::update(cx, |vim, cx| {
|
||||||
let selection = vim.workspace_state.recorded_selection.clone();
|
let actions = vim.workspace_state.recorded_actions.clone();
|
||||||
match selection {
|
let Some(editor) = vim.active_editor.clone() else {
|
||||||
RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
|
return None;
|
||||||
vim.workspace_state.recorded_count = None;
|
|
||||||
vim.switch_mode(Mode::Visual, false, cx)
|
|
||||||
}
|
|
||||||
RecordedSelection::VisualLine { .. } => {
|
|
||||||
vim.workspace_state.recorded_count = None;
|
|
||||||
vim.switch_mode(Mode::VisualLine, false, cx)
|
|
||||||
}
|
|
||||||
RecordedSelection::VisualBlock { .. } => {
|
|
||||||
vim.workspace_state.recorded_count = None;
|
|
||||||
vim.switch_mode(Mode::VisualBlock, false, cx)
|
|
||||||
}
|
|
||||||
RecordedSelection::None => {
|
|
||||||
if let Some(count) = count {
|
|
||||||
vim.workspace_state.recorded_count = Some(count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(editor) = editor.upgrade(cx) {
|
|
||||||
editor.update(cx, |editor, _| {
|
|
||||||
editor.show_local_selections = false;
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((actions, editor, selection))
|
|
||||||
}) else {
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
let count = vim.take_count();
|
||||||
|
|
||||||
|
let selection = vim.workspace_state.recorded_selection.clone();
|
||||||
match selection {
|
match selection {
|
||||||
RecordedSelection::SingleLine { cols } => {
|
RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
|
||||||
if cols > 1 {
|
vim.workspace_state.recorded_count = None;
|
||||||
visual_motion(Motion::Right, Some(cols as usize - 1), cx)
|
vim.switch_mode(Mode::Visual, false, cx)
|
||||||
|
}
|
||||||
|
RecordedSelection::VisualLine { .. } => {
|
||||||
|
vim.workspace_state.recorded_count = None;
|
||||||
|
vim.switch_mode(Mode::VisualLine, false, cx)
|
||||||
|
}
|
||||||
|
RecordedSelection::VisualBlock { .. } => {
|
||||||
|
vim.workspace_state.recorded_count = None;
|
||||||
|
vim.switch_mode(Mode::VisualBlock, false, cx)
|
||||||
|
}
|
||||||
|
RecordedSelection::None => {
|
||||||
|
if let Some(count) = count {
|
||||||
|
vim.workspace_state.recorded_count = Some(count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RecordedSelection::Visual { rows, cols } => {
|
|
||||||
visual_motion(
|
|
||||||
Motion::Down {
|
|
||||||
display_lines: false,
|
|
||||||
},
|
|
||||||
Some(rows as usize),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
visual_motion(
|
|
||||||
Motion::StartOfLine {
|
|
||||||
display_lines: false,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
if cols > 1 {
|
|
||||||
visual_motion(Motion::Right, Some(cols as usize - 1), cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RecordedSelection::VisualBlock { rows, cols } => {
|
|
||||||
visual_motion(
|
|
||||||
Motion::Down {
|
|
||||||
display_lines: false,
|
|
||||||
},
|
|
||||||
Some(rows as usize),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
if cols > 1 {
|
|
||||||
visual_motion(Motion::Right, Some(cols as usize - 1), cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RecordedSelection::VisualLine { rows } => {
|
|
||||||
visual_motion(
|
|
||||||
Motion::Down {
|
|
||||||
display_lines: false,
|
|
||||||
},
|
|
||||||
Some(rows as usize),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
RecordedSelection::None => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let window = cx.window();
|
if let Some(editor) = editor.upgrade(cx) {
|
||||||
cx.app_context()
|
editor.update(cx, |editor, _| {
|
||||||
.spawn(move |mut cx| async move {
|
editor.show_local_selections = false;
|
||||||
for action in actions {
|
|
||||||
match action {
|
|
||||||
ReplayableAction::Action(action) => {
|
|
||||||
if should_replay(&action) {
|
|
||||||
window
|
|
||||||
.dispatch_action(editor.id(), action.as_ref(), &mut cx)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("window was closed"))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReplayableAction::Insertion {
|
|
||||||
text,
|
|
||||||
utf16_range_to_replace,
|
|
||||||
} => editor.update(&mut cx, |editor, cx| {
|
|
||||||
editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
|
|
||||||
}),
|
|
||||||
}?
|
|
||||||
}
|
|
||||||
window
|
|
||||||
.dispatch_action(editor.id(), &EndRepeat, &mut cx)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("window was closed"))
|
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
} else {
|
||||||
});
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((actions, editor, selection))
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match selection {
|
||||||
|
RecordedSelection::SingleLine { cols } => {
|
||||||
|
if cols > 1 {
|
||||||
|
visual_motion(Motion::Right, Some(cols as usize - 1), cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecordedSelection::Visual { rows, cols } => {
|
||||||
|
visual_motion(
|
||||||
|
Motion::Down {
|
||||||
|
display_lines: false,
|
||||||
|
},
|
||||||
|
Some(rows as usize),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
visual_motion(
|
||||||
|
Motion::StartOfLine {
|
||||||
|
display_lines: false,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
if cols > 1 {
|
||||||
|
visual_motion(Motion::Right, Some(cols as usize - 1), cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecordedSelection::VisualBlock { rows, cols } => {
|
||||||
|
visual_motion(
|
||||||
|
Motion::Down {
|
||||||
|
display_lines: false,
|
||||||
|
},
|
||||||
|
Some(rows as usize),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
if cols > 1 {
|
||||||
|
visual_motion(Motion::Right, Some(cols as usize - 1), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecordedSelection::VisualLine { rows } => {
|
||||||
|
visual_motion(
|
||||||
|
Motion::Down {
|
||||||
|
display_lines: false,
|
||||||
|
},
|
||||||
|
Some(rows as usize),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RecordedSelection::None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert internally uses repeat to handle counts
|
||||||
|
// vim doesn't treat 3a1 as though you literally repeated a1
|
||||||
|
// 3 times, instead it inserts the content thrice at the insert position.
|
||||||
|
if let Some(to_repeat) = repeatable_insert(&actions[0]) {
|
||||||
|
if let Some(ReplayableAction::Action(action)) = actions.last() {
|
||||||
|
if action.id() == NormalBefore.id() {
|
||||||
|
actions.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_actions = actions.clone();
|
||||||
|
actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());
|
||||||
|
|
||||||
|
let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
|
||||||
|
|
||||||
|
// if we came from insert mode we're just doing repititions 2 onwards.
|
||||||
|
if from_insert_mode {
|
||||||
|
count -= 1;
|
||||||
|
new_actions[0] = actions[0].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 1..count {
|
||||||
|
new_actions.append(actions.clone().as_mut());
|
||||||
|
}
|
||||||
|
new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));
|
||||||
|
actions = new_actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
|
||||||
|
let window = cx.window();
|
||||||
|
cx.app_context()
|
||||||
|
.spawn(move |mut cx| async move {
|
||||||
|
for action in actions {
|
||||||
|
match action {
|
||||||
|
ReplayableAction::Action(action) => {
|
||||||
|
if should_replay(&action) {
|
||||||
|
window
|
||||||
|
.dispatch_action(editor.id(), action.as_ref(), &mut cx)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("window was closed"))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplayableAction::Insertion {
|
||||||
|
text,
|
||||||
|
utf16_range_to_replace,
|
||||||
|
} => editor.update(&mut cx, |editor, cx| {
|
||||||
|
editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
|
||||||
|
}),
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
window
|
||||||
|
.dispatch_action(editor.id(), &EndRepeat, &mut cx)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("window was closed"))
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -15,8 +15,8 @@ use anyhow::Result;
|
|||||||
use collections::{CommandPaletteFilter, HashMap};
|
use collections::{CommandPaletteFilter, HashMap};
|
||||||
use editor::{movement, Editor, EditorMode, Event};
|
use editor::{movement, Editor, EditorMode, Event};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext,
|
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, Action,
|
||||||
Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||||
};
|
};
|
||||||
use language::{CursorShape, Point, Selection, SelectionGoal};
|
use language::{CursorShape, Point, Selection, SelectionGoal};
|
||||||
pub use mode_indicator::ModeIndicator;
|
pub use mode_indicator::ModeIndicator;
|
||||||
@ -284,6 +284,16 @@ impl Vim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
|
||||||
|
if self.workspace_state.recording {
|
||||||
|
self.workspace_state
|
||||||
|
.recorded_actions
|
||||||
|
.push(ReplayableAction::Action(action.boxed_clone()));
|
||||||
|
self.workspace_state.recording = false;
|
||||||
|
self.workspace_state.stop_recording_after_next_action = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn record_current_action(&mut self, cx: &mut WindowContext) {
|
pub fn record_current_action(&mut self, cx: &mut WindowContext) {
|
||||||
self.start_recording(cx);
|
self.start_recording(cx);
|
||||||
self.stop_recording();
|
self.stop_recording();
|
||||||
|
36
crates/vim/test_data/test_insert_with_counts.json
Normal file
36
crates/vim/test_data/test_insert_with_counts.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{"Put":{"state":"ˇhello\n"}}
|
||||||
|
{"Key":"5"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"-"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"----ˇ-hello\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"ˇhello\n"}}
|
||||||
|
{"Key":"5"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"-"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"h----ˇ-ello\n","mode":"Normal"}}
|
||||||
|
{"Key":"4"}
|
||||||
|
{"Key":"shift-i"}
|
||||||
|
{"Key":"-"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"---ˇ-h-----ello\n","mode":"Normal"}}
|
||||||
|
{"Key":"3"}
|
||||||
|
{"Key":"shift-a"}
|
||||||
|
{"Key":"-"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"----h-----ello--ˇ-\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"ˇhello\n"}}
|
||||||
|
{"Key":"3"}
|
||||||
|
{"Key":"o"}
|
||||||
|
{"Key":"o"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"hello\noi\noi\noˇi\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"ˇhello\n"}}
|
||||||
|
{"Key":"3"}
|
||||||
|
{"Key":"shift-o"}
|
||||||
|
{"Key":"o"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"oi\noi\noˇi\nhello\n","mode":"Normal"}}
|
23
crates/vim/test_data/test_insert_with_repeat.json
Normal file
23
crates/vim/test_data/test_insert_with_repeat.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{"Put":{"state":"ˇhello\n"}}
|
||||||
|
{"Key":"3"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"-"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"--ˇ-hello\n","mode":"Normal"}}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"----ˇ--hello\n","mode":"Normal"}}
|
||||||
|
{"Key":"2"}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"-----ˇ---hello\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"ˇhello\n"}}
|
||||||
|
{"Key":"2"}
|
||||||
|
{"Key":"o"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"hello\nkk\nkˇk\n","mode":"Normal"}}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"hello\nkk\nkk\nkk\nkˇk\n","mode":"Normal"}}
|
||||||
|
{"Key":"1"}
|
||||||
|
{"Key":"."}
|
||||||
|
{"Get":{"state":"hello\nkk\nkk\nkk\nkk\nkˇk\n","mode":"Normal"}}
|
Loading…
Reference in New Issue
Block a user