diff --git a/src/app.rs b/src/app.rs index abed56c..83a06e0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -152,18 +152,17 @@ impl App { } pub(crate) fn draw(&mut self, frame: &mut Frame, area: Rect, focused: bool) { - if area.width < 38 || area.height < 12 { + if area.width < 38 || area.height < 10 { frame.render_widget( Paragraph::new("Enlarge terminal to at least 70×12. q to quit."), area, ); return; } - let [header, body, status, help] = Layout::vertical([ + let [header, body, error_area] = Layout::vertical([ Constraint::Length(1), Constraint::Min(4), - Constraint::Length(2), - Constraint::Length(2), + Constraint::Length(1), ]) .areas(area); frame.render_widget( @@ -181,7 +180,6 @@ impl App { } else { " Rows · s for schema " }; - let content_block = panel(title, focused); let count = if self.show_schema { 5 } else { @@ -197,6 +195,40 @@ impl App { Constraint::Length(20) } }); + let description = if let Some(index) = self.active { + let relation = &self.relations[index]; + if self.show_schema { + format!( + " {} [{}] · {} columns · fields {}–{}/5", + relation.label, + relation.kind, + self.columns.len(), + self.column_offset + 1, + self.column_offset + visible + ) + } else { + let start = if self.page.rows.is_empty() { + 0 + } else { + self.page_index * PAGE_SIZE + 1 + }; + let end = self.page_index * PAGE_SIZE + self.page.rows.len(); + format!( + " {} [{}] · rows {start}–{end} · page {}{} · columns {}–{}/{}", + relation.label, + relation.kind, + self.page_index + 1, + if self.page.has_next { " (more)" } else { "" }, + if count > 0 { self.column_offset + 1 } else { 0 }, + self.column_offset + visible, + count + ) + } + } else { + " No readable relations. r to refresh.".into() + }; + let content_block = + panel(title, focused).title_bottom(Line::from(description).left_aligned()); if self.active.is_none() { frame.render_widget( Paragraph::new("Choose a relation and press Enter.").block(content_block), @@ -266,50 +298,12 @@ impl App { .highlight_symbol("› "); frame.render_stateful_widget(table, content, &mut self.rows_state); } - let description = if let Some(index) = self.active { - let relation = &self.relations[index]; - if self.show_schema { - format!( - " {} [{}] · {} columns · fields {}–{}/5", - relation.label, - relation.kind, - self.columns.len(), - self.column_offset + 1, - self.column_offset + visible - ) - } else { - let start = if self.page.rows.is_empty() { - 0 - } else { - self.page_index * PAGE_SIZE + 1 - }; - let end = self.page_index * PAGE_SIZE + self.page.rows.len(); - format!( - " {} [{}] · rows {start}–{end} · page {}{} · columns {}–{}/{}", - relation.label, - relation.kind, - self.page_index + 1, - if self.page.has_next { " (more)" } else { "" }, - if count > 0 { self.column_offset + 1 } else { 0 }, - self.column_offset + visible, - count - ) - } - } else { - " No readable relations. r to refresh.".into() - }; - let [description_area, error_area] = - Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(status); - frame.render_widget(Paragraph::new(description), description_area); if let Some(error) = &self.error { frame.render_widget( Paragraph::new(error.as_str()).style(Style::default().fg(Color::Red)), error_area, ); } - frame.render_widget(Paragraph::new( - " Tab focus · Esc explorer · ↑↓/jk move · Enter open · s schema/rows\n ←→/hl columns · n/p page · r refresh · c add database · q quit" - ).style(Style::default().fg(Color::DarkGray)), help); } } @@ -358,4 +352,28 @@ mod tests { assert_eq!(moved_index(Some(1), 3, isize::MIN), Some(0)); assert_eq!(moved_index(Some(1), 3, isize::MAX), Some(2)); } + + #[test] + fn relation_status_is_in_the_bottom_border() -> crate::Result<()> { + let file = tempfile::NamedTempFile::new()?; + rusqlite::Connection::open(file.path())?.execute_batch( + "CREATE TABLE comments (id INTEGER); INSERT INTO comments VALUES (1);", + )?; + let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?; + let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(100, 20))?; + for schema in [false, true] { + app.show_schema = schema; + terminal.draw(|frame| app.draw(frame, frame.area(), true))?; + let buffer = terminal.backend().buffer(); + let border: String = (0..100).map(|x| buffer[(x, 18)].symbol()).collect(); + assert!(border.starts_with("└ main.comments [table]"), "{border}"); + assert!(border.ends_with('┘'), "{border}"); + assert!(border.contains(if schema { + "fields 1–4/5" + } else { + "rows 1–1" + })); + } + Ok(()) + } } diff --git a/src/connection.rs b/src/connection.rs index 4a20a99..9eef685 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -34,8 +34,10 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option) -> Result< expanded, ); terminal.draw(|frame| { + let [main, help] = + Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(frame.area()); let [sidebar, content] = Layout::horizontal([Constraint::Length(32), Constraint::Min(1)]) - .areas(frame.area()); + .areas(main); let items = entries.iter().map(|entry| { ListItem::new(match *entry { ExplorerEntry::Database(index) => { @@ -62,6 +64,9 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option) -> Result< if let Some(error) = &error { frame.render_widget(Paragraph::new(error.as_str()).style(Style::default().fg(Color::Red)), Rect::new(content.x, content.bottom().saturating_sub(1), content.width, 1)); } + frame.render_widget(Paragraph::new( + " Tab focus · Esc explorer · ↑↓/jk move · Enter open · s schema/rows\n ←→/hl columns · n/p page · r refresh · c add database · q quit" + ).style(Style::default().fg(Color::DarkGray)), help); if show_form { form.draw(frame); } diff --git a/src/main.rs b/src/main.rs index 587dd55..8c3f1a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,8 @@ Navigation: r Refresh relations and the active relation Connected databases show their tables and views beneath them in the explorer. Enter on the connected database collapses/expands it without reconnecting. + Navigation help spans the bottom of the terminal beneath both panes. + Relation, page, and column status appears in the rows/schema bottom border. q / Ctrl-C Quit New database window: Up/Down chooses a provider; Enter starts editing. Tab/Shift-Tab changes fields; Left/Right changes TLS when that field is focused.