From ef4797497928ee965b9652e5d1ec6acc8f0ad6dd Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:43:19 +0900 Subject: [PATCH 1/9] adding README.md for s key --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 39b6ec9..f3692e2 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ If you want to add connections, you need to edit your config file. For more info | h, j, k, l | Scroll left/down/up/right | | Ctrl + u, Ctrl + d | Scroll up/down multiple lines | | g , G | Scroll to top/bottom | +| s | Sort by selected column | | H, J, K, L | Extend selection by one cell left/down/up/right | | y | Copy a cell value | | , | Move focus to left/right | From ceda08c9ac3704a09f8bf736791c8ea1ff03866e Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:44:50 +0900 Subject: [PATCH 2/9] add Order and OrderManager in Table Components --- src/components/table.rs | 176 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 2 deletions(-) diff --git a/src/components/table.rs b/src/components/table.rs index 0c5da62..7efc5be 100644 --- a/src/components/table.rs +++ b/src/components/table.rs @@ -17,11 +17,91 @@ use tui::{ }; use unicode_width::UnicodeWidthStr; +#[derive(Debug, PartialEq)] +struct Order { + pub column_number: usize, + pub is_asc: bool, +} + +impl Order { + pub fn new(column_number: usize, is_asc: bool) -> Self { + Self { + column_number, + is_asc, + } + } + + fn query(&self) -> String { + let order = if self.is_asc { "ASC" } else { "DESC" }; + + return format!( + "{column} {order}", + column = self.column_number, + order = order + ); + } +} + +#[derive(PartialEq)] +struct OrderManager { + orders: Vec, +} + +impl OrderManager { + fn new() -> Self { + Self { orders: vec![] } + } + + fn generate_order_query(&mut self) -> Option { + let order_query = self + .orders + .iter() + .map(|order| order.query()) + .collect::>(); + + if !order_query.is_empty() { + return Some("ORDER BY ".to_string() + &order_query.join(", ")); + } else { + return None; + } + } + + fn generate_header_icons(&mut self, header_length: usize) -> Vec { + let mut header_icons = vec![String::new(); header_length]; + + for (index, order) in self.orders.iter().enumerate() { + let arrow = if order.is_asc { "↑" } else { "↓" }; + header_icons[order.column_number - 1] = + format!("{arrow}{number}", arrow = arrow, number = index + 1); + } + return header_icons; + } + + fn add_order(&mut self, selected_column: usize) { + let selected_column_number = selected_column + 1; + if let Some(position) = self + .orders + .iter() + .position(|order| order.column_number == selected_column_number) + { + if self.orders[position].is_asc { + self.orders[position].is_asc = false; + } else { + self.orders.remove(position); + } + } else { + let order = Order::new(selected_column_number, true); + self.orders.push(order); + } + } +} + pub struct TableComponent { pub headers: Vec, pub rows: Vec>, pub eod: bool, pub selected_row: TableState, + orders: OrderManager, table: Option<(Database, DTable)>, selected_column: usize, selection_area_corner: Option<(usize, usize)>, @@ -36,6 +116,7 @@ impl TableComponent { selected_row: TableState::default(), headers: vec![], rows: vec![], + orders: OrderManager::new(), table: None, selected_column: 0, selection_area_corner: None, @@ -58,6 +139,7 @@ impl TableComponent { headers: Vec, database: Database, table: DTable, + hold_cusor_position: bool, ) { self.selected_row.select(None); if !rows.is_empty() { @@ -65,7 +147,11 @@ impl TableComponent { } self.headers = headers; self.rows = rows; - self.selected_column = 0; + self.selected_column = if hold_cusor_position { + self.selected_column + } else { + 0 + }; self.selection_area_corner = None; self.column_page_start = std::cell::Cell::new(0); self.scroll = VerticalScroll::new(false, false); @@ -77,6 +163,7 @@ impl TableComponent { self.selected_row.select(None); self.headers = Vec::new(); self.rows = Vec::new(); + self.orders = OrderManager::new(); self.selected_column = 0; self.selection_area_corner = None; self.column_page_start = std::cell::Cell::new(0); @@ -89,6 +176,18 @@ impl TableComponent { self.selection_area_corner = None; } + pub fn add_order(&mut self) { + self.orders.add_order(self.selected_column) + } + + pub fn generate_order_query(&mut self) -> Option { + return self.orders.generate_order_query(); + } + + pub fn generate_header_icons(&mut self) -> Vec { + return self.orders.generate_header_icons(self.headers.len()); + } + pub fn end(&mut self) { self.eod = true; } @@ -522,6 +621,7 @@ impl Component for TableComponent { out.push(CommandInfo::new(command::extend_selection_by_one_cell( &self.key_config, ))); + out.push(CommandInfo::new(command::sort_by_column(&self.key_config))); } fn event(&mut self, key: Key) -> Result { @@ -568,7 +668,7 @@ impl Component for TableComponent { #[cfg(test)] mod test { - use super::{KeyConfig, TableComponent}; + use super::{KeyConfig, Order, OrderManager, TableComponent}; use tui::layout::Constraint; #[test] @@ -588,6 +688,78 @@ mod test { assert_eq!(component.rows(1, 2), vec![vec!["1", "b"], vec!["2", "e"]],) } + #[test] + fn test_query() { + let asc_order = Order::new(1, true); + let desc_order = Order::new(2, false); + + assert_eq!(asc_order.query(), "1 ASC".to_string()); + assert_eq!(desc_order.query(), "2 DESC".to_string()); + } + + #[test] + fn test_generate_order_query() { + let mut order_manager = OrderManager::new(); + + // If orders is empty, it should return None. + assert_eq!(order_manager.generate_order_query(), None); + + order_manager.add_order(1); + order_manager.add_order(1); + order_manager.add_order(2); + assert_eq!( + order_manager.generate_order_query(), + Some("ORDER BY 2 DESC, 3 ASC".to_string()) + ) + } + + #[test] + fn test_generate_header_icons() { + let mut order_manager = OrderManager::new(); + assert_eq!(order_manager.generate_header_icons(1), vec![String::new()]); + + order_manager.add_order(1); + order_manager.add_order(1); + order_manager.add_order(2); + assert_eq!( + order_manager.generate_header_icons(3), + vec![String::new(), "↓1".to_string(), "↑2".to_string()] + ); + assert_eq!( + order_manager.generate_header_icons(4), + vec![ + String::new(), + "↓1".to_string(), + "↑2".to_string(), + String::new() + ] + ); + } + + #[test] + fn test_add_order() { + let mut order_manager = OrderManager::new(); + + // press first time, condition is asc. + order_manager.add_order(1); + assert_eq!(order_manager.orders, vec![Order::new(2, true)]); + + // press twice times, condition is desc. + order_manager.add_order(1); + assert_eq!(order_manager.orders, vec![Order::new(2, false)]); + + // press another column, this column is second order. + order_manager.add_order(2); + assert_eq!( + order_manager.orders, + vec![Order::new(2, false), Order::new(3, true)] + ); + + // press three times, removed. + order_manager.add_order(1); + assert_eq!(order_manager.orders, vec![Order::new(3, true)]); + } + #[test] fn test_expand_selected_area_x_left() { // before From 7a32f3ac12d93b0d95ea442f5845e191ff078145 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:47:32 +0900 Subject: [PATCH 3/9] adding keymap for s --- src/components/command.rs | 7 +++++++ src/config.rs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/components/command.rs b/src/components/command.rs index 961b8c6..bbe7425 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -62,6 +62,13 @@ pub fn scroll_to_top_bottom(key: &KeyConfig) -> CommandText { ) } +pub fn sort_by_column(key: &KeyConfig) -> CommandText { + CommandText::new( + format!("Sort by column [{}]", key.sort_by_column), + CMD_GROUP_DATABASES, + ) +} + pub fn expand_collapse(key: &KeyConfig) -> CommandText { CommandText::new( format!("Expand/Collapse [{},{}]", key.scroll_right, key.scroll_left,), diff --git a/src/config.rs b/src/config.rs index 3d41831..fad288f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -101,6 +101,7 @@ pub struct KeyConfig { pub scroll_up_multiple_lines: Key, pub scroll_to_top: Key, pub scroll_to_bottom: Key, + pub sort_by_column: 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, @@ -140,6 +141,7 @@ impl Default for KeyConfig { scroll_up_multiple_lines: Key::Ctrl('u'), scroll_to_top: Key::Char('g'), scroll_to_bottom: Key::Char('G'), + sort_by_column: Key::Char('s'), 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'), From f4e7877c6e7e22076e7eff419a9198683a49d0e8 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:48:01 +0900 Subject: [PATCH 4/9] impl for passing order and header_icons --- src/database/mod.rs | 40 +++++++++++++++++++++++++ src/database/mysql.rs | 35 +++++++++++++++++----- src/database/postgres.rs | 63 ++++++++++++++++++++++++++++++++++++---- src/database/sqlite.rs | 29 ++++++++++++++---- 4 files changed, 149 insertions(+), 18 deletions(-) diff --git a/src/database/mod.rs b/src/database/mod.rs index 46a771a..7cad19f 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -22,6 +22,8 @@ pub trait Pool: Send + Sync { table: &Table, page: u16, filter: Option, + orders: Option, + header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)>; async fn get_columns( &self, @@ -46,6 +48,20 @@ pub trait Pool: Send + Sync { async fn close(&self); } +fn concat_headers(headers: Vec, header_icons: Option>) -> Vec { + if let Some(header_icons) = &header_icons { + let mut new_headers = vec![String::new(); headers.len()]; + for (index, header) in headers.iter().enumerate() { + new_headers[index] = format!("{} {}", header, header_icons[index]) + .trim() + .to_string(); + } + return new_headers; + } else { + return headers; + } +} + pub enum ExecuteResult { Read { headers: Vec, @@ -69,3 +85,27 @@ macro_rules! get_or_null { $value.map_or("NULL".to_string(), |v| v.to_string()) }; } + +#[cfg(test)] +mod test { + use super::concat_headers; + #[test] + fn test_concat_headers() { + let headers = vec![ + "ID".to_string(), + "NAME".to_string(), + "TIMESTAMP".to_string(), + ]; + let header_icons = vec!["".to_string(), "↑1".to_string(), "↓2".to_string()]; + let concat_headers: Vec = concat_headers(headers, Some(header_icons)); + + assert_eq!( + concat_headers, + vec![ + "ID".to_string(), + "NAME ↑1".to_string(), + "TIMESTAMP ↓2".to_string() + ] + ) + } +} diff --git a/src/database/mysql.rs b/src/database/mysql.rs index 160306f..c810cdd 100644 --- a/src/database/mysql.rs +++ b/src/database/mysql.rs @@ -1,6 +1,6 @@ use crate::get_or_null; -use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; +use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; use async_trait::async_trait; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use database_tree::{Child, Database, Table}; @@ -228,21 +228,42 @@ impl Pool for MySqlPool { table: &Table, page: u16, filter: Option, + orders: Option, + header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)> { - let query = if let Some(filter) = filter { + let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { + format!( + "SELECT * FROM `{database}`.`{table}` WHERE {filter} {orders} LIMIT {page}, {limit}", + database = database.name, + table = table.name, + filter = filter, + page = page, + limit = RECORDS_LIMIT_PER_PAGE, + orders = orders + ) + } else if let Some(filter) = filter { format!( "SELECT * FROM `{database}`.`{table}` WHERE {filter} LIMIT {page}, {limit}", database = database.name, table = table.name, filter = filter, page = page, - limit = RECORDS_LIMIT_PER_PAGE + limit = RECORDS_LIMIT_PER_PAGE, + ) + } else if let Some(orders) = orders { + format!( + "SELECT * FROM `{database}`.`{table}` {orders} LIMIT {page}, {limit}", + database = database.name, + table = table.name, + orders = orders, + page = page, + limit = RECORDS_LIMIT_PER_PAGE, ) } else { format!( - "SELECT * FROM `{}`.`{}` LIMIT {page}, {limit}", - database.name, - table.name, + "SELECT * FROM `{database}`.`{table}` LIMIT {page}, {limit}", + database = database.name, + table = table.name, page = page, limit = RECORDS_LIMIT_PER_PAGE ) @@ -262,7 +283,7 @@ impl Pool for MySqlPool { } records.push(new_row) } - Ok((headers, records)) + Ok((concat_headers(headers, header_icons), records)) } async fn get_columns( diff --git a/src/database/postgres.rs b/src/database/postgres.rs index ab954b0..78b6bc0 100644 --- a/src/database/postgres.rs +++ b/src/database/postgres.rs @@ -1,6 +1,6 @@ use crate::get_or_null; -use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; +use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; use async_trait::async_trait; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use database_tree::{Child, Database, Schema, Table}; @@ -245,8 +245,21 @@ impl Pool for PostgresPool { table: &Table, page: u16, filter: Option, + orders: Option, + header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)> { - let query = if let Some(filter) = filter.as_ref() { + let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { + format!( + r#"SELECT * FROM "{database}"."{table_schema}"."{table}" WHERE {filter} {orders} LIMIT {limit} OFFSET {page}"#, + database = database.name, + table = table.name, + filter = filter, + orders = orders, + table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()), + page = page, + limit = RECORDS_LIMIT_PER_PAGE + ) + } else if let Some(filter) = &filter { format!( r#"SELECT * FROM "{database}"."{table_schema}"."{table}" WHERE {filter} LIMIT {limit} OFFSET {page}"#, database = database.name, @@ -256,6 +269,16 @@ impl Pool for PostgresPool { page = page, limit = RECORDS_LIMIT_PER_PAGE ) + } else if let Some(orders) = &orders { + format!( + r#"SELECT * FROM "{database}"."{table_schema}"."{table}" {orders} LIMIT {limit} OFFSET {page}"#, + database = database.name, + table = table.name, + orders = orders, + table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()), + page = page, + limit = RECORDS_LIMIT_PER_PAGE + ) } else { format!( r#"SELECT * FROM "{database}"."{table_schema}"."{table}" LIMIT {limit} OFFSET {page}"#, @@ -283,8 +306,14 @@ impl Pool for PostgresPool { Err(_) => { if json_records.is_none() { json_records = Some( - self.get_json_records(database, table, page, filter.clone()) - .await?, + self.get_json_records( + database, + table, + page, + filter.clone(), + orders.clone(), + ) + .await?, ); } if let Some(json_records) = &json_records { @@ -315,7 +344,7 @@ impl Pool for PostgresPool { } records.push(new_row) } - Ok((headers, records)) + Ok((concat_headers(headers, header_icons), records)) } async fn get_columns( @@ -479,8 +508,20 @@ impl PostgresPool { table: &Table, page: u16, filter: Option, + orders: Option, ) -> anyhow::Result> { - let query = if let Some(filter) = filter { + let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { + format!( + r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" WHERE {filter} {orders} LIMIT {limit} OFFSET {page}"#, + database = database.name, + table = table.name, + filter = filter, + orders = orders, + table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()), + page = page, + limit = RECORDS_LIMIT_PER_PAGE + ) + } else if let Some(filter) = filter { format!( r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" WHERE {filter} LIMIT {limit} OFFSET {page}"#, database = database.name, @@ -490,6 +531,16 @@ impl PostgresPool { page = page, limit = RECORDS_LIMIT_PER_PAGE ) + } else if let Some(orders) = orders { + format!( + r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" {orders} LIMIT {limit} OFFSET {page}"#, + database = database.name, + table = table.name, + orders = orders, + table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()), + page = page, + limit = RECORDS_LIMIT_PER_PAGE + ) } else { format!( r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" LIMIT {limit} OFFSET {page}"#, diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 83063c7..166f069 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -1,6 +1,6 @@ use crate::get_or_null; -use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; +use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; use async_trait::async_trait; use chrono::NaiveDateTime; use database_tree::{Child, Database, Table}; @@ -230,19 +230,38 @@ impl Pool for SqlitePool { table: &Table, page: u16, filter: Option, + orders: Option, + header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)> { - let query = if let Some(filter) = filter { + let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { + format!( + "SELECT * FROM `{table}` WHERE {filter} {orders} LIMIT {page}, {limit}", + table = table.name, + filter = filter, + page = page, + limit = RECORDS_LIMIT_PER_PAGE, + orders = orders + ) + } else if let Some(filter) = filter { format!( "SELECT * FROM `{table}` WHERE {filter} LIMIT {page}, {limit}", table = table.name, filter = filter, page = page, + limit = RECORDS_LIMIT_PER_PAGE, + ) + } else if let Some(orders) = orders { + format!( + "SELECT * FROM `{table}`{orders} LIMIT {page}, {limit}", + table = table.name, + orders = orders, + page = page, limit = RECORDS_LIMIT_PER_PAGE ) } else { format!( - "SELECT * FROM `{}` LIMIT {page}, {limit}", - table.name, + "SELECT * FROM `{table}` LIMIT {page}, {limit}", + table = table.name, page = page, limit = RECORDS_LIMIT_PER_PAGE ) @@ -262,7 +281,7 @@ impl Pool for SqlitePool { } records.push(new_row) } - Ok((headers, records)) + Ok((concat_headers(headers, header_icons), records)) } async fn get_columns( From 24756655c3c54f23b9d7d7fde44f67d81f1b9984 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:49:48 +0900 Subject: [PATCH 5/9] Add processing when the key is pressed --- src/app.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index abfc7b1..2f3968b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -249,6 +249,17 @@ impl App { return Ok(EventState::Consumed); }; + if key == self.config.key_config.sort_by_column + && !self.record_table.table.headers.is_empty() + { + self.record_table.table.add_order(); + let order_query = self.record_table.table.generate_order_query(); + let header_icons = self.record_table.table.generate_header_icons(); + self.update_record_table(order_query, Some(header_icons), true) + .await?; + return Ok(EventState::Consumed); + }; + if key == self.config.key_config.copy { if let Some(text) = self.record_table.table.selected_cells() { copy_to_clipboard(text.as_str())? @@ -258,7 +269,10 @@ impl App { if key == self.config.key_config.enter && self.record_table.filter_focused() { self.record_table.focus = crate::components::record_table::Focus::Table; - self.update_record_table().await?; + let order_query = self.record_table.table.generate_order_query(); + let header_icons = self.record_table.table.generate_header_icons(); + self.update_record_table(order_query, Some(header_icons), false) + .await?; } if self.record_table.table.eod { From 405de596afd6d6d93b7d9460c799611dc7e75620 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:50:19 +0900 Subject: [PATCH 6/9] adding args for passing order_query and header_icons --- src/app.rs | 31 +++++++++++++++++++++++++------ src/components/properties.rs | 4 ++++ src/components/record_table.rs | 4 +++- src/components/sql_editor.rs | 2 +- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2f3968b..2cab8d8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -162,7 +162,12 @@ impl App { Ok(()) } - async fn update_record_table(&mut self) -> anyhow::Result<()> { + async fn update_record_table( + &mut self, + orders: Option, + header_icons: Option>, + hold_cursor_position: bool, + ) -> anyhow::Result<()> { if let Some((database, table)) = self.databases.tree().selected_table() { let (headers, records) = self .pool @@ -177,10 +182,17 @@ impl App { } else { Some(self.record_table.filter.input_str()) }, + orders, + header_icons, ) .await?; - self.record_table - .update(records, headers, database.clone(), table.clone()); + self.record_table.update( + records, + headers, + database.clone(), + table.clone(), + hold_cursor_position, + ); } Ok(()) } @@ -230,10 +242,15 @@ impl App { .pool .as_ref() .unwrap() - .get_records(&database, &table, 0, None) + .get_records(&database, &table, 0, None, None, None) .await?; - self.record_table - .update(records, headers, database.clone(), table.clone()); + self.record_table.update( + records, + headers, + database.clone(), + table.clone(), + false, + ); self.properties .update(database.clone(), table.clone(), self.pool.as_ref().unwrap()) .await?; @@ -297,6 +314,8 @@ impl App { } else { Some(self.record_table.filter.input_str()) }, + None, + None, ) .await?; if !records.is_empty() { diff --git a/src/components/properties.rs b/src/components/properties.rs index acd6ee4..4e42c18 100644 --- a/src/components/properties.rs +++ b/src/components/properties.rs @@ -77,6 +77,7 @@ impl PropertiesComponent { columns.get(0).unwrap().fields(), database.clone(), table.clone(), + false, ); } self.constraint_table.reset(); @@ -90,6 +91,7 @@ impl PropertiesComponent { constraints.get(0).unwrap().fields(), database.clone(), table.clone(), + false, ); } self.foreign_key_table.reset(); @@ -103,6 +105,7 @@ impl PropertiesComponent { foreign_keys.get(0).unwrap().fields(), database.clone(), table.clone(), + false, ); } self.index_table.reset(); @@ -116,6 +119,7 @@ impl PropertiesComponent { indexes.get(0).unwrap().fields(), database.clone(), table.clone(), + false, ); } Ok(()) diff --git a/src/components/record_table.rs b/src/components/record_table.rs index d4857aa..2f5c84f 100644 --- a/src/components/record_table.rs +++ b/src/components/record_table.rs @@ -39,8 +39,10 @@ impl RecordTableComponent { headers: Vec, database: Database, table: DTable, + hold_cusor_position: bool, ) { - self.table.update(rows, headers, database, table.clone()); + self.table + .update(rows, headers, database, table.clone(), hold_cusor_position); self.filter.table = Some(table); } diff --git a/src/components/sql_editor.rs b/src/components/sql_editor.rs index 50e53cf..b8ad596 100644 --- a/src/components/sql_editor.rs +++ b/src/components/sql_editor.rs @@ -269,7 +269,7 @@ impl Component for SqlEditorComponent { database, table, } => { - self.table.update(rows, headers, database, table); + self.table.update(rows, headers, database, table, false); self.focus = Focus::Table; self.query_result = None; } From 75bcf73cd63d6a6219a231708f9150fc62590001 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 12:36:12 +0900 Subject: [PATCH 7/9] remove needless return --- src/components/table.rs | 11 ++++++----- src/database/mod.rs | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/table.rs b/src/components/table.rs index 7efc5be..09b5595 100644 --- a/src/components/table.rs +++ b/src/components/table.rs @@ -61,9 +61,9 @@ impl OrderManager { if !order_query.is_empty() { return Some("ORDER BY ".to_string() + &order_query.join(", ")); - } else { - return None; } + + None } fn generate_header_icons(&mut self, header_length: usize) -> Vec { @@ -74,7 +74,8 @@ impl OrderManager { header_icons[order.column_number - 1] = format!("{arrow}{number}", arrow = arrow, number = index + 1); } - return header_icons; + + header_icons } fn add_order(&mut self, selected_column: usize) { @@ -181,11 +182,11 @@ impl TableComponent { } pub fn generate_order_query(&mut self) -> Option { - return self.orders.generate_order_query(); + self.orders.generate_order_query() } pub fn generate_header_icons(&mut self) -> Vec { - return self.orders.generate_header_icons(self.headers.len()); + self.orders.generate_header_icons(self.headers.len()) } pub fn end(&mut self) { diff --git a/src/database/mod.rs b/src/database/mod.rs index 7cad19f..059fb3b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -57,9 +57,9 @@ fn concat_headers(headers: Vec, header_icons: Option>) -> Ve .to_string(); } return new_headers; - } else { - return headers; } + + headers } pub enum ExecuteResult { From 35a2ae7d18e7d855eaadbc938a616acf21e2f40c Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 11 Apr 2022 13:46:19 +0900 Subject: [PATCH 8/9] refactor concat_headers concat_headers method moves to App --- src/app.rs | 45 ++++++++++++++++++++++++++++++++++++---- src/database/mod.rs | 39 ---------------------------------- src/database/mysql.rs | 5 ++--- src/database/postgres.rs | 5 ++--- src/database/sqlite.rs | 5 ++--- 5 files changed, 47 insertions(+), 52 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2cab8d8..1dcab65 100644 --- a/src/app.rs +++ b/src/app.rs @@ -183,12 +183,11 @@ impl App { Some(self.record_table.filter.input_str()) }, orders, - header_icons, ) .await?; self.record_table.update( records, - headers, + self.concat_headers(headers, header_icons), database.clone(), table.clone(), hold_cursor_position, @@ -242,7 +241,7 @@ impl App { .pool .as_ref() .unwrap() - .get_records(&database, &table, 0, None, None, None) + .get_records(&database, &table, 0, None, None) .await?; self.record_table.update( records, @@ -315,7 +314,6 @@ impl App { Some(self.record_table.filter.input_str()) }, None, - None, ) .await?; if !records.is_empty() { @@ -406,6 +404,24 @@ impl App { } Ok(EventState::NotConsumed) } + + fn concat_headers( + &self, + headers: Vec, + header_icons: Option>, + ) -> Vec { + if let Some(header_icons) = &header_icons { + let mut new_headers = vec![String::new(); headers.len()]; + for (index, header) in headers.iter().enumerate() { + new_headers[index] = format!("{} {}", header, header_icons[index]) + .trim() + .to_string(); + } + return new_headers; + } + + headers + } } #[cfg(test)] @@ -441,4 +457,25 @@ mod test { ); assert_eq!(app.left_main_chunk_percentage, 15); } + + #[test] + fn test_concat_headers() { + let app = App::new(Config::default()); + let headers = vec![ + "ID".to_string(), + "NAME".to_string(), + "TIMESTAMP".to_string(), + ]; + let header_icons = vec!["".to_string(), "↑1".to_string(), "↓2".to_string()]; + let concat_headers: Vec = app.concat_headers(headers, Some(header_icons)); + + assert_eq!( + concat_headers, + vec![ + "ID".to_string(), + "NAME ↑1".to_string(), + "TIMESTAMP ↓2".to_string() + ] + ) + } } diff --git a/src/database/mod.rs b/src/database/mod.rs index 059fb3b..76a874a 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -23,7 +23,6 @@ pub trait Pool: Send + Sync { page: u16, filter: Option, orders: Option, - header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)>; async fn get_columns( &self, @@ -48,20 +47,6 @@ pub trait Pool: Send + Sync { async fn close(&self); } -fn concat_headers(headers: Vec, header_icons: Option>) -> Vec { - if let Some(header_icons) = &header_icons { - let mut new_headers = vec![String::new(); headers.len()]; - for (index, header) in headers.iter().enumerate() { - new_headers[index] = format!("{} {}", header, header_icons[index]) - .trim() - .to_string(); - } - return new_headers; - } - - headers -} - pub enum ExecuteResult { Read { headers: Vec, @@ -85,27 +70,3 @@ macro_rules! get_or_null { $value.map_or("NULL".to_string(), |v| v.to_string()) }; } - -#[cfg(test)] -mod test { - use super::concat_headers; - #[test] - fn test_concat_headers() { - let headers = vec![ - "ID".to_string(), - "NAME".to_string(), - "TIMESTAMP".to_string(), - ]; - let header_icons = vec!["".to_string(), "↑1".to_string(), "↓2".to_string()]; - let concat_headers: Vec = concat_headers(headers, Some(header_icons)); - - assert_eq!( - concat_headers, - vec![ - "ID".to_string(), - "NAME ↑1".to_string(), - "TIMESTAMP ↓2".to_string() - ] - ) - } -} diff --git a/src/database/mysql.rs b/src/database/mysql.rs index c810cdd..1f70f73 100644 --- a/src/database/mysql.rs +++ b/src/database/mysql.rs @@ -1,6 +1,6 @@ use crate::get_or_null; -use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; +use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; use async_trait::async_trait; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use database_tree::{Child, Database, Table}; @@ -229,7 +229,6 @@ impl Pool for MySqlPool { page: u16, filter: Option, orders: Option, - header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)> { let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { format!( @@ -283,7 +282,7 @@ impl Pool for MySqlPool { } records.push(new_row) } - Ok((concat_headers(headers, header_icons), records)) + Ok((headers, records)) } async fn get_columns( diff --git a/src/database/postgres.rs b/src/database/postgres.rs index 78b6bc0..f7493a0 100644 --- a/src/database/postgres.rs +++ b/src/database/postgres.rs @@ -1,6 +1,6 @@ use crate::get_or_null; -use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; +use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; use async_trait::async_trait; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use database_tree::{Child, Database, Schema, Table}; @@ -246,7 +246,6 @@ impl Pool for PostgresPool { page: u16, filter: Option, orders: Option, - header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)> { let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { format!( @@ -344,7 +343,7 @@ impl Pool for PostgresPool { } records.push(new_row) } - Ok((concat_headers(headers, header_icons), records)) + Ok((headers, records)) } async fn get_columns( diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 166f069..5a0d5f4 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -1,6 +1,6 @@ use crate::get_or_null; -use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; +use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE}; use async_trait::async_trait; use chrono::NaiveDateTime; use database_tree::{Child, Database, Table}; @@ -231,7 +231,6 @@ impl Pool for SqlitePool { page: u16, filter: Option, orders: Option, - header_icons: Option>, ) -> anyhow::Result<(Vec, Vec>)> { let query = if let (Some(filter), Some(orders)) = (&filter, &orders) { format!( @@ -281,7 +280,7 @@ impl Pool for SqlitePool { } records.push(new_row) } - Ok((concat_headers(headers, header_icons), records)) + Ok((headers, records)) } async fn get_columns( From 1fc730e863ba135444ae2ae0c254921f8b4c74ff Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Thu, 14 Apr 2022 13:28:43 +0900 Subject: [PATCH 9/9] fix CommandText TYPE, because it mistaked --- src/components/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/command.rs b/src/components/command.rs index bbe7425..1e8f9ce 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -65,7 +65,7 @@ pub fn scroll_to_top_bottom(key: &KeyConfig) -> CommandText { pub fn sort_by_column(key: &KeyConfig) -> CommandText { CommandText::new( format!("Sort by column [{}]", key.sort_by_column), - CMD_GROUP_DATABASES, + CMD_GROUP_TABLE, ) }