extern crate self as escriba_mode;
use escriba_core::{Mode, Operator};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PendingOp {
pub count: Option<u32>,
pub operator: Option<Operator>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(tag = "mode")]
pub enum ModalState {
Normal { pending: PendingOp },
Insert,
Visual,
VisualLine,
Command { minibuffer: String },
}
impl Default for ModalState {
fn default() -> Self {
Self::Normal {
pending: PendingOp::default(),
}
}
}
impl ModalState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn mode(&self) -> Mode {
match self {
Self::Normal { .. } => Mode::Normal,
Self::Insert => Mode::Insert,
Self::Visual => Mode::Visual,
Self::VisualLine => Mode::VisualLine,
Self::Command { .. } => Mode::Command,
}
}
pub fn enter_normal(&mut self) {
*self = Self::Normal {
pending: PendingOp::default(),
};
}
pub fn enter_insert(&mut self) {
*self = Self::Insert;
}
pub fn enter_visual(&mut self) {
*self = Self::Visual;
}
pub fn enter_visual_line(&mut self) {
*self = Self::VisualLine;
}
pub fn enter_command(&mut self) {
*self = Self::Command {
minibuffer: String::new(),
};
}
pub fn escape(&mut self) {
self.enter_normal();
}
pub fn enter(&mut self, mode: Mode) {
match mode {
Mode::Normal => self.enter_normal(),
Mode::Insert => self.enter_insert(),
Mode::Visual => self.enter_visual(),
Mode::VisualLine => self.enter_visual_line(),
Mode::Command => self.enter_command(),
}
}
pub fn set_operator(&mut self, op: Operator) {
if let Self::Normal { pending } = self {
pending.operator = Some(op);
}
}
pub fn append_count(&mut self, digit: u32) {
if let Self::Normal { pending } = self {
let n = pending.count.unwrap_or(0);
pending.count = Some(n.saturating_mul(10).saturating_add(digit));
}
}
#[must_use]
pub const fn pending_count(&self) -> Option<u32> {
match self {
Self::Normal { pending } => pending.count,
_ => None,
}
}
#[must_use]
pub const fn pending_operator(&self) -> Option<Operator> {
match self {
Self::Normal { pending } => pending.operator,
_ => None,
}
}
#[must_use]
pub fn consume_count(&mut self) -> u32 {
match self {
Self::Normal { pending } => pending.count.take().unwrap_or(1).max(1),
_ => 1,
}
}
pub fn clear_count(&mut self) {
if let Self::Normal { pending } = self {
pending.count = None;
}
}
#[must_use]
pub fn consume_operator(&mut self) -> Option<Operator> {
match self {
Self::Normal { pending } => pending.operator.take(),
_ => None,
}
}
#[must_use]
pub fn minibuffer(&self) -> &str {
match self {
Self::Command { minibuffer } => minibuffer,
_ => "",
}
}
pub fn push_minibuffer(&mut self, ch: char) {
if let Self::Command { minibuffer } = self {
minibuffer.push(ch);
}
}
pub fn pop_minibuffer(&mut self) -> Option<char> {
match self {
Self::Command { minibuffer } => minibuffer.pop(),
_ => None,
}
}
pub fn push_minibuffer_str(&mut self, s: &str) {
if let Self::Command { minibuffer } = self {
minibuffer.push_str(s);
}
}
pub fn clear_minibuffer(&mut self) {
if let Self::Command { minibuffer } = self {
minibuffer.clear();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_clean_normal() {
let s = ModalState::new();
assert_eq!(s.mode(), Mode::Normal);
assert_eq!(s.pending_count(), None);
assert_eq!(s.pending_operator(), None);
}
#[test]
fn enter_transitions_set_mode() {
let mut s = ModalState::new();
s.enter_insert();
assert_eq!(s.mode(), Mode::Insert);
s.enter_visual();
assert_eq!(s.mode(), Mode::Visual);
s.enter_visual_line();
assert_eq!(s.mode(), Mode::VisualLine);
s.enter_command();
assert_eq!(s.mode(), Mode::Command);
s.escape();
assert_eq!(s.mode(), Mode::Normal);
}
#[test]
fn enter_by_mode_value_dispatches() {
for m in [
Mode::Normal,
Mode::Insert,
Mode::Visual,
Mode::VisualLine,
Mode::Command,
] {
let mut s = ModalState::new();
s.enter(m);
assert_eq!(s.mode(), m);
}
}
#[test]
fn leaving_normal_structurally_drops_pending() {
let mut s = ModalState::new();
s.set_operator(Operator::Delete);
s.append_count(5);
assert_eq!(s.pending_operator(), Some(Operator::Delete));
assert_eq!(s.pending_count(), Some(5));
s.enter_insert();
assert_eq!(s.pending_operator(), None);
assert_eq!(s.pending_count(), None);
}
#[test]
fn pending_ops_are_noops_outside_normal() {
let mut s = ModalState::new();
s.enter_insert();
s.set_operator(Operator::Yank);
s.append_count(9);
assert_eq!(s.pending_operator(), None);
assert_eq!(s.pending_count(), None);
assert_eq!(s.consume_count(), 1, "no count exists outside Normal");
assert_eq!(s.consume_operator(), None);
}
#[test]
fn minibuffer_is_noop_outside_command() {
let mut s = ModalState::new();
s.enter_insert();
s.push_minibuffer('w');
assert_eq!(s.minibuffer(), "", "insert mode has no minibuffer");
assert_eq!(s.pop_minibuffer(), None);
}
#[test]
fn entering_command_clears_prior_minibuffer() {
let mut s = ModalState::new();
s.enter_command();
s.push_minibuffer('q');
assert_eq!(s.minibuffer(), "q");
s.enter_command();
assert_eq!(s.minibuffer(), "");
}
#[test]
fn normal_resets_pending_state() {
let mut s = ModalState::new();
s.enter_insert();
s.set_operator(Operator::Delete);
s.append_count(5);
s.enter_normal();
assert!(s.pending_count().is_none());
assert!(s.pending_operator().is_none());
}
#[test]
fn count_accumulates() {
let mut s = ModalState::new();
s.append_count(5);
s.append_count(3);
assert_eq!(s.consume_count(), 53);
assert_eq!(s.consume_count(), 1); }
#[test]
fn operator_round_trip() {
let mut s = ModalState::new();
s.set_operator(Operator::Yank);
assert_eq!(s.consume_operator(), Some(Operator::Yank));
assert_eq!(s.consume_operator(), None);
}
#[test]
fn minibuffer_append_pop() {
let mut s = ModalState::new();
s.enter_command();
s.push_minibuffer('w');
assert_eq!(s.minibuffer(), "w");
assert_eq!(s.pop_minibuffer(), Some('w'));
}
#[test]
fn minibuffer_str_and_clear() {
let mut s = ModalState::new();
s.enter_command();
s.push_minibuffer_str("__quit__");
assert!(s.minibuffer().contains("__quit__"));
s.clear_minibuffer();
assert_eq!(s.minibuffer(), "");
}
#[test]
fn serde_round_trip_per_variant() {
for s in [
ModalState::new(),
{
let mut n = ModalState::new();
n.append_count(12);
n.set_operator(Operator::Delete);
n
},
ModalState::Insert,
ModalState::Visual,
ModalState::VisualLine,
{
let mut c = ModalState::new();
c.enter_command();
c.push_minibuffer('x');
c
},
] {
let json = serde_json::to_string(&s).unwrap();
let back: ModalState = serde_json::from_str(&json).unwrap();
assert_eq!(s, back);
}
}
}