Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-tui/src/widgets/table.rs')
| -rw-r--r-- | helix-tui/src/widgets/table.rs | 136 |
1 files changed, 73 insertions, 63 deletions
diff --git a/helix-tui/src/widgets/table.rs b/helix-tui/src/widgets/table.rs index 5f4667b7..a8f428a7 100644 --- a/helix-tui/src/widgets/table.rs +++ b/helix-tui/src/widgets/table.rs @@ -4,8 +4,14 @@ use crate::{ text::Text, widgets::{Block, Widget}, }; +use cassowary::{ + strength::{MEDIUM, REQUIRED, WEAK}, + WeightedRelation::*, + {Expression, Solver}, +}; use helix_core::unicode::width::UnicodeWidthStr; use helix_view::graphics::{Rect, Style}; +use std::collections::HashMap; /// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`]. /// @@ -34,17 +40,11 @@ pub struct Cell<'a> { style: Style, } -impl Cell<'_> { +impl<'a> Cell<'a> { /// Set the `Style` of this cell. pub fn style(mut self, style: Style) -> Self { - self.set_style(style); - self - } - - /// Set the `Style` of this cell. - pub fn set_style(&mut self, style: Style) { self.style = style; - self.content.patch_style(style); + self } } @@ -126,17 +126,6 @@ impl<'a> Row<'a> { fn total_height(&self) -> u16 { self.height.saturating_add(self.bottom_margin) } - - /// Returns the contents of cells as plain text, without styles and colors. - pub fn cell_text(&self) -> impl Iterator<Item = String> + '_ { - self.cells.iter().map(|cell| String::from(&cell.content)) - } -} - -impl<'a, T: Into<Cell<'a>>> From<T> for Row<'a> { - fn from(cell: T) -> Self { - Row::new(vec![cell.into()]) - } } /// A widget to display data in formatted columns. @@ -271,32 +260,69 @@ impl<'a> Table<'a> { } fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> { - let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1); + let mut solver = Solver::new(); + let mut var_indices = HashMap::new(); + let mut ccs = Vec::new(); + let mut variables = Vec::new(); + for i in 0..self.widths.len() { + let var = cassowary::Variable::new(); + variables.push(var); + var_indices.insert(var, i); + } + let spacing_width = (variables.len() as u16).saturating_sub(1) * self.column_spacing; + let mut available_width = max_width.saturating_sub(spacing_width); if has_selection { let highlight_symbol_width = self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0); - constraints.push(Constraint::Length(highlight_symbol_width)); + available_width = available_width.saturating_sub(highlight_symbol_width); } - for constraint in self.widths { - constraints.push(*constraint); - constraints.push(Constraint::Length(self.column_spacing)); + for (i, constraint) in self.widths.iter().enumerate() { + ccs.push(variables[i] | GE(WEAK) | 0.); + ccs.push(match *constraint { + Constraint::Length(v) => variables[i] | EQ(MEDIUM) | f64::from(v), + Constraint::Percentage(v) => { + variables[i] | EQ(WEAK) | (f64::from(v * available_width) / 100.0) + } + Constraint::Ratio(n, d) => { + variables[i] + | EQ(WEAK) + | (f64::from(available_width) * f64::from(n) / f64::from(d)) + } + Constraint::Min(v) => variables[i] | GE(WEAK) | f64::from(v), + Constraint::Max(v) => variables[i] | LE(WEAK) | f64::from(v), + }) } - if !self.widths.is_empty() { - constraints.pop(); + solver + .add_constraint( + variables + .iter() + .fold(Expression::from_constant(0.), |acc, v| acc + *v) + | LE(REQUIRED) + | f64::from(available_width), + ) + .unwrap(); + solver.add_constraints(&ccs).unwrap(); + let mut widths = vec![0; variables.len()]; + for &(var, value) in solver.fetch_changes() { + let index = var_indices[&var]; + let value = if value.is_sign_negative() { + 0 + } else { + value.round() as u16 + }; + widths[index] = value; } - let mut chunks = crate::layout::Layout::default() - .direction(crate::layout::Direction::Horizontal) - .constraints(constraints) - .split(Rect { - x: 0, - y: 0, - width: max_width, - height: 1, - }); - if has_selection { - chunks.remove(0); + // Cassowary could still return columns widths greater than the max width when there are + // fixed length constraints that cannot be satisfied. Therefore, we clamp the widths from + // left to right. + let mut available_width = max_width; + for w in &mut widths { + *w = available_width.min(*w); + available_width = available_width + .saturating_sub(*w) + .saturating_sub(self.column_spacing); } - chunks.iter().step_by(2).map(|c| c.width).collect() + widths } fn get_row_bounds( @@ -337,7 +363,6 @@ impl<'a> Table<'a> { } } -/// Track [Table] scroll offset and selection #[derive(Debug, Default, Clone)] pub struct TableState { pub offset: usize, @@ -358,16 +383,10 @@ impl TableState { } // impl<'a> StatefulWidget for Table<'a> { -impl Table<'_> { +impl<'a> Table<'a> { // type State = TableState; - pub fn render_table( - mut self, - area: Rect, - buf: &mut Buffer, - state: &mut TableState, - truncate: bool, - ) { + pub fn render_table(mut self, area: Rect, buf: &mut Buffer, state: &mut TableState) { if area.area() == 0 { return; } @@ -414,7 +433,6 @@ impl Table<'_> { width: *width, height: max_header_height, }, - truncate, ); col += *width + self.column_spacing; } @@ -457,12 +475,6 @@ impl Table<'_> { } else { col }; - if is_selected { - buf.set_style(table_row_area, self.highlight_style); - for cell in &mut table_row.cells { - cell.set_style(self.highlight_style); - } - } let mut col = table_row_start_col; for (width, cell) in columns_widths.iter().zip(table_row.cells.iter()) { render_cell( @@ -474,32 +486,30 @@ impl Table<'_> { width: *width, height: table_row.height, }, - truncate, ); col += *width + self.column_spacing; } + if is_selected { + buf.set_style(table_row_area, self.highlight_style); + } } } } -fn render_cell(buf: &mut Buffer, cell: &Cell, area: Rect, truncate: bool) { +fn render_cell(buf: &mut Buffer, cell: &Cell, area: Rect) { buf.set_style(area, cell.style); for (i, spans) in cell.content.lines.iter().enumerate() { if i as u16 >= area.height { break; } - if truncate { - buf.set_spans_truncated(area.x, area.y + i as u16, spans, area.width); - } else { - buf.set_spans(area.x, area.y + i as u16, spans, area.width); - } + buf.set_spans(area.x, area.y + i as u16, spans, area.width); } } -impl Widget for Table<'_> { +impl<'a> Widget for Table<'a> { fn render(self, area: Rect, buf: &mut Buffer) { let mut state = TableState::default(); - Table::render_table(self, area, buf, &mut state, false); + Table::render_table(self, area, buf, &mut state); } } |