vim . to replay (#2936)
Release Notes: - vim: Add `.` to replay ([#946](https://github.com/zed-industries/community/issues/946)) - vim: Fix `J` in visual mode, and with counts.
This commit is contained in:
commit
5d782b6cf0
25 changed files with 991 additions and 53 deletions
|
@ -18,17 +18,19 @@ use gpui::{
|
|||
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext,
|
||||
Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use language::{CursorShape, Selection, SelectionGoal};
|
||||
use language::{CursorShape, Point, Selection, SelectionGoal};
|
||||
pub use mode_indicator::ModeIndicator;
|
||||
use motion::Motion;
|
||||
use normal::normal_replace;
|
||||
use serde::Deserialize;
|
||||
use settings::{Setting, SettingsStore};
|
||||
use state::{EditorState, Mode, Operator, WorkspaceState};
|
||||
use std::sync::Arc;
|
||||
use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use visual::{visual_block_motion, visual_replace};
|
||||
use workspace::{self, Workspace};
|
||||
|
||||
use crate::state::ReplayableAction;
|
||||
|
||||
struct VimModeSetting(bool);
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
|
@ -102,6 +104,19 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
|
|||
return true;
|
||||
}
|
||||
if let Some(handled_by) = handled_by {
|
||||
Vim::update(cx, |vim, _| {
|
||||
if vim.workspace_state.recording {
|
||||
vim.workspace_state
|
||||
.recorded_actions
|
||||
.push(ReplayableAction::Action(handled_by.boxed_clone()));
|
||||
|
||||
if vim.workspace_state.stop_recording_after_next_action {
|
||||
vim.workspace_state.recording = false;
|
||||
vim.workspace_state.stop_recording_after_next_action = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Keystroke is handled by the vim system, so continue forward
|
||||
if handled_by.namespace() == "vim" {
|
||||
return true;
|
||||
|
@ -156,7 +171,12 @@ impl Vim {
|
|||
}
|
||||
Event::InputIgnored { text } => {
|
||||
Vim::active_editor_input_ignored(text.clone(), cx);
|
||||
Vim::record_insertion(text, None, cx)
|
||||
}
|
||||
Event::InputHandled {
|
||||
text,
|
||||
utf16_range_to_replace: range_to_replace,
|
||||
} => Vim::record_insertion(text, range_to_replace.clone(), cx),
|
||||
_ => {}
|
||||
}));
|
||||
|
||||
|
@ -176,6 +196,27 @@ impl Vim {
|
|||
self.sync_vim_settings(cx);
|
||||
}
|
||||
|
||||
fn record_insertion(
|
||||
text: &Arc<str>,
|
||||
range_to_replace: Option<Range<isize>>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
Vim::update(cx, |vim, _| {
|
||||
if vim.workspace_state.recording {
|
||||
vim.workspace_state
|
||||
.recorded_actions
|
||||
.push(ReplayableAction::Insertion {
|
||||
text: text.clone(),
|
||||
utf16_range_to_replace: range_to_replace,
|
||||
});
|
||||
if vim.workspace_state.stop_recording_after_next_action {
|
||||
vim.workspace_state.recording = false;
|
||||
vim.workspace_state.stop_recording_after_next_action = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn update_active_editor<S>(
|
||||
&self,
|
||||
cx: &mut WindowContext,
|
||||
|
@ -184,6 +225,71 @@ impl Vim {
|
|||
let editor = self.active_editor.clone()?.upgrade(cx)?;
|
||||
Some(editor.update(cx, update))
|
||||
}
|
||||
// ~, shift-j, x, shift-x, p
|
||||
// shift-c, shift-d, shift-i, i, a, o, shift-o, s
|
||||
// c, d
|
||||
// r
|
||||
|
||||
// TODO: shift-j?
|
||||
//
|
||||
pub fn start_recording(&mut self, cx: &mut WindowContext) {
|
||||
if !self.workspace_state.replaying {
|
||||
self.workspace_state.recording = true;
|
||||
self.workspace_state.recorded_actions = Default::default();
|
||||
self.workspace_state.recorded_count =
|
||||
if let Some(Operator::Number(number)) = self.active_operator() {
|
||||
Some(number)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let selections = self
|
||||
.active_editor
|
||||
.and_then(|editor| editor.upgrade(cx))
|
||||
.map(|editor| {
|
||||
let editor = editor.read(cx);
|
||||
(
|
||||
editor.selections.oldest::<Point>(cx),
|
||||
editor.selections.newest::<Point>(cx),
|
||||
)
|
||||
});
|
||||
|
||||
if let Some((oldest, newest)) = selections {
|
||||
self.workspace_state.recorded_selection = match self.state().mode {
|
||||
Mode::Visual if newest.end.row == newest.start.row => {
|
||||
RecordedSelection::SingleLine {
|
||||
cols: newest.end.column - newest.start.column,
|
||||
}
|
||||
}
|
||||
Mode::Visual => RecordedSelection::Visual {
|
||||
rows: newest.end.row - newest.start.row,
|
||||
cols: newest.end.column,
|
||||
},
|
||||
Mode::VisualLine => RecordedSelection::VisualLine {
|
||||
rows: newest.end.row - newest.start.row,
|
||||
},
|
||||
Mode::VisualBlock => RecordedSelection::VisualBlock {
|
||||
rows: newest.end.row.abs_diff(oldest.start.row),
|
||||
cols: newest.end.column.abs_diff(oldest.start.column),
|
||||
},
|
||||
_ => RecordedSelection::None,
|
||||
}
|
||||
} else {
|
||||
self.workspace_state.recorded_selection = RecordedSelection::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop_recording(&mut self) {
|
||||
if self.workspace_state.recording {
|
||||
self.workspace_state.stop_recording_after_next_action = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_current_action(&mut self, cx: &mut WindowContext) {
|
||||
self.start_recording(cx);
|
||||
self.stop_recording();
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
|
||||
let state = self.state();
|
||||
|
@ -247,6 +353,12 @@ impl Vim {
|
|||
}
|
||||
|
||||
fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
|
||||
if matches!(
|
||||
operator,
|
||||
Operator::Change | Operator::Delete | Operator::Replace
|
||||
) {
|
||||
self.start_recording(cx)
|
||||
};
|
||||
self.update_state(|state| state.operator_stack.push(operator));
|
||||
self.sync_vim_settings(cx);
|
||||
}
|
||||
|
@ -272,6 +384,12 @@ impl Vim {
|
|||
}
|
||||
|
||||
fn pop_number_operator(&mut self, cx: &mut WindowContext) -> Option<usize> {
|
||||
if self.workspace_state.replaying {
|
||||
if let Some(number) = self.workspace_state.recorded_count {
|
||||
return Some(number);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Operator::Number(number)) = self.active_operator() {
|
||||
self.pop_operator(cx);
|
||||
return Some(number);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue