// src/config/binds/config.rs use serde::Deserialize; use std::collections::HashMap; use std::path::Path; use crossterm::event::{KeyCode, KeyModifiers}; #[derive(Debug, Deserialize, Default)] pub struct ColorsConfig { #[serde(default = "default_theme")] pub theme: String, } fn default_theme() -> String { "light".to_string() } #[derive(Debug, Deserialize)] pub struct Config { #[serde(rename = "keybindings")] pub keybindings: ModeKeybindings, #[serde(default)] pub colors: ColorsConfig, } #[derive(Debug, Deserialize)] pub struct ModeKeybindings { #[serde(default)] pub general: HashMap>, #[serde(default)] pub read_only: HashMap>, #[serde(default)] pub edit: HashMap>, #[serde(default)] pub highlight: HashMap>, #[serde(default)] pub command: HashMap>, #[serde(default)] pub common: HashMap>, #[serde(flatten)] pub global: HashMap>, } impl Config { /// Loads the configuration from "config.toml" in the client crate directory. pub fn load() -> Result> { let manifest_dir = env!("CARGO_MANIFEST_DIR"); let config_path = Path::new(manifest_dir).join("config.toml"); let config_str = std::fs::read_to_string(&config_path) .map_err(|e| format!("Failed to read config file at {:?}: {}", config_path, e))?; let config: Config = toml::from_str(&config_str)?; Ok(config) } pub fn get_general_action(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.general, key, modifiers) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } /// Common actions for Edit/Read-only modes pub fn get_common_action(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers) } /// Gets an action for a key in Read-Only mode, also checking common keybindings. pub fn get_read_only_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.read_only, key, modifiers) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } /// Gets an action for a key in Edit mode, also checking common keybindings. pub fn get_edit_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.edit, key, modifiers) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } /// Gets an action for a key in Highlight mode, also checking common/global keybindings. pub fn get_highlight_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.highlight, key, modifiers) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.read_only, key, modifiers)) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } /// Gets an action for a key in Command mode, also checking common keybindings. pub fn get_command_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.command, key, modifiers) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } /// Context-aware keybinding resolution pub fn get_action_for_current_context( &self, is_edit_mode: bool, command_mode: bool, key: KeyCode, modifiers: KeyModifiers ) -> Option<&str> { match (command_mode, is_edit_mode) { (true, _) => self.get_command_action_for_key(key, modifiers), (_, true) => self.get_edit_action_for_key(key, modifiers) .or_else(|| self.get_common_action(key, modifiers)), _ => self.get_read_only_action_for_key(key, modifiers) .or_else(|| self.get_common_action(key, modifiers)) // Add global bindings check for read-only mode .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)), } } /// Helper function to get an action for a key in a specific mode. pub fn get_action_for_key_in_mode<'a>( &self, mode_bindings: &'a HashMap>, key: KeyCode, modifiers: KeyModifiers, ) -> Option<&'a str> { for (action, bindings) in mode_bindings { for binding in bindings { if Self::matches_keybinding(binding, key, modifiers) { return Some(action.as_str()); } } } None } /// Checks if a sequence of keys matches any keybinding. pub fn matches_key_sequence(&self, sequence: &[KeyCode]) -> Option<&str> { if sequence.is_empty() { return None; } // Convert key sequence to a string (for simple character sequences). let sequence_str: String = sequence.iter().filter_map(|key| { if let KeyCode::Char(c) = key { Some(*c) } else { None } }).collect(); if sequence_str.is_empty() { return None; } // Check if this sequence matches any binding in the mode-specific sections. for (action, bindings) in &self.keybindings.read_only { for binding in bindings { if binding == &sequence_str { return Some(action); } } } for (action, bindings) in &self.keybindings.edit { for binding in bindings { if binding == &sequence_str { return Some(action); } } } for (action, bindings) in &self.keybindings.command { for binding in bindings { if binding == &sequence_str { return Some(action); } } } // Check common keybindings for (action, bindings) in &self.keybindings.common { for binding in bindings { if binding == &sequence_str { return Some(action); } } } // Finally check global bindings for (action, bindings) in &self.keybindings.global { for binding in bindings { if binding == &sequence_str { return Some(action); } } } None } /// Checks if a keybinding matches a key and modifiers. fn matches_keybinding( binding: &str, key: KeyCode, modifiers: KeyModifiers, ) -> bool { // For multi-character bindings without modifiers, handle them in matches_key_sequence. if binding.len() > 1 && !binding.contains('+') { return match binding.to_lowercase().as_str() { "left" => key == KeyCode::Left, "right" => key == KeyCode::Right, "up" => key == KeyCode::Up, "down" => key == KeyCode::Down, "esc" => key == KeyCode::Esc, "enter" => key == KeyCode::Enter, "delete" => key == KeyCode::Delete, "backspace" => key == KeyCode::Backspace, "tab" => key == KeyCode::Tab, "backtab" => key == KeyCode::BackTab, _ => false, }; } let parts: Vec<&str> = binding.split('+').collect(); let mut expected_modifiers = KeyModifiers::empty(); let mut expected_key = None; for part in parts { match part.to_lowercase().as_str() { "ctrl" => expected_modifiers |= KeyModifiers::CONTROL, "shift" => expected_modifiers |= KeyModifiers::SHIFT, "alt" => expected_modifiers |= KeyModifiers::ALT, "left" => expected_key = Some(KeyCode::Left), "right" => expected_key = Some(KeyCode::Right), "up" => expected_key = Some(KeyCode::Up), "down" => expected_key = Some(KeyCode::Down), "esc" => expected_key = Some(KeyCode::Esc), "enter" => expected_key = Some(KeyCode::Enter), "delete" => expected_key = Some(KeyCode::Delete), "backspace" => expected_key = Some(KeyCode::Backspace), "tab" => expected_key = Some(KeyCode::Tab), "backtab" => expected_key = Some(KeyCode::BackTab), ":" => expected_key = Some(KeyCode::Char(':')), part => { if part.len() == 1 { let c = part.chars().next().unwrap(); expected_key = Some(KeyCode::Char(c)); } } } } modifiers == expected_modifiers && Some(key) == expected_key } /// Gets an action for a command string. pub fn get_action_for_command(&self, command: &str) -> Option<&str> { // First check command mode bindings for (action, bindings) in &self.keybindings.command { for binding in bindings { if binding == command { return Some(action); } } } // Then check common bindings for (action, bindings) in &self.keybindings.common { for binding in bindings { if binding == command { return Some(action); } } } // Finally check global bindings for (action, bindings) in &self.keybindings.global { for binding in bindings { if binding == command { return Some(action); } } } None } /// Checks if a key is bound to entering Edit mode (before cursor). pub fn is_enter_edit_mode_before(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.read_only.get("enter_edit_mode_before") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { false } } /// Checks if a key is bound to entering Edit mode (after cursor). pub fn is_enter_edit_mode_after(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.read_only.get("enter_edit_mode_after") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { false } } /// Checks if a key is bound to entering Edit mode. pub fn is_enter_edit_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { self.is_enter_edit_mode_before(key, modifiers) || self.is_enter_edit_mode_after(key, modifiers) } /// Checks if a key is bound to exiting Edit mode. pub fn is_exit_edit_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.edit.get("exit_edit_mode") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { false } } /// Checks if a key is bound to entering Command mode. /// This method is no longer used in event.rs since we now handle command mode entry only in read-only mode directly. pub fn is_enter_command_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.command.get("enter_command_mode") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { false } } /// Checks if a key is bound to exiting Command mode. pub fn is_exit_command_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.command.get("exit_command_mode") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { false } } /// Checks if a key is bound to executing a command. pub fn is_command_execute(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.command.get("command_execute") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { // Fall back to Enter key if no command_execute is defined. key == KeyCode::Enter && modifiers.is_empty() } } /// Checks if a key is bound to backspacing in Command mode. pub fn is_command_backspace(&self, key: KeyCode, modifiers: KeyModifiers) -> bool { if let Some(bindings) = self.keybindings.command.get("command_backspace") { bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers)) } else { // Fall back to Backspace key if no command_backspace is defined. key == KeyCode::Backspace && modifiers.is_empty() } } /// Checks if a key is bound to a specific action. pub fn has_key_for_action(&self, action: &str, key_char: char) -> bool { // Check all mode-specific keybindings for the action if let Some(bindings) = self.keybindings.read_only.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } if let Some(bindings) = self.keybindings.edit.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } if let Some(bindings) = self.keybindings.command.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } if let Some(bindings) = self.keybindings.common.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } if let Some(bindings) = self.keybindings.global.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } false } /// This method handles all keybinding formats, both with and without + pub fn matches_key_sequence_generalized(&self, sequence: &[KeyCode]) -> Option<&str> { if sequence.is_empty() { return None; } // Get string representations of the sequence let sequence_str = sequence.iter() .map(|k| crate::config::binds::key_sequences::key_to_string(k)) .collect::>() .join(""); // Add the missing sequence_plus definition let sequence_plus = sequence.iter() .map(|k| crate::config::binds::key_sequences::key_to_string(k)) .collect::>() .join("+"); // Check for matches in all binding formats across all modes // First check read_only mode if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.read_only, &sequence_str, &sequence_plus, sequence) { return Some(action); } // Then check edit mode if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.edit, &sequence_str, &sequence_plus, sequence) { return Some(action); } // Then check command mode if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.command, &sequence_str, &sequence_plus, sequence) { return Some(action); } // Then check common keybindings if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.common, &sequence_str, &sequence_plus, sequence) { return Some(action); } // Finally check global bindings if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.global, &sequence_str, &sequence_plus, sequence) { return Some(action); } None } /// Helper method to check a specific mode's bindings against a key sequence fn check_bindings_for_sequence<'a>( &self, mode_bindings: &'a HashMap>, sequence_str: &str, sequence_plus: &str, sequence: &[KeyCode] ) -> Option<&'a str> { for (action, bindings) in mode_bindings { for binding in bindings { let normalized_binding = binding.to_lowercase(); // Check if binding matches any of our formats if normalized_binding == sequence_str || normalized_binding == sequence_plus { return Some(action); } // Special case for + format in bindings if binding.contains('+') { let normalized_sequence = sequence.iter() .map(|k| crate::config::binds::key_sequences::key_to_string(k)) .collect::>(); let binding_parts: Vec<&str> = binding.split('+').collect(); if binding_parts.len() == sequence.len() { let matches = binding_parts.iter().enumerate().all(|(i, part)| { part.to_lowercase() == normalized_sequence[i].to_lowercase() }); if matches { return Some(action); } } } } } None } /// Check if the current key sequence is a prefix of a longer binding pub fn is_key_sequence_prefix(&self, sequence: &[KeyCode]) -> bool { if sequence.is_empty() { return false; } // Get string representation of the sequence let sequence_str = sequence.iter() .map(|k| crate::config::binds::key_sequences::key_to_string(k)) .collect::>() .join(""); // Check in each mode if our sequence is a prefix if self.is_prefix_in_mode(&self.keybindings.read_only, &sequence_str, sequence) { return true; } if self.is_prefix_in_mode(&self.keybindings.edit, &sequence_str, sequence) { return true; } if self.is_prefix_in_mode(&self.keybindings.command, &sequence_str, sequence) { return true; } if self.is_prefix_in_mode(&self.keybindings.common, &sequence_str, sequence) { return true; } if self.is_prefix_in_mode(&self.keybindings.global, &sequence_str, sequence) { return true; } false } /// Helper method to check if a sequence is a prefix in a specific mode fn is_prefix_in_mode( &self, mode_bindings: &HashMap>, sequence_str: &str, sequence: &[KeyCode] ) -> bool { for (_, bindings) in mode_bindings { for binding in bindings { let normalized_binding = binding.to_lowercase(); // Check standard format if normalized_binding.starts_with(sequence_str) && normalized_binding.len() > sequence_str.len() { return true; } // Check + format if binding.contains('+') { let binding_parts: Vec<&str> = binding.split('+').collect(); let sequence_parts = sequence.iter() .map(|k| crate::config::binds::key_sequences::key_to_string(k)) .collect::>(); if binding_parts.len() > sequence_parts.len() { let prefix_matches = sequence_parts.iter().enumerate().all(|(i, part)| { binding_parts.get(i).map_or(false, |b| b.to_lowercase() == part.to_lowercase()) }); if prefix_matches { return true; } } } } } false } }