use std::fmt::{self, Debug, Formatter};
use super::{Operator, VimOperation};
#[derive(Clone, Default, Eq, PartialEq)]
pub struct RepeatState {
last_change: Option<RepeatableOperation>,
}
impl RepeatState {
#[must_use]
pub fn last_change(&self) -> Option<RepeatableOperation> {
self.last_change.clone()
}
pub fn record(&mut self, operation: RepeatableOperation) {
self.last_change = Some(operation);
}
}
impl Debug for RepeatState {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("RepeatState")
.field("shape", &RepeatStateShape::from_repeat_state(self))
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RepeatStateShape {
Empty,
Present(RepeatableOperationShape),
}
impl RepeatStateShape {
#[must_use]
pub fn from_repeat_state(state: &RepeatState) -> Self {
state
.last_change
.as_ref()
.map_or(Self::Empty, |operation| Self::Present(operation.shape()))
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct RepeatableOperation {
operation: VimOperation,
}
impl RepeatableOperation {
#[must_use]
pub fn from_operation(operation: VimOperation) -> Option<Self> {
match operation {
VimOperation::ApplyOperator {
operator: Operator::Delete | Operator::Change,
..
}
| VimOperation::Paste { .. } => Some(Self { operation }),
_ => None,
}
}
#[must_use]
pub fn operation(&self) -> VimOperation {
self.operation.clone()
}
#[must_use]
pub const fn shape(&self) -> RepeatableOperationShape {
match &self.operation {
VimOperation::ApplyOperator { operator, .. } => {
RepeatableOperationShape::Operator(*operator)
}
VimOperation::Paste { .. } => RepeatableOperationShape::Paste,
_ => RepeatableOperationShape::Unsupported,
}
}
}
impl Debug for RepeatableOperation {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("RepeatableOperation")
.field("shape", &self.shape())
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RepeatableOperationShape {
Operator(Operator),
Paste,
Unsupported,
}
#[cfg(test)]
mod tests {
use crate::vim::{
Count, Counted, Motion, NormalOperatorTarget, Operator, RepeatState, RepeatStateShape,
RepeatableOperation, VimOperation,
};
#[test]
fn repeat_accepts_buffer_changing_normal_operations() {
let operation = VimOperation::ApplyOperator {
count: Count::default(),
operator: Operator::Delete,
target: NormalOperatorTarget::Motion(Counted::once(Motion::Right)).into(),
};
assert_eq!(
RepeatableOperation::from_operation(operation.clone())
.map(|operation| operation.operation()),
Some(operation)
);
}
#[test]
fn repeat_rejects_yank_and_motion() {
let yank = VimOperation::ApplyOperator {
count: Count::default(),
operator: Operator::Yank,
target: NormalOperatorTarget::CurrentLine.into(),
};
assert!(RepeatableOperation::from_operation(yank).is_none());
assert!(
RepeatableOperation::from_operation(VimOperation::MoveCursor(Counted::once(
Motion::Right
)))
.is_none()
);
}
#[test]
fn repeat_debug_output_is_shape_only() {
let mut state = RepeatState::default();
state.record(
RepeatableOperation::from_operation(VimOperation::PrefilledCommandLine(String::from(
"secret",
)))
.unwrap_or_else(|| {
RepeatableOperation::from_operation(VimOperation::Paste {
count: Count::default(),
placement: crate::vim::PastePlacement::After,
})
.unwrap()
}),
);
let debug = format!("{state:?}");
assert!(debug.contains("RepeatState"));
assert!(!debug.contains("secret"));
assert!(matches!(
RepeatStateShape::from_repeat_state(&state),
RepeatStateShape::Present(_)
));
}
}