nu-explore: Add vertical lines && fix index/transpose issue (#13147)

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.
This commit is contained in:
Maxim Zhiburt 2024-06-21 22:07:57 +03:00 committed by GitHub
parent 91d44f15c1
commit 10e84038af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 123 additions and 49 deletions

View File

@ -131,7 +131,7 @@ impl RecordView {
Orientation::Left => (column, row), 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 // actually must never happen; unless cursor works incorrectly
// if being sure about cursor it can be deleted; // if being sure about cursor it can be deleted;
return Value::nothing(Span::unknown()); return Value::nothing(Span::unknown());
@ -610,7 +610,7 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 {
/// scroll to the end of the data /// scroll to the end of the data
fn tail_data(state: &mut RecordView, page_size: usize) { fn tail_data(state: &mut RecordView, page_size: usize) {
let layer = state.get_layer_last_mut(); 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 { if count_rows > page_size {
layer layer
.cursor .cursor
@ -722,43 +722,66 @@ fn get_percentage(value: usize, max: usize) -> usize {
} }
fn transpose_table(layer: &mut RecordLayer) { 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_rows = layer.record_values.len();
let count_columns = layer.column_names.len(); let count_columns = layer.column_names.len();
if layer.was_transposed { if let Some(data) = &mut layer.record_text {
let headers = pop_first_column(&mut layer.record_values); pop_first_column(data);
let headers = headers *data = _transpose_table(data, count_rows, count_columns - 1);
.into_iter() }
.map(|value| match value {
Value::String { val, .. } => val,
_ => unreachable!("must never happen"),
})
.collect();
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; let data = _transpose_table(&layer.record_values, count_rows, count_columns - 1);
layer.column_names = headers;
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); let mut data = _transpose_table(&layer.record_values, count_rows, count_columns);
for (column, column_name) in layer.column_names.iter().enumerate() { for (column, column_name) in layer.column_names.iter().enumerate() {
let value = Value::string(column_name, NuSpan::unknown()); let value = Value::string(column_name, NuSpan::unknown());
data[column].insert(0, value); data[column].insert(0, value);
} }
layer.record_values = data; layer.record_values = data;
layer.column_names = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect(); 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> { fn pop_first_column<T>(values: &mut [Vec<T>]) -> Vec<T>
let mut data = vec![Value::default(); values.len()]; where
T: Default + Clone,
{
let mut data = vec![T::default(); values.len()];
for (row, values) in values.iter_mut().enumerate() { for (row, values) in values.iter_mut().enumerate() {
data[row] = values.remove(0); data[row] = values.remove(0);
} }
@ -766,12 +789,11 @@ fn pop_first_column(values: &mut [Vec<Value>]) -> Vec<Value> {
data data
} }
fn _transpose_table( fn _transpose_table<T>(values: &[Vec<T>], count_rows: usize, count_columns: usize) -> Vec<Vec<T>>
values: &[Vec<Value>], where
count_rows: usize, T: Clone + Default,
count_columns: usize, {
) -> Vec<Vec<Value>> { let mut data = vec![vec![T::default(); count_rows]; count_columns];
let mut data = vec![vec![Value::default(); count_rows]; count_columns];
for (row, values) in values.iter().enumerate() { for (row, values) in values.iter().enumerate() {
for (column, value) in values.iter().enumerate() { for (column, value) in values.iter().enumerate() {
data[column][row].clone_from(value); data[column][row].clone_from(value);

View File

@ -88,6 +88,7 @@ impl StatefulWidget for TableWidget<'_> {
// todo: refactoring these to methods as they have quite a bit in common. // todo: refactoring these to methods as they have quite a bit in common.
impl<'a> TableWidget<'a> { 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) { 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_l = self.config.column_padding_left as u16;
let padding_r = self.config.column_padding_right as u16; let padding_r = self.config.column_padding_right as u16;
@ -130,25 +131,16 @@ impl<'a> TableWidget<'a> {
} }
if show_index { if show_index {
let area = Rect::new(width, data_y, area.width, data_height);
width += render_index( width += render_index(
buf, buf,
area, Rect::new(width, data_y, area.width, data_height),
self.style_computer, self.style_computer,
self.index_row, self.index_row,
padding_l, padding_l,
padding_r, padding_r,
); );
width += render_vertical_line_with_split( width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
buf,
width,
data_y,
data_height,
show_head,
false,
separator_s,
);
} }
// if there is more data than we can show, add an ellipsis to the column headers to hint at that // if there is more data than we can show, add an ellipsis to the column headers to hint at that
@ -162,6 +154,11 @@ impl<'a> TableWidget<'a> {
} }
for col in self.index_column..self.columns.len() { 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 mut column = create_column(data, col);
let column_width = calculate_column_width(&column); let column_width = calculate_column_width(&column);
@ -200,6 +197,7 @@ impl<'a> TableWidget<'a> {
} }
let head_iter = [(&head, head_style)].into_iter(); 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; let mut w = width;
w += render_space(buf, w, head_y, 1, padding_l); w += render_space(buf, w, head_y, 1, padding_l);
w += render_column(buf, w, head_y, use_space, head_iter); w += render_column(buf, w, head_y, use_space, head_iter);
@ -209,10 +207,10 @@ impl<'a> TableWidget<'a> {
state.layout.push(&head, x, head_y, use_space, 1); 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_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); width += render_space(buf, width, data_y, data_height, padding_r);
for (row, (text, _)) in column.iter().enumerate() { for (row, (text, _)) in column.iter().enumerate() {
@ -235,15 +233,7 @@ impl<'a> TableWidget<'a> {
} }
if width < area.width { if width < area.width {
width += render_vertical_line_with_split( width += render_split_line(buf, width, area.y, area.height, show_head, separator_s);
buf,
width,
data_y,
data_height,
show_head,
false,
separator_s,
);
} }
let rest = area.width.saturating_sub(width); let rest = area.width.saturating_sub(width);
@ -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) { fn render_table_vertical(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
if area.width == 0 || area.height == 0 { if area.width == 0 || area.height == 0 {
return; return;
@ -353,6 +344,9 @@ impl<'a> TableWidget<'a> {
state.count_rows = columns.len(); state.count_rows = columns.len();
state.count_columns = 0; 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() { for col in self.index_column..self.data.len() {
let mut column = let mut column =
self.data[col][self.index_row..self.index_row + columns.len()].to_vec(); self.data[col][self.index_row..self.index_row + columns.len()].to_vec();
@ -361,6 +355,13 @@ impl<'a> TableWidget<'a> {
break; 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 column_width = column_width as u16;
let available = area.width - left_w; let available = area.width - left_w;
let is_last = col + 1 == self.data.len(); let is_last = col + 1 == self.data.len();
@ -555,6 +556,51 @@ fn render_index(
width 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( fn render_vertical_line_with_split(
buf: &mut Buffer, buf: &mut Buffer,
x: u16, x: u16,
@ -668,6 +714,12 @@ fn render_bottom_connector(buf: &mut Buffer, x: u16, y: u16, style: NuStyle) {
buf.set_span(x, y, &span, 1); 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 { fn calculate_column_width(column: &[NuText]) -> usize {
column column
.iter() .iter()