diff --git a/README.md b/README.md
index 9d92fc5..c38f5c4 100644
--- a/README.md
+++ b/README.md
@@ -31,9 +31,9 @@ $ cargo install --version 0.1.0-alpha.0 gobang
| Key | Description |
| ---- | ---- |
-| h | move left |
-| j | move down |
-| k | move up |
-| l | move right |
+| h | scroll left |
+| j | scroll down |
+| k | scroll up |
+| l | scroll right |
| Ctrl + d | scroll down multiple lines |
| Ctrl + u | scroll up multiple lines |
diff --git a/database-tree/src/databasetree.rs b/database-tree/src/databasetree.rs
index 844691a..b8842d3 100644
--- a/database-tree/src/databasetree.rs
+++ b/database-tree/src/databasetree.rs
@@ -1,8 +1,8 @@
-use crate::Table;
use crate::{
databasetreeitems::DatabaseTreeItems, error::Result, item::DatabaseTreeItemKind,
tree_iter::TreeIterator,
};
+use crate::{Database, Table};
use std::{collections::BTreeSet, usize};
///
@@ -14,8 +14,6 @@ pub enum MoveSelection {
Right,
Top,
End,
- PageDown,
- PageUp,
}
#[derive(Debug, Clone, Copy)]
@@ -76,13 +74,13 @@ impl DatabaseTree {
.and_then(|index| self.items.tree_items.get(index))
}
- pub fn selected_table(&self) -> Option<(Table, String)> {
+ pub fn selected_table(&self) -> Option<(Database, Table)> {
self.selection.and_then(|index| {
let item = &self.items.tree_items[index];
match item.kind() {
DatabaseTreeItemKind::Database { .. } => None,
DatabaseTreeItemKind::Table { table, database } => {
- Some((table.clone(), database.clone()))
+ Some((database.clone(), table.clone()))
}
}
})
@@ -109,7 +107,6 @@ impl DatabaseTree {
MoveSelection::Right => self.selection_right(selection),
MoveSelection::Top => Self::selection_start(selection),
MoveSelection::End => self.selection_end(selection),
- MoveSelection::PageDown | MoveSelection::PageUp => None,
};
let changed_index = new_index.map(|i| i != selection).unwrap_or_default();
diff --git a/database-tree/src/item.rs b/database-tree/src/item.rs
index a3cc268..edab342 100644
--- a/database-tree/src/item.rs
+++ b/database-tree/src/item.rs
@@ -34,7 +34,7 @@ impl TreeItemInfo {
#[derive(PartialEq, Debug, Clone)]
pub enum DatabaseTreeItemKind {
Database { name: String, collapsed: bool },
- Table { database: String, table: Table },
+ Table { database: Database, table: Table },
}
impl DatabaseTreeItemKind {
@@ -63,7 +63,7 @@ impl DatabaseTreeItemKind {
pub fn database_name(&self) -> Option {
match self {
Self::Database { .. } => None,
- Self::Table { database, .. } => Some(database.clone()),
+ Self::Table { database, .. } => Some(database.name.clone()),
}
}
}
@@ -82,7 +82,7 @@ impl DatabaseTreeItem {
Ok(Self {
info: TreeItemInfo::new(indent, false),
kind: DatabaseTreeItemKind::Table {
- database: database.name.clone(),
+ database: database.clone(),
table: table.clone(),
},
})
diff --git a/database-tree/src/lib.rs b/database-tree/src/lib.rs
index 0303210..34f107d 100644
--- a/database-tree/src/lib.rs
+++ b/database-tree/src/lib.rs
@@ -11,7 +11,7 @@ pub use crate::{
item::{DatabaseTreeItem, TreeItemInfo},
};
-#[derive(Clone)]
+#[derive(Clone, PartialEq, Debug)]
pub struct Database {
pub name: String,
pub tables: Vec,
diff --git a/src/app.rs b/src/app.rs
index d44aee5..683dbeb 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -5,10 +5,10 @@ use crate::utils::{MySqlPool, Pool};
use crate::{
components::tab::Tab,
components::{
- ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent,
+ command, ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent,
RecordTableComponent, TabComponent, TableComponent, TableStatusComponent,
},
- user_config::UserConfig,
+ config::Config,
};
use database_tree::Database;
use tui::{
@@ -33,36 +33,25 @@ pub struct App {
table_status: TableStatusComponent,
clipboard: Clipboard,
pool: Option>,
- pub user_config: Option,
+ pub config: Config,
pub error: ErrorComponent,
}
-impl Default for App {
- fn default() -> App {
- App {
- record_table: RecordTableComponent::default(),
- structure_table: TableComponent::default(),
- focus: Focus::DabataseList,
- tab: TabComponent::default(),
- help: HelpComponent::new(),
- user_config: None,
- databases: DatabasesComponent::new(),
- connections: ConnectionsComponent::default(),
- table_status: TableStatusComponent::default(),
- clipboard: Clipboard::new(),
- pool: None,
- error: ErrorComponent::default(),
- }
- }
-}
-
impl App {
- pub fn new(user_config: UserConfig) -> App {
+ pub fn new(config: Config) -> App {
Self {
- user_config: Some(user_config.clone()),
- connections: ConnectionsComponent::new(user_config.conn),
+ config: config.clone(),
+ connections: ConnectionsComponent::new(config.key_config.clone(), config.conn),
+ record_table: RecordTableComponent::new(config.key_config.clone()),
+ structure_table: TableComponent::new(config.key_config.clone()),
+ tab: TabComponent::new(config.key_config.clone()),
+ help: HelpComponent::new(config.key_config.clone()),
+ databases: DatabasesComponent::new(config.key_config.clone()),
+ table_status: TableStatusComponent::default(),
+ error: ErrorComponent::new(config.key_config),
focus: Focus::ConnectionList,
- ..App::default()
+ clipboard: Clipboard::new(),
+ pool: None,
}
}
@@ -119,34 +108,24 @@ impl App {
}
fn commands(&self) -> Vec {
- let res = vec![
- CommandInfo::new(crate::components::command::move_left("h"), true, true),
- CommandInfo::new(crate::components::command::move_down("j"), true, true),
- CommandInfo::new(crate::components::command::move_up("k"), true, true),
- CommandInfo::new(crate::components::command::move_right("l"), true, true),
- CommandInfo::new(crate::components::command::filter("/"), true, true),
- CommandInfo::new(
- crate::components::command::move_focus_to_right_widget(
- Key::Right.to_string().as_str(),
- ),
- true,
- true,
- ),
+ let mut res = vec![
+ CommandInfo::new(command::scroll(&self.config.key_config)),
+ CommandInfo::new(command::scroll_to_top_bottom(&self.config.key_config)),
+ CommandInfo::new(command::move_focus(&self.config.key_config)),
+ CommandInfo::new(command::filter(&self.config.key_config)),
+ CommandInfo::new(command::help(&self.config.key_config)),
+ CommandInfo::new(command::toggle_tabs(&self.config.key_config)),
];
+ self.databases.commands(&mut res);
+ self.record_table.commands(&mut res);
+
res
}
pub async fn event(&mut self, key: Key) -> anyhow::Result {
self.update_commands();
- if let Key::Esc = key {
- if self.error.error.is_some() {
- self.error.error = None;
- return Ok(EventState::Consumed);
- }
- }
-
if self.components_event(key).await?.is_consumed() {
return Ok(EventState::Consumed);
};
@@ -158,7 +137,11 @@ impl App {
}
pub async fn components_event(&mut self, key: Key) -> anyhow::Result {
- if self.help.event(key)?.is_consumed() {
+ if self.error.event(key)?.is_consumed() {
+ return Ok(EventState::Consumed);
+ }
+
+ if !matches!(self.focus, Focus::ConnectionList) && self.help.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
}
@@ -168,8 +151,7 @@ impl App {
return Ok(EventState::Consumed);
}
- if let Key::Enter = key {
- self.record_table.reset();
+ if key == self.config.key_config.enter {
if let Some(conn) = self.connections.selected_connection() {
if let Some(pool) = self.pool.as_ref() {
pool.close().await;
@@ -189,7 +171,8 @@ impl App {
None => self.pool.as_ref().unwrap().get_databases().await?,
};
self.databases.update(databases.as_slice()).unwrap();
- self.focus = Focus::DabataseList
+ self.focus = Focus::DabataseList;
+ self.record_table.reset();
}
return Ok(EventState::Consumed);
}
@@ -199,25 +182,30 @@ impl App {
return Ok(EventState::Consumed);
}
- if matches!(key, Key::Enter) && self.databases.tree_focused() {
- if let Some((table, database)) = self.databases.tree().selected_table() {
+ if key == self.config.key_config.enter && self.databases.tree_focused() {
+ if let Some((database, table)) = self.databases.tree().selected_table() {
self.focus = Focus::Table;
let (headers, records) = self
.pool
.as_ref()
.unwrap()
- .get_records(&database, &table.name, 0, None)
+ .get_records(&database.name, &table.name, 0, None)
.await?;
- self.record_table = RecordTableComponent::new(records, headers);
- self.record_table.set_table(table.name.to_string());
+ self.record_table
+ .update(records, headers, database.clone(), table.clone());
let (headers, records) = self
.pool
.as_ref()
.unwrap()
- .get_columns(&database, &table.name)
+ .get_columns(&database.name, &table.name)
.await?;
- self.structure_table = TableComponent::new(records, headers);
+ self.structure_table.update(
+ records,
+ headers,
+ database.clone(),
+ table.clone(),
+ );
self.table_status
.update(self.record_table.len() as u64, table);
}
@@ -231,22 +219,23 @@ impl App {
return Ok(EventState::Consumed);
};
- if let Key::Char('y') = key {
+ if key == self.config.key_config.copy {
if let Some(text) = self.record_table.table.selected_cells() {
self.clipboard.store(text)
}
}
- if matches!(key, Key::Enter) && self.record_table.filter_focused() {
+ if key == self.config.key_config.enter && self.record_table.filter_focused()
+ {
self.record_table.focus = crate::components::record_table::Focus::Table;
- if let Some((table, database)) = self.databases.tree().selected_table()
+ if let Some((database, table)) = self.databases.tree().selected_table()
{
let (headers, records) = self
.pool
.as_ref()
.unwrap()
.get_records(
- &database.clone(),
+ &database.name.clone(),
&table.name,
0,
if self.record_table.filter.input.is_empty() {
@@ -256,7 +245,7 @@ impl App {
},
)
.await?;
- self.record_table.update(records, headers);
+ self.record_table.update(records, headers, database, table);
}
}
@@ -269,7 +258,7 @@ impl App {
% crate::utils::RECORDS_LIMIT_PER_PAGE as usize
== 0
{
- if let Some((table, database)) =
+ if let Some((database, table)) =
self.databases.tree().selected_table()
{
let (_, records) = self
@@ -277,7 +266,7 @@ impl App {
.as_ref()
.unwrap()
.get_records(
- &database.clone(),
+ &database.name.clone(),
&table.name,
index as u16,
if self.record_table.filter.input.is_empty() {
@@ -301,7 +290,7 @@ impl App {
return Ok(EventState::Consumed);
};
- if let Key::Char('y') = key {
+ if key == self.config.key_config.copy {
if let Some(text) = self.structure_table.selected_cells() {
self.clipboard.store(text)
}
@@ -314,7 +303,7 @@ impl App {
}
pub fn move_focus(&mut self, key: Key) -> anyhow::Result {
- if let Key::Char('c') = key {
+ if key == self.config.key_config.focus_connections {
self.focus = Focus::ConnectionList;
return Ok(EventState::Consumed);
}
@@ -323,19 +312,19 @@ impl App {
}
match self.focus {
Focus::ConnectionList => {
- if let Key::Enter = key {
+ if key == self.config.key_config.enter {
self.focus = Focus::DabataseList;
return Ok(EventState::Consumed);
}
}
Focus::DabataseList => {
- if matches!(key, Key::Right) && self.databases.tree_focused() {
+ if key == self.config.key_config.focus_right && self.databases.tree_focused() {
self.focus = Focus::Table;
return Ok(EventState::Consumed);
}
}
Focus::Table => {
- if let Key::Left = key {
+ if key == self.config.key_config.focus_left {
self.focus = Focus::DabataseList;
return Ok(EventState::Consumed);
}
diff --git a/src/components/command.rs b/src/components/command.rs
index 7c94ba3..c1a816d 100644
--- a/src/components/command.rs
+++ b/src/components/command.rs
@@ -1,18 +1,20 @@
+use crate::config::KeyConfig;
+
static CMD_GROUP_GENERAL: &str = "-- General --";
+static CMD_GROUP_TABLE: &str = "-- Table --";
+static CMD_GROUP_DATABASES: &str = "-- Databases --";
#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
pub struct CommandText {
pub name: String,
- pub desc: &'static str,
pub group: &'static str,
pub hide_help: bool,
}
impl CommandText {
- pub const fn new(name: String, desc: &'static str, group: &'static str) -> Self {
+ pub const fn new(name: String, group: &'static str) -> Self {
Self {
name,
- desc,
group,
hide_help: false,
}
@@ -21,70 +23,103 @@ impl CommandText {
pub struct CommandInfo {
pub text: CommandText,
- pub enabled: bool,
- pub quick_bar: bool,
- pub available: bool,
- pub order: i8,
}
impl CommandInfo {
- pub const fn new(text: CommandText, enabled: bool, available: bool) -> Self {
- Self {
- text,
- enabled,
- quick_bar: true,
- available,
- order: 0,
- }
+ pub const fn new(text: CommandText) -> Self {
+ Self { text }
}
+}
- pub const fn order(self, order: i8) -> Self {
- let mut res = self;
- res.order = order;
- res
- }
+pub fn scroll(key: &KeyConfig) -> CommandText {
+ CommandText::new(
+ format!(
+ "Scroll up/down/left/right [{},{},{},{}]",
+ key.scroll_up.to_string(),
+ key.scroll_down.to_string(),
+ key.scroll_left.to_string(),
+ key.scroll_right.to_string()
+ ),
+ CMD_GROUP_GENERAL,
+ )
}
-pub fn move_down(key: &str) -> CommandText {
+pub fn scroll_to_top_bottom(key: &KeyConfig) -> CommandText {
CommandText::new(
- format!("Move down [{}]", key),
- "move down",
+ format!(
+ "Scroll to top/bottom [{},{}]",
+ key.scroll_to_top.to_string(),
+ key.scroll_to_bottom.to_string(),
+ ),
CMD_GROUP_GENERAL,
)
}
-pub fn move_up(key: &str) -> CommandText {
- CommandText::new(format!("Move up [{}]", key), "move up", CMD_GROUP_GENERAL)
+pub fn expand_collapse(key: &KeyConfig) -> CommandText {
+ CommandText::new(
+ format!(
+ "Expand/Collapse [{},{}]",
+ key.scroll_right.to_string(),
+ key.scroll_left.to_string(),
+ ),
+ CMD_GROUP_DATABASES,
+ )
}
-pub fn move_right(key: &str) -> CommandText {
+pub fn filter(key: &KeyConfig) -> CommandText {
CommandText::new(
- format!("Move right [{}]", key),
- "move right",
+ format!("Filter [{}]", key.filter.to_string()),
CMD_GROUP_GENERAL,
)
}
-pub fn move_left(key: &str) -> CommandText {
+pub fn move_focus(key: &KeyConfig) -> CommandText {
CommandText::new(
- format!("Move left [{}]", key),
- "move left",
+ format!(
+ "Move focus to left/right [{},{}]",
+ key.focus_left, key.focus_right
+ ),
CMD_GROUP_GENERAL,
)
}
-pub fn filter(key: &str) -> CommandText {
+pub fn extend_selection_by_one_cell(key: &KeyConfig) -> CommandText {
+ CommandText::new(
+ format!(
+ "Extend selection by one cell up/down/left/right [{},{},{},{}]",
+ key.extend_selection_by_one_cell_up,
+ key.extend_selection_by_one_cell_down,
+ key.extend_selection_by_one_cell_left,
+ key.extend_selection_by_one_cell_right
+ ),
+ CMD_GROUP_TABLE,
+ )
+}
+
+pub fn tab_records(key: &KeyConfig) -> CommandText {
+ CommandText::new(format!("Records [{}]", key.tab_records), CMD_GROUP_TABLE)
+}
+
+pub fn tab_structure(key: &KeyConfig) -> CommandText {
+ CommandText::new(
+ format!("Structure [{}]", key.tab_structure),
+ CMD_GROUP_TABLE,
+ )
+}
+
+pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText {
CommandText::new(
- format!("Filter [{}]", key),
- "enter input for filter",
+ format!(
+ "Tab [{},{}]",
+ key_config.tab_records, key_config.tab_structure
+ ),
CMD_GROUP_GENERAL,
)
}
-pub fn move_focus_to_right_widget(key: &str) -> CommandText {
+pub fn help(key_config: &KeyConfig) -> CommandText {
CommandText::new(
- format!("Move focus to right [{}]", key),
- "move focus to right",
+ format!("Help [{}]", key_config.open_help),
CMD_GROUP_GENERAL,
)
}
diff --git a/src/components/connections.rs b/src/components/connections.rs
index 1d9c33c..4d87bb5 100644
--- a/src/components/connections.rs
+++ b/src/components/connections.rs
@@ -1,7 +1,7 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
+use crate::config::{Connection, KeyConfig};
use crate::event::Key;
-use crate::user_config::Connection;
use anyhow::Result;
use tui::{
backend::Backend,
@@ -15,22 +15,15 @@ use tui::{
pub struct ConnectionsComponent {
connections: Vec,
state: ListState,
-}
-
-impl Default for ConnectionsComponent {
- fn default() -> Self {
- Self {
- connections: Vec::new(),
- state: ListState::default(),
- }
- }
+ key_config: KeyConfig,
}
impl ConnectionsComponent {
- pub fn new(connections: Vec) -> Self {
+ pub fn new(key_config: KeyConfig, connections: Vec) -> Self {
Self {
connections,
- ..Self::default()
+ key_config,
+ state: ListState::default(),
}
}
@@ -100,19 +93,15 @@ impl DrawableComponent for ConnectionsComponent {
}
impl Component for ConnectionsComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
fn event(&mut self, key: Key) -> Result {
- match key {
- Key::Char('j') => {
- self.next_connection();
- return Ok(EventState::Consumed);
- }
- Key::Char('k') => {
- self.previous_connection();
- return Ok(EventState::Consumed);
- }
- _ => (),
+ if key == self.key_config.scroll_down {
+ self.next_connection();
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_up {
+ self.previous_connection();
+ return Ok(EventState::Consumed);
}
Ok(EventState::NotConsumed)
}
diff --git a/src/components/databases.rs b/src/components/databases.rs
index f12c570..b764420 100644
--- a/src/components/databases.rs
+++ b/src/components/databases.rs
@@ -2,7 +2,8 @@ use super::{
compute_character_width, utils::scroll_vertical::VerticalScroll, Component, DrawableComponent,
EventState,
};
-use crate::components::command::CommandInfo;
+use crate::components::command::{self, CommandInfo};
+use crate::config::KeyConfig;
use crate::event::Key;
use crate::ui::common_nav;
use crate::ui::scrolllist::draw_list_block;
@@ -26,6 +27,7 @@ const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}";
const FOLDER_ICON_EXPANDED: &str = "\u{25be}";
const EMPTY_STR: &str = "";
+#[derive(PartialEq)]
pub enum FocusBlock {
Filter,
Tree,
@@ -39,10 +41,11 @@ pub struct DatabasesComponent {
input_idx: usize,
input_cursor_position: u16,
focus_block: FocusBlock,
+ key_config: KeyConfig,
}
impl DatabasesComponent {
- pub fn new() -> Self {
+ pub fn new(key_config: KeyConfig) -> Self {
Self {
tree: DatabaseTree::default(),
filterd_tree: None,
@@ -51,6 +54,7 @@ impl DatabasesComponent {
input_idx: 0,
input_cursor_position: 0,
focus_block: FocusBlock::Tree,
+ key_config,
}
}
@@ -192,7 +196,9 @@ impl DrawableComponent for DatabasesComponent {
}
impl Component for DatabasesComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, out: &mut Vec) {
+ out.push(CommandInfo::new(command::expand_collapse(&self.key_config)))
+ }
fn event(&mut self, key: Key) -> Result {
let input_str: String = self.input.iter().collect();
@@ -203,15 +209,16 @@ impl Component for DatabasesComponent {
&mut self.tree
},
key,
+ &self.key_config,
) {
return Ok(EventState::Consumed);
}
+ if key == self.key_config.filter && self.focus_block == FocusBlock::Tree {
+ self.focus_block = FocusBlock::Filter;
+ return Ok(EventState::Consumed);
+ }
match key {
- Key::Char('/') if matches!(self.focus_block, FocusBlock::Tree) => {
- self.focus_block = FocusBlock::Filter;
- return Ok(EventState::Consumed);
- }
- Key::Char(c) if matches!(self.focus_block, FocusBlock::Filter) => {
+ Key::Char(c) if self.focus_block == FocusBlock::Filter => {
self.input.insert(self.input_idx, c);
self.input_idx += 1;
self.input_cursor_position += compute_character_width(c);
@@ -276,8 +283,8 @@ impl Component for DatabasesComponent {
}
}
-fn tree_nav(tree: &mut DatabaseTree, key: Key) -> bool {
- if let Some(common_nav) = common_nav(key) {
+fn tree_nav(tree: &mut DatabaseTree, key: Key, key_config: &KeyConfig) -> bool {
+ if let Some(common_nav) = common_nav(key, key_config) {
tree.move_selection(common_nav)
} else {
false
diff --git a/src/components/error.rs b/src/components/error.rs
index acac088..9836d00 100644
--- a/src/components/error.rs
+++ b/src/components/error.rs
@@ -1,5 +1,6 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
+use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
use tui::{
@@ -11,27 +12,34 @@ use tui::{
};
pub struct ErrorComponent {
- pub error: Option,
+ pub error: String,
+ visible: bool,
+ key_config: KeyConfig,
}
-impl Default for ErrorComponent {
- fn default() -> Self {
- Self { error: None }
+impl ErrorComponent {
+ pub fn new(key_config: KeyConfig) -> Self {
+ Self {
+ error: String::new(),
+ visible: false,
+ key_config,
+ }
}
}
impl ErrorComponent {
- pub fn set(&mut self, error: String) {
- self.error = Some(error);
+ pub fn set(&mut self, error: String) -> anyhow::Result<()> {
+ self.error = error;
+ self.show()
}
}
impl DrawableComponent for ErrorComponent {
fn draw(&mut self, f: &mut Frame, _area: Rect, _focused: bool) -> Result<()> {
- if let Some(error) = self.error.as_ref() {
+ if self.visible {
let width = 65;
let height = 10;
- let error = Paragraph::new(error.to_string())
+ let error = Paragraph::new(self.error.to_string())
.block(Block::default().title("Error").borders(Borders::ALL))
.style(Style::default().fg(Color::Red))
.alignment(Alignment::Left)
@@ -50,9 +58,27 @@ impl DrawableComponent for ErrorComponent {
}
impl Component for ErrorComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
- fn event(&mut self, _key: Key) -> Result {
+ fn event(&mut self, key: Key) -> Result {
+ if self.visible {
+ if key == self.key_config.exit_popup {
+ self.error = String::new();
+ self.hide();
+ return Ok(EventState::Consumed);
+ }
+ return Ok(EventState::NotConsumed);
+ }
Ok(EventState::NotConsumed)
}
+
+ fn hide(&mut self) {
+ self.visible = false;
+ }
+
+ fn show(&mut self) -> Result<()> {
+ self.visible = true;
+
+ Ok(())
+ }
}
diff --git a/src/components/help.rs b/src/components/help.rs
index f1bc03f..2b68838 100644
--- a/src/components/help.rs
+++ b/src/components/help.rs
@@ -1,5 +1,6 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
+use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
use itertools::Itertools;
@@ -17,6 +18,7 @@ pub struct HelpComponent {
cmds: Vec,
visible: bool,
selection: u16,
+ key_config: KeyConfig,
}
impl DrawableComponent for HelpComponent {
@@ -69,27 +71,22 @@ impl DrawableComponent for HelpComponent {
}
impl Component for HelpComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
fn event(&mut self, key: Key) -> Result {
if self.visible {
- match key {
- Key::Esc => {
- self.hide();
- return Ok(EventState::Consumed);
- }
- Key::Char('j') => {
- self.move_selection(true);
- return Ok(EventState::Consumed);
- }
- Key::Char('k') => {
- self.move_selection(false);
- return Ok(EventState::Consumed);
- }
- _ => (),
+ if key == self.key_config.exit_popup {
+ self.hide();
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_down {
+ self.scroll_selection(true);
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_up {
+ self.scroll_selection(false);
+ return Ok(EventState::Consumed);
}
return Ok(EventState::NotConsumed);
- } else if let Key::Char('?') = key {
+ } else if key == self.key_config.open_help {
self.show()?;
return Ok(EventState::Consumed);
}
@@ -108,11 +105,12 @@ impl Component for HelpComponent {
}
impl HelpComponent {
- pub const fn new() -> Self {
+ pub const fn new(key_config: KeyConfig) -> Self {
Self {
cmds: vec![],
visible: false,
selection: 0,
+ key_config,
}
}
@@ -123,7 +121,7 @@ impl HelpComponent {
.collect::>();
}
- fn move_selection(&mut self, inc: bool) {
+ fn scroll_selection(&mut self, inc: bool) {
let mut new_selection = self.selection;
new_selection = if inc {
@@ -152,7 +150,7 @@ impl HelpComponent {
processed += 1;
txt.push(Spans::from(Span::styled(
- format!("{}{:w$}", command_info.text.name, w = width),
+ format!(" {}{:w$}", command_info.text.name, w = width),
if is_selected {
Style::default().bg(Color::Blue)
} else {
@@ -168,15 +166,16 @@ impl HelpComponent {
#[cfg(test)]
mod test {
- use super::{Color, CommandInfo, HelpComponent, Modifier, Span, Spans, Style};
+ use super::{Color, CommandInfo, HelpComponent, KeyConfig, Modifier, Span, Spans, Style};
#[test]
fn test_get_text() {
let width = 3;
- let mut component = HelpComponent::new();
+ let key_config = KeyConfig::default();
+ let mut component = HelpComponent::new(key_config.clone());
component.set_cmds(vec![
- CommandInfo::new(crate::components::command::move_left("h"), true, true),
- CommandInfo::new(crate::components::command::move_right("l"), true, true),
+ CommandInfo::new(crate::components::command::scroll(&key_config)),
+ CommandInfo::new(crate::components::command::filter(&key_config)),
]);
assert_eq!(
component.get_text(width),
@@ -186,10 +185,10 @@ mod test {
Style::default().add_modifier(Modifier::REVERSED)
)),
Spans::from(Span::styled(
- "Move left [h] 3",
+ " Scroll up/down/left/right [k,j,h,l] 3",
Style::default().bg(Color::Blue)
)),
- Spans::from(Span::styled("Move right [l] 3", Style::default()))
+ Spans::from(Span::styled(" Filter [/] 3", Style::default()))
]
);
}
diff --git a/src/components/record_table.rs b/src/components/record_table.rs
index 8b2a087..7c9898c 100644
--- a/src/components/record_table.rs
+++ b/src/components/record_table.rs
@@ -1,8 +1,10 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::components::{TableComponent, TableFilterComponent};
+use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
+use database_tree::{Database, Table as DTable};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
@@ -18,52 +20,39 @@ pub struct RecordTableComponent {
pub filter: TableFilterComponent,
pub table: TableComponent,
pub focus: Focus,
+ key_config: KeyConfig,
}
-impl Default for RecordTableComponent {
- fn default() -> Self {
+impl RecordTableComponent {
+ pub fn new(key_config: KeyConfig) -> Self {
Self {
filter: TableFilterComponent::default(),
- table: TableComponent::default(),
+ table: TableComponent::new(key_config.clone()),
focus: Focus::Table,
+ key_config,
}
}
-}
-impl RecordTableComponent {
- pub fn new(rows: Vec>, headers: Vec) -> Self {
- Self {
- table: TableComponent::new(rows, headers),
- ..Self::default()
- }
- }
-
- pub fn update(&mut self, rows: Vec>, headers: Vec) {
- self.table.rows = rows;
- self.table.headers = headers;
- if !self.table.rows.is_empty() {
- self.table.selected_row.select(None);
- self.table.selected_row.select(Some(0));
- }
+ pub fn update(
+ &mut self,
+ rows: Vec>,
+ headers: Vec,
+ database: Database,
+ table: DTable,
+ ) {
+ self.table.update(rows, headers, database, table.clone());
+ self.filter.table = Some(table);
}
pub fn reset(&mut self) {
- self.table = TableComponent::default();
- if !self.table.rows.is_empty() {
- self.table.selected_row.select(None);
- self.table.selected_row.select(Some(0))
- }
- self.filter = TableFilterComponent::default();
+ self.table.reset();
+ self.filter.reset();
}
pub fn len(&self) -> usize {
self.table.rows.len()
}
- pub fn set_table(&mut self, table: String) {
- self.filter.table = Some(table)
- }
-
pub fn filter_focused(&self) -> bool {
matches!(self.focus, Focus::Filter)
}
@@ -86,14 +75,16 @@ impl DrawableComponent for RecordTableComponent {
}
impl Component for RecordTableComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, out: &mut Vec) {
+ self.table.commands(out)
+ }
fn event(&mut self, key: Key) -> Result {
+ if key == self.key_config.filter {
+ self.focus = Focus::Filter;
+ return Ok(EventState::Consumed);
+ }
match key {
- Key::Char('/') => {
- self.focus = Focus::Filter;
- return Ok(EventState::Consumed);
- }
key if matches!(self.focus, Focus::Filter) => return self.filter.event(key),
key if matches!(self.focus, Focus::Table) => return self.table.event(key),
_ => (),
diff --git a/src/components/tab.rs b/src/components/tab.rs
index 206f5be..4e7d030 100644
--- a/src/components/tab.rs
+++ b/src/components/tab.rs
@@ -1,8 +1,8 @@
use super::{Component, DrawableComponent, EventState};
-use crate::components::command::CommandInfo;
+use crate::components::command::{self, CommandInfo};
+use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
-use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use tui::{
backend::Backend,
@@ -25,29 +25,30 @@ impl std::fmt::Display for Tab {
}
}
-impl Tab {
- pub fn names() -> Vec {
- Self::iter()
- .map(|tab| format!("{} [{}]", tab, tab as u8 + 1))
- .collect()
- }
-}
-
pub struct TabComponent {
pub selected_tab: Tab,
+ key_config: KeyConfig,
}
-impl Default for TabComponent {
- fn default() -> Self {
+impl TabComponent {
+ pub fn new(key_config: KeyConfig) -> Self {
Self {
selected_tab: Tab::Records,
+ key_config,
}
}
+
+ fn names(&self) -> Vec {
+ vec![
+ command::tab_records(&self.key_config).name,
+ command::tab_structure(&self.key_config).name,
+ ]
+ }
}
impl DrawableComponent for TabComponent {
fn draw(&mut self, f: &mut Frame, area: Rect, _focused: bool) -> Result<()> {
- let titles = Tab::names().iter().cloned().map(Spans::from).collect();
+ let titles = self.names().iter().cloned().map(Spans::from).collect();
let tabs = Tabs::new(titles)
.block(Block::default().borders(Borders::ALL))
.select(self.selected_tab as usize)
@@ -63,19 +64,16 @@ impl DrawableComponent for TabComponent {
}
impl Component for TabComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
fn event(&mut self, key: Key) -> Result {
- match key {
- Key::Char('1') => {
- self.selected_tab = Tab::Records;
- Ok(EventState::Consumed)
- }
- Key::Char('2') => {
- self.selected_tab = Tab::Structure;
- Ok(EventState::Consumed)
- }
- _ => Ok(EventState::NotConsumed),
+ if key == self.key_config.tab_records {
+ self.selected_tab = Tab::Records;
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.tab_structure {
+ self.selected_tab = Tab::Structure;
+ return Ok(EventState::Consumed);
}
+ Ok(EventState::NotConsumed)
}
}
diff --git a/src/components/table.rs b/src/components/table.rs
index 6bc4406..c4ba7e7 100644
--- a/src/components/table.rs
+++ b/src/components/table.rs
@@ -2,9 +2,11 @@ use super::{
utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, EventState,
TableValueComponent,
};
-use crate::components::command::CommandInfo;
+use crate::components::command::{self, CommandInfo};
+use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
+use database_tree::{Database, Table as DTable};
use std::convert::From;
use tui::{
backend::Backend,
@@ -20,43 +22,70 @@ pub struct TableComponent {
pub rows: Vec>,
pub eod: bool,
pub selected_row: TableState,
+ table: Option<(Database, DTable)>,
selected_column: usize,
selection_area_corner: Option<(usize, usize)>,
column_page_start: std::cell::Cell,
scroll: VerticalScroll,
+ key_config: KeyConfig,
}
-impl Default for TableComponent {
- fn default() -> Self {
+impl TableComponent {
+ pub fn new(key_config: KeyConfig) -> Self {
Self {
selected_row: TableState::default(),
headers: vec![],
rows: vec![],
+ table: None,
selected_column: 0,
selection_area_corner: None,
column_page_start: std::cell::Cell::new(0),
scroll: VerticalScroll::new(),
eod: false,
+ key_config,
}
}
-}
-impl TableComponent {
- pub fn new(rows: Vec>, headers: Vec) -> Self {
- let mut selected_row = TableState::default();
+ fn title(&self) -> String {
+ self.table.as_ref().map_or(" - ".to_string(), |table| {
+ format!("{}/{}", table.0.name, table.1.name)
+ })
+ }
+
+ pub fn update(
+ &mut self,
+ rows: Vec>,
+ headers: Vec,
+ database: Database,
+ table: DTable,
+ ) {
if !rows.is_empty() {
- selected_row.select(None);
- selected_row.select(Some(0))
- }
- Self {
- headers,
- rows,
- selected_row,
- ..Self::default()
+ self.selected_row.select(None);
+ self.selected_row.select(Some(0))
}
+ self.headers = headers;
+ self.rows = rows;
+ self.selected_column = 0;
+ self.selection_area_corner = None;
+ self.column_page_start = std::cell::Cell::new(0);
+ self.scroll = VerticalScroll::new();
+ self.eod = false;
+ self.table = Some((database, table));
+ }
+
+ pub fn reset(&mut self) {
+ self.selected_row.select(None);
+ self.headers = Vec::new();
+ self.rows = Vec::new();
+ self.selected_column = 0;
+ self.selection_area_corner = None;
+ self.column_page_start = std::cell::Cell::new(0);
+ self.scroll = VerticalScroll::new();
+ self.eod = false;
+ self.table = None;
}
- fn reset(&mut self) {
+ fn reset_selection(&mut self) {
self.selection_area_corner = None;
}
@@ -75,7 +104,7 @@ impl TableComponent {
}
None => None,
};
- self.reset();
+ self.reset_selection();
self.selected_row.select(i);
}
@@ -90,7 +119,7 @@ impl TableComponent {
}
None => None,
};
- self.reset();
+ self.reset_selection();
self.selected_row.select(i);
}
@@ -98,7 +127,7 @@ impl TableComponent {
if self.rows.is_empty() {
return;
}
- self.reset();
+ self.reset_selection();
self.selected_row.select(Some(0));
}
@@ -106,7 +135,7 @@ impl TableComponent {
if self.rows.is_empty() {
return;
}
- self.reset();
+ self.reset_selection();
self.selected_row.select(Some(self.rows.len() - 1));
}
@@ -117,7 +146,7 @@ impl TableComponent {
if self.selected_column >= self.headers.len().saturating_sub(1) {
return;
}
- self.reset();
+ self.reset_selection();
self.selected_column += 1;
}
@@ -128,7 +157,7 @@ impl TableComponent {
if self.selected_column == 0 {
return;
}
- self.reset();
+ self.reset_selection();
self.selected_column -= 1;
}
@@ -389,7 +418,7 @@ impl DrawableComponent for TableComponent {
TableValueComponent::new(self.selected_cells().unwrap_or_default())
.draw(f, layout[0], focused)?;
- let block = Block::default().borders(Borders::ALL).title("Records");
+ let block = Block::default().borders(Borders::ALL).title(self.title());
let (selected_column_index, headers, rows, constraints) =
self.calculate_cell_widths(block.inner(layout[1]).width);
let header_cells = headers.iter().enumerate().map(|(column_index, h)| {
@@ -448,59 +477,49 @@ impl DrawableComponent for TableComponent {
}
impl Component for TableComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, out: &mut Vec) {
+ out.push(CommandInfo::new(command::extend_selection_by_one_cell(
+ &self.key_config,
+ )));
+ }
fn event(&mut self, key: Key) -> Result {
- match key {
- Key::Char('h') => {
- self.previous_column();
- return Ok(EventState::Consumed);
- }
- Key::Char('j') => {
- self.next_row(1);
- return Ok(EventState::NotConsumed);
- }
- Key::Ctrl('d') => {
- self.next_row(10);
- return Ok(EventState::NotConsumed);
- }
- Key::Char('k') => {
- self.previous_row(1);
- return Ok(EventState::Consumed);
- }
- Key::Ctrl('u') => {
- self.previous_row(10);
- return Ok(EventState::Consumed);
- }
- Key::Char('g') => {
- self.scroll_top();
- return Ok(EventState::Consumed);
- }
- Key::Char('G') => {
- self.scroll_bottom();
- return Ok(EventState::Consumed);
- }
- Key::Char('l') => {
- self.next_column();
- return Ok(EventState::Consumed);
- }
- Key::Char('H') => {
- self.expand_selected_area_x(false);
- return Ok(EventState::Consumed);
- }
- Key::Char('K') => {
- self.expand_selected_area_y(false);
- return Ok(EventState::Consumed);
- }
- Key::Char('J') => {
- self.expand_selected_area_y(true);
- return Ok(EventState::Consumed);
- }
- Key::Char('L') => {
- self.expand_selected_area_x(true);
- return Ok(EventState::Consumed);
- }
- _ => (),
+ if key == self.key_config.scroll_left {
+ self.previous_column();
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_down {
+ self.next_row(1);
+ return Ok(EventState::NotConsumed);
+ } else if key == self.key_config.scroll_down_multiple_lines {
+ self.next_row(10);
+ return Ok(EventState::NotConsumed);
+ } else if key == self.key_config.scroll_up {
+ self.previous_row(1);
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_up_multiple_lines {
+ self.previous_row(10);
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_to_top {
+ self.scroll_top();
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_to_bottom {
+ self.scroll_bottom();
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.scroll_right {
+ self.next_column();
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.extend_selection_by_one_cell_left {
+ self.expand_selected_area_x(false);
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.extend_selection_by_one_cell_up {
+ self.expand_selected_area_y(false);
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.extend_selection_by_one_cell_down {
+ self.expand_selected_area_y(true);
+ return Ok(EventState::Consumed);
+ } else if key == self.key_config.extend_selection_by_one_cell_right {
+ self.expand_selected_area_x(true);
+ return Ok(EventState::Consumed);
}
Ok(EventState::NotConsumed)
}
@@ -508,19 +527,19 @@ impl Component for TableComponent {
#[cfg(test)]
mod test {
- use super::TableComponent;
+ use super::{KeyConfig, TableComponent};
use tui::layout::Constraint;
#[test]
fn test_headers() {
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect();
assert_eq!(component.headers(1, 2), vec!["", "b"])
}
#[test]
fn test_rows() {
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
@@ -540,7 +559,7 @@ mod test {
// 1 a b c
// 2 |d e| f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -565,7 +584,7 @@ mod test {
// 1 a b c
// 2 d |e f|
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -590,7 +609,7 @@ mod test {
// 1 a |b| c
// 2 d |e| f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
@@ -614,7 +633,7 @@ mod test {
// 1 a |b| c
// 2 d |e| f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
@@ -628,7 +647,7 @@ mod test {
#[test]
fn test_is_number_column() {
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -645,7 +664,7 @@ mod test {
// 1 |a| b c
// 2 d e f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -661,7 +680,7 @@ mod test {
// 1 |a b| c
// 2 |d e| f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -678,7 +697,7 @@ mod test {
// 1 |a| b c
// 2 d e f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -699,7 +718,7 @@ mod test {
// 1 |a b| c
// 2 |d e| f
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
@@ -721,7 +740,7 @@ mod test {
#[test]
fn test_calculate_cell_widths() {
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["aaaaa", "bbbbb", "ccccc"]
@@ -765,7 +784,7 @@ mod test {
]
);
- let mut component = TableComponent::default();
+ let mut component = TableComponent::new(KeyConfig::default());
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["aaaaa", "bbbbb", "ccccc"]
diff --git a/src/components/table_filter.rs b/src/components/table_filter.rs
index bcf93dd..e91932a 100644
--- a/src/components/table_filter.rs
+++ b/src/components/table_filter.rs
@@ -2,6 +2,7 @@ use super::{compute_character_width, Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
+use database_tree::Table;
use tui::{
backend::Backend,
layout::Rect,
@@ -13,7 +14,7 @@ use tui::{
use unicode_width::UnicodeWidthStr;
pub struct TableFilterComponent {
- pub table: Option,
+ pub table: Option,
pub input: Vec,
input_idx: usize,
input_cursor_position: u16,
@@ -34,6 +35,13 @@ impl TableFilterComponent {
pub fn input_str(&self) -> String {
self.input.iter().collect()
}
+
+ pub fn reset(&mut self) {
+ self.table = None;
+ self.input = Vec::new();
+ self.input_idx = 0;
+ self.input_cursor_position = 0;
+ }
}
impl DrawableComponent for TableFilterComponent {
@@ -42,7 +50,7 @@ impl DrawableComponent for TableFilterComponent {
Span::styled(
self.table
.as_ref()
- .map_or("-".to_string(), |table| table.to_string()),
+ .map_or("-".to_string(), |table| table.name.to_string()),
Style::default().fg(Color::Blue),
),
Span::from(format!(
@@ -67,7 +75,7 @@ impl DrawableComponent for TableFilterComponent {
+ (1 + self
.table
.as_ref()
- .map_or(String::new(), |table| table.to_string())
+ .map_or(String::new(), |table| table.name.to_string())
.width()
+ 1) as u16)
.saturating_add(self.input_cursor_position),
@@ -79,7 +87,7 @@ impl DrawableComponent for TableFilterComponent {
}
impl Component for TableFilterComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
fn event(&mut self, key: Key) -> Result {
let input_str: String = self.input.iter().collect();
diff --git a/src/components/table_status.rs b/src/components/table_status.rs
index 582e368..89998d5 100644
--- a/src/components/table_status.rs
+++ b/src/components/table_status.rs
@@ -86,7 +86,7 @@ impl DrawableComponent for TableStatusComponent {
}
impl Component for TableStatusComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
fn event(&mut self, _key: Key) -> Result {
Ok(EventState::NotConsumed)
diff --git a/src/components/table_value.rs b/src/components/table_value.rs
index eed0726..243803d 100644
--- a/src/components/table_value.rs
+++ b/src/components/table_value.rs
@@ -46,7 +46,7 @@ impl DrawableComponent for TableValueComponent {
}
impl Component for TableValueComponent {
- fn commands(&self, out: &mut Vec) {}
+ fn commands(&self, _out: &mut Vec) {}
fn event(&mut self, _key: Key) -> Result {
todo!("scroll");
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..cb85e04
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,131 @@
+use crate::Key;
+use serde::Deserialize;
+use std::fs::File;
+use std::io::{BufReader, Read};
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct Config {
+ pub conn: Vec,
+ #[serde(default)]
+ pub key_config: KeyConfig,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ conn: vec![Connection {
+ name: None,
+ user: "root".to_string(),
+ host: "localhost".to_string(),
+ port: 3306,
+ database: None,
+ }],
+ key_config: KeyConfig::default(),
+ }
+ }
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct Connection {
+ name: Option,
+ user: String,
+ host: String,
+ port: u64,
+ pub database: Option,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct KeyConfig {
+ pub scroll_up: Key,
+ pub scroll_down: Key,
+ pub scroll_right: Key,
+ pub scroll_left: Key,
+ pub copy: Key,
+ pub enter: Key,
+ pub exit: Key,
+ pub quit: Key,
+ pub exit_popup: Key,
+ pub focus_right: Key,
+ pub focus_left: Key,
+ pub focus_connections: Key,
+ pub open_help: Key,
+ pub filter: Key,
+ pub scroll_down_multiple_lines: Key,
+ pub scroll_up_multiple_lines: Key,
+ pub scroll_to_top: Key,
+ pub scroll_to_bottom: Key,
+ pub extend_selection_by_one_cell_left: Key,
+ pub extend_selection_by_one_cell_right: Key,
+ pub extend_selection_by_one_cell_up: Key,
+ pub extend_selection_by_one_cell_down: Key,
+ pub tab_records: Key,
+ pub tab_structure: Key,
+}
+
+impl Default for KeyConfig {
+ fn default() -> Self {
+ Self {
+ scroll_up: Key::Char('k'),
+ scroll_down: Key::Char('j'),
+ scroll_right: Key::Char('l'),
+ scroll_left: Key::Char('h'),
+ copy: Key::Char('y'),
+ enter: Key::Enter,
+ exit: Key::Ctrl('c'),
+ quit: Key::Char('q'),
+ exit_popup: Key::Esc,
+ focus_right: Key::Right,
+ focus_left: Key::Left,
+ focus_connections: Key::Char('c'),
+ open_help: Key::Char('?'),
+ filter: Key::Char('/'),
+ scroll_down_multiple_lines: Key::Ctrl('d'),
+ scroll_up_multiple_lines: Key::Ctrl('u'),
+ scroll_to_top: Key::Char('g'),
+ scroll_to_bottom: Key::Char('G'),
+ extend_selection_by_one_cell_left: Key::Char('H'),
+ extend_selection_by_one_cell_right: Key::Char('L'),
+ extend_selection_by_one_cell_down: Key::Char('J'),
+ extend_selection_by_one_cell_up: Key::Char('K'),
+ tab_records: Key::Char('1'),
+ tab_structure: Key::Char('2'),
+ }
+ }
+}
+
+impl Config {
+ pub fn new(path: &str) -> anyhow::Result {
+ if let Ok(file) = File::open(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);
+ match config {
+ Ok(config) => return Ok(config),
+ Err(e) => panic!("fail to parse config file: {}", e),
+ }
+ }
+ Ok(Config::default())
+ }
+}
+
+impl Connection {
+ pub fn database_url(&self) -> String {
+ match &self.database {
+ Some(database) => format!(
+ "mysql://{user}:@{host}:{port}/{database}",
+ user = self.user,
+ host = self.host,
+ port = self.port,
+ database = database
+ ),
+ None => format!(
+ "mysql://{user}:@{host}:{port}",
+ user = self.user,
+ host = self.host,
+ port = self.port,
+ ),
+ }
+ }
+}
diff --git a/src/event/events.rs b/src/event/events.rs
index afa3aba..a01e93d 100644
--- a/src/event/events.rs
+++ b/src/event/events.rs
@@ -3,11 +3,8 @@ use crossterm::event;
use std::{sync::mpsc, thread, time::Duration};
#[derive(Debug, Clone, Copy)]
-/// Configuration for event handling.
pub struct EventConfig {
- /// The key that is used to exit the application.
pub exit_key: Key,
- /// The tick rate at which the application will sent an tick event.
pub tick_rate: Duration,
}
@@ -20,25 +17,18 @@ impl Default for EventConfig {
}
}
-/// An occurred event.
#[derive(Copy, Clone)]
pub enum Event {
- /// An input event occurred.
Input(I),
- /// An tick event occurred.
Tick,
}
-/// A small event handler that wrap crossterm input and tick event. Each event
-/// type is handled in its own thread and returned to a common `Receiver`
pub struct Events {
rx: mpsc::Receiver>,
- // Need to be kept around to prevent disposing the sender side.
_tx: mpsc::Sender>,
}
impl Events {
- /// Constructs an new instance of `Events` with the default config.
pub fn new(tick_rate: u64) -> Events {
Events::with_config(EventConfig {
tick_rate: Duration::from_millis(tick_rate),
@@ -46,31 +36,25 @@ impl Events {
})
}
- /// Constructs an new instance of `Events` from given config.
pub fn with_config(config: EventConfig) -> Events {
let (tx, rx) = mpsc::channel();
let event_tx = tx.clone();
- thread::spawn(move || {
- loop {
- // poll for tick rate duration, if no event, sent tick event.
- if event::poll(config.tick_rate).unwrap() {
- if let event::Event::Key(key) = event::read().unwrap() {
- let key = Key::from(key);
+ thread::spawn(move || loop {
+ if event::poll(config.tick_rate).unwrap() {
+ if let event::Event::Key(key) = event::read().unwrap() {
+ let key = Key::from(key);
- event_tx.send(Event::Input(key)).unwrap();
- }
+ event_tx.send(Event::Input(key)).unwrap();
}
-
- event_tx.send(Event::Tick).unwrap();
}
+
+ event_tx.send(Event::Tick).unwrap();
});
Events { rx, _tx: tx }
}
- /// Attempts to read an event.
- /// This function will block the current thread.
pub fn next(&self) -> Result, mpsc::RecvError> {
self.rx.recv()
}
diff --git a/src/event/key.rs b/src/event/key.rs
index 4e29c35..cfbe1eb 100644
--- a/src/event/key.rs
+++ b/src/event/key.rs
@@ -1,8 +1,9 @@
use crossterm::event;
+use serde::Deserialize;
use std::fmt;
/// Represents a key.
-#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
+#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Deserialize)]
pub enum Key {
/// Both Enter (or Return) and numpad Enter
Enter,
@@ -104,7 +105,10 @@ impl fmt::Display for Key {
Key::Alt(c) => write!(f, "", c),
Key::Ctrl(c) => write!(f, "", c),
Key::Char(c) => write!(f, "{}", c),
- Key::Left | Key::Right | Key::Up | Key::Down => write!(f, "<{:?} Arrow Key>", self),
+ Key::Left => write!(f, "\u{2190}"), //←
+ Key::Right => write!(f, "\u{2192}"), //→
+ Key::Up => write!(f, "\u{2191}"), //↑
+ Key::Down => write!(f, "\u{2193}"), //↓
Key::Enter
| Key::Tab
| Key::Backspace
diff --git a/src/main.rs b/src/main.rs
index 3da4693..8fe10a2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,9 @@
mod app;
mod clipboard;
mod components;
+mod config;
mod event;
mod ui;
-mod user_config;
mod utils;
#[macro_use]
@@ -28,7 +28,7 @@ async fn main() -> anyhow::Result<()> {
outln!("gobang logger");
- let user_config = user_config::UserConfig::new("sample.toml").ok();
+ let config = config::Config::new("sample.toml")?;
let stdout = stdout();
setup_terminal()?;
@@ -36,7 +36,7 @@ async fn main() -> anyhow::Result<()> {
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let events = event::Events::new(250);
- let mut app = App::new(user_config.unwrap());
+ let mut app = App::new(config);
terminal.clear()?;
@@ -45,11 +45,13 @@ async fn main() -> anyhow::Result<()> {
match events.next()? {
Event::Input(key) => match app.event(key).await {
Ok(state) => {
- if !state.is_consumed() && (key == Key::Char('q') || key == Key::Ctrl('c')) {
+ if !state.is_consumed()
+ && (key == app.config.key_config.quit || key == app.config.key_config.exit)
+ {
break;
}
}
- Err(err) => app.error.set(err.to_string()),
+ Err(err) => app.error.set(err.to_string())?,
},
Event::Tick => (),
}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index 45766f3..6f81822 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -1,22 +1,23 @@
+use crate::config::KeyConfig;
use crate::event::Key;
use database_tree::MoveSelection;
pub mod scrollbar;
pub mod scrolllist;
-pub fn common_nav(key: Key) -> Option {
- if key == Key::Char('j') {
+pub fn common_nav(key: Key, key_config: &KeyConfig) -> Option {
+ if key == key_config.scroll_down {
Some(MoveSelection::Down)
- } else if key == Key::Char('k') {
+ } else if key == key_config.scroll_up {
Some(MoveSelection::Up)
- } else if key == Key::PageUp {
- Some(MoveSelection::PageUp)
- } else if key == Key::PageDown {
- Some(MoveSelection::PageDown)
- } else if key == Key::Char('l') {
+ } else if key == key_config.scroll_right {
Some(MoveSelection::Right)
- } else if key == Key::Char('h') {
+ } else if key == key_config.scroll_left {
Some(MoveSelection::Left)
+ } else if key == key_config.scroll_to_top {
+ Some(MoveSelection::Top)
+ } else if key == key_config.scroll_to_bottom {
+ Some(MoveSelection::End)
} else {
None
}
diff --git a/src/user_config.rs b/src/user_config.rs
deleted file mode 100644
index dc7d13a..0000000
--- a/src/user_config.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use serde::Deserialize;
-use std::fs::File;
-use std::io::{BufReader, Read};
-
-#[derive(Debug, Deserialize, Clone)]
-pub struct UserConfig {
- pub conn: Vec,
-}
-
-#[derive(Debug, Deserialize, Clone)]
-pub struct Connection {
- name: Option,
- user: String,
- host: String,
- port: u64,
- pub database: Option,
-}
-
-impl UserConfig {
- pub fn new(path: &str) -> anyhow::Result {
- let file = File::open(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);
- match config {
- Ok(config) => Ok(config),
- Err(e) => panic!("fail to parse config file: {}", e),
- }
- }
-}
-
-impl Connection {
- pub fn database_url(&self) -> String {
- match &self.database {
- Some(database) => format!(
- "mysql://{user}:@{host}:{port}/{database}",
- user = self.user,
- host = self.host,
- port = self.port,
- database = database
- ),
- None => format!(
- "mysql://{user}:@{host}:{port}",
- user = self.user,
- host = self.host,
- port = self.port,
- ),
- }
- }
-}