Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ impl App {

// Validate git repo
if !GitCommands::is_valid_repo(&repo_path) {
anyhow::bail!(
"'{}' is not a git repository",
repo_path.display()
);
anyhow::bail!("'{}' is not a git repository", repo_path.display());
}

Ok(Self { config, repo_path })
Expand All @@ -32,8 +29,7 @@ impl App {
self.config.app_state.add_recent_repo(&repo_str);
let _ = self.config.save_state();

let git = GitCommands::new(&self.repo_path)
.context("Failed to initialize git commands")?;
let git = GitCommands::new(&self.repo_path).context("Failed to initialize git commands")?;

let mut gui = Gui::new(self.config, git)?;
gui.run()?;
Expand Down
92 changes: 88 additions & 4 deletions src/config/keybindings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
Expand All @@ -10,6 +11,8 @@ pub struct KeybindingConfig {
pub branches: BranchesKeybinding,
pub commits: CommitsKeybinding,
pub stash: StashKeybinding,
#[serde(default)]
pub overrides: HashMap<String, String>,
#[serde(rename = "commitMessage")]
pub commit_message: CommitMessageKeybinding,
}
Expand All @@ -23,11 +26,89 @@ impl Default for KeybindingConfig {
branches: BranchesKeybinding::default(),
commits: CommitsKeybinding::default(),
stash: StashKeybinding::default(),
overrides: HashMap::new(),
commit_message: CommitMessageKeybinding::default(),
}
}
}

impl KeybindingConfig {
pub fn apply_overrides(&mut self) -> Vec<String> {
if self.overrides.is_empty() {
return Vec::new();
}

let mut patched = match serde_yaml::to_value(&*self) {
Ok(v) => v,
Err(_) => return self.overrides.keys().cloned().collect(),
};
let mut unknown = Vec::new();

for (raw_path, key) in self.overrides.clone() {
let normalized = raw_path.trim();
if normalized.is_empty() {
continue;
}
if !set_override_value(&mut patched, normalized, key) {
unknown.push(raw_path);
}
}

if let Ok(mut parsed) = serde_yaml::from_value::<KeybindingConfig>(patched) {
parsed.overrides = self.overrides.clone();
*self = parsed;
}

unknown
}
}

fn set_override_value(root: &mut serde_yaml::Value, raw_path: &str, value: String) -> bool {
let mut path = raw_path;
if let Some(stripped) = path.strip_prefix("keybinding.") {
path = stripped;
}
if let Some(stripped) = path.strip_prefix("keybinding/") {
path = stripped;
}
let segments: Vec<&str> = path.split(['.', '/']).filter(|s| !s.is_empty()).collect();
if segments.is_empty() {
return false;
}

if segments[0] == "overrides" {
return false;
}

let mut current = root;
for segment in &segments[..segments.len().saturating_sub(1)] {
let Some(map) = current.as_mapping_mut() else {
return false;
};
let key = serde_yaml::Value::String((*segment).to_string());
let Some(next) = map.get_mut(&key) else {
return false;
};
current = next;
}

let Some(last) = segments.last() else {
return false;
};
let Some(map) = current.as_mapping_mut() else {
return false;
};
let last_key = serde_yaml::Value::String((*last).to_string());
let Some(slot) = map.get_mut(&last_key) else {
return false;
};
if !matches!(slot, serde_yaml::Value::String(_)) {
return false;
}
*slot = serde_yaml::Value::String(value);
true
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct UniversalKeybinding {
Expand Down Expand Up @@ -110,6 +191,10 @@ pub struct UniversalKeybinding {
pub prev_screen_mode: String,
#[serde(rename = "createPatchOptionsMenu")]
pub create_patch_options_menu: String,
#[serde(rename = "prevRevertBlock")]
pub prev_revert_block: String,
#[serde(rename = "nextRevertBlock")]
pub next_revert_block: String,
}

impl Default for UniversalKeybinding {
Expand Down Expand Up @@ -157,6 +242,8 @@ impl Default for UniversalKeybinding {
next_screen_mode: "+".into(),
prev_screen_mode: "_".into(),
create_patch_options_menu: "<c-p>".into(),
prev_revert_block: "<c-k>".into(),
next_revert_block: "<c-j>".into(),
}
}
}
Expand Down Expand Up @@ -387,10 +474,7 @@ pub fn parse_key(s: &str) -> Option<KeyEvent> {
// Ctrl modifier
if let Some(key) = inner.strip_prefix("c-") {
let ch = key.chars().next()?;
return Some(KeyEvent::new(
KeyCode::Char(ch),
KeyModifiers::CONTROL,
));
return Some(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::CONTROL));
}

// Alt modifier
Expand Down
2 changes: 1 addition & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use anyhow::Result;

pub use app_state::AppState;
pub use keybindings::KeybindingConfig;
pub use theme::{Theme, ColorTheme, COLOR_THEMES};
pub use theme::{COLOR_THEMES, ColorTheme, Theme};
pub use user_config::UserConfig;

pub fn config_dir_candidates() -> Vec<PathBuf> {
Expand Down
Loading
Loading