From 61d57d6de11bb538165c9d3dd41ffecd2ba2482c Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 17 Apr 2025 01:51:10 +0200 Subject: [PATCH] Automatically hide columns if they do not fit --- src/lib.rs | 1 + src/ssh/renderer.rs | 91 ++++++++++++++++++++++++++++++++++----------- src/tunnel/tui.rs | 8 ++-- 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b14abe7..acf1da5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![feature(let_chains)] +#![feature(iter_intersperse)] mod helper; mod io; pub mod ldap; diff --git a/src/ssh/renderer.rs b/src/ssh/renderer.rs index fdba7e8..5d98db3 100644 --- a/src/ssh/renderer.rs +++ b/src/ssh/renderer.rs @@ -1,5 +1,6 @@ use std::cmp::{self, max}; use std::io::Write as _; +use std::iter::once; use std::time::Duration; use futures::StreamExt; @@ -37,6 +38,42 @@ struct RendererInner { rx: UnboundedReceiver, } +fn compute_widths(rows: &Vec>>) -> Vec { + let table_header = Tunnel::header(); + std::iter::once(&table_header) + .chain(rows) + .map(|row| row.iter().map(|cell| cell.width() as u16)) + .fold(vec![0; table_header.len()], |acc, row| { + acc.into_iter() + .zip(row) + .map(|v| cmp::max(v.0, v.1)) + .collect() + }) +} + +fn compute_column_skip( + widths: &[u16], + column_spacing: u16, + highlight_symbol: usize, + max_width: u16, +) -> (usize, usize) { + for pattern in [(7, 0), (4, 0), (4, 1), (4, 2)] { + let width: u16 = widths + .iter() + .take(pattern.0) + .skip(pattern.1) + .intersperse(&column_spacing) + .chain(once(&(highlight_symbol as u16))) + .sum(); + + if width <= max_width { + return pattern; + } + } + + (4, 3) +} + impl RendererInner { fn new(rx: UnboundedReceiver) -> Self { Self { @@ -109,22 +146,6 @@ impl RendererInner { (height as u16, Paragraph::new(text).centered().block(block)) } - fn compute_widths(&mut self, rows: &Vec>>) -> Vec { - let table_header = Tunnel::header(); - std::iter::once(&table_header) - .chain(rows) - .map(|row| row.iter().map(|cell| cell.width() as u16)) - .fold(vec![0; table_header.len()], |acc, row| { - acc.into_iter() - .zip(row) - .map(|v| cmp::max(v.0, v.1)) - .collect() - }) - .into_iter() - .map(|c| Constraint::Length(c + 1)) - .collect() - } - fn render(&mut self, frame: &mut Frame) { self.render_title(frame, frame.area()); @@ -153,15 +174,41 @@ impl RendererInner { let highlight_style = Style::default().bold(); let header_style = Style::default().bold().reversed(); let row_style = Style::default(); + let highlight_symbol = Line::from("> "); + let column_spacing = 3; - let r = self + let rows = self .rows .iter() .map(From::from) .collect::>>>(); - let rows = r.iter().map(|row| { + let widths = compute_widths(&rows); + let (take, skip) = compute_column_skip( + &widths, + column_spacing, + highlight_symbol.width(), + rect.width, + ); + + let constraints: Vec<_> = widths + .into_iter() + .take(take) + .enumerate() + .map(|(index, width)| { + if index == 3 { + Constraint::Min(width) + } else { + Constraint::Length(width) + } + }) + .skip(skip) + .collect(); + + let rows = rows.iter().map(|row| { row.iter() + .take(take) + .skip(skip) .cloned() .map(Cell::from) .collect::() @@ -171,6 +218,8 @@ impl RendererInner { let header = Tunnel::header() .iter() + .take(take) + .skip(skip) .cloned() .map(Cell::from) .collect::() @@ -181,10 +230,10 @@ impl RendererInner { .header(header) .rows(rows) .flex(Flex::Start) - .column_spacing(3) - .widths(self.compute_widths(&r)) + .column_spacing(column_spacing) + .widths(&constraints) .row_highlight_style(highlight_style) - .highlight_symbol(Line::from("> ")) + .highlight_symbol(highlight_symbol) .highlight_spacing(HighlightSpacing::Always); frame.render_stateful_widget(t, rect, &mut self.state); diff --git a/src/tunnel/tui.rs b/src/tunnel/tui.rs index 965ab5d..793da01 100644 --- a/src/tunnel/tui.rs +++ b/src/tunnel/tui.rs @@ -9,8 +9,8 @@ use crate::io::Stats; pub struct TunnelRow { name: Span<'static>, - access: Span<'static>, port: Span<'static>, + access: Span<'static>, address: Span<'static>, stats: Arc, } @@ -19,8 +19,8 @@ impl From<&TunnelRow> for Vec> { fn from(row: &TunnelRow) -> Self { vec![ row.name.clone(), - row.access.clone(), row.port.clone(), + row.access.clone(), row.address.clone(), row.stats.connections().to_string().into(), row.stats.rx().to_string().into(), @@ -33,8 +33,8 @@ impl Tunnel { pub fn header() -> Vec> { vec![ "Name".into(), - "Access".into(), "Port".into(), + "Access".into(), "Address".into(), "Conn".into(), "Rx".into(), @@ -56,8 +56,8 @@ impl Tunnel { TunnelRow { name: tunnel.registry_entry.get_name().to_string().into(), - access, port: tunnel.inner.port.to_string().into(), + access, address, stats: tunnel.inner.stats.clone(), }