project structure redesign

This commit is contained in:
filipriec
2025-02-25 23:20:29 +01:00
parent 08bbd9bd29
commit 5010b73bab
15 changed files with 67 additions and 14 deletions

134
client/src/config/config.rs Normal file
View File

@@ -0,0 +1,134 @@
// client/src/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 {
pub keybindings: HashMap<String, Vec<String>>,
#[serde(default)]
pub colors: ColorsConfig,
}
impl Config {
/// Loads the configuration from "config.toml" in the client crate directory.
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
// env!("CARGO_MANIFEST_DIR") resolves to the directory where the Cargo.toml of
// the client crate is located.
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_action_for_key(
&self,
key: KeyCode,
modifiers: KeyModifiers,
) -> Option<&str> {
for (action, bindings) in &self.keybindings {
// Skip mode toggle actions
if action == "enter_edit_mode" || action == "exit_edit_mode" {
continue;
}
for binding in bindings {
if Self::matches_keybinding(binding, key, modifiers) {
return Some(action);
}
}
}
None
}
fn matches_keybinding(
binding: &str,
key: KeyCode,
modifiers: KeyModifiers,
) -> bool {
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,
"esc" => expected_key = Some(KeyCode::Esc),
":" => 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
}
pub fn get_action_for_command(&self, command: &str) -> Option<&str> {
for (action, bindings) in &self.keybindings {
for binding in bindings {
if binding.starts_with(':') && binding.trim_start_matches(':') == command {
return Some(action);
}
}
}
None
}
pub fn is_enter_edit_mode_before(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
if let Some(bindings) = self.keybindings.get("enter_edit_mode_before") {
bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers))
} else {
false
}
}
pub fn is_enter_edit_mode_after(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
if let Some(bindings) = self.keybindings.get("enter_edit_mode_after") {
bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers))
} else {
false
}
}
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)
}
pub fn is_exit_edit_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
if let Some(bindings) = self.keybindings.get("exit_edit_mode") {
bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers))
} else {
false
}
}
pub fn has_key_for_action(&self, action: &str, key_char: char) -> bool {
if let Some(bindings) = self.keybindings.get(action) {
for binding in bindings {
if binding == &key_char.to_string() {
return true;
}
}
}
false
}
}