diff --git a/Cargo.lock b/Cargo.lock index 44d5163..558884e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,6 +608,7 @@ dependencies = [ "futures", "itertools", "pretty_assertions", + "ron", "rust_decimal", "serde", "serde_json", @@ -1242,6 +1243,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "ron" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678" +dependencies = [ + "base64", + "bitflags", + "serde", +] + [[package]] name = "rsa" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 3d06c94..a9549c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,8 @@ chrono = "0.4" tokio = { version = "1.11.0", features = ["full"] } futures = "0.3.5" serde_json = "1.0" -serde = "1.0" +ron = "0.7" +serde = { version = "1", features = ["derive"] } toml = "0.4" strum = "0.21" strum_macros = "0.21" diff --git a/README.md b/README.md index 39b6ec9..a982e7f 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ If you want to add connections, you need to edit your config file. For more info ## Keymap +### Default keymap + | Key | Description | | ---- | ---- | | h, j, k, l | Scroll left/down/up/right | @@ -111,6 +113,16 @@ If you want to add connections, you need to edit your config file. For more info | ? | Help | | 1, 2, 3, 4, 5 | Switch to records/columns/constraints/foreign keys/indexes tab | +### Custom keymap +The location of the file depends on your OS: + +- macOS: `$HOME/.config/gobang/key_bind.ron` +- Linux: `$HOME/.config/gobang/key_bind.ron` +- Windows: `%APPDATA%/gobang/key_bind.ron` + +A sample `key_bind.ron` is [here](https://github.com/TaKO8Ki/gobang/tree/main/examples/key_bind.ron). + + ## Configuration The location of the file depends on your OS: @@ -119,34 +131,7 @@ The location of the file depends on your OS: - Linux: `$HOME/.config/gobang/config.toml` - Windows: `%APPDATA%/gobang/config.toml` -The following is a sample config.toml file: - -```toml -[[conn]] -type = "mysql" -user = "root" -host = "localhost" -port = 3306 - -[[conn]] -type = "mysql" -user = "root" -host = "localhost" -port = 3306 -password = "password" -database = "foo" - -[[conn]] -type = "postgres" -user = "root" -host = "localhost" -port = 5432 -database = "bar" - -[[conn]] -type = "sqlite" -path = "/path/to/baz.db" -``` +A sample `config.toml` file is [here](https://github.com/TaKO8Ki/gobang/tree/main/examples/config.toml) ## Contribution diff --git a/examples/config.toml b/examples/config.toml new file mode 100644 index 0000000..8e9b5ea --- /dev/null +++ b/examples/config.toml @@ -0,0 +1,24 @@ +[[conn]] +type = "mysql" +user = "root" +host = "localhost" +port = 3306 + +[[conn]] +type = "mysql" +user = "root" +host = "localhost" +port = 3306 +password = "password" +database = "foo" + +[[conn]] +type = "postgres" +user = "root" +host = "localhost" +port = 5432 +database = "bar" + +[[conn]] +type = "sqlite" +path = "/path/to/baz.db" diff --git a/examples/key_bind.ron b/examples/key_bind.ron new file mode 100644 index 0000000..050d7c1 --- /dev/null +++ b/examples/key_bind.ron @@ -0,0 +1,46 @@ +/* + * This file is a custom key configuration file. + * Place this file in `$HOME/.config/gobang/key_bind.ron`. + * + * You can find the available keys here + * https://github.com/TaKO8Ki/gobang/blob/b13e4bb255ea533db16240e1cb5138fca4241264/src/event/key.rs + * + * You can get the latest custom key configuration file here + * https://github.com/TaKO8Ki/gobang/blob/b13e4bb255ea533db16240e1cb5138fca4241264/examples/key_bind.ron +*/ +( + scroll_up: Some(Char('k')), + scroll_down: Some(Char('j')), + scroll_right: Some(Char('l')), + scroll_left: Some(Char('h')), + move_up: Some(Up), + move_down: Some(Down), + copy: Some(Char('y')), + enter: Some(Enter), + exit: Some(Ctrl('c')), + quit: Some(Char('q')), + exit_popup: Some(Esc), + focus_right: Some(Right), + focus_left: Some(Left), + focus_above: Some(Up), + focus_connections: Some(Char('c')), + open_help: Some(Char('?')), + filter: Some(Char('/')), + scroll_down_multiple_lines: Some(Ctrl('d')), + scroll_up_multiple_lines: Some(Ctrl('u')), + scroll_to_top: Some(Char('g')), + scroll_to_bottom: Some(Char('G')), + extend_selection_by_one_cell_left: Some(Char('H')), + extend_selection_by_one_cell_right: Some(Char('L')), + extend_selection_by_one_cell_down: Some(Char('J')), + extend_selection_by_one_cell_up: Some(Char('K')), + tab_records: Some(Char('1')), + tab_properties: Some(Char('2')), + tab_sql_editor: Some(Char('3')), + tab_columns: Some(Char('4')), + tab_constraints: Some(Char('5')), + tab_foreign_keys: Some(Char('6')), + tab_indexes: Some(Char('7')), + extend_or_shorten_widget_width_to_right: Some(Char('>')), + extend_or_shorten_widget_width_to_left: Some(Char('<')), +) diff --git a/src/config.rs b/src/config.rs index 3d41831..b36b0a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::key_bind::KeyBind; use crate::log::LogLevel; use crate::Key; use serde::Deserialize; @@ -15,6 +16,10 @@ pub struct CliConfig { /// Set the config file #[structopt(long, short, global = true)] config_path: Option, + + /// Set the key bind file + #[structopt(long, short, global = true)] + key_bind_path: Option, } #[derive(Debug, Deserialize, Clone)] @@ -26,6 +31,13 @@ pub struct Config { pub log_level: LogLevel, } +#[derive(Debug, Deserialize, Clone)] +pub struct ReadConfig { + pub conn: Vec, + #[serde(default)] + pub log_level: LogLevel, +} + #[derive(Debug, Deserialize, Clone)] enum DatabaseType { #[serde(rename = "mysql")] @@ -78,7 +90,7 @@ pub struct Connection { } #[derive(Debug, Deserialize, Clone)] -#[cfg_attr(test, derive(Serialize))] +#[cfg_attr(test, derive(Serialize, PartialEq))] pub struct KeyConfig { pub scroll_up: Key, pub scroll_down: Key, @@ -164,19 +176,35 @@ impl Config { } else { get_app_config_path()?.join("config.toml") }; + + let key_bind_path = if let Some(key_bind_path) = &config.key_bind_path { + key_bind_path.clone() + } else { + get_app_config_path()?.join("key_bind.ron") + }; + if let Ok(file) = File::open(config_path) { let mut buf_reader = BufReader::new(file); let mut contents = String::new(); buf_reader.read_to_string(&mut contents)?; - - let config: Result = toml::from_str(&contents); + let config: Result = toml::from_str(&contents); match config { - Ok(config) => return Ok(config), + Ok(config) => return Ok(Config::build(config, key_bind_path)), Err(e) => panic!("fail to parse config file: {}", e), } } + Ok(Config::default()) } + + fn build(read_config: ReadConfig, key_bind_path: PathBuf) -> Self { + let key_bind = KeyBind::load(key_bind_path).unwrap(); + Config { + conn: read_config.conn, + log_level: read_config.log_level, + key_config: KeyConfig::from(key_bind), + } + } } impl Connection { @@ -325,10 +353,20 @@ fn expand_path(path: &Path) -> Option { #[cfg(test)] mod test { - use super::{expand_path, KeyConfig, Path, PathBuf}; + use super::{expand_path, CliConfig, Config, KeyConfig, Path, PathBuf}; use serde_json::Value; use std::env; + #[test] + fn test_load_config() { + let cli_config = CliConfig { + config_path: Some(Path::new("examples/config.toml").to_path_buf()), + key_bind_path: Some(Path::new("examples/key_bind.ron").to_path_buf()), + }; + + assert_eq!(Config::new(&cli_config).is_ok(), true); + } + #[test] fn test_overlappted_key() { let value: Value = diff --git a/src/key_bind.rs b/src/key_bind.rs new file mode 100644 index 0000000..6c7799f --- /dev/null +++ b/src/key_bind.rs @@ -0,0 +1,164 @@ +use crate::config::KeyConfig; +use crate::event::Key; +use serde::Deserialize; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::path::PathBuf; + +#[derive(Debug, Deserialize, Clone, Default)] +pub struct KeyBind { + pub scroll_up: Option, + pub scroll_down: Option, + pub scroll_right: Option, + pub scroll_left: Option, + pub move_up: Option, + pub move_down: Option, + pub copy: Option, + pub enter: Option, + pub exit: Option, + pub quit: Option, + pub exit_popup: Option, + pub focus_right: Option, + pub focus_left: Option, + pub focus_above: Option, + pub focus_connections: Option, + pub open_help: Option, + pub filter: Option, + pub scroll_down_multiple_lines: Option, + pub scroll_up_multiple_lines: Option, + pub scroll_to_top: Option, + pub scroll_to_bottom: Option, + pub extend_selection_by_one_cell_left: Option, + pub extend_selection_by_one_cell_right: Option, + pub extend_selection_by_one_cell_up: Option, + pub extend_selection_by_one_cell_down: Option, + pub tab_records: Option, + pub tab_columns: Option, + pub tab_constraints: Option, + pub tab_foreign_keys: Option, + pub tab_indexes: Option, + pub tab_sql_editor: Option, + pub tab_properties: Option, + pub extend_or_shorten_widget_width_to_right: Option, + pub extend_or_shorten_widget_width_to_left: Option, +} + +impl KeyBind { + pub fn load(config_path: PathBuf) -> anyhow::Result { + if let Ok(file) = File::open(config_path) { + let mut buf_reader = BufReader::new(file); + let mut contents = String::new(); + buf_reader.read_to_string(&mut contents)?; + let key_bind: Result = ron::from_str(&contents); + match key_bind { + Ok(key_bind) => return Ok(key_bind), + Err(e) => panic!("fail to parse key bind file: {}", e), + } + } + + return Ok(Self::default()); + } +} + +macro_rules! merge { + ($kc:expr, $kt:expr) => { + $kc = $kt.unwrap_or_else(|| $kc) + }; +} + +impl From for KeyConfig { + #[allow(clippy::field_reassign_with_default)] + fn from(kb: KeyBind) -> Self { + let mut kc = KeyConfig::default(); + merge!(kc.scroll_up, kb.scroll_up); + merge!(kc.scroll_down, kb.scroll_down); + merge!(kc.scroll_right, kb.scroll_right); + merge!(kc.scroll_left, kb.scroll_left); + merge!(kc.scroll_down, kb.scroll_down); + merge!(kc.move_up, kb.move_up); + merge!(kc.move_down, kb.move_down); + merge!(kc.copy, kb.copy); + merge!(kc.enter, kb.enter); + merge!(kc.exit, kb.exit); + merge!(kc.quit, kb.quit); + merge!(kc.exit_popup, kb.exit_popup); + merge!(kc.focus_right, kb.focus_right); + merge!(kc.focus_left, kb.focus_left); + merge!(kc.focus_above, kb.focus_above); + merge!(kc.focus_connections, kb.focus_connections); + merge!(kc.open_help, kb.open_help); + merge!(kc.filter, kb.filter); + merge!(kc.scroll_down_multiple_lines, kb.scroll_down_multiple_lines); + merge!(kc.scroll_up_multiple_lines, kb.scroll_up_multiple_lines); + merge!(kc.scroll_to_top, kb.scroll_to_top); + merge!(kc.scroll_to_bottom, kb.scroll_to_bottom); + merge!( + kc.extend_selection_by_one_cell_left, + kb.extend_selection_by_one_cell_left + ); + merge!( + kc.extend_selection_by_one_cell_right, + kb.extend_selection_by_one_cell_right + ); + merge!( + kc.extend_selection_by_one_cell_down, + kb.extend_selection_by_one_cell_down + ); + merge!( + kc.extend_selection_by_one_cell_up, + kb.extend_selection_by_one_cell_up + ); + merge!(kc.tab_records, kb.tab_records); + merge!(kc.tab_properties, kb.tab_properties); + merge!(kc.tab_sql_editor, kb.tab_sql_editor); + merge!(kc.tab_columns, kb.tab_columns); + merge!(kc.tab_constraints, kb.tab_constraints); + merge!(kc.tab_foreign_keys, kb.tab_foreign_keys); + merge!(kc.tab_indexes, kb.tab_indexes); + merge!( + kc.extend_or_shorten_widget_width_to_right, + kb.extend_or_shorten_widget_width_to_right + ); + merge!( + kc.extend_or_shorten_widget_width_to_left, + kb.extend_or_shorten_widget_width_to_left + ); + kc + } +} + +#[cfg(test)] +mod test { + use super::KeyBind; + use crate::config::KeyConfig; + use crate::event::Key; + use std::path::Path; + + #[test] + fn test_exist_file() { + let config_path = Path::new("examples/key_bind.ron").to_path_buf(); + assert_eq!(config_path.exists(), true); + assert_eq!(KeyBind::load(config_path).is_ok(), true); + } + + #[test] + fn test_not_exist_file() { + let config_path = Path::new("examples/not_exist.ron").to_path_buf(); + assert_eq!(config_path.exists(), false); + assert_eq!(KeyBind::load(config_path).is_ok(), true); + } + + #[test] + fn test_key_config_from_key_bind() { + // Default Config + let empty_kb = KeyBind::default(); + let kc = KeyConfig::default(); + assert_eq!(KeyConfig::from(empty_kb), kc); + + // Merged Config + let mut kb = KeyBind::default(); + kb.scroll_up = Some(Key::Char('M')); + let build_kc = KeyConfig::from(kb); + assert_eq!(build_kc.scroll_up, Key::Char('M')); + } +} diff --git a/src/main.rs b/src/main.rs index 221fb7c..f4fcb77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod components; mod config; mod database; mod event; +mod key_bind; mod ui; mod version;