Create PropertiesComponent and redesign layout (#128)

* create properties component

* refactor app.rs

* add a test for checking if gobang has overlappted keys

* add keys for switching tabs to properties

* fix tab

* add serialize

* update record_table

* add properties group

* use serialize only in tests

* remove alias

* remove query field
pull/137/head
Takayuki Maeda 3 years ago committed by GitHub
parent 4bcd4802fc
commit 36b1da0afa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

49
Cargo.lock generated

@ -37,6 +37,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.41" version = "1.0.41"
@ -204,7 +213,7 @@ version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [ dependencies = [
"ansi_term", "ansi_term 0.11.0",
"atty", "atty",
"bitflags", "bitflags",
"strsim", "strsim",
@ -321,6 +330,16 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "database-tree" name = "database-tree"
version = "0.1.0-alpha.5" version = "0.1.0-alpha.5"
@ -330,6 +349,12 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.9.0" version = "0.9.0"
@ -582,6 +607,7 @@ dependencies = [
"easy-cast", "easy-cast",
"futures", "futures",
"itertools", "itertools",
"pretty_assertions",
"rust_decimal", "rust_decimal",
"serde", "serde",
"serde_json", "serde_json",
@ -964,6 +990,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "output_vt100"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.1" version = "0.11.1"
@ -1044,6 +1079,18 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pretty_assertions"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc"
dependencies = [
"ansi_term 0.12.1",
"ctor",
"diff",
"output_vt100",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"

@ -42,3 +42,6 @@ unicode-segmentation = "1.7"
[target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies] [target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies]
which = "4.1" which = "4.1"
[dev-dependencies]
pretty_assertions = "1.0.0"

@ -8,11 +8,10 @@ use crate::{
components::tab::Tab, components::tab::Tab,
components::{ components::{
command, ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent, command, ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent,
RecordTableComponent, SqlEditorComponent, TabComponent, TableComponent, PropertiesComponent, RecordTableComponent, SqlEditorComponent, TabComponent,
}, },
config::Config, config::Config,
}; };
use database_tree::Database;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -26,10 +25,7 @@ pub enum Focus {
} }
pub struct App { pub struct App {
record_table: RecordTableComponent, record_table: RecordTableComponent,
column_table: TableComponent, properties: PropertiesComponent,
constraint_table: TableComponent,
foreign_key_table: TableComponent,
index_table: TableComponent,
sql_editor: SqlEditorComponent, sql_editor: SqlEditorComponent,
focus: Focus, focus: Focus,
tab: TabComponent, tab: TabComponent,
@ -48,10 +44,7 @@ impl App {
config: config.clone(), config: config.clone(),
connections: ConnectionsComponent::new(config.key_config.clone(), config.conn), connections: ConnectionsComponent::new(config.key_config.clone(), config.conn),
record_table: RecordTableComponent::new(config.key_config.clone()), record_table: RecordTableComponent::new(config.key_config.clone()),
column_table: TableComponent::new(config.key_config.clone()), properties: PropertiesComponent::new(config.key_config.clone()),
constraint_table: TableComponent::new(config.key_config.clone()),
foreign_key_table: TableComponent::new(config.key_config.clone()),
index_table: TableComponent::new(config.key_config.clone()),
sql_editor: SqlEditorComponent::new(config.key_config.clone()), sql_editor: SqlEditorComponent::new(config.key_config.clone()),
tab: TabComponent::new(config.key_config.clone()), tab: TabComponent::new(config.key_config.clone()),
help: HelpComponent::new(config.key_config.clone()), help: HelpComponent::new(config.key_config.clone()),
@ -100,28 +93,14 @@ impl App {
self.record_table self.record_table
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))? .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
} }
Tab::Columns => {
self.column_table
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
}
Tab::Constraints => self.constraint_table.draw(
f,
right_chunks[1],
matches!(self.focus, Focus::Table),
)?,
Tab::ForeignKeys => self.foreign_key_table.draw(
f,
right_chunks[1],
matches!(self.focus, Focus::Table),
)?,
Tab::Indexes => {
self.index_table
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
}
Tab::Sql => { Tab::Sql => {
self.sql_editor self.sql_editor
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?; .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?;
} }
Tab::Properties => {
self.properties
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?;
}
} }
self.error.draw(f, Rect::default(), false)?; self.error.draw(f, Rect::default(), false)?;
self.help.draw(f, Rect::default(), false)?; self.help.draw(f, Rect::default(), false)?;
@ -150,6 +129,7 @@ impl App {
self.databases.commands(&mut res); self.databases.commands(&mut res);
self.record_table.commands(&mut res); self.record_table.commands(&mut res);
self.properties.commands(&mut res);
res res
} }
@ -172,18 +152,9 @@ impl App {
SqlitePool::new(conn.database_url()?.as_str()).await?, SqlitePool::new(conn.database_url()?.as_str()).await?,
)) ))
}; };
let databases = match &conn.database { self.databases
Some(database) => vec![Database::new( .update(conn, self.pool.as_ref().unwrap())
database.clone(), .await?;
self.pool
.as_ref()
.unwrap()
.get_tables(database.clone())
.await?,
)],
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(); self.record_table.reset();
self.tab.reset(); self.tab.reset();
@ -191,95 +162,6 @@ impl App {
Ok(()) Ok(())
} }
async fn update_table(&mut self) -> anyhow::Result<()> {
if let Some((database, table)) = self.databases.tree().selected_table() {
self.focus = Focus::Table;
self.record_table.reset();
let (headers, records) = self
.pool
.as_ref()
.unwrap()
.get_records(&database, &table, 0, None)
.await?;
self.record_table
.update(records, headers, database.clone(), table.clone());
self.column_table.reset();
let columns = self
.pool
.as_ref()
.unwrap()
.get_columns(&database, &table)
.await?;
if !columns.is_empty() {
self.column_table.update(
columns
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
columns.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
self.constraint_table.reset();
let constraints = self
.pool
.as_ref()
.unwrap()
.get_constraints(&database, &table)
.await?;
if !constraints.is_empty() {
self.constraint_table.update(
constraints
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
constraints.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
self.foreign_key_table.reset();
let foreign_keys = self
.pool
.as_ref()
.unwrap()
.get_foreign_keys(&database, &table)
.await?;
if !foreign_keys.is_empty() {
self.foreign_key_table.update(
foreign_keys
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
foreign_keys.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
self.index_table.reset();
let indexes = self
.pool
.as_ref()
.unwrap()
.get_indexes(&database, &table)
.await?;
if !indexes.is_empty() {
self.index_table.update(
indexes
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
indexes.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
}
Ok(())
}
async fn update_record_table(&mut self) -> anyhow::Result<()> { async fn update_record_table(&mut self) -> anyhow::Result<()> {
if let Some((database, table)) = self.databases.tree().selected_table() { if let Some((database, table)) = self.databases.tree().selected_table() {
let (headers, records) = self let (headers, records) = self
@ -342,7 +224,21 @@ impl App {
} }
if key == self.config.key_config.enter && self.databases.tree_focused() { if key == self.config.key_config.enter && self.databases.tree_focused() {
self.update_table().await?; if let Some((database, table)) = self.databases.tree().selected_table() {
self.record_table.reset();
let (headers, records) = self
.pool
.as_ref()
.unwrap()
.get_records(&database, &table, 0, None)
.await?;
self.record_table
.update(records, headers, database.clone(), table.clone());
self.properties
.update(database.clone(), table.clone(), self.pool.as_ref().unwrap())
.await?;
self.focus = Focus::Table;
}
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
} }
@ -398,50 +294,6 @@ impl App {
} }
}; };
} }
Tab::Columns => {
if self.column_table.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
};
if key == self.config.key_config.copy {
if let Some(text) = self.column_table.selected_cells() {
copy_to_clipboard(text.as_str())?
}
};
}
Tab::Constraints => {
if self.constraint_table.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
};
if key == self.config.key_config.copy {
if let Some(text) = self.constraint_table.selected_cells() {
copy_to_clipboard(text.as_str())?
}
};
}
Tab::ForeignKeys => {
if self.foreign_key_table.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
};
if key == self.config.key_config.copy {
if let Some(text) = self.foreign_key_table.selected_cells() {
copy_to_clipboard(text.as_str())?
}
};
}
Tab::Indexes => {
if self.index_table.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
};
if key == self.config.key_config.copy {
if let Some(text) = self.index_table.selected_cells() {
copy_to_clipboard(text.as_str())?
}
};
}
Tab::Sql => { Tab::Sql => {
if self.sql_editor.event(key)?.is_consumed() if self.sql_editor.event(key)?.is_consumed()
|| self || self
@ -453,6 +305,11 @@ impl App {
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
}; };
} }
Tab::Properties => {
if self.properties.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
};
}
}; };
} }
} }

