diff --git a/CHANGELOG.md b/CHANGELOG.md index 04988dd9..89be427d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased - + +### Breaking Change +Have you used `key_config.ron` for custom key bindings before? +The way this works got changed and simplified ([See docs](https://github.com/extrawurst/gitui/blob/master/KEY_CONFIG.md) for more info): +* You only define the keys that should differ from the default. +* The file is renamed to `key_bindings.ron` +* Future addition of new keys will not break anymore + ### Added - add `trace-libgit` feature to make git tracing optional [[@dm9pZCAq](https://github.com/dm9pZCAq)] ([#902](https://github.com/extrawurst/gitui/issues/902)) - support merging and rebasing remote branches ([#920](https://github.com/extrawurst/gitui/issues/920)) @@ -14,7 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - support `home` and `end` keys in branchlist ([#957](https://github.com/extrawurst/gitui/issues/957)) - add `ghemoji` feature to make gh-emoji (GitHub emoji) optional ([#954](https://github.com/extrawurst/gitui/pull/954)) - allow customizing key symbols like `⏎` & `⇧` ([see docs](https://github.com/extrawurst/gitui/blob/master/KEY_CONFIG.md#key-symbols)) ([#465](https://github.com/extrawurst/gitui/issues/465)) -- fuzzy finder up/down keys compatible with typing search patterns ([#993](https://github.com/extrawurst/gitui/pull/993)) +- simplify key overrides ([see docs](https://github.com/extrawurst/gitui/blob/master/KEY_CONFIG.md)) ([#946](https://github.com/extrawurst/gitui/issues/946)) +- dedicated fuzzy finder up/down keys to allow vim overrides ([#993](https://github.com/extrawurst/gitui/pull/993)) ### Fixed - honor options (for untracked files) in `stage_all` command ([#933](https://github.com/extrawurst/gitui/issues/933)) diff --git a/KEY_CONFIG.md b/KEY_CONFIG.md index 13fd03d2..08ed5e31 100644 --- a/KEY_CONFIG.md +++ b/KEY_CONFIG.md @@ -4,17 +4,17 @@ The default keys are based on arrow keys to navigate. However popular demand lead to fully customizability of the key bindings. -On first start `gitui` will create `key_config.ron` file automatically based on the defaults. +On first start `gitui` will create `key_bindings.ron` file automatically based on the defaults. This file allows changing every key binding. The config file format based on the [Ron file format](https://github.com/ron-rs/ron). The location of the file depends on your OS: -* `$HOME/.config/gitui/key_config.ron` (mac) -* `$XDG_CONFIG_HOME/gitui/key_config.ron` (linux using XDG) -* `$HOME/.config/gitui/key_config.ron` (linux) -* `%APPDATA%/gitui/key_config.ron` (Windows) +* `$HOME/.config/gitui/key_bindings.ron` (mac) +* `$XDG_CONFIG_HOME/gitui/key_bindings.ron` (linux using XDG) +* `$HOME/.config/gitui/key_bindings.ron` (linux) +* `%APPDATA%/gitui/key_bindings.ron` (Windows) -Here is a [vim style key config](vim_style_key_config.ron) with `h`, `j`, `k`, `l` to navigate. Use it to copy the content into `key_config.ron` to get vim style key bindings. +Here is a [vim style key config](vim_style_key_config.ron) with `h`, `j`, `k`, `l` to navigate. Use it to copy the content into `key_bindings.ron` to get vim style key bindings. # Key Symbols diff --git a/src/keys/key_config.rs b/src/keys/key_config.rs index 463457de..0033cc2c 100644 --- a/src/keys/key_config.rs +++ b/src/keys/key_config.rs @@ -1,6 +1,6 @@ use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use std::{fs, path::PathBuf, rc::Rc}; +use std::{path::PathBuf, rc::Rc}; use crate::{args::get_app_config_path, strings::symbol}; @@ -17,7 +17,7 @@ pub struct KeyConfig { impl KeyConfig { fn get_config_file() -> Result { let app_home = get_app_config_path()?; - Ok(app_home.join("key_config.ron")) + Ok(app_home.join("key_bindings.ron")) } fn get_symbols_file() -> Result { @@ -25,34 +25,8 @@ impl KeyConfig { Ok(app_home.join("key_symbols.ron")) } - fn init_keys() -> Result { - let file = Self::get_config_file()?; - if file.exists() { - match KeysList::read_file(file.clone()) { - Err(e) => { - let config_path = file.clone(); - let config_path_old = - format!("{}.old", file.to_string_lossy()); - fs::rename( - config_path.clone(), - config_path_old.clone(), - )?; - - KeysList::default().save(file)?; - - Err(anyhow::anyhow!("{}\n Old file was renamed to {:?}.\n Defaults loaded and saved as {:?}", - e,config_path_old,config_path.to_string_lossy())) - } - Ok(keys) => Ok(keys), - } - } else { - KeysList::default().save(file)?; - Ok(KeysList::default()) - } - } - pub fn init() -> Result { - let keys = Self::init_keys()?; + let keys = KeysList::init(Self::get_config_file()?); let symbols = KeySymbols::init(Self::get_symbols_file()?); Ok(Self { keys, symbols }) } diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index 311d9605..f0280537 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -1,17 +1,8 @@ -use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use ron::{ - self, - ser::{to_string_pretty, PrettyConfig}, -}; -use serde::{Deserialize, Serialize}; -use std::{ - fs::File, - io::{Read, Write}, - path::PathBuf, -}; +use std::path::PathBuf; + +use super::key_list_file::KeysListFile; -#[derive(Serialize, Deserialize, Debug)] pub struct KeysList { pub tab_status: KeyEvent, pub tab_log: KeyEvent, @@ -164,31 +155,13 @@ impl Default for KeysList { } impl KeysList { - pub fn save(&self, file: PathBuf) -> Result<()> { - let mut file = File::create(file)?; - let data = to_string_pretty(self, PrettyConfig::default())?; - file.write_all(data.as_bytes())?; - Ok(()) - } - - pub fn read_file(config_file: PathBuf) -> Result { - let mut f = File::open(config_file)?; - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer)?; - Ok(ron::de::from_bytes(&buffer)?) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_load_vim_style_example() { - assert_eq!( - KeysList::read_file("vim_style_key_config.ron".into()) - .is_ok(), - true - ); + pub fn init(file: PathBuf) -> Self { + if file.exists() { + let file = + KeysListFile::read_file(file).unwrap_or_default(); + file.get_list() + } else { + Self::default() + } } } diff --git a/src/keys/key_list_file.rs b/src/keys/key_list_file.rs new file mode 100644 index 00000000..7290d510 --- /dev/null +++ b/src/keys/key_list_file.rs @@ -0,0 +1,185 @@ +use anyhow::Result; +use crossterm::event::KeyEvent; +use ron::{self}; +use serde::{Deserialize, Serialize}; +use std::{fs::File, io::Read, path::PathBuf}; + +use super::key_list::KeysList; + +#[derive(Serialize, Deserialize, Default)] +pub struct KeysListFile { + pub tab_status: Option, + pub tab_log: Option, + pub tab_files: Option, + pub tab_stashing: Option, + pub tab_stashes: Option, + pub tab_toggle: Option, + pub tab_toggle_reverse: Option, + pub toggle_workarea: Option, + pub focus_right: Option, + pub focus_left: Option, + pub focus_above: Option, + pub focus_below: Option, + pub exit: Option, + pub quit: Option, + pub exit_popup: Option, + pub open_commit: Option, + pub open_commit_editor: Option, + pub open_help: Option, + pub open_options: Option, + pub move_left: Option, + pub move_right: Option, + pub tree_collapse_recursive: Option, + pub tree_expand_recursive: Option, + pub home: Option, + pub end: Option, + pub move_up: Option, + pub move_down: Option, + pub popup_up: Option, + pub popup_down: Option, + pub page_down: Option, + pub page_up: Option, + pub shift_up: Option, + pub shift_down: Option, + pub enter: Option, + pub blame: Option, + pub edit_file: Option, + pub status_stage_all: Option, + pub status_reset_item: Option, + pub status_ignore_file: Option, + pub diff_stage_lines: Option, + pub diff_reset_lines: Option, + pub stashing_save: Option, + pub stashing_toggle_untracked: Option, + pub stashing_toggle_index: Option, + pub stash_apply: Option, + pub stash_open: Option, + pub stash_drop: Option, + pub cmd_bar_toggle: Option, + pub log_tag_commit: Option, + pub log_mark_commit: Option, + pub commit_amend: Option, + pub copy: Option, + pub create_branch: Option, + pub rename_branch: Option, + pub select_branch: Option, + pub delete_branch: Option, + pub merge_branch: Option, + pub rebase_branch: Option, + pub compare_commits: Option, + pub tags: Option, + pub delete_tag: Option, + pub select_tag: Option, + pub push: Option, + pub open_file_tree: Option, + pub file_find: Option, + pub force_push: Option, + pub pull: Option, + pub abort_merge: Option, + pub undo_commit: Option, + pub stage_unstage_item: Option, +} + +impl KeysListFile { + #[allow(dead_code)] + pub fn read_file(config_file: PathBuf) -> Result { + let mut f = File::open(config_file)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + Ok(ron::de::from_bytes(&buffer)?) + } + + #[rustfmt::skip] + pub fn get_list(self) -> KeysList { + let default = KeysList::default(); + + KeysList { + tab_status: self.tab_status.unwrap_or(default.tab_status), + tab_log: self.tab_log.unwrap_or(default.tab_log), + tab_files: self.tab_files.unwrap_or(default.tab_files), + tab_stashing: self.tab_stashing.unwrap_or(default.tab_stashing), + tab_stashes: self.tab_stashes.unwrap_or(default.tab_stashes), + tab_toggle: self.tab_toggle.unwrap_or(default.tab_toggle), + tab_toggle_reverse: self.tab_toggle_reverse.unwrap_or(default.tab_toggle_reverse), + toggle_workarea: self.toggle_workarea.unwrap_or(default.toggle_workarea), + focus_right: self.focus_right.unwrap_or(default.focus_right), + focus_left: self.focus_left.unwrap_or(default.focus_left), + focus_above: self.focus_above.unwrap_or(default.focus_above), + focus_below: self.focus_below.unwrap_or(default.focus_below), + exit: self.exit.unwrap_or(default.exit), + quit: self.quit.unwrap_or(default.quit), + exit_popup: self.exit_popup.unwrap_or(default.exit_popup), + open_commit: self.open_commit.unwrap_or(default.open_commit), + open_commit_editor: self.open_commit_editor.unwrap_or(default.open_commit_editor), + open_help: self.open_help.unwrap_or(default.open_help), + open_options: self.open_options.unwrap_or(default.open_options), + move_left: self.move_left.unwrap_or(default.move_left), + move_right: self.move_right.unwrap_or(default.move_right), + tree_collapse_recursive: self.tree_collapse_recursive.unwrap_or(default.tree_collapse_recursive), + tree_expand_recursive: self.tree_expand_recursive.unwrap_or(default.tree_expand_recursive), + home: self.home.unwrap_or(default.home), + end: self.end.unwrap_or(default.end), + move_up: self.move_up.unwrap_or(default.move_up), + move_down: self.move_down.unwrap_or(default.move_down), + popup_up: self.popup_up.unwrap_or(default.popup_up), + popup_down: self.popup_down.unwrap_or(default.popup_down), + page_down: self.page_down.unwrap_or(default.page_down), + page_up: self.page_up.unwrap_or(default.page_up), + shift_up: self.shift_up.unwrap_or(default.shift_up), + shift_down: self.shift_down.unwrap_or(default.shift_down), + enter: self.enter.unwrap_or(default.enter), + blame: self.blame.unwrap_or(default.blame), + edit_file: self.edit_file.unwrap_or(default.edit_file), + status_stage_all: self.status_stage_all.unwrap_or(default.status_stage_all), + status_reset_item: self.status_reset_item.unwrap_or(default.status_reset_item), + status_ignore_file: self.status_ignore_file.unwrap_or(default.status_ignore_file), + diff_stage_lines: self.diff_stage_lines.unwrap_or(default.diff_stage_lines), + diff_reset_lines: self.diff_reset_lines.unwrap_or(default.diff_reset_lines), + stashing_save: self.stashing_save.unwrap_or(default.stashing_save), + stashing_toggle_untracked: self.stashing_toggle_untracked.unwrap_or(default.stashing_toggle_untracked), + stashing_toggle_index: self.stashing_toggle_index.unwrap_or(default.stashing_toggle_index), + stash_apply: self.stash_apply.unwrap_or(default.stash_apply), + stash_open: self.stash_open.unwrap_or(default.stash_open), + stash_drop: self.stash_drop.unwrap_or(default.stash_drop), + cmd_bar_toggle: self.cmd_bar_toggle.unwrap_or(default.cmd_bar_toggle), + log_tag_commit: self.log_tag_commit.unwrap_or(default.log_tag_commit), + log_mark_commit: self.log_mark_commit.unwrap_or(default.log_mark_commit), + commit_amend: self.commit_amend.unwrap_or(default.commit_amend), + copy: self.copy.unwrap_or(default.copy), + create_branch: self.create_branch.unwrap_or(default.create_branch), + rename_branch: self.rename_branch.unwrap_or(default.rename_branch), + select_branch: self.select_branch.unwrap_or(default.select_branch), + delete_branch: self.delete_branch.unwrap_or(default.delete_branch), + merge_branch: self.merge_branch.unwrap_or(default.merge_branch), + rebase_branch: self.rebase_branch.unwrap_or(default.rebase_branch), + compare_commits: self.compare_commits.unwrap_or(default.compare_commits), + tags: self.tags.unwrap_or(default.tags), + delete_tag: self.delete_tag.unwrap_or(default.delete_tag), + select_tag: self.select_tag.unwrap_or(default.select_tag), + push: self.push.unwrap_or(default.push), + open_file_tree: self.open_file_tree.unwrap_or(default.open_file_tree), + file_find: self.file_find.unwrap_or(default.file_find), + force_push: self.force_push.unwrap_or(default.force_push), + pull: self.pull.unwrap_or(default.pull), + abort_merge: self.abort_merge.unwrap_or(default.abort_merge), + undo_commit: self.undo_commit.unwrap_or(default.undo_commit), + stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_load_vim_style_example() { + assert_eq!( + KeysListFile::read_file( + "vim_style_key_config.ron".into() + ) + .is_ok(), + true + ); + } +} diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 1b8ea444..e572572e 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -1,5 +1,6 @@ mod key_config; mod key_list; +mod key_list_file; mod symbols; pub use key_config::{KeyConfig, SharedKeyConfig}; diff --git a/vim_style_key_config.ron b/vim_style_key_config.ron index e2fde72c..c3c2533b 100644 --- a/vim_style_key_config.ron +++ b/vim_style_key_config.ron @@ -6,104 +6,42 @@ // Note: // If the default key layout is lower case, // and you want to use `Shift + q` to trigger the exit event, -// the setting should like this `exit: ( code: Char('Q'), modifiers: ( bits: 1,),),` +// the setting should like this `exit: Some(( code: Char('Q'), modifiers: ( bits: 1,),)),` // The Char should be upper case, and the shift modified bit should be set to 1. +// +// Note: +// find `KeysList` type in src/keys/key_list.rs for all possible keys. +// every key not overwritten via the config file will use the default specified there ( - tab_status: ( code: Char('1'), modifiers: ( bits: 0,),), - tab_log: ( code: Char('2'), modifiers: ( bits: 0,),), - tab_files: ( code: Char('3'), modifiers: ( bits: 0,),), - tab_stashing: ( code: Char('4'), modifiers: ( bits: 0,),), - tab_stashes: ( code: Char('5'), modifiers: ( bits: 0,),), + focus_right: Some(( code: Char('l'), modifiers: ( bits: 0,),)), + focus_left: Some(( code: Char('h'), modifiers: ( bits: 0,),)), + focus_above: Some(( code: Char('k'), modifiers: ( bits: 0,),)), + focus_below: Some(( code: Char('j'), modifiers: ( bits: 0,),)), - tab_toggle: ( code: Tab, modifiers: ( bits: 0,),), - tab_toggle_reverse: ( code: BackTab, modifiers: ( bits: 1,),), - toggle_workarea: ( code: Char('w'), modifiers: (bits: 0,),), + open_help: Some(( code: F(1), modifiers: ( bits: 0,),)), - focus_right: ( code: Char('l'), modifiers: ( bits: 0,),), - focus_left: ( code: Char('h'), modifiers: ( bits: 0,),), - focus_above: ( code: Char('k'), modifiers: ( bits: 0,),), - focus_below: ( code: Char('j'), modifiers: ( bits: 0,),), + move_left: Some(( code: Char('h'), modifiers: ( bits: 0,),)), + move_right: Some(( code: Char('l'), modifiers: ( bits: 0,),)), + move_up: Some(( code: Char('k'), modifiers: ( bits: 0,),)), + move_down: Some(( code: Char('j'), modifiers: ( bits: 0,),)), + popup_up: Some(( code: Char('p'), modifiers: ( bits: 2,),)), + popup_down: Some(( code: Char('n'), modifiers: ( bits: 2,),)), + page_up: Some(( code: Char('b'), modifiers: ( bits: 2,),)), + page_down: Some(( code: Char('f'), modifiers: ( bits: 2,),)), + shift_up: Some(( code: Char('K'), modifiers: ( bits: 1,),)), + shift_down: Some(( code: Char('J'), modifiers: ( bits: 1,),)), - open_help: ( code: F(1), modifiers: ( bits: 0,),), - open_options: ( code: Char('o'), modifiers: ( bits: 0,),), + edit_file: Some(( code: Char('I'), modifiers: ( bits: 1,),)), - exit: ( code: Char('c'), modifiers: ( bits: 2,),), - quit: ( code: Char('q'), modifiers: ( bits: 0,),), - exit_popup: ( code: Esc, modifiers: ( bits: 0,),), + status_reset_item: Some(( code: Char('U'), modifiers: ( bits: 1,),)), - open_commit: ( code: Char('c'), modifiers: ( bits: 0,),), - // Note: the shift modifier does not work for open_commit_editor - // Also just plain text characters will not work because the commit - // msg editor will interpret them as text input - open_commit_editor: ( code: Char('e'), modifiers: ( bits: 2,),), - undo_commit: ( code: Char('U'), modifiers: ( bits: 1,),), + diff_reset_lines: Some(( code: Char('u'), modifiers: ( bits: 0,),)), + diff_stage_lines: Some(( code: Char('s'), modifiers: ( bits: 0,),)), - move_left: ( code: Char('h'), modifiers: ( bits: 0,),), - move_right: ( code: Char('l'), modifiers: ( bits: 0,),), - home: ( code: Home, modifiers: ( bits: 0,),), - end: ( code: End, modifiers: ( bits: 0,),), - move_up: ( code: Char('k'), modifiers: ( bits: 0,),), - move_down: ( code: Char('j'), modifiers: ( bits: 0,),), - popup_up: ( code: Char('p'), modifiers: ( bits: 2,),), - popup_down: ( code: Char('n'), modifiers: ( bits: 2,),), - page_up: ( code: Char('b'), modifiers: ( bits: 2,),), - page_down: ( code: Char('f'), modifiers: ( bits: 2,),), - tree_collapse_recursive: ( code: Left, modifiers: ( bits: 1,),), - tree_expand_recursive: ( code: Right, modifiers: ( bits: 1,),), + stashing_save: Some(( code: Char('w'), modifiers: ( bits: 0,),)), + stashing_toggle_index: Some(( code: Char('m'), modifiers: ( bits: 0,),)), - shift_up: ( code: Char('K'), modifiers: ( bits: 1,),), - shift_down: ( code: Char('J'), modifiers: ( bits: 1,),), + stash_open: Some(( code: Char('l'), modifiers: ( bits: 0,),)), - enter: ( code: Enter, modifiers: ( bits: 0,),), - blame: ( code: Char('B'), modifiers: ( bits: 1,),), - - edit_file: ( code: Char('I'), modifiers: ( bits: 1,),), - - status_stage_all: ( code: Char('a'), modifiers: ( bits: 0,),), - status_reset_item: ( code: Char('U'), modifiers: ( bits: 1,),), - status_ignore_file: ( code: Char('i'), modifiers: ( bits: 0,),), - - diff_reset_lines: ( code: Char('u'), modifiers: ( bits: 0,),), - diff_stage_lines: ( code: Char('s'), modifiers: ( bits: 0,),), - - stashing_save: ( code: Char('w'), modifiers: ( bits: 0,),), - stashing_toggle_untracked: ( code: Char('u'), modifiers: ( bits: 0,),), - stashing_toggle_index: ( code: Char('m'), modifiers: ( bits: 0,),), - - stash_apply: ( code: Char('a'), modifiers: ( bits: 0,),), - stash_open: ( code: Char('l'), modifiers: ( bits: 0,),), - stash_drop: ( code: Char('D'), modifiers: ( bits: 1,),), - - cmd_bar_toggle: ( code: Char('.'), modifiers: ( bits: 0,),), - - log_tag_commit: ( code: Char('t'), modifiers: ( bits: 0,),), - log_mark_commit: ( code: Char(' '), modifiers: ( bits: 0,),), - - commit_amend: ( code: Char('a'), modifiers: ( bits: 2,),), - copy: ( code: Char('y'), modifiers: ( bits: 0,),), - create_branch: ( code: Char('c'), modifiers: ( bits: 0,),), - rename_branch: ( code: Char('r'), modifiers: ( bits: 0,),), - select_branch: ( code: Char('b'), modifiers: ( bits: 0,),), - delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),), - merge_branch: ( code: Char('m'), modifiers: ( bits: 0,),), - rebase_branch: ( code: Char('R'), modifiers: ( bits: 1,),), - abort_merge: ( code: Char('M'), modifiers: ( bits: 1,),), - - compare_commits: ( code: Char('C'), modifiers: ( bits: 1,),), - - tags: ( code: Char('T'), modifiers: ( bits: 1,),), - delete_tag: ( code: Char('D'), modifiers: ( bits: 1,),), - select_tag: ( code: Enter, modifiers: ( bits: 0,),), - - push: ( code: Char('p'), modifiers: ( bits: 0,),), - force_push: ( code: Char('P'), modifiers: ( bits: 1,),), - pull: ( code: Char('f'), modifiers: ( bits: 0,),), - - open_file_tree: ( code: Char('F'), modifiers: ( bits: 1,),), - file_find: ( code: Char('f'), modifiers: ( bits: 0,),), - - stage_unstage_item: ( code: Enter, modifiers: ( bits: 0,),), - - //removed in 0.11 - //tab_toggle_reverse_windows: ( code: BackTab, modifiers: ( bits: 1,),), + abort_merge: Some(( code: Char('M'), modifiers: ( bits: 1,),)), )