simplify key bindings file handling (#946)

This commit is contained in:
Stephan Dilly 2021-11-21 21:53:49 +01:00
parent f689c428b2
commit d617ba76fa
7 changed files with 244 additions and 165 deletions

View file

@ -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))

View file

@ -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

View file

@ -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<PathBuf> {
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<PathBuf> {
@ -25,34 +25,8 @@ impl KeyConfig {
Ok(app_home.join("key_symbols.ron"))
}
fn init_keys() -> Result<KeysList> {
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<Self> {
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 })
}

View file

@ -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<Self> {
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()
}
}
}

185
src/keys/key_list_file.rs Normal file
View file

@ -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<KeyEvent>,
pub tab_log: Option<KeyEvent>,
pub tab_files: Option<KeyEvent>,
pub tab_stashing: Option<KeyEvent>,
pub tab_stashes: Option<KeyEvent>,
pub tab_toggle: Option<KeyEvent>,
pub tab_toggle_reverse: Option<KeyEvent>,
pub toggle_workarea: Option<KeyEvent>,
pub focus_right: Option<KeyEvent>,
pub focus_left: Option<KeyEvent>,
pub focus_above: Option<KeyEvent>,
pub focus_below: Option<KeyEvent>,
pub exit: Option<KeyEvent>,
pub quit: Option<KeyEvent>,
pub exit_popup: Option<KeyEvent>,
pub open_commit: Option<KeyEvent>,
pub open_commit_editor: Option<KeyEvent>,
pub open_help: Option<KeyEvent>,
pub open_options: Option<KeyEvent>,
pub move_left: Option<KeyEvent>,
pub move_right: Option<KeyEvent>,
pub tree_collapse_recursive: Option<KeyEvent>,
pub tree_expand_recursive: Option<KeyEvent>,
pub home: Option<KeyEvent>,
pub end: Option<KeyEvent>,
pub move_up: Option<KeyEvent>,
pub move_down: Option<KeyEvent>,
pub popup_up: Option<KeyEvent>,
pub popup_down: Option<KeyEvent>,
pub page_down: Option<KeyEvent>,
pub page_up: Option<KeyEvent>,
pub shift_up: Option<KeyEvent>,
pub shift_down: Option<KeyEvent>,
pub enter: Option<KeyEvent>,
pub blame: Option<KeyEvent>,
pub edit_file: Option<KeyEvent>,
pub status_stage_all: Option<KeyEvent>,
pub status_reset_item: Option<KeyEvent>,
pub status_ignore_file: Option<KeyEvent>,
pub diff_stage_lines: Option<KeyEvent>,
pub diff_reset_lines: Option<KeyEvent>,
pub stashing_save: Option<KeyEvent>,
pub stashing_toggle_untracked: Option<KeyEvent>,
pub stashing_toggle_index: Option<KeyEvent>,
pub stash_apply: Option<KeyEvent>,
pub stash_open: Option<KeyEvent>,
pub stash_drop: Option<KeyEvent>,
pub cmd_bar_toggle: Option<KeyEvent>,
pub log_tag_commit: Option<KeyEvent>,
pub log_mark_commit: Option<KeyEvent>,
pub commit_amend: Option<KeyEvent>,
pub copy: Option<KeyEvent>,
pub create_branch: Option<KeyEvent>,
pub rename_branch: Option<KeyEvent>,
pub select_branch: Option<KeyEvent>,
pub delete_branch: Option<KeyEvent>,
pub merge_branch: Option<KeyEvent>,
pub rebase_branch: Option<KeyEvent>,
pub compare_commits: Option<KeyEvent>,
pub tags: Option<KeyEvent>,
pub delete_tag: Option<KeyEvent>,
pub select_tag: Option<KeyEvent>,
pub push: Option<KeyEvent>,
pub open_file_tree: Option<KeyEvent>,
pub file_find: Option<KeyEvent>,
pub force_push: Option<KeyEvent>,
pub pull: Option<KeyEvent>,
pub abort_merge: Option<KeyEvent>,
pub undo_commit: Option<KeyEvent>,
pub stage_unstage_item: Option<KeyEvent>,
}
impl KeysListFile {
#[allow(dead_code)]
pub fn read_file(config_file: PathBuf) -> Result<Self> {
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
);
}
}

View file

@ -1,5 +1,6 @@
mod key_config;
mod key_list;
mod key_list_file;
mod symbols;
pub use key_config::{KeyConfig, SharedKeyConfig};

View file

@ -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,),)),
)