#![allow(clippy::missing_panics_doc)]
use std::sync::RwLock;
use {
reovim_driver_input::{
ExtensionMap, KeyEvent, KeySequence, ModeKeyResolver, ModeState, ModeTransition,
ResolveContext, ResolveInput, ResolveResult, SessionApiDyn,
},
reovim_driver_session::OperatorPendingState,
reovim_kernel::api::v1::{ModeId, Position},
};
use {
super::operator_common::{
KeymapAction, OperatorState, OperatorType, apply_keymap_policy, build_cancelled,
build_operator_execute, is_count_digit, is_escape, is_inclusive_motion,
is_line_operator_key, is_linewise_motion, is_word_forward_motion,
},
crate::{
modes::VimMode,
session_state::{
ChangeType, LastChange, OperatorType as SessionOperatorType, PendingMotion,
},
},
};
pub struct VimDeleteResolver {
mode_id: ModeId,
parent_mode_id: ModeId,
pub state: RwLock<OperatorState>,
}
impl VimDeleteResolver {
#[must_use]
pub fn new() -> Self {
Self {
mode_id: VimMode::DELETE_ID,
parent_mode_id: VimMode::NORMAL_ID,
state: RwLock::new(OperatorState::new(OperatorType::Delete)),
}
}
#[must_use]
pub fn with_context(count: Option<usize>, register: Option<char>) -> Self {
Self {
mode_id: VimMode::DELETE_ID,
parent_mode_id: VimMode::NORMAL_ID,
state: RwLock::new(OperatorState::with_context(OperatorType::Delete, count, register)),
}
}
#[cfg(test)]
pub fn state(&self) -> OperatorState {
self.state.read().expect("lock poisoned").clone()
}
fn clear_state(&self) {
self.state.write().expect("lock poisoned").reset();
}
}
impl Default for VimDeleteResolver {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl ModeKeyResolver for VimDeleteResolver {
fn resolve_with_keymap(
&self,
key: &KeyEvent,
_state: &mut ModeState,
input: &ResolveInput<'_>,
) -> ResolveResult {
if is_escape(key) {
self.clear_state();
return ResolveResult::ModeTransition(build_cancelled());
}
let mut state = self.state.write().expect("lock poisoned");
if is_count_digit(key, state.has_motion_count()) {
state.accumulate_motion_count(key);
return ResolveResult::Pending;
}
if is_line_operator_key(key, OperatorType::Delete) {
let count = state.operator_count;
let motion_count = state.take_motion_count().unwrap_or(1);
let register = state.register;
state.clear_keys();
drop(state);
return ResolveResult::ModeTransition(ModeTransition::Pop {
result: Some(build_operator_execute(
OperatorType::Delete,
Position::new(0, 0),
Position::new(0, 0),
true,
Some(count.unwrap_or(1) * motion_count),
register,
)),
});
}
state.push_key(*key);
let keys = state.keys();
let lookup_state = {
let state = input.keymap.query(input.mode, &keys);
if matches!(state, reovim_driver_input::KeyLookupState::NotFound) {
input.keymap.query(&self.parent_mode_id, &keys)
} else {
state
}
};
match apply_keymap_policy(&lookup_state) {
KeymapAction::Execute(cmd) => {
let explicit_count = state.explicit_count();
let _motion_count = state.take_motion_count();
state.clear_keys();
drop(state);
let ctx = ResolveContext {
count: explicit_count,
register: None,
keys,
metadata: std::collections::HashMap::new(),
};
ResolveResult::Execute(cmd, ctx)
}
KeymapAction::Pending => {
drop(state);
ResolveResult::Pending
}
KeymapAction::Cancel => {
state.clear_keys();
drop(state);
self.clear_state();
ResolveResult::ModeTransition(build_cancelled())
}
}
}
#[allow(clippy::too_many_lines)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn resolve_with_session(
&self,
key: &KeyEvent,
_mstate: &mut ModeState,
input: &ResolveInput<'_>,
session: &mut dyn SessionApiDyn,
_shared_extensions: &mut ExtensionMap,
client_extensions: &mut ExtensionMap,
) -> ResolveResult {
tracing::debug!(key = ?key, "delete resolver: resolve_with_session");
if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
vim.record_repeat_key(*key);
}
if is_escape(key) {
self.clear_state();
return ResolveResult::ModeTransition(build_cancelled());
}
let mut state = self.state.write().expect("lock poisoned");
if !state.initialized {
if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
state.operator_count = vim.pending_count.take();
state.register = vim.pending_register.take();
tracing::debug!(
operator_count = ?state.operator_count,
register = ?state.register,
"delete resolver: initialized from VimSessionState"
);
}
state.initialized = true;
}
if is_count_digit(key, state.has_motion_count()) {
state.accumulate_motion_count(key);
return ResolveResult::Pending;
}
if is_line_operator_key(key, OperatorType::Delete) {
let count = state.operator_count;
let motion_count = state.take_motion_count().unwrap_or(1);
let register = state.register;
state.clear_keys();
drop(state);
let (start, end) = session.cursor_position().map_or_else(
|| {
(Position::new(0, 0), Position::new(0, 0))
},
|cursor_pos| {
let start = Position::new(cursor_pos.line, 0);
let total_count = count.unwrap_or(1) * motion_count;
let end_line = cursor_pos.line + total_count - 1;
let end = Position::new(end_line, 0);
(start, end)
},
);
if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
vim.last_change = Some(LastChange {
change_type: ChangeType::OperatorMotion {
operator: SessionOperatorType::Delete,
linewise: true,
},
count,
register,
keys: Vec::new(),
});
vim.finish_repeat_recording();
}
return ResolveResult::ModeTransition(ModeTransition::Pop {
result: Some(build_operator_execute(
OperatorType::Delete,
start,
end,
true, Some(count.unwrap_or(1) * motion_count),
register,
)),
});
}
state.push_key(*key);
let keys = state.keys();
let lookup_state = {
let state = input.keymap.query(input.mode, &keys);
if matches!(state, reovim_driver_input::KeyLookupState::NotFound) {
input.keymap.query(&self.parent_mode_id, &keys)
} else {
state
}
};
match apply_keymap_policy(&lookup_state) {
KeymapAction::Execute(cmd) => {
let linewise = is_linewise_motion(&cmd);
let explicit_count = state.explicit_count();
tracing::debug!(
operator_count = ?state.operator_count,
motion_count = ?state.motion_count,
explicit_count = ?explicit_count,
cmd = %cmd,
linewise,
"delete resolver: executing motion"
);
let _motion_count = state.take_motion_count();
state.clear_keys();
if let Some(start_pos) = session.cursor_position() {
state.set_start_position(start_pos);
}
let inclusive = !linewise && is_inclusive_motion(&cmd);
let word_forward = !linewise && is_word_forward_motion(&cmd);
if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
vim.pending_motion =
Some(PendingMotion::new(linewise, inclusive, word_forward));
}
drop(state);
let ctx = ResolveContext {
count: explicit_count,
register: None,
keys,
metadata: std::collections::HashMap::new(),
};
ResolveResult::Execute(cmd, ctx)
}
KeymapAction::Pending => {
drop(state);
ResolveResult::Pending
}
KeymapAction::Cancel => {
state.clear_keys();
drop(state);
self.clear_state();
ResolveResult::ModeTransition(build_cancelled())
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn on_command_complete(
&self,
session: &mut dyn SessionApiDyn,
_shared_extensions: &mut ExtensionMap,
client_extensions: &mut ExtensionMap,
) -> Option<ModeTransition> {
let state = self.state.read().expect("lock poisoned");
let count = state.operator_count;
let register = state.register;
drop(state);
if let Some(op_state) = client_extensions.get_mut::<OperatorPendingState>()
&& let Some(textobj_range) = op_state.take_textobj_range()
{
tracing::debug!(
range_start = ?textobj_range.start,
range_end = ?textobj_range.end,
linewise = textobj_range.is_linewise,
"delete resolver: using text object range"
);
if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
vim.last_change = Some(LastChange {
change_type: ChangeType::OperatorTextObject {
operator: SessionOperatorType::Delete,
linewise: textobj_range.is_linewise,
},
count,
register,
keys: Vec::new(),
});
vim.finish_repeat_recording();
}
self.clear_state();
return Some(ModeTransition::Pop {
result: Some(build_operator_execute(
OperatorType::Delete,
textobj_range.start,
textobj_range.end,
textobj_range.is_linewise,
count,
register,
)),
});
}
let vim = client_extensions.get_mut::<crate::VimSessionState>()?;
let _ = vim.pending_motion.as_ref()?;
let state = self.state.read().expect("lock poisoned");
let start_pos = state.start_position?;
drop(state);
let end_pos = session.cursor_position()?;
if start_pos == end_pos {
return None;
}
let motion = vim.pending_motion.take()?;
let (range_start, range_end) = {
let (start, end) = if start_pos <= end_pos {
(start_pos, end_pos)
} else {
(end_pos, start_pos)
};
if !motion.linewise && motion.inclusive {
(start, Position::new(end.line, end.column + 1))
} else {
(start, end)
}
};
if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
vim.last_change = Some(LastChange {
change_type: ChangeType::OperatorMotion {
operator: SessionOperatorType::Delete,
linewise: motion.linewise,
},
count,
register,
keys: Vec::new(),
});
vim.finish_repeat_recording();
}
self.clear_state();
Some(ModeTransition::Pop {
result: Some(build_operator_execute(
OperatorType::Delete,
range_start,
range_end,
motion.linewise,
count,
register,
)),
})
}
fn mode_id(&self) -> &ModeId {
&self.mode_id
}
fn inherits_from(&self) -> Option<&ModeId> {
Some(&self.parent_mode_id)
}
fn pending_keys(&self) -> KeySequence {
self.state.read().expect("lock poisoned").keys()
}
fn reset(&mut self) {
self.state.write().expect("lock poisoned").reset();
}
}