From 6bc3afef12d52b2a97c7e1b11001a0fae541c127 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Sun, 5 Sep 2021 22:50:38 +0900 Subject: [PATCH 1/3] move table_status under table --- sample.toml | 8 ++- src/app.rs | 11 +--- src/components/databases.rs | 2 +- src/components/table.rs | 50 +++++++++++--- src/components/table_status.rs | 88 +++++++++++-------------- src/components/utils/scroll_vertical.rs | 15 ++++- src/ui/scrollbar.rs | 25 +++++-- 7 files changed, 121 insertions(+), 78 deletions(-) diff --git a/sample.toml b/sample.toml index e33e232..f76968f 100644 --- a/sample.toml +++ b/sample.toml @@ -1,6 +1,12 @@ [[conn]] type = "mysql" -name = "sample" +user = "root" +host = "localhost" +database = "world" +port = 3306 + +[[conn]] +type = "mysql" user = "root" host = "localhost" port = 3306 diff --git a/src/app.rs b/src/app.rs index fd37b3a..b522e50 100644 --- a/src/app.rs +++ b/src/app.rs @@ -33,7 +33,6 @@ pub struct App { help: HelpComponent, databases: DatabasesComponent, connections: ConnectionsComponent, - table_status: TableStatusComponent, pool: Option>, pub config: Config, pub error: ErrorComponent, @@ -52,7 +51,6 @@ impl App { 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, pool: None, @@ -77,15 +75,10 @@ impl App { .direction(Direction::Horizontal) .constraints([Constraint::Percentage(15), Constraint::Percentage(85)]) .split(f.size()); - let left_chunks = Layout::default() - .constraints([Constraint::Min(8), Constraint::Length(7)].as_ref()) - .split(main_chunks[0]); self.databases - .draw(f, left_chunks[0], matches!(self.focus, Focus::DabataseList)) + .draw(f, main_chunks[0], matches!(self.focus, Focus::DabataseList)) .unwrap(); - self.table_status - .draw(f, left_chunks[1], matches!(self.focus, Focus::DabataseList))?; let right_chunks = Layout::default() .direction(Direction::Vertical) @@ -268,8 +261,6 @@ impl App { table.clone(), ); } - self.table_status - .update(self.record_table.len() as u64, table); } Ok(()) } diff --git a/src/components/databases.rs b/src/components/databases.rs index a11cf3e..e439f08 100644 --- a/src/components/databases.rs +++ b/src/components/databases.rs @@ -49,7 +49,7 @@ impl DatabasesComponent { Self { tree: DatabaseTree::default(), filterd_tree: None, - scroll: VerticalScroll::new(), + scroll: VerticalScroll::new(true, true), input: Vec::new(), input_idx: 0, input_cursor_position: 0, diff --git a/src/components/table.rs b/src/components/table.rs index 1237732..5e38f59 100644 --- a/src/components/table.rs +++ b/src/components/table.rs @@ -1,6 +1,6 @@ use super::{ utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, EventState, - TableValueComponent, + TableStatusComponent, TableValueComponent, }; use crate::components::command::{self, CommandInfo}; use crate::config::KeyConfig; @@ -40,7 +40,7 @@ impl TableComponent { selected_column: 0, selection_area_corner: None, column_page_start: std::cell::Cell::new(0), - scroll: VerticalScroll::new(), + scroll: VerticalScroll::new(false, false), eod: false, key_config, } @@ -68,7 +68,7 @@ impl TableComponent { self.selected_column = 0; self.selection_area_corner = None; self.column_page_start = std::cell::Cell::new(0); - self.scroll = VerticalScroll::new(); + self.scroll = VerticalScroll::new(false, false); self.eod = false; self.table = Some((database, table)); } @@ -80,7 +80,7 @@ impl TableComponent { self.selected_column = 0; self.selection_area_corner = None; self.column_page_start = std::cell::Cell::new(0); - self.scroll = VerticalScroll::new(); + self.scroll = VerticalScroll::new(false, false); self.eod = false; self.table = None; } @@ -406,6 +406,25 @@ impl DrawableComponent for TableComponent { .constraints(vec![Constraint::Length(3), Constraint::Length(5)]) .split(area); + f.render_widget( + Block::default() + .title(self.title()) + .borders(Borders::ALL) + .style(if focused { + Style::default() + } else { + Style::default().fg(Color::DarkGray) + }), + layout[1], + ); + + let chunks = Layout::default() + .vertical_margin(1) + .horizontal_margin(1) + .direction(Direction::Vertical) + .constraints([Constraint::Min(1), Constraint::Length(2)].as_ref()) + .split(layout[1]); + self.selected_row.selected().map_or_else( || { self.scroll.reset(); @@ -422,9 +441,9 @@ 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(self.title()); + let block = Block::default().borders(Borders::NONE); let (selected_column_index, headers, rows, constraints) = - self.calculate_cell_widths(block.inner(layout[1]).width); + self.calculate_cell_widths(block.inner(chunks[0]).width); let header_cells = headers.iter().enumerate().map(|(column_index, h)| { Cell::from(h.to_string()).style(if selected_column_index == column_index { Style::default().add_modifier(Modifier::BOLD) @@ -466,7 +485,7 @@ impl DrawableComponent for TableComponent { let mut state = self.selected_row.clone(); f.render_stateful_widget( table, - layout[1], + chunks[0], if let Some((_, y)) = self.selection_area_corner { state.select(Some(y)); &mut state @@ -475,7 +494,22 @@ impl DrawableComponent for TableComponent { }, ); - self.scroll.draw(f, layout[1]); + TableStatusComponent::new( + if self.rows.is_empty() { + None + } else { + Some(self.rows.len()) + }, + if self.headers.is_empty() { + None + } else { + Some(self.headers.len()) + }, + self.table.as_ref().map_or(None, |t| Some(t.1.clone())), + ) + .draw(f, chunks[1], focused)?; + + self.scroll.draw(f, chunks[0]); Ok(()) } } diff --git a/src/components/table_status.rs b/src/components/table_status.rs index 89998d5..7c0bee3 100644 --- a/src/components/table_status.rs +++ b/src/components/table_status.rs @@ -8,79 +8,65 @@ use tui::{ layout::Rect, style::{Color, Style}, text::{Span, Spans}, - widgets::{Block, Borders, List, ListItem}, + widgets::{Block, Borders, Paragraph, Wrap}, Frame, }; pub struct TableStatusComponent { - rows_count: u64, + column_count: Option, + row_count: Option, table: Option, } impl Default for TableStatusComponent { fn default() -> Self { Self { - rows_count: 0, + row_count: None, + column_count: None, table: None, } } } impl TableStatusComponent { - pub fn update(&mut self, count: u64, table: Table) { - self.rows_count = count; - self.table = Some(table); - } - - fn status_str(&self) -> Vec { - if let Some(table) = self.table.as_ref() { - return vec![ - format!( - "created: {}", - table - .create_time - .map(|time| time.to_string()) - .unwrap_or_default() - ), - format!( - "updated: {}", - table - .update_time - .map(|time| time.to_string()) - .unwrap_or_default() - ), - format!( - "engine: {}", - table - .engine - .as_ref() - .map(|engine| engine.to_string()) - .unwrap_or_default() - ), - format!("rows: {}", self.rows_count), - ]; + pub fn new( + row_count: Option, + column_count: Option, + table: Option
, + ) -> Self { + Self { + row_count, + column_count, + table, } - Vec::new() } } impl DrawableComponent for TableStatusComponent { fn draw(&mut self, f: &mut Frame, area: Rect, focused: bool) -> Result<()> { - let table_status: Vec = self - .status_str() - .iter() - .map(|i| { - ListItem::new(vec![Spans::from(Span::raw(i.to_string()))]).style(Style::default()) - }) - .collect(); - let tasks = List::new(table_status).block(Block::default().borders(Borders::ALL).style( - if focused { - Style::default() - } else { - Style::default().fg(Color::DarkGray) - }, - )); - f.render_widget(tasks, area); + let status = Paragraph::new(Spans::from(vec![ + Span::from("rows: "), + Span::from(format!( + "{}, ", + self.row_count.map_or("-".to_string(), |c| c.to_string()) + )), + Span::from("columns: "), + Span::from(format!( + "{}, ", + self.column_count.map_or("-".to_string(), |c| c.to_string()) + )), + Span::from("engine: "), + Span::from(self.table.as_ref().map_or("-".to_string(), |c| { + c.engine.as_ref().map_or("-".to_string(), |e| e.to_string()) + })), + ])) + .block(Block::default().borders(Borders::TOP).style(if focused { + Style::default() + } else { + Style::default().fg(Color::DarkGray) + })) + .wrap(Wrap { trim: true }); + f.render_widget(status, area); Ok(()) } } diff --git a/src/components/utils/scroll_vertical.rs b/src/components/utils/scroll_vertical.rs index d54cf5a..3d8f310 100644 --- a/src/components/utils/scroll_vertical.rs +++ b/src/components/utils/scroll_vertical.rs @@ -5,13 +5,17 @@ use tui::{backend::Backend, layout::Rect, Frame}; pub struct VerticalScroll { top: Cell, max_top: Cell, + inside: bool, + border: bool, } impl VerticalScroll { - pub const fn new() -> Self { + pub const fn new(border: bool, inside: bool) -> Self { Self { top: Cell::new(0), max_top: Cell::new(0), + border, + inside, } } @@ -38,7 +42,14 @@ impl VerticalScroll { } pub fn draw(&self, f: &mut Frame, r: Rect) { - draw_scrollbar(f, r, self.max_top.get(), self.top.get()); + draw_scrollbar( + f, + r, + self.max_top.get(), + self.top.get(), + self.border, + self.inside, + ); } } diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs index 0b2344a..a58972e 100644 --- a/src/ui/scrollbar.rs +++ b/src/ui/scrollbar.rs @@ -15,15 +15,19 @@ struct Scrollbar { pos: u16, style_bar: Style, style_pos: Style, + inside: bool, + border: bool, } impl Scrollbar { - fn new(max: usize, pos: usize) -> Self { + fn new(max: usize, pos: usize, border: bool, inside: bool) -> Self { Self { max: u16::try_from(max).unwrap_or_default(), pos: u16::try_from(pos).unwrap_or_default(), style_pos: Style::default(), style_bar: Style::default(), + inside, + border, } } } @@ -38,7 +42,11 @@ impl Widget for Scrollbar { return; } - let right = area.right().saturating_sub(1); + let right = if self.inside { + area.right().saturating_sub(1) + } else { + area.right() + }; if right <= area.left() { return; }; @@ -46,7 +54,7 @@ impl Widget for Scrollbar { let (bar_top, bar_height) = { let scrollbar_area = area.inner(&Margin { horizontal: 0, - vertical: 1, + vertical: if self.border { 1 } else { 0 }, }); (scrollbar_area.top(), scrollbar_area.height) @@ -67,8 +75,15 @@ impl Widget for Scrollbar { } } -pub fn draw_scrollbar(f: &mut Frame, r: Rect, max: usize, pos: usize) { - let mut widget = Scrollbar::new(max, pos); +pub fn draw_scrollbar( + f: &mut Frame, + r: Rect, + max: usize, + pos: usize, + border: bool, + inside: bool, +) { + let mut widget = Scrollbar::new(max, pos, border, inside); widget.style_pos = Style::default().fg(Color::Blue); f.render_widget(widget, r); } From 1cd7ddf4256a992cfd38a53ea98aa576feb6e204 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Sun, 5 Sep 2021 23:20:42 +0900 Subject: [PATCH 2/3] use a outside scroll bar with no borders --- src/components/databases.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/databases.rs b/src/components/databases.rs index e439f08..84b4d57 100644 --- a/src/components/databases.rs +++ b/src/components/databases.rs @@ -49,7 +49,7 @@ impl DatabasesComponent { Self { tree: DatabaseTree::default(), filterd_tree: None, - scroll: VerticalScroll::new(true, true), + scroll: VerticalScroll::new(false, false), input: Vec::new(), input_idx: 0, input_cursor_position: 0, @@ -218,7 +218,8 @@ impl DatabasesComponent { }); draw_list_block(f, chunks[1], Block::default().borders(Borders::NONE), items); - self.scroll.draw(f, area); + self.scroll.draw(f, chunks[1]); + if let Focus::Filter = self.focus { f.set_cursor(area.x + self.input_cursor_position + 1, area.y + 1) } From 4a3d32cf9bed3e28396407d2a0206fd35859c47e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Sun, 5 Sep 2021 23:40:48 +0900 Subject: [PATCH 3/3] fix clippy warnings --- src/app.rs | 2 +- src/components/record_table.rs | 4 ---- src/components/table.rs | 2 +- src/components/utils/scroll_vertical.rs | 15 +++++++++++++++ src/config.rs | 12 ++++++------ 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/app.rs b/src/app.rs index b522e50..b48deef 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,7 @@ use crate::{ components::tab::Tab, components::{ command, ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent, - RecordTableComponent, TabComponent, TableComponent, TableStatusComponent, + RecordTableComponent, TabComponent, TableComponent, }, config::Config, }; diff --git a/src/components/record_table.rs b/src/components/record_table.rs index 7c9898c..ebdf1ba 100644 --- a/src/components/record_table.rs +++ b/src/components/record_table.rs @@ -49,10 +49,6 @@ impl RecordTableComponent { self.filter.reset(); } - pub fn len(&self) -> usize { - self.table.rows.len() - } - pub fn filter_focused(&self) -> bool { matches!(self.focus, Focus::Filter) } diff --git a/src/components/table.rs b/src/components/table.rs index 5e38f59..abf7914 100644 --- a/src/components/table.rs +++ b/src/components/table.rs @@ -505,7 +505,7 @@ impl DrawableComponent for TableComponent { } else { Some(self.headers.len()) }, - self.table.as_ref().map_or(None, |t| Some(t.1.clone())), + self.table.as_ref().map(|t| t.1.clone()), ) .draw(f, chunks[1], focused)?; diff --git a/src/components/utils/scroll_vertical.rs b/src/components/utils/scroll_vertical.rs index 3d8f310..0c2fb88 100644 --- a/src/components/utils/scroll_vertical.rs +++ b/src/components/utils/scroll_vertical.rs @@ -74,3 +74,18 @@ const fn calc_scroll_top( current_top } } + +#[cfg(test)] +mod tests { + use super::calc_scroll_top; + + #[test] + fn test_scroll_no_scroll_to_top() { + assert_eq!(calc_scroll_top(1, 10, 4, 4), 0); + } + + #[test] + fn test_scroll_zero_height() { + assert_eq!(calc_scroll_top(4, 0, 4, 3), 0); + } +} diff --git a/src/config.rs b/src/config.rs index 7352997..3384e0b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -159,15 +159,15 @@ impl Connection { let user = self .user .as_ref() - .ok_or(anyhow::anyhow!("user is not set"))?; + .ok_or_else(|| anyhow::anyhow!("user is not set"))?; let host = self .host .as_ref() - .ok_or(anyhow::anyhow!("host is not set"))?; + .ok_or_else(|| anyhow::anyhow!("host is not set"))?; let port = self .port .as_ref() - .ok_or(anyhow::anyhow!("port is not set"))?; + .ok_or_else(|| anyhow::anyhow!("port is not set"))?; match self.database.as_ref() { Some(database) => Ok(format!( @@ -189,15 +189,15 @@ impl Connection { let user = self .user .as_ref() - .ok_or(anyhow::anyhow!("user is not set"))?; + .ok_or_else(|| anyhow::anyhow!("user is not set"))?; let host = self .host .as_ref() - .ok_or(anyhow::anyhow!("host is not set"))?; + .ok_or_else(|| anyhow::anyhow!("host is not set"))?; let port = self .port .as_ref() - .ok_or(anyhow::anyhow!("port is not set"))?; + .ok_or_else(|| anyhow::anyhow!("port is not set"))?; match self.database.as_ref() { Some(database) => Ok(format!(