@ -3,6 +3,7 @@ use crate::config::KeyConfig;
static CMD_GROUP_GENERAL: &str = "-- General --"; static CMD_GROUP_GENERAL: &str = "-- General --";
static CMD_GROUP_TABLE: &str = "-- Table --"; static CMD_GROUP_TABLE: &str = "-- Table --";
static CMD_GROUP_DATABASES: &str = "-- Databases --"; static CMD_GROUP_DATABASES: &str = "-- Databases --";
static CMD_GROUP_PROPERTIES: &str = "-- Properties --";
#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)] #[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
pub struct CommandText { pub struct CommandText {
@ -135,17 +136,33 @@ pub fn tab_sql_editor(key: &KeyConfig) -> CommandText {
CommandText::new(format!("SQL [{}]", key.tab_sql_editor), CMD_GROUP_TABLE) CommandText::new(format!("SQL [{}]", key.tab_sql_editor), CMD_GROUP_TABLE)
} }
pub fn tab_properties(key: &KeyConfig) -> CommandText {
CommandText::new(
format!("Properties [{}]", key.tab_properties),
CMD_GROUP_TABLE,
)
}
pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText { pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText {
CommandText::new( CommandText::new(
format!( format!(
"Tab [{},{},{},{},{}]", "Tab [{},{},{}]",
key_config.tab_records, key_config.tab_records, key_config.tab_properties, key_config.tab_sql_editor
),
CMD_GROUP_GENERAL,
)
}
pub fn toggle_property_tabs(key_config: &KeyConfig) -> CommandText {
CommandText::new(
format!(
"Tab [{},{},{},{}]",
key_config.tab_columns, key_config.tab_columns,
key_config.tab_constraints, key_config.tab_constraints,
key_config.tab_foreign_keys, key_config.tab_foreign_keys,
key_config.tab_indexes key_config.tab_indexes
), ),
CMD_GROUP_GENERAL, CMD_GROUP_PROPERTIES,
) )
} }

@ -3,7 +3,8 @@ use super::{
EventState, EventState,
}; };
use crate::components::command::{self, CommandInfo}; use crate::components::command::{self, CommandInfo};
use crate::config::KeyConfig; use crate::config::{Connection, KeyConfig};
use crate::database::Pool;
use crate::event::Key; use crate::event::Key;
use crate::ui::common_nav; use crate::ui::common_nav;
use crate::ui::scrolllist::draw_list_block; use crate::ui::scrolllist::draw_list_block;
@ -53,8 +54,15 @@ impl DatabasesComponent {
} }
} }
pub fn update(&mut self, list: &[Database]) -> Result<()> { pub async fn update(&mut self, connection: &Connection, pool: &Box<dyn Pool>) -> Result<()> {
self.tree = DatabaseTree::new(list, &BTreeSet::new())?; let databases = match &connection.database {
Some(database) => vec![Database::new(
database.clone(),
pool.get_tables(database.clone()).await?,
)],
None => pool.get_databases().await?,
};
self.tree = DatabaseTree::new(databases.as_slice(), &BTreeSet::new())?;
self.filterd_tree = None; self.filterd_tree = None;
self.filter.reset(); self.filter.reset();
Ok(()) Ok(())

@ -5,6 +5,7 @@ pub mod database_filter;
pub mod databases; pub mod databases;
pub mod error; pub mod error;
pub mod help; pub mod help;
pub mod properties;
pub mod record_table; pub mod record_table;
pub mod sql_editor; pub mod sql_editor;
pub mod tab; pub mod tab;
@ -24,6 +25,7 @@ pub use database_filter::DatabaseFilterComponent;
pub use databases::DatabasesComponent; pub use databases::DatabasesComponent;
pub use error::ErrorComponent; pub use error::ErrorComponent;
pub use help::HelpComponent; pub use help::HelpComponent;
pub use properties::PropertiesComponent;
pub use record_table::RecordTableComponent; pub use record_table::RecordTableComponent;
pub use sql_editor::SqlEditorComponent; pub use sql_editor::SqlEditorComponent;
pub use tab::TabComponent; pub use tab::TabComponent;

@ -0,0 +1,200 @@
use super::{Component, EventState, StatefulDrawableComponent};
use crate::clipboard::copy_to_clipboard;
use crate::components::command::{self, CommandInfo};
use crate::components::TableComponent;
use crate::config::KeyConfig;
use crate::database::Pool;
use crate::event::Key;
use anyhow::Result;
use async_trait::async_trait;
use database_tree::{Database, Table};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
widgets::{Block, Borders, List, ListItem},
Frame,
};
#[derive(Debug, PartialEq)]
pub enum Focus {
Column,
Constraint,
ForeignKey,
Index,
}
impl std::fmt::Display for Focus {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct PropertiesComponent {
column_table: TableComponent,
constraint_table: TableComponent,
foreign_key_table: TableComponent,
index_table: TableComponent,
focus: Focus,
key_config: KeyConfig,
}
impl PropertiesComponent {
pub fn new(key_config: KeyConfig) -> Self {
Self {
column_table: TableComponent::new(key_config.clone()),
constraint_table: TableComponent::new(key_config.clone()),
foreign_key_table: TableComponent::new(key_config.clone()),
index_table: TableComponent::new(key_config.clone()),
focus: Focus::Column,
key_config,
}
}
fn focused_component(&mut self) -> &mut TableComponent {
match self.focus {
Focus::Column => &mut self.column_table,
Focus::Constraint => &mut self.constraint_table,
Focus::ForeignKey => &mut self.foreign_key_table,
Focus::Index => &mut self.index_table,
}
}
pub async fn update(
&mut self,
database: Database,
table: Table,
pool: &Box<dyn Pool>,
) -> Result<()> {
self.column_table.reset();
let columns = pool.get_columns(&database, &table).await?;
if !columns.is_empty() {
self.column_table.update(
columns
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
columns.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
self.constraint_table.reset();
let constraints = pool.get_constraints(&database, &table).await?;
if !constraints.is_empty() {
self.constraint_table.update(
constraints
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
constraints.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
self.foreign_key_table.reset();
let foreign_keys = pool.get_foreign_keys(&database, &table).await?;
if !foreign_keys.is_empty() {
self.foreign_key_table.update(
foreign_keys
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
foreign_keys.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
self.index_table.reset();
let indexes = pool.get_indexes(&database, &table).await?;
if !indexes.is_empty() {
self.index_table.update(
indexes
.iter()
.map(|c| c.columns())
.collect::<Vec<Vec<String>>>(),
indexes.get(0).unwrap().fields(),
database.clone(),
table.clone(),
);
}
Ok(())
}
fn tab_names(&self) -> Vec<(Focus, String)> {
vec![
(Focus::Column, command::tab_columns(&self.key_config).name),
(
Focus::Constraint,
command::tab_constraints(&self.key_config).name,
),
(
Focus::ForeignKey,
command::tab_foreign_keys(&self.key_config).name,
),
(Focus::Index, command::tab_indexes(&self.key_config).name),
]
}
}
impl StatefulDrawableComponent for PropertiesComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![Constraint::Length(20), Constraint::Min(1)])
.split(area);
let tab_names = self
.tab_names()
.iter()
.map(|(f, c)| {
ListItem::new(c.to_string()).style(if *f == self.focus {
Style::default().bg(Color::Blue)
} else {
Style::default()
})
})
.collect::<Vec<ListItem>>();
let tab_list = List::new(tab_names)
.block(Block::default().borders(Borders::ALL).style(if focused {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
}))
.style(Style::default());
f.render_widget(tab_list, layout[0]);
self.focused_component().draw(f, layout[1], focused)?;
Ok(())
}
}
#[async_trait]
impl Component for PropertiesComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {
out.push(CommandInfo::new(command::toggle_property_tabs(
&self.key_config,
)));
}
fn event(&mut self, key: Key) -> Result<EventState> {
self.focused_component().event(key)?;
if key == self.key_config.copy {
if let Some(text) = self.focused_component().selected_cells() {
copy_to_clipboard(text.as_str())?
}
} else if key == self.key_config.tab_columns {
self.focus = Focus::Column;
} else if key == self.key_config.tab_constraints {
self.focus = Focus::Constraint;
} else if key == self.key_config.tab_foreign_keys {
self.focus = Focus::ForeignKey;
} else if key == self.key_config.tab_indexes {
self.focus = Focus::Index;
}
Ok(EventState::NotConsumed)
}
}

@ -20,7 +20,6 @@ use unicode_width::UnicodeWidthStr;
struct QueryResult { struct QueryResult {
updated_rows: u64, updated_rows: u64,
query: String,
} }
impl QueryResult { impl QueryResult {
@ -275,10 +274,7 @@ impl Component for SqlEditorComponent {
self.query_result = None; self.query_result = None;
} }
ExecuteResult::Write { updated_rows } => { ExecuteResult::Write { updated_rows } => {
self.query_result = Some(QueryResult { self.query_result = Some(QueryResult { updated_rows })
updated_rows,
query: query.to_string(),
})
} }
} }
return Ok(EventState::Consumed); return Ok(EventState::Consumed);

@ -16,10 +16,7 @@ use tui::{
#[derive(Debug, Clone, Copy, EnumIter)] #[derive(Debug, Clone, Copy, EnumIter)]
pub enum Tab { pub enum Tab {
Records, Records,
Columns, Properties,
Constraints,
ForeignKeys,
Indexes,
Sql, Sql,
} }
@ -49,10 +46,7 @@ impl TabComponent {
fn names(&self) -> Vec<String> { fn names(&self) -> Vec<String> {
vec![ vec![
command::tab_records(&self.key_config).name, command::tab_records(&self.key_config).name,
command::tab_columns(&self.key_config).name, command::tab_properties(&self.key_config).name,
command::tab_constraints(&self.key_config).name,
command::tab_foreign_keys(&self.key_config).name,
command::tab_indexes(&self.key_config).name,
command::tab_sql_editor(&self.key_config).name, command::tab_sql_editor(&self.key_config).name,
] ]
} }
@ -82,18 +76,12 @@ impl Component for TabComponent {
if key == self.key_config.tab_records { if key == self.key_config.tab_records {
self.selected_tab = Tab::Records; self.selected_tab = Tab::Records;
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} else if key == self.key_config.tab_columns { } else if key == self.key_config.tab_sql_editor {
self.selected_tab = Tab::Columns;
return Ok(EventState::Consumed);
} else if key == self.key_config.tab_constraints {
self.selected_tab = Tab::Constraints;
return Ok(EventState::Consumed);
} else if key == self.key_config.tab_foreign_keys {
self.selected_tab = Tab::ForeignKeys;
return Ok(EventState::Consumed);
} else if key == self.key_config.tab_indexes {
self.selected_tab = Tab::Sql; self.selected_tab = Tab::Sql;
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} else if key == self.key_config.tab_properties {
self.selected_tab = Tab::Properties;
return Ok(EventState::Consumed);
} }
Ok(EventState::NotConsumed) Ok(EventState::NotConsumed)
} }

@ -7,6 +7,9 @@ use std::io::{BufReader, Read};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use structopt::StructOpt; use structopt::StructOpt;
#[cfg(test)]
use serde::Serialize;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct CliConfig { pub struct CliConfig {
/// Set the config file /// Set the config file
@ -73,6 +76,7 @@ pub struct Connection {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(test, derive(Serialize))]
pub struct KeyConfig { pub struct KeyConfig {
pub scroll_up: Key, pub scroll_up: Key,
pub scroll_down: Key, pub scroll_down: Key,
@ -104,9 +108,10 @@ pub struct KeyConfig {
pub tab_constraints: Key, pub tab_constraints: Key,
pub tab_foreign_keys: Key, pub tab_foreign_keys: Key,
pub tab_indexes: Key, pub tab_indexes: Key,
pub tab_sql_editor: Key,
pub tab_properties: Key,
pub extend_or_shorten_widget_width_to_right: Key, pub extend_or_shorten_widget_width_to_right: Key,
pub extend_or_shorten_widget_width_to_left: Key, pub extend_or_shorten_widget_width_to_left: Key,
pub tab_sql_editor: Key,
} }
impl Default for KeyConfig { impl Default for KeyConfig {
@ -138,13 +143,14 @@ impl Default for KeyConfig {
extend_selection_by_one_cell_down: Key::Char('J'), extend_selection_by_one_cell_down: Key::Char('J'),
extend_selection_by_one_cell_up: Key::Char('K'), extend_selection_by_one_cell_up: Key::Char('K'),
tab_records: Key::Char('1'), tab_records: Key::Char('1'),
tab_columns: Key::Char('2'), tab_properties: Key::Char('2'),
tab_constraints: Key::Char('3'), tab_sql_editor: Key::Char('3'),
tab_foreign_keys: Key::Char('4'), tab_columns: Key::Char('4'),
tab_indexes: Key::Char('5'), tab_constraints: Key::Char('5'),
tab_foreign_keys: Key::Char('6'),
tab_indexes: Key::Char('7'),
extend_or_shorten_widget_width_to_right: Key::Char('>'), extend_or_shorten_widget_width_to_right: Key::Char('>'),
extend_or_shorten_widget_width_to_left: Key::Char('<'), extend_or_shorten_widget_width_to_left: Key::Char('<'),
tab_sql_editor: Key::Char('6'),
} }
} }
} }
@ -304,9 +310,30 @@ fn expand_path(path: &Path) -> Option<PathBuf> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::{expand_path, Path, PathBuf}; use super::{expand_path, KeyConfig, Path, PathBuf};
use serde_json::Value;
use std::env; use std::env;
#[test]
fn test_overlappted_key() {
let value: Value =
serde_json::from_str(&serde_json::to_string(&KeyConfig::default()).unwrap()).unwrap();
if let Value::Object(map) = value {
let mut values: Vec<String> = map
.values()
.map(|v| match v {
Value::Object(map) => Some(format!("{:?}", map)),
_ => None,
})
.flatten()
.collect();
values.sort();
let before_values = values.clone();
values.dedup();
pretty_assertions::assert_eq!(before_values, values);
}
}
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn test_expand_path() { fn test_expand_path() {

@ -2,8 +2,12 @@ use crossterm::event;
use serde::Deserialize; use serde::Deserialize;
use std::fmt; use std::fmt;
#[cfg(test)]
use serde::Serialize;
/// Represents a key. /// Represents a key.
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Deserialize)] #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Deserialize)]
#[cfg_attr(test, derive(Serialize))]
pub enum Key { pub enum Key {
/// Both Enter (or Return) and numpad Enter /// Both Enter (or Return) and numpad Enter
Enter, Enter,

Loading…
Cancel
Save