Refactor Table component (#31)

* fix table component field names

* refactor function name

* add a test for is_number_column

* fix variable names

* make some fields private
pull/36/head
Takayuki Maeda 3 years ago committed by GitHub
parent 7e8291be22
commit c768ffd461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,7 +16,6 @@ use database_tree::Database;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
widgets::ListState,
Frame, Frame,
}; };
@ -26,17 +25,16 @@ pub enum Focus {
ConnectionList, ConnectionList,
} }
pub struct App { pub struct App {
pub record_table: RecordTableComponent, record_table: RecordTableComponent,
pub structure_table: TableComponent, structure_table: TableComponent,
pub focus: Focus, focus: Focus,
pub tab: TabComponent, tab: TabComponent,
databases: DatabasesComponent,
connections: ConnectionsComponent,
table_status: TableStatusComponent,
clipboard: Clipboard,
pool: Option<Box<dyn Pool>>,
pub user_config: Option<UserConfig>, pub user_config: Option<UserConfig>,
pub selected_connection: ListState,
pub databases: DatabasesComponent,
pub connections: ConnectionsComponent,
pub table_status: TableStatusComponent,
pub clipboard: Clipboard,
pub pool: Option<Box<dyn Pool>>,
pub error: ErrorComponent, pub error: ErrorComponent,
} }
@ -48,7 +46,6 @@ impl Default for App {
focus: Focus::DabataseList, focus: Focus::DabataseList,
tab: TabComponent::default(), tab: TabComponent::default(),
user_config: None, user_config: None,
selected_connection: ListState::default(),
databases: DatabasesComponent::new(), databases: DatabasesComponent::new(),
connections: ConnectionsComponent::default(), connections: ConnectionsComponent::default(),
table_status: TableStatusComponent::default(), table_status: TableStatusComponent::default(),
@ -237,7 +234,7 @@ impl App {
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
if let Some(index) = self.record_table.table.state.selected() { if let Some(index) = self.record_table.table.selected_row.selected() {
if index.saturating_add(1) if index.saturating_add(1)
% crate::utils::RECORDS_LIMIT_PER_PAGE as usize % crate::utils::RECORDS_LIMIT_PER_PAGE as usize
== 0 == 0

@ -1,24 +1,14 @@
#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)] #[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
pub struct CommandText { pub struct CommandText {
///
pub name: String, pub name: String,
///
pub desc: &'static str, pub desc: &'static str,
///
pub group: &'static str, pub group: &'static str,
///
pub hide_help: bool, pub hide_help: bool,
} }
pub struct CommandInfo { pub struct CommandInfo {
///
pub text: CommandText, pub text: CommandText,
/// available but not active in the context
pub enabled: bool, pub enabled: bool,
/// will show up in the quick bar
pub quick_bar: bool, pub quick_bar: bool,
/// available in current app state
pub available: bool, pub available: bool,
/// used to order commands in quickbar
pub order: i8, pub order: i8,
} }

@ -12,8 +12,8 @@ use tui::{
}; };
pub struct ConnectionsComponent { pub struct ConnectionsComponent {
pub connections: Vec<Connection>, connections: Vec<Connection>,
pub state: ListState, state: ListState,
} }
impl Default for ConnectionsComponent { impl Default for ConnectionsComponent {
@ -33,7 +33,7 @@ impl ConnectionsComponent {
} }
} }
pub fn next_connection(&mut self) { fn next_connection(&mut self) {
let i = match self.state.selected() { let i = match self.state.selected() {
Some(i) => { Some(i) => {
if i >= self.connections.len() - 1 { if i >= self.connections.len() - 1 {
@ -47,7 +47,7 @@ impl ConnectionsComponent {
self.state.select(Some(i)); self.state.select(Some(i));
} }
pub fn previous_connection(&mut self) { fn previous_connection(&mut self) {
let i = match self.state.selected() { let i = match self.state.selected() {
Some(i) => { Some(i) => {
if i == 0 { if i == 0 {

@ -2,7 +2,6 @@ use super::{
compute_character_width, utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, compute_character_width, utils::scroll_vertical::VerticalScroll, Component, DrawableComponent,
EventState, EventState,
}; };
use crate::components::RecordTableComponent;
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;
@ -33,14 +32,13 @@ pub enum FocusBlock {
} }
pub struct DatabasesComponent { pub struct DatabasesComponent {
pub tree: DatabaseTree, tree: DatabaseTree,
pub filterd_tree: Option<DatabaseTree>, filterd_tree: Option<DatabaseTree>,
pub scroll: VerticalScroll, scroll: VerticalScroll,
pub input: Vec<char>, input: Vec<char>,
pub input_idx: usize, input_idx: usize,
pub input_cursor_position: u16, input_cursor_position: u16,
pub record_table: RecordTableComponent, focus_block: FocusBlock,
pub focus_block: FocusBlock,
} }
impl DatabasesComponent { impl DatabasesComponent {
@ -52,12 +50,11 @@ impl DatabasesComponent {
input: Vec::new(), input: Vec::new(),
input_idx: 0, input_idx: 0,
input_cursor_position: 0, input_cursor_position: 0,
record_table: RecordTableComponent::default(),
focus_block: FocusBlock::Tree, focus_block: FocusBlock::Tree,
} }
} }
pub fn input_str(&self) -> String { fn input_str(&self) -> String {
self.input.iter().collect() self.input.iter().collect()
} }

@ -41,16 +41,16 @@ impl RecordTableComponent {
self.table.rows = rows; self.table.rows = rows;
self.table.headers = headers; self.table.headers = headers;
if !self.table.rows.is_empty() { if !self.table.rows.is_empty() {
self.table.state.select(None); self.table.selected_row.select(None);
self.table.state.select(Some(0)); self.table.selected_row.select(Some(0));
} }
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.table = TableComponent::default(); self.table = TableComponent::default();
if !self.table.rows.is_empty() { if !self.table.rows.is_empty() {
self.table.state.select(None); self.table.selected_row.select(None);
self.table.state.select(Some(0)) self.table.selected_row.select(Some(0))
} }
self.filter = TableFilterComponent::default(); self.filter = TableFilterComponent::default();
} }

@ -15,30 +15,26 @@ use tui::{
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
pub struct TableComponent { pub struct TableComponent {
pub state: TableState,
pub headers: Vec<String>, pub headers: Vec<String>,
pub rows: Vec<Vec<String>>, pub rows: Vec<Vec<String>>,
pub eod: bool, pub eod: bool,
select_state: TableState, pub selected_row: TableState,
selected_left_column_index: usize, selected_column: usize,
selected_right_cell: Option<(usize, usize)>, selection_area_corner: Option<(usize, usize)>,
column_page_start: std::cell::Cell<usize>, column_page_start: std::cell::Cell<usize>,
scroll: VerticalScroll, scroll: VerticalScroll,
select_entire_row: bool,
} }
impl Default for TableComponent { impl Default for TableComponent {
fn default() -> Self { fn default() -> Self {
Self { Self {
state: TableState::default(), selected_row: TableState::default(),
select_state: TableState::default(),
headers: vec![], headers: vec![],
rows: vec![], rows: vec![],
selected_left_column_index: 0, selected_column: 0,
selected_right_cell: None, selection_area_corner: None,
column_page_start: std::cell::Cell::new(0), column_page_start: std::cell::Cell::new(0),
scroll: VerticalScroll::new(), scroll: VerticalScroll::new(),
select_entire_row: false,
eod: false, eod: false,
} }
} }
@ -46,31 +42,29 @@ impl Default for TableComponent {
impl TableComponent { impl TableComponent {
pub fn new(rows: Vec<Vec<String>>, headers: Vec<String>) -> Self { pub fn new(rows: Vec<Vec<String>>, headers: Vec<String>) -> Self {
let mut state = TableState::default(); let mut selected_row = TableState::default();
if !rows.is_empty() { if !rows.is_empty() {
state.select(None); selected_row.select(None);
state.select(Some(0)) selected_row.select(Some(0))
} }
Self { Self {
state,
headers, headers,
rows, rows,
selected_row,
..Self::default() ..Self::default()
} }
} }
pub fn reset(&mut self) { fn reset(&mut self) {
self.select_state.select(None); self.selection_area_corner = None;
self.selected_right_cell = None;
self.select_entire_row = false;
} }
pub fn end(&mut self) { pub fn end(&mut self) {
self.eod = true; self.eod = true;
} }
pub fn next(&mut self, lines: usize) { fn next_row(&mut self, lines: usize) {
let i = match self.state.selected() { let i = match self.selected_row.selected() {
Some(i) => { Some(i) => {
if i + lines >= self.rows.len() { if i + lines >= self.rows.len() {
Some(self.rows.len() - 1) Some(self.rows.len() - 1)
@ -81,25 +75,11 @@ impl TableComponent {
None => None, None => None,
}; };
self.reset(); self.reset();
self.state.select(i); self.selected_row.select(i);
} }
pub fn next_select(&mut self, lines: usize) { fn previous_row(&mut self, lines: usize) {
let i = match self.select_state.selected() { let i = match self.selected_row.selected() {
Some(i) => {
if i + lines >= self.rows.len() {
Some(self.rows.len() - 1)
} else {
Some(i + lines)
}
}
None => None,
};
self.select_state.select(i);
}
pub fn previous(&mut self, lines: usize) {
let i = match self.state.selected() {
Some(i) => { Some(i) => {
if i <= lines { if i <= lines {
Some(0) Some(0)
@ -110,70 +90,56 @@ impl TableComponent {
None => None, None => None,
}; };
self.reset(); self.reset();
self.state.select(i); self.selected_row.select(i);
} }
pub fn previout_select(&mut self, lines: usize) { fn scroll_top(&mut self) {
let i = match self.select_state.selected() {
Some(i) => {
if i <= lines {
Some(0)
} else {
Some(i - lines)
}
}
None => None,
};
self.select_state.select(i);
}
pub fn scroll_top(&mut self) {
if self.rows.is_empty() { if self.rows.is_empty() {
return; return;
} }
self.reset(); self.reset();
self.state.select(Some(0)); self.selected_row.select(Some(0));
} }
pub fn scroll_bottom(&mut self) { fn scroll_bottom(&mut self) {
if self.rows.is_empty() { if self.rows.is_empty() {
return; return;
} }
self.reset(); self.reset();
self.state.select(Some(self.rows.len() - 1)); self.selected_row.select(Some(self.rows.len() - 1));
} }
pub fn next_column(&mut self) { fn next_column(&mut self) {
if self.rows.is_empty() { if self.rows.is_empty() {
return; return;
} }
if self.selected_left_column_index >= self.headers.len().saturating_sub(1) { if self.selected_column >= self.headers.len().saturating_sub(1) {
return; return;
} }
self.reset(); self.reset();
self.selected_left_column_index += 1; self.selected_column += 1;
} }
pub fn previous_column(&mut self) { fn previous_column(&mut self) {
if self.rows.is_empty() { if self.rows.is_empty() {
return; return;
} }
if self.selected_left_column_index == 0 { if self.selected_column == 0 {
return; return;
} }
self.reset(); self.reset();
self.selected_left_column_index -= 1; self.selected_column -= 1;
} }
pub fn expand_selected_area_x(&mut self, positive: bool) { fn expand_selected_area_x(&mut self, positive: bool) {
if self.selected_right_cell.is_none() { if self.selection_area_corner.is_none() {
self.selected_right_cell = Some(( self.selection_area_corner = Some((
self.selected_left_column_index, self.selected_column,
self.state.selected().unwrap_or(0), self.selected_row.selected().unwrap_or(0),
)); ));
} }
if let Some((x, y)) = self.selected_right_cell { if let Some((x, y)) = self.selection_area_corner {
self.selected_right_cell = Some(( self.selection_area_corner = Some((
if positive { if positive {
(x + 1).min(self.headers.len().saturating_sub(1)) (x + 1).min(self.headers.len().saturating_sub(1))
} else { } else {
@ -184,25 +150,15 @@ impl TableComponent {
} }
} }
pub fn expand_selected_area_y(&mut self, positive: bool) { fn expand_selected_area_y(&mut self, positive: bool) {
if self.select_state.selected().is_none() { if self.selection_area_corner.is_none() {
self.select_state = self.state.clone(); self.selection_area_corner = Some((
} self.selected_column,
self.selected_row.selected().unwrap_or(0),
if positive {
self.next_select(1);
} else {
self.previout_select(1);
}
if self.selected_right_cell.is_none() {
self.selected_right_cell = Some((
self.selected_left_column_index,
self.state.selected().unwrap_or(0),
)); ));
} }
if let Some((x, y)) = self.selected_right_cell { if let Some((x, y)) = self.selection_area_corner {
self.selected_right_cell = Some(( self.selection_area_corner = Some((
x, x,
if positive { if positive {
(y + 1).min(self.rows.len().saturating_sub(1)) (y + 1).min(self.rows.len().saturating_sub(1))
@ -213,67 +169,71 @@ impl TableComponent {
} }
} }
pub fn is_row_number_clumn(&self, row_index: usize, column_index: usize) -> bool {
matches!(self.state.selected(), Some(selected_row_index) if row_index == selected_row_index && 0 == column_index)
}
pub fn selected_cells(&self) -> Option<String> { pub fn selected_cells(&self) -> Option<String> {
if let Some((x, y)) = self.selected_right_cell { if let Some((x, y)) = self.selection_area_corner {
let selected_row_index = self.state.selected()?; let selected_row_index = self.selected_row.selected()?;
return Some( return Some(
self.rows[y.min(selected_row_index)..y.max(selected_row_index) + 1] self.rows[y.min(selected_row_index)..y.max(selected_row_index) + 1]
.iter() .iter()
.map(|row| { .map(|row| {
row[x.min(self.selected_left_column_index) row[x.min(self.selected_column)..x.max(self.selected_column) + 1].join(",")
..x.max(self.selected_left_column_index) + 1]
.join(",")
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"), .join("\n"),
); );
} }
self.rows self.rows
.get(self.state.selected()?)? .get(self.selected_row.selected()?)?
.get(self.selected_left_column_index) .get(self.selected_column)
.map(|cell| cell.to_string()) .map(|cell| cell.to_string())
} }
pub fn selected_column_index(&self) -> usize { fn selected_column_index(&self) -> usize {
if let Some((x, _)) = self.selected_right_cell { if let Some((x, _)) = self.selection_area_corner {
return x; return x;
} }
self.selected_left_column_index self.selected_column
} }
pub fn is_selected_cell( fn is_selected_cell(
&self, &self,
row_index: usize, row_index: usize,
column_index: usize, column_index: usize,
selected_column_index: usize, selected_column_index: usize,
) -> bool { ) -> bool {
if let Some((x, y)) = self.selected_right_cell { if let Some((x, y)) = self.selection_area_corner {
let x_in_page = x let x_in_page = x
.saturating_add(1) .saturating_add(1)
.saturating_sub(self.column_page_start.get()); .saturating_sub(self.column_page_start.get());
return matches!( return matches!(
self.state.selected(), self.selected_row.selected(),
Some(selected_row_index) Some(selected_row_index)
if (x_in_page.min(selected_column_index).max(1)..x_in_page.max(selected_column_index) + 1) if (x_in_page.min(selected_column_index).max(1)..x_in_page.max(selected_column_index) + 1)
.contains(&column_index) .contains(&column_index)
&& (y.min(selected_row_index)..y.max(selected_row_index) + 1) && (y.min(selected_row_index)..y.max(selected_row_index) + 1)
.contains(&row_index) .contains(&row_index)
); );
} }
matches!(self.state.selected(), Some(selected_row_index) if row_index == selected_row_index && column_index == selected_column_index) matches!(
self.selected_row.selected(),
Some(selected_row_index) if row_index == selected_row_index && column_index == selected_column_index
)
}
fn is_number_column(&self, row_index: usize, column_index: usize) -> bool {
matches!(
self.selected_row.selected(),
Some(selected_row_index) if row_index == selected_row_index && 0 == column_index
)
} }
pub fn headers_with_number(&self, left: usize, right: usize) -> Vec<String> { fn headers(&self, left: usize, right: usize) -> Vec<String> {
let mut headers = self.headers.clone()[left..right].to_vec(); let mut headers = self.headers.clone()[left..right].to_vec();
headers.insert(0, "".to_string()); headers.insert(0, "".to_string());
headers headers
} }
pub fn rows_with_number(&self, left: usize, right: usize) -> Vec<Vec<String>> { fn rows(&self, left: usize, right: usize) -> Vec<Vec<String>> {
let rows = self let rows = self
.rows .rows
.iter() .iter()
@ -287,7 +247,7 @@ impl TableComponent {
new_rows new_rows
} }
pub fn calculate_widths( fn calculate_cell_widths(
&self, &self,
area_width: u16, area_width: u16,
) -> (usize, Vec<String>, Vec<Vec<String>>, Vec<Constraint>) { ) -> (usize, Vec<String>, Vec<Vec<String>>, Vec<Constraint>) {
@ -298,10 +258,10 @@ impl TableComponent {
self.column_page_start.set(self.selected_column_index()); self.column_page_start.set(self.selected_column_index());
} }
let right_column_index = self.selected_column_index(); let far_right_column_index = self.selected_column_index();
let mut column_index = self.selected_column_index(); let mut column_index = self.selected_column_index();
let number_clomn_width = (self.rows.len() + 1).to_string().width() as u16; let number_column_width = (self.rows.len() + 1).to_string().width() as u16;
let mut widths = vec![]; let mut widths = Vec::new();
loop { loop {
let length = self let length = self
.rows .rows
@ -324,7 +284,7 @@ impl TableComponent {
.clamp(&3, &20) .clamp(&3, &20)
}); });
if widths.iter().map(|(_, width)| width).sum::<usize>() + length + widths.len() if widths.iter().map(|(_, width)| width).sum::<usize>() + length + widths.len()
> area_width.saturating_sub(number_clomn_width) as usize >= area_width.saturating_sub(number_column_width) as usize
{ {
column_index += 1; column_index += 1;
break; break;
@ -335,12 +295,13 @@ impl TableComponent {
} }
column_index -= 1; column_index -= 1;
} }
let left_column_index = column_index;
widths.reverse(); widths.reverse();
let far_left_column_index = column_index;
let selected_column_index = widths.len().saturating_sub(1); let selected_column_index = widths.len().saturating_sub(1);
let mut column_index = right_column_index + 1; let mut column_index = far_right_column_index + 1;
while widths.iter().map(|(_, width)| width).sum::<usize>() + widths.len() while widths.iter().map(|(_, width)| width).sum::<usize>() + widths.len()
<= area_width.saturating_sub(number_clomn_width) as usize <= area_width.saturating_sub(number_column_width) as usize
{ {
let length = self let length = self
.rows .rows
@ -375,7 +336,7 @@ impl TableComponent {
if self.selected_column_index() != self.headers.len().saturating_sub(1) { if self.selected_column_index() != self.headers.len().saturating_sub(1) {
widths.pop(); widths.pop();
} }
let right_column_index = column_index; let far_right_column_index = column_index;
let mut constraints = widths let mut constraints = widths
.iter() .iter()
.map(|(_, width)| Constraint::Length(*width as u16)) .map(|(_, width)| Constraint::Length(*width as u16))
@ -383,21 +344,22 @@ impl TableComponent {
if self.selected_column_index() != self.headers.len().saturating_sub(1) { if self.selected_column_index() != self.headers.len().saturating_sub(1) {
constraints.push(Constraint::Min(10)); constraints.push(Constraint::Min(10));
} }
constraints.insert(0, Constraint::Length(number_clomn_width)); constraints.insert(0, Constraint::Length(number_column_width));
self.column_page_start.set(left_column_index); self.column_page_start.set(far_left_column_index);
( (
self.selected_right_cell self.selection_area_corner
.map_or(selected_column_index + 1, |(x, _)| { .map_or(selected_column_index + 1, |(x, _)| {
if x > self.selected_left_column_index { if x > self.selected_column {
(selected_column_index + 1) (selected_column_index + 1)
.saturating_sub(x.saturating_sub(self.selected_left_column_index)) .saturating_sub(x.saturating_sub(self.selected_column))
} else { } else {
(selected_column_index + 1) (selected_column_index + 1)
.saturating_add(self.selected_left_column_index.saturating_sub(x)) .saturating_add(self.selected_column.saturating_sub(x))
} }
}), }),
self.headers_with_number(left_column_index, right_column_index), self.headers(far_left_column_index, far_right_column_index),
self.rows_with_number(left_column_index, right_column_index), self.rows(far_left_column_index, far_right_column_index),
constraints, constraints,
) )
} }
@ -410,7 +372,7 @@ impl DrawableComponent for TableComponent {
.constraints(vec![Constraint::Length(3), Constraint::Length(5)]) .constraints(vec![Constraint::Length(3), Constraint::Length(5)])
.split(area); .split(area);
self.state.selected().map_or_else( self.selected_row.selected().map_or_else(
|| { || {
self.scroll.reset(); self.scroll.reset();
}, },
@ -428,7 +390,7 @@ impl DrawableComponent for TableComponent {
let block = Block::default().borders(Borders::ALL).title("Records"); let block = Block::default().borders(Borders::ALL).title("Records");
let (selected_column_index, headers, rows, constraints) = let (selected_column_index, headers, rows, constraints) =
self.calculate_widths(block.inner(layout[1]).width); self.calculate_cell_widths(block.inner(layout[1]).width);
let header_cells = headers.iter().enumerate().map(|(column_index, h)| { let header_cells = headers.iter().enumerate().map(|(column_index, h)| {
Cell::from(h.to_string()).style(if selected_column_index == column_index { Cell::from(h.to_string()).style(if selected_column_index == column_index {
Style::default().add_modifier(Modifier::BOLD) Style::default().add_modifier(Modifier::BOLD)
@ -448,7 +410,7 @@ impl DrawableComponent for TableComponent {
Cell::from(c.to_string()).style( Cell::from(c.to_string()).style(
if self.is_selected_cell(row_index, column_index, selected_column_index) { if self.is_selected_cell(row_index, column_index, selected_column_index) {
Style::default().bg(Color::Blue) Style::default().bg(Color::Blue)
} else if self.is_row_number_clumn(row_index, column_index) { } else if self.is_number_column(row_index, column_index) {
Style::default().add_modifier(Modifier::BOLD) Style::default().add_modifier(Modifier::BOLD)
} else { } else {
Style::default() Style::default()
@ -461,24 +423,21 @@ impl DrawableComponent for TableComponent {
let table = Table::new(rows) let table = Table::new(rows)
.header(header) .header(header)
.block(block) .block(block)
.highlight_style(if self.select_entire_row {
Style::default().bg(Color::Blue)
} else {
Style::default()
})
.style(if focused { .style(if focused {
Style::default() Style::default()
} else { } else {
Style::default().fg(Color::DarkGray) Style::default().fg(Color::DarkGray)
}) })
.widths(&constraints); .widths(&constraints);
let mut state = self.selected_row.clone();
f.render_stateful_widget( f.render_stateful_widget(
table, table,
layout[1], layout[1],
if self.select_state.selected().is_some() { if let Some((_, y)) = self.selection_area_corner {
&mut self.select_state state.select(Some(y));
&mut state
} else { } else {
&mut self.state &mut self.selected_row
}, },
); );
@ -495,29 +454,25 @@ impl Component for TableComponent {
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
Key::Char('j') => { Key::Char('j') => {
self.next(1); self.next_row(1);
return Ok(EventState::NotConsumed); return Ok(EventState::NotConsumed);
} }
Key::Ctrl('d') => { Key::Ctrl('d') => {
self.next(10); self.next_row(10);
return Ok(EventState::NotConsumed); return Ok(EventState::NotConsumed);
} }
Key::Char('k') => { Key::Char('k') => {
self.previous(1); self.previous_row(1);
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
Key::Ctrl('u') => { Key::Ctrl('u') => {
self.previous(10); self.previous_row(10);
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
Key::Char('g') => { Key::Char('g') => {
self.scroll_top(); self.scroll_top();
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} }
Key::Char('r') => {
self.select_entire_row = true;
return Ok(EventState::Consumed);
}
Key::Char('G') => { Key::Char('G') => {
self.scroll_bottom(); self.scroll_bottom();
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
@ -557,7 +512,7 @@ mod test {
fn test_headers() { fn test_headers() {
let mut component = TableComponent::default(); let mut component = TableComponent::default();
component.headers = vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(); component.headers = vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect();
assert_eq!(component.headers_with_number(1, 2), vec!["", "b"]) assert_eq!(component.headers(1, 2), vec!["", "b"])
} }
#[test] #[test]
@ -567,10 +522,7 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
assert_eq!( assert_eq!(component.rows(1, 2), vec![vec!["1", "b"], vec!["2", "e"]],)
component.rows_with_number(1, 2),
vec![vec!["1", "b"], vec!["2", "e"]],
)
} }
#[test] #[test]
@ -591,10 +543,10 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(1)); component.selected_row.select(Some(1));
component.selected_left_column_index = 1; component.selected_column = 1;
component.expand_selected_area_x(false); component.expand_selected_area_x(false);
assert_eq!(component.selected_right_cell, Some((0, 1))); assert_eq!(component.selection_area_corner, Some((0, 1)));
assert_eq!(component.selected_cells(), Some("d,e".to_string())); assert_eq!(component.selected_cells(), Some("d,e".to_string()));
} }
@ -616,10 +568,10 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(1)); component.selected_row.select(Some(1));
component.selected_left_column_index = 1; component.selected_column = 1;
component.expand_selected_area_x(true); component.expand_selected_area_x(true);
assert_eq!(component.selected_right_cell, Some((2, 1))); assert_eq!(component.selection_area_corner, Some((2, 1)));
assert_eq!(component.selected_cells(), Some("e,f".to_string())); assert_eq!(component.selected_cells(), Some("e,f".to_string()));
} }
@ -640,10 +592,10 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(1)); component.selected_row.select(Some(1));
component.selected_left_column_index = 1; component.selected_column = 1;
component.expand_selected_area_y(false); component.expand_selected_area_y(false);
assert_eq!(component.selected_right_cell, Some((1, 0))); assert_eq!(component.selection_area_corner, Some((1, 0)));
assert_eq!(component.selected_cells(), Some("b\ne".to_string())); assert_eq!(component.selected_cells(), Some("b\ne".to_string()));
} }
@ -664,13 +616,26 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(0)); component.selected_row.select(Some(0));
component.selected_left_column_index = 1; component.selected_column = 1;
component.expand_selected_area_y(true); component.expand_selected_area_y(true);
assert_eq!(component.selected_right_cell, Some((1, 1))); assert_eq!(component.selection_area_corner, Some((1, 1)));
assert_eq!(component.selected_cells(), Some("b\ne".to_string())); assert_eq!(component.selected_cells(), Some("b\ne".to_string()));
} }
#[test]
fn test_is_number_column() {
let mut component = TableComponent::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(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
];
component.selected_row.select(Some(0));
assert!(component.is_number_column(0, 0));
assert!(!component.is_number_column(0, 1));
}
#[test] #[test]
fn test_selected_cell_when_one_cell_selected() { fn test_selected_cell_when_one_cell_selected() {
// 1 2 3 // 1 2 3
@ -683,7 +648,7 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(0)); component.selected_row.select(Some(0));
assert_eq!(component.selected_cells(), Some("a".to_string())); assert_eq!(component.selected_cells(), Some("a".to_string()));
} }
@ -699,8 +664,8 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(0)); component.selected_row.select(Some(0));
component.selected_right_cell = Some((1, 1)); component.selection_area_corner = Some((1, 1));
assert_eq!(component.selected_cells(), Some("a,b\nd,e".to_string())); assert_eq!(component.selected_cells(), Some("a,b\nd,e".to_string()));
} }
@ -716,7 +681,7 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(0)); component.selected_row.select(Some(0));
// a // a
assert!(component.is_selected_cell(0, 1, 1)); assert!(component.is_selected_cell(0, 1, 1));
// d // d
@ -737,8 +702,8 @@ mod test {
vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(), vec!["a", "b", "c"].iter().map(|h| h.to_string()).collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
component.state.select(Some(0)); component.selected_row.select(Some(0));
component.selected_right_cell = Some((1, 1)); component.selection_area_corner = Some((1, 1));
// a // a
assert!(component.is_selected_cell(0, 1, 1)); assert!(component.is_selected_cell(0, 1, 1));
// b // b
@ -752,7 +717,7 @@ mod test {
} }
#[test] #[test]
fn test_calculate_widths() { fn test_calculate_cell_widths() {
let mut component = TableComponent::default(); let mut component = TableComponent::default();
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect(); component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![ component.rows = vec![
@ -762,7 +727,8 @@ mod test {
.collect(), .collect(),
vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(), vec!["d", "e", "f"].iter().map(|h| h.to_string()).collect(),
]; ];
let (selected_column_index, headers, rows, constraints) = component.calculate_widths(10); let (selected_column_index, headers, rows, constraints) =
component.calculate_cell_widths(10);
assert_eq!(selected_column_index, 1); assert_eq!(selected_column_index, 1);
assert_eq!(headers, vec!["", "1", "2"]); assert_eq!(headers, vec!["", "1", "2"]);
assert_eq!(rows, vec![vec!["1", "aaaaa", "bbbbb"], vec!["2", "d", "e"]]); assert_eq!(rows, vec![vec!["1", "aaaaa", "bbbbb"], vec!["2", "d", "e"]]);
@ -775,7 +741,8 @@ mod test {
] ]
); );
let (selected_column_index, headers, rows, constraints) = component.calculate_widths(20); let (selected_column_index, headers, rows, constraints) =
component.calculate_cell_widths(20);
assert_eq!(selected_column_index, 1); assert_eq!(selected_column_index, 1);
assert_eq!(headers, vec!["", "1", "2", "3"]); assert_eq!(headers, vec!["", "1", "2", "3"]);
assert_eq!( assert_eq!(
@ -794,5 +761,39 @@ mod test {
Constraint::Min(10), Constraint::Min(10),
] ]
); );
let mut component = TableComponent::default();
component.headers = vec!["1", "2", "3"].iter().map(|h| h.to_string()).collect();
component.rows = vec![
vec!["aaaaa", "bbbbb", "ccccc"]
.iter()
.map(|h| h.to_string())
.collect(),
vec!["dddddddddd", "e", "f"]
.iter()
.map(|h| h.to_string())
.collect(),
];
let (selected_column_index, headers, rows, constraints) =
component.calculate_cell_widths(20);
assert_eq!(selected_column_index, 1);
assert_eq!(headers, vec!["", "1", "2", "3"]);
assert_eq!(
rows,
vec![
vec!["1", "aaaaa", "bbbbb", "ccccc"],
vec!["2", "dddddddddd", "e", "f"]
]
);
assert_eq!(
constraints,
vec![
Constraint::Length(1),
Constraint::Length(10),
Constraint::Length(5),
Constraint::Min(10),
]
);
} }
} }

@ -14,8 +14,8 @@ use unicode_width::UnicodeWidthStr;
pub struct TableFilterComponent { pub struct TableFilterComponent {
pub table: Option<String>, pub table: Option<String>,
pub input: Vec<char>, pub input: Vec<char>,
pub input_idx: usize, input_idx: usize,
pub input_cursor_position: u16, input_cursor_position: u16,
} }
impl Default for TableFilterComponent { impl Default for TableFilterComponent {

@ -12,8 +12,8 @@ use tui::{
}; };
pub struct TableStatusComponent { pub struct TableStatusComponent {
pub rows_count: u64, rows_count: u64,
pub table: Option<Table>, table: Option<Table>,
} }
impl Default for TableStatusComponent { impl Default for TableStatusComponent {
@ -31,7 +31,7 @@ impl TableStatusComponent {
self.table = Some(table); self.table = Some(table);
} }
pub fn status_str(&self) -> Vec<String> { fn status_str(&self) -> Vec<String> {
if let Some(table) = self.table.as_ref() { if let Some(table) = self.table.as_ref() {
return vec![ return vec![
format!( format!(

@ -11,7 +11,7 @@ use tui::{
}; };
pub struct TableValueComponent { pub struct TableValueComponent {
pub value: String, value: String,
} }
impl TableValueComponent { impl TableValueComponent {

@ -9,10 +9,10 @@ pub struct UserConfig {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct Connection { pub struct Connection {
pub name: Option<String>, name: Option<String>,
pub user: String, user: String,
pub host: String, host: String,
pub port: u64, port: u64,
pub database: Option<String>, pub database: Option<String>,
} }

Loading…
Cancel
Save