diff --git a/Cargo.toml b/Cargo.toml index 4cc47f7..8739649 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" description = "Read-only PostgreSQL and SQLite inspection in your terminal" [dependencies] -ratatui = { version = "0.30.2", default-features = false, features = ["crossterm"] } +ratatui = { version = "0.30.2", default-features = false, features = ["crossterm", "unstable-rendered-line-info"] } crossterm = "0.29" postgres = "0.19" postgres-native-tls = "0.5" diff --git a/TODO.md b/TODO.md index fd03203..e69de29 100644 --- a/TODO.md +++ b/TODO.md @@ -1 +0,0 @@ -- [ ] make sure that sensitive information (e.g. passwords) for configured databases are encrypted in such a way that credentials can't be snatched by malicious programs \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 83a06e0..351ddd1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -159,10 +159,10 @@ impl App { ); return; } - let [header, body, error_area] = Layout::vertical([ + let [header, error_area, body] = Layout::vertical([ Constraint::Length(1), + Constraint::Length(u16::from(self.error.is_some())), Constraint::Min(4), - Constraint::Length(1), ]) .areas(area); frame.render_widget( @@ -262,7 +262,7 @@ impl App { } else if self.page.rows.is_empty() { frame.render_widget( Paragraph::new(if self.error.is_some() { - "Could not load rows. See error below." + "Could not load rows. See error above." } else { "No rows." }) @@ -361,11 +361,12 @@ mod tests { )?; 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] { + for (schema, error) in [(false, None), (true, None), (false, Some("Read failed"))] { app.show_schema = schema; + app.error = error.map(str::to_owned); 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(); + let border: String = (0..100).map(|x| buffer[(x, 19)].symbol()).collect(); assert!(border.starts_with("└ main.comments [table]"), "{border}"); assert!(border.ends_with('┘'), "{border}"); assert!(border.contains(if schema { @@ -373,6 +374,10 @@ mod tests { } else { "rows 1–1" })); + if let Some(error) = error { + let message: String = (0..100).map(|x| buffer[(x, 1)].symbol()).collect(); + assert!(message.starts_with(error), "{message}"); + } } Ok(()) } diff --git a/src/connection.rs b/src/connection.rs index 9eef685..3f2a321 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -34,8 +34,12 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option) -> Result< expanded, ); terminal.draw(|frame| { + let navigation = Paragraph::new( + " Tab focus · Esc explorer · ↑↓/jk move · Enter open · s schema/rows · ←→/hl columns · n/p page · r refresh · c add database · q quit" + ).wrap(Wrap { trim: true }).style(Style::default().fg(Color::DarkGray)); + let help_height = navigation.line_count(frame.area().width) as u16; let [main, help] = - Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(frame.area()); + Layout::vertical([Constraint::Min(1), Constraint::Length(help_height)]).areas(frame.area()); let [sidebar, content] = Layout::horizontal([Constraint::Length(32), Constraint::Min(1)]) .areas(main); let items = entries.iter().map(|entry| { @@ -64,9 +68,7 @@ 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); + frame.render_widget(navigation, help); if show_form { form.draw(frame); } diff --git a/src/main.rs b/src/main.rs index 8c3f1a9..9f39ed6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,8 +39,9 @@ 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. + Navigation help spans the bottom as one line, wrapping only when needed. + Explorer and rows/schema bottom borders align; relation/page/column status + appears in the rows/schema border. Read errors appear above that pane. 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.