Skip to content

Commit

Permalink
nu-explore: Add vertical lines && fix index/transpose issue (#13147)
Browse files Browse the repository at this point in the history
Somehow I believe that split lines were implemented originally; (I
haven't got to find it though; from a quick look)
I mean a long time ago before a lot a changes were made.

Probably adding horizontal lines would make also some sense.

ref #13116
close #13140

Take care

________________

If `explore` is used, frequently, or planned to be so.
I guess it would be a good one to create a test suite for it; to not
break things occasionally 😅

I did approached it one time back then using `expectrl` (literally
`expect`), but there was some issues.
Maybe smth. did change.
Or some `clean` mode could be introduced for it, to being able to be
used by outer programs; to control `nu`.

Just thoughts here.
  • Loading branch information
zhiburt committed Jun 21, 2024
1 parent 91d44f1 commit 10e8403
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 49 deletions.
76 changes: 49 additions & 27 deletions crates/nu-explore/src/views/record/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ impl RecordView {
Orientation::Left => (column, row),
};

if row >= layer.count_rows() || column >= layer.count_columns() {
if row >= layer.record_values.len() || column >= layer.column_names.len() {
// actually must never happen; unless cursor works incorrectly
// if being sure about cursor it can be deleted;
return Value::nothing(Span::unknown());
Expand Down Expand Up @@ -610,7 +610,7 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 {
/// scroll to the end of the data
fn tail_data(state: &mut RecordView, page_size: usize) {
let layer = state.get_layer_last_mut();
let count_rows = layer.count_rows();
let count_rows = layer.record_values.len();
if count_rows > page_size {
layer
.cursor
Expand Down Expand Up @@ -722,56 +722,78 @@ fn get_percentage(value: usize, max: usize) -> usize {
}

fn transpose_table(layer: &mut RecordLayer) {
if layer.was_transposed {
transpose_from(layer);
} else {
transpose_to(layer);
}

layer.was_transposed = !layer.was_transposed;
}

fn transpose_from(layer: &mut RecordLayer) {
let count_rows = layer.record_values.len();
let count_columns = layer.column_names.len();

if layer.was_transposed {
let headers = pop_first_column(&mut layer.record_values);
let headers = headers
.into_iter()
.map(|value| match value {
Value::String { val, .. } => val,
_ => unreachable!("must never happen"),
})
.collect();
if let Some(data) = &mut layer.record_text {
pop_first_column(data);
*data = _transpose_table(data, count_rows, count_columns - 1);
}

let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);
let headers = pop_first_column(&mut layer.record_values);
let headers = headers
.into_iter()
.map(|value| match value {
Value::String { val, .. } => val,
_ => unreachable!("must never happen"),
})
.collect();

layer.record_values = data;
layer.column_names = headers;
let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);

return;
layer.record_values = data;
layer.column_names = headers;
}

fn transpose_to(layer: &mut RecordLayer) {
let count_rows = layer.record_values.len();
let count_columns = layer.column_names.len();

if let Some(data) = &mut layer.record_text {
*data = _transpose_table(data, count_rows, count_columns);
for (column, column_name) in layer.column_names.iter().enumerate() {
let value = (column_name.to_owned(), Default::default());
data[column].insert(0, value);
}
}

let mut data = _transpose_table(&layer.record_values, count_rows, count_columns);

for (column, column_name) in layer.column_names.iter().enumerate() {
let value = Value::string(column_name, NuSpan::unknown());

data[column].insert(0, value);
}

layer.record_values = data;
layer.column_names = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect();

layer.was_transposed = !layer.was_transposed;
}

fn pop_first_column(values: &mut [Vec<Value>]) -> Vec<Value> {
let mut data = vec![Value::default(); values.len()];
fn pop_first_column<T>(values: &mut [Vec<T>]) -> Vec<T>
where
T: Default + Clone,
{
let mut data = vec![T::default(); values.len()];
for (row, values) in values.iter_mut().enumerate() {
data[row] = values.remove(0);
}

data
}

fn _transpose_table(
values: &[Vec<Value>],
count_rows: usize,
count_columns: usize,
) -> Vec<Vec<Value>> {
let mut data = vec![vec![Value::default(); count_rows]; count_columns];
fn _transpose_table<T>(values: &[Vec<T>], count_rows: usize, count_columns: usize) -> Vec<Vec<T>>
where
T: Clone + Default,
{
let mut data = vec![vec![T::default(); count_rows]; count_columns];
for (row, values) in values.iter().enumerate() {
for (column, value) in values.iter().enumerate() {
data[column][row].clone_from(value);
Expand Down
96 changes: 74 additions & 22 deletions crates/nu-explore/src/views/record/table_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl StatefulWidget for TableWidget<'_> {

// todo: refactoring these to methods as they have quite a bit in common.
impl<'a> TableWidget<'a> {
// header at the top; header is always 1 line
fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
let padding_l = self.config.column_padding_left as u16;
let padding_r = self.config.column_padding_right as u16;
Expand Down Expand Up @@ -130,25 +131,16 @@ impl<'a> TableWidget<'a> {
}

if show_index {
let area = Rect::new(width, data_y, area.width, data_height);
width += render_index(
buf,
area,
Rect::new(width, data_y, area.width, data_height),
self.style_computer,
self.index_row,
padding_l,
padding_r,
);

width += render_vertical_line_with_split(
buf,
width,
data_y,
data_height,
show_head,
false,
separator_s,
);
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
}

// if there is more data than we can show, add an ellipsis to the column headers to hint at that
Expand All @@ -162,6 +154,11 @@ impl<'a> TableWidget<'a> {
}

for col in self.index_column..self.columns.len() {
let need_split_line = state.count_columns > 0 && width < area.width;
if need_split_line {
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
}

let mut column = create_column(data, col);
let column_width = calculate_column_width(&column);

Expand Down Expand Up @@ -200,6 +197,7 @@ impl<'a> TableWidget<'a> {
}
let head_iter = [(&head, head_style)].into_iter();

// we don't change width here cause the whole column have the same width; so we add it when we print data
let mut w = width;
w += render_space(buf, w, head_y, 1, padding_l);
w += render_column(buf, w, head_y, use_space, head_iter);
Expand All @@ -209,10 +207,10 @@ impl<'a> TableWidget<'a> {
state.layout.push(&head, x, head_y, use_space, 1);
}

let head_rows = column.iter().map(|(t, s)| (t, *s));
let column_rows = column.iter().map(|(t, s)| (t, *s));

width += render_space(buf, width, data_y, data_height, padding_l);
width += render_column(buf, width, data_y, use_space, head_rows);
width += render_column(buf, width, data_y, use_space, column_rows);
width += render_space(buf, width, data_y, data_height, padding_r);

for (row, (text, _)) in column.iter().enumerate() {
Expand All @@ -235,15 +233,7 @@ impl<'a> TableWidget<'a> {
}

if width < area.width {
width += render_vertical_line_with_split(
buf,
width,
data_y,
data_height,
show_head,
false,
separator_s,
);
width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
}

let rest = area.width.saturating_sub(width);
Expand All @@ -255,6 +245,7 @@ impl<'a> TableWidget<'a> {
}
}

// header at the left; header is always 1 line
fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
if area.width == 0 || area.height == 0 {
return;
Expand Down Expand Up @@ -353,6 +344,9 @@ impl<'a> TableWidget<'a> {
state.count_rows = columns.len();
state.count_columns = 0;

// note: is there a time where we would have more then 1 column?
// seems like not really; cause it's literally KV table, or am I wrong?

for col in self.index_column..self.data.len() {
let mut column =
self.data[col][self.index_row..self.index_row + columns.len()].to_vec();
Expand All @@ -361,6 +355,13 @@ impl<'a> TableWidget<'a> {
break;
}

// see KV comment; this block might never got used
let need_split_line = state.count_columns > 0 && left_w < area.width;
if need_split_line {
render_vertical_line(buf, area.x + left_w, area.y, area.height, separator_s);
left_w += 1;
}

let column_width = column_width as u16;
let available = area.width - left_w;
let is_last = col + 1 == self.data.len();
Expand Down Expand Up @@ -555,6 +556,51 @@ fn render_index(
width
}

fn render_split_line(
buf: &mut Buffer,
x: u16,
y: u16,
height: u16,
has_head: bool,
style: NuStyle,
) -> u16 {
if has_head {
render_vertical_split_line(buf, x, y, height, &[0], &[2], &[], style);
} else {
render_vertical_split_line(buf, x, y, height, &[], &[], &[], style);
}

1
}

#[allow(clippy::too_many_arguments)]
fn render_vertical_split_line(
buf: &mut Buffer,
x: u16,
y: u16,
height: u16,
top_slit: &[u16],
inner_slit: &[u16],
bottom_slit: &[u16],
style: NuStyle,
) -> u16 {
render_vertical_line(buf, x, y, height, style);

for &y in top_slit {
render_top_connector(buf, x, y, style);
}

for &y in inner_slit {
render_inner_connector(buf, x, y, style);
}

for &y in bottom_slit {
render_bottom_connector(buf, x, y, style);
}

1
}

fn render_vertical_line_with_split(
buf: &mut Buffer,
x: u16,
Expand Down Expand Up @@ -668,6 +714,12 @@ fn render_bottom_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
buf.set_span(x, y, &span, 1);
}

fn render_inner_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
let style = nu_style_to_tui(style);
let span = Span::styled("┼", style);
buf.set_span(x, y, &span, 1);
}

fn calculate_column_width(column: &[NuText]) -> usize {
column
.iter()
Expand Down

0 comments on commit 10e8403

Please sign in to comment.