From db9ad754f60360984a3df038dfd060a7ad8e93a5 Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Fri, 13 May 2022 12:52:50 +0900
Subject: [PATCH] impl custom keybind
---
Cargo.lock | 12 ++++
Cargo.toml | 3 +-
README.md | 41 ++++-------
examples/config.toml | 24 +++++++
examples/key_bind.ron | 46 ++++++++++++
src/config.rs | 48 +++++++++++--
src/key_bind.rs | 164 ++++++++++++++++++++++++++++++++++++++++++
src/main.rs | 1 +
8 files changed, 305 insertions(+), 34 deletions(-)
create mode 100644 examples/config.toml
create mode 100644 examples/key_bind.ron
create mode 100644 src/key_bind.rs
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;