fix: align pane bottoms and wrap navigation help

This commit is contained in:
Valdemar Lorenzen
2026-10-19 13:52:26 +02:00
parent 0ffcef494f
commit b15460d5f8
5 changed files with 20 additions and 13 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ edition = "2024"
description = "Read-only PostgreSQL and SQLite inspection in your terminal" description = "Read-only PostgreSQL and SQLite inspection in your terminal"
[dependencies] [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" crossterm = "0.29"
postgres = "0.19" postgres = "0.19"
postgres-native-tls = "0.5" postgres-native-tls = "0.5"
-1
View File
@@ -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
+10 -5
View File
@@ -159,10 +159,10 @@ impl App {
); );
return; return;
} }
let [header, body, error_area] = Layout::vertical([ let [header, error_area, body] = Layout::vertical([
Constraint::Length(1), Constraint::Length(1),
Constraint::Length(u16::from(self.error.is_some())),
Constraint::Min(4), Constraint::Min(4),
Constraint::Length(1),
]) ])
.areas(area); .areas(area);
frame.render_widget( frame.render_widget(
@@ -262,7 +262,7 @@ impl App {
} else if self.page.rows.is_empty() { } else if self.page.rows.is_empty() {
frame.render_widget( frame.render_widget(
Paragraph::new(if self.error.is_some() { Paragraph::new(if self.error.is_some() {
"Could not load rows. See error below." "Could not load rows. See error above."
} else { } else {
"No rows." "No rows."
}) })
@@ -361,11 +361,12 @@ mod tests {
)?; )?;
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?; 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))?; 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.show_schema = schema;
app.error = error.map(str::to_owned);
terminal.draw(|frame| app.draw(frame, frame.area(), true))?; terminal.draw(|frame| app.draw(frame, frame.area(), true))?;
let buffer = terminal.backend().buffer(); 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.starts_with("└ main.comments [table]"), "{border}");
assert!(border.ends_with('┘'), "{border}"); assert!(border.ends_with('┘'), "{border}");
assert!(border.contains(if schema { assert!(border.contains(if schema {
@@ -373,6 +374,10 @@ mod tests {
} else { } else {
"rows 1–1" "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(()) Ok(())
} }
+6 -4
View File
@@ -34,8 +34,12 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
expanded, expanded,
); );
terminal.draw(|frame| { 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] = 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)]) let [sidebar, content] = Layout::horizontal([Constraint::Length(32), Constraint::Min(1)])
.areas(main); .areas(main);
let items = entries.iter().map(|entry| { let items = entries.iter().map(|entry| {
@@ -64,9 +68,7 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
if let Some(error) = &error { 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(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( frame.render_widget(navigation, help);
" 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 { if show_form {
form.draw(frame); form.draw(frame);
} }
+3 -2
View File
@@ -39,8 +39,9 @@ Navigation:
r Refresh relations and the active relation r Refresh relations and the active relation
Connected databases show their tables and views beneath them in the explorer. Connected databases show their tables and views beneath them in the explorer.
Enter on the connected database collapses/expands it without reconnecting. Enter on the connected database collapses/expands it without reconnecting.
Navigation help spans the bottom of the terminal beneath both panes. Navigation help spans the bottom as one line, wrapping only when needed.
Relation, page, and column status appears in the rows/schema bottom border. 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 q / Ctrl-C Quit
New database window: Up/Down chooses a provider; Enter starts editing. 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. Tab/Shift-Tab changes fields; Left/Right changes TLS when that field is focused.