use super::keys::KeyId;
use super::registry::{Action, KeybindingsManager};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeybindingConflict {
pub key: KeyId,
pub actions: Vec<Action>,
}
impl fmt::Display for KeybindingConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Key '{}' is bound to: {}",
self.key,
self.actions
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
}
pub fn detect_conflicts(manager: &KeybindingsManager) -> Vec<KeybindingConflict> {
let mut key_to_actions: HashMap<KeyId, Vec<Action>> = HashMap::new();
for (action, keys) in manager.all_bindings() {
for key in keys {
key_to_actions.entry(key.clone()).or_default().push(*action);
}
}
key_to_actions
.into_iter()
.filter(|(_, actions)| actions.len() > 1)
.map(|(key, actions)| KeybindingConflict { key, actions })
.collect()
}
pub fn validate_user_bindings(
user_bindings: &HashMap<Action, Vec<KeyId>>,
defaults: &HashMap<Action, Vec<KeyId>>,
) -> Vec<String> {
let mut warnings = Vec::new();
let mut default_keys: HashMap<KeyId, Action> = HashMap::new();
for (action, keys) in defaults {
for key in keys {
default_keys.insert(key.clone(), *action);
}
}
let essential_actions = [Action::Quit, Action::Cancel, Action::Submit];
for (user_action, user_keys) in user_bindings {
for user_key in user_keys {
if let Some(default_action) = default_keys.get(user_key) {
if essential_actions.contains(default_action) && *user_action != *default_action {
warnings.push(format!(
"Warning: Binding '{}' to '{}' shadows essential binding '{}' (key: {})",
user_key, user_action, default_action, user_key,
));
}
}
}
}
warnings
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keybindings::keys::parse_key_id;
#[test]
fn test_no_conflicts_in_defaults() {
let mgr = KeybindingsManager::new();
let conflicts = detect_conflicts(&mgr);
for conflict in &conflicts {
eprintln!("Conflict: {}", conflict);
}
}
#[test]
fn test_detect_conflict() {
let mut mgr = KeybindingsManager::new();
let mut config = HashMap::new();
config.insert(
"Quit".to_string(),
vec!["Ctrl+c".to_string(), "Ctrl+x".to_string()],
);
mgr.set_user_bindings(&config);
let conflicts = detect_conflicts(&mgr);
assert!(conflicts
.iter()
.all(|c| !c.actions.contains(&Action::Quit) || c.actions.len() <= 1));
}
#[test]
fn test_validate_essential_shadowing() {
let mgr = KeybindingsManager::new();
let user_keys = vec![parse_key_id("Ctrl+c").unwrap()];
let mut user_bindings = HashMap::new();
user_bindings.insert(Action::OpenModelSelect, user_keys);
let warnings = validate_user_bindings(&user_bindings, mgr.all_bindings());
assert!(!warnings.is_empty());
assert!(warnings[0].contains("shadows essential"));
}
}