use keymap_parser::parse_seq;
use serde::{
de::{MapAccess, Visitor},
Deserialize, Deserializer,
};
use std::{fmt, marker::PhantomData, ops::Deref};
use crate::{keymap::ToKeyMap, matcher::Matcher, KeyMap};
pub trait KeyMapConfig<T> {
fn keymap_config() -> Config<T>;
fn keymap_item(&self) -> Item;
fn bind(&self, _keys: &[KeyMap]) -> Self
where
Self: Clone,
{
self.clone()
}
}
#[derive(Debug)]
pub struct Config<T> {
pub items: Vec<(T, Item)>,
matcher: Matcher<usize>,
}
#[derive(Debug)]
pub struct DerivedConfig<T>(Config<T>);
impl<T> Deref for DerivedConfig<T> {
type Target = Config<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct Item {
pub keys: Vec<String>,
#[serde(default)]
pub description: String,
}
impl<T> Config<T> {
pub fn new(items: Vec<(T, Item)>) -> Self {
let mut matcher = Matcher::new();
items.iter().enumerate().for_each(|(index, (_, item))| {
item.keys
.iter()
.map(|keys| parse_seq(keys).expect("a valid key"))
.for_each(|keys| {
matcher.add(keys, index);
});
});
Self { items, matcher }
}
pub fn get<K: ToKeyMap>(&self, key: &K) -> Option<&T> {
self.get_by_keymap(&key.to_keymap().ok()?)
}
pub fn get_item<K: ToKeyMap>(&self, key: &K) -> Option<(&T, &Item)> {
self.get_item_by_keymap(&key.to_keymap().ok()?)
}
pub fn get_seq<K: ToKeyMap>(&self, keys: &[K]) -> Option<&T> {
let nodes = keys
.iter()
.map(|key| key.to_keymap().ok())
.collect::<Option<Vec<_>>>()?;
self.get_item_by_keymaps(&nodes).map(|(t, _)| t)
}
pub fn get_bound_seq<K: ToKeyMap>(&self, keys: &[K]) -> Option<T>
where
T: KeyMapConfig<T> + Clone,
{
let nodes = keys
.iter()
.map(|key| key.to_keymap().ok())
.collect::<Option<Vec<_>>>()?;
self.get_bound_item_by_keymaps(&nodes).map(|(t, _)| t)
}
pub fn get_item_by_keymap(&self, node: &KeyMap) -> Option<(&T, &Item)> {
self.get_item_by_keymaps(std::slice::from_ref(node))
}
pub fn get_by_keymap(&self, node: &KeyMap) -> Option<&T> {
self.get_item_by_keymap(node).map(|(t, _)| t)
}
pub fn get_bound<K: ToKeyMap>(&self, key: &K) -> Option<T>
where
T: KeyMapConfig<T> + Clone,
{
self.get_bound_by_keymap(&key.to_keymap().ok()?)
}
pub fn get_bound_by_keymap(&self, node: &KeyMap) -> Option<T>
where
T: KeyMapConfig<T> + Clone,
{
let keys = std::slice::from_ref(node);
self.get_item_by_keymaps(keys).map(|(t, _)| t.bind(keys))
}
pub fn get_item_by_keymaps(&self, keys: &[KeyMap]) -> Option<(&T, &Item)> {
self.matcher
.get(keys)
.map(|i| (&self.items[*i].0, &self.items[*i].1))
}
pub fn get_bound_item_by_keymaps(&self, keys: &[KeyMap]) -> Option<(T, &Item)>
where
T: KeyMapConfig<T> + Clone,
{
self.matcher
.get(keys)
.map(|i| (self.items[*i].0.bind(keys), &self.items[*i].1))
}
pub fn get_item_by_key_str(&self, key: &str) -> Option<(&T, &Item)> {
self.get_item_by_keymaps(parse_seq(key).ok()?.as_slice())
}
}
impl Item {
pub fn new(keys: Vec<String>, description: String) -> Self {
Self { keys, description }
}
}
impl<'de, T> Deserialize<'de> for Config<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ConfigVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for ConfigVisitor<T>
where
T: Deserialize<'de>,
{
type Value = Config<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map of items (key = T, value = Item)")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut items = Vec::new();
while let Some((t, item)) = map.next_entry::<T, Item>()? {
items.push((t, item));
}
Ok(Config::new(items))
}
}
deserializer.deserialize_map(ConfigVisitor(PhantomData))
}
}
impl<'de, T: KeyMapConfig<T> + PartialEq + Eq + std::hash::Hash> Deserialize<'de>
for DerivedConfig<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ConfigVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for ConfigVisitor<T>
where
T: Deserialize<'de> + KeyMapConfig<T> + PartialEq + Eq + std::hash::Hash,
{
type Value = DerivedConfig<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map of items with defaults from KeyMapConfig")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut config = T::keymap_config();
while let Some((t, mut item)) = map.next_entry::<T, Item>()? {
if let Some(pos) = config
.items
.iter()
.position(|(existing_key, _)| existing_key == &t)
{
if item.description.is_empty() {
item.description = config.items[pos].1.description.clone();
}
config.items[pos].1 = item;
} else {
config.items.push((t, item));
}
}
Ok(DerivedConfig(Config::new(config.items)))
}
}
deserializer.deserialize_map(ConfigVisitor(PhantomData))
}
}
#[cfg(test)]
mod tests {
use super::*;
const CONFIG: &str = r#"
Create = { keys = ["c"], description = "Create a new item" }
Delete = { keys = ["d", "d e", "@digit"], description = "Delete an item" }
"#;
#[derive(Debug, Deserialize, PartialEq, Eq, Hash)]
enum Action {
Create,
Update,
Delete,
}
impl KeyMapConfig<Action> for Action {
fn keymap_config() -> Config<Action> {
Config::new(vec![
(
Action::Create,
Item::new(vec!["c".into()], "Default Create".into()),
),
(
Action::Update,
Item::new(vec!["u".into()], "Default Update".into()),
),
(
Action::Delete,
Item::new(vec!["d".into()], "Default Delete".into()),
),
])
}
fn keymap_item(&self) -> Item {
match self {
Action::Create => Item::new(vec!["c".into()], "Default Create".into()),
Action::Update => Item::new(vec!["u".into()], "Default Update".into()),
Action::Delete => Item::new(vec!["d".into()], "Default Delete".into()),
}
}
}
#[test]
fn test_deserialize_string_keys() {
let config: Config<String> = toml::from_str(CONFIG).unwrap();
let (action, item) = config.get_item_by_key_str("c").unwrap();
assert_eq!(action, "Create");
assert_eq!(item.description, "Create a new item");
let (action, item) = config
.get_item_by_keymaps(&parse_seq("d e").unwrap())
.unwrap();
assert_eq!(action, "Delete");
assert_eq!(item.description, "Delete an item");
let (action, _) = config.get_item_by_key_str("1").unwrap();
assert_eq!(action, "Delete");
}
#[test]
fn test_deserialize_enum_keys() {
let config: Config<Action> = toml::from_str(CONFIG).unwrap();
let (action, _) = config.get_item_by_key_str("c").unwrap();
assert_eq!(*action, Action::Create);
assert!(config.get_item_by_key_str("u").is_none());
let (action, _) = config.get_item_by_key_str("d").unwrap();
assert_eq!(*action, Action::Delete);
let (action, _) = config.get_item_by_key_str("1").unwrap();
assert_eq!(*action, Action::Delete);
}
#[test]
fn test_deserialize_with_override() {
let config: DerivedConfig<Action> = toml::from_str(CONFIG).unwrap();
let (action, item) = config.get_item_by_key_str("c").unwrap();
assert_eq!(*action, Action::Create);
assert_eq!(item.description, "Create a new item");
let (action, item) = config.get_item_by_key_str("u").unwrap();
assert_eq!(*action, Action::Update);
assert_eq!(item.description, "Default Update");
let (action, item) = config.get_item_by_key_str("d").unwrap();
assert_eq!(*action, Action::Delete);
assert_eq!(item.description, "Delete an item");
}
#[test]
fn test_derive_config_merges_description_when_empty() {
let toml = r#"
Create = { keys = ["c"] }
"#;
let config: DerivedConfig<Action> = toml::from_str(toml).unwrap();
let (action, item) = config.get_item_by_key_str("c").unwrap();
assert_eq!(*action, Action::Create);
assert_eq!(item.description, "Default Create");
}
}