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.rs157
1 files changed, 88 insertions, 69 deletions
diff --git a/helix-tui/src/widgets/table.rs b/helix-tui/src/widgets/table.rs
index 5f4667b7..d7caa0b0 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`].
///
@@ -28,23 +34,17 @@ use helix_view::graphics::{Rect, Style};
///
/// You can apply a [`Style`] on the entire [`Cell`] using [`Cell::style`] or rely on the styling
/// capabilities of [`Text`].
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, PartialEq, Default)]
pub struct Cell<'a> {
pub content: Text<'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
}
}
@@ -68,7 +68,7 @@ where
/// Row::new(vec!["Cell1", "Cell2", "Cell3"]);
/// ```
///
-/// But if you need a bit more control over individual cells, you can explicitly create [`Cell`]s:
+/// But if you need a bit more control over individual cells, you can explicity create [`Cell`]s:
/// ```rust
/// # use helix_tui::widgets::{Row, Cell};
/// # use helix_view::graphics::{Style, Color};
@@ -79,7 +79,7 @@ where
/// ```
///
/// By default, a row has a height of 1 but you can change this using [`Row::height`].
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, PartialEq, Default)]
pub struct Row<'a> {
pub cells: Vec<Cell<'a>>,
height: u16,
@@ -109,7 +109,7 @@ impl<'a> Row<'a> {
self
}
- /// Set the [`Style`] of the entire row. This [`Style`] can be overridden by the [`Style`] of a
+ /// Set the [`Style`] of the entire row. This [`Style`] can be overriden by the [`Style`] of a
/// any individual [`Cell`] or event by their [`Text`] content.
pub fn style(mut self, style: Style) -> Self {
self.style = style;
@@ -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.
@@ -190,7 +179,7 @@ impl<'a, T: Into<Cell<'a>>> From<T> for Row<'a> {
/// // ...and potentially show a symbol in front of the selection.
/// .highlight_symbol(">>");
/// ```
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq)]
pub struct Table<'a> {
/// A block to wrap the widget in
block: Option<Block<'a>>,
@@ -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,13 +363,21 @@ impl<'a> Table<'a> {
}
}
-/// Track [Table] scroll offset and selection
-#[derive(Debug, Default, Clone)]
+#[derive(Debug, Clone)]
pub struct TableState {
pub offset: usize,
pub selected: Option<usize>,
}
+impl Default for TableState {
+ fn default() -> TableState {
+ TableState {
+ offset: 0,
+ selected: None,
+ }
+ }
+}
+
impl TableState {
pub fn selected(&self) -> Option<usize> {
self.selected
@@ -358,16 +392,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 +442,6 @@ impl Table<'_> {
width: *width,
height: max_header_height,
},
- truncate,
);
col += *width + self.column_spacing;
}
@@ -457,12 +484,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 +495,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);
}
}