embers-client 0.1.0

Client rendering, input handling, configuration, and scripting support for Embers.
use std::collections::BTreeMap;

use crate::input::keyparse::KeySequence;

pub const NORMAL_MODE: &str = "normal";
pub const COPY_MODE: &str = "copy";
pub const SEARCH_MODE: &str = "search";
pub const SELECT_MODE: &str = "select";

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum FallbackPolicy {
    #[default]
    Passthrough,
    Ignore,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ModeSpec {
    pub name: String,
    pub fallback_policy: FallbackPolicy,
}

impl ModeSpec {
    pub fn new(name: impl Into<String>, fallback_policy: FallbackPolicy) -> Self {
        Self {
            name: name.into(),
            fallback_policy,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InputState {
    current_mode: String,
    pending_sequence: KeySequence,
}

impl Default for InputState {
    fn default() -> Self {
        Self {
            current_mode: NORMAL_MODE.to_owned(),
            pending_sequence: Vec::new(),
        }
    }
}

impl InputState {
    pub fn current_mode(&self) -> &str {
        &self.current_mode
    }

    pub fn pending_sequence(&self) -> &[crate::input::KeyToken] {
        &self.pending_sequence
    }

    pub fn set_mode(&mut self, mode: impl Into<String>) {
        let mode = mode.into();
        if self.current_mode != mode {
            self.current_mode = mode;
            self.pending_sequence.clear();
        }
    }

    pub fn clear_pending(&mut self) {
        self.pending_sequence.clear();
    }

    pub(crate) fn push_pending(&mut self, key: crate::input::KeyToken) {
        self.pending_sequence.push(key);
    }
}

pub fn builtin_modes() -> BTreeMap<String, ModeSpec> {
    BTreeMap::from([
        (
            NORMAL_MODE.to_owned(),
            ModeSpec::new(NORMAL_MODE, FallbackPolicy::Passthrough),
        ),
        (
            COPY_MODE.to_owned(),
            ModeSpec::new(COPY_MODE, FallbackPolicy::Ignore),
        ),
        (
            SEARCH_MODE.to_owned(),
            ModeSpec::new(SEARCH_MODE, FallbackPolicy::Ignore),
        ),
        (
            SELECT_MODE.to_owned(),
            ModeSpec::new(SELECT_MODE, FallbackPolicy::Ignore),
        ),
    ])
}

#[cfg(test)]
mod tests {
    use super::{InputState, builtin_modes};

    #[test]
    fn switching_modes_clears_pending_state() {
        let mut state = InputState::default();
        state.push_pending(crate::input::KeyToken::Char('a'));

        state.set_mode("copy");

        assert_eq!(state.current_mode(), "copy");
        assert!(state.pending_sequence().is_empty());
    }

    #[test]
    fn builtin_modes_include_normal_copy_search_and_select() {
        let modes = builtin_modes();
        assert!(modes.contains_key("normal"));
        assert!(modes.contains_key("copy"));
        assert!(modes.contains_key("search"));
        assert!(modes.contains_key("select"));
    }
}