Optimize nu-explore (a little bit)

Search is too slow
This commit is contained in:
Maxim Zhiburt 2024-06-10 10:21:45 +03:00
parent c54d223ea0
commit 452a05bd06
7 changed files with 150 additions and 108 deletions

View File

@ -27,7 +27,7 @@ impl NuCmd {
}
impl ViewCommand for NuCmd {
type View = NuView<'static>;
type View = NuView;
fn name(&self) -> &'static str {
Self::NAME
@ -72,12 +72,12 @@ impl ViewCommand for NuCmd {
}
}
pub enum NuView<'a> {
Records(RecordView<'a>),
pub enum NuView {
Records(RecordView),
Preview(Preview),
}
impl View for NuView<'_> {
impl View for NuView {
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
match self {
NuView::Records(v) => v.draw(f, area, cfg, layout),

View File

@ -39,7 +39,7 @@ impl TableCmd {
}
impl ViewCommand for TableCmd {
type View = RecordView<'static>;
type View = RecordView;
fn name(&self) -> &'static str {
Self::NAME

View File

@ -22,7 +22,7 @@ impl TryCmd {
}
impl ViewCommand for TryCmd {
type View = InteractiveView<'static>;
type View = InteractiveView;
fn name(&self) -> &'static str {
Self::NAME

View File

@ -22,18 +22,18 @@ use ratatui::{
};
use std::cmp::min;
pub struct InteractiveView<'a> {
pub struct InteractiveView {
input: Value,
command: String,
immediate: bool,
table: Option<RecordView<'a>>,
table: Option<RecordView>,
table_theme: TableTheme,
view_mode: bool,
border_color: Style,
highlighted_color: Style,
}
impl<'a> InteractiveView<'a> {
impl InteractiveView {
pub fn new(input: Value) -> Self {
Self {
input,
@ -60,7 +60,7 @@ impl<'a> InteractiveView<'a> {
}
}
impl View for InteractiveView<'_> {
impl View for InteractiveView {
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
let border_color = self.border_color;
let highlighted_color = self.highlighted_color;
@ -282,7 +282,7 @@ fn run_command(
input: &Value,
engine_state: &EngineState,
stack: &mut Stack,
) -> Result<RecordView<'static>> {
) -> Result<RecordView> {
let pipeline = run_command_with_value(command, input, engine_state, stack)?;
let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..));

View File

@ -23,25 +23,39 @@ use nu_protocol::{
Record, Value,
};
use ratatui::{layout::Rect, widgets::Block};
use std::{borrow::Cow, collections::HashMap};
use std::collections::HashMap;
pub use self::tablew::Orientation;
#[derive(Debug, Clone)]
pub struct RecordView<'a> {
layer_stack: Vec<RecordLayer<'a>>,
pub struct RecordView {
layer_stack: Vec<LayerData>,
mode: UIMode,
orientation: Orientation,
theme: TableTheme,
}
impl<'a> RecordView<'a> {
pub fn new(
columns: impl Into<Cow<'a, [String]>>,
records: impl Into<Cow<'a, [Vec<Value>]>>,
) -> Self {
#[derive(Debug, Clone)]
struct LayerData {
record: RecordLayer,
widget_data: Option<RecordData>,
}
#[derive(Debug, Clone)]
struct RecordData {
columns: Vec<String>,
records: Vec<Vec<NuText>>,
}
impl RecordView {
pub fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
let layer = LayerData {
record: RecordLayer::new(columns, records),
widget_data: None,
};
Self {
layer_stack: vec![RecordLayer::new(columns, records)],
layer_stack: vec![layer],
mode: UIMode::View,
orientation: Orientation::Top,
theme: TableTheme::default(),
@ -110,13 +124,22 @@ impl<'a> RecordView<'a> {
}
// todo: rename to get_layer
pub fn get_layer_last(&self) -> &RecordLayer<'a> {
pub fn get_layer_last(&self) -> &RecordLayer {
&self.get_layer2().record
}
pub fn get_layer_last_mut(&mut self) -> &mut RecordLayer {
&mut self.get_layer2_mut().record
}
// todo: rename to get_layer
fn get_layer2(&self) -> &LayerData {
self.layer_stack
.last()
.expect("we guarantee that 1 entry is always in a list")
}
pub fn get_layer_last_mut(&mut self) -> &mut RecordLayer<'a> {
fn get_layer2_mut(&mut self) -> &mut LayerData {
self.layer_stack
.last_mut()
.expect("we guarantee that 1 entry is always in a list")
@ -135,7 +158,7 @@ impl<'a> RecordView<'a> {
fn reset_cursors(&mut self) {
for layer in &mut self.layer_stack {
layer.reset_cursor();
layer.record.reset_cursor();
}
}
@ -183,24 +206,41 @@ impl<'a> RecordView<'a> {
layer.records[row][column].clone()
}
fn create_tablew(&'a self, cfg: ViewConfig<'a>) -> TableW<'a> {
let layer = self.get_layer_last();
let mut data = convert_records_to_string(&layer.records, cfg.nu_config, cfg.style_computer);
lscolorize(&layer.columns, &mut data, cfg.lscolors);
let headers = layer.columns.as_ref();
fn create_tablew<'a>(&'a mut self, cfg: ViewConfig<'a>) -> TableW<'a> {
let style = self.theme.table;
let style_computer = cfg.style_computer;
let (row, column) = self.get_current_offset();
let layer = self.get_layer2_mut();
if layer.widget_data.is_none() {
let mut data =
convert_records_to_string(&layer.record.records, cfg.nu_config, cfg.style_computer);
lscolorize(&layer.record.columns, &mut data, cfg.lscolors);
let columns = layer
.record
.columns
.iter()
.map(|s| make_head_text(s))
.collect();
layer.widget_data = Some(RecordData {
records: data,
columns,
})
}
let headers = &layer.widget_data.as_ref().expect("ok").columns;
let data = &layer.widget_data.as_ref().expect("ok").records;
TableW::new(
headers,
data,
style_computer,
row,
column,
self.theme.table,
layer.orientation,
style,
layer.record.orientation,
)
}
@ -231,11 +271,11 @@ impl<'a> RecordView<'a> {
}
}
impl View for RecordView<'_> {
impl View for RecordView {
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
let mut table_layout = TableWState::default();
let table = self.create_tablew(cfg);
f.render_stateful_widget(table, area, &mut table_layout);
let tablew = self.create_tablew(cfg);
f.render_stateful_widget(tablew, area, &mut table_layout);
*layout = table_layout.layout;
@ -376,22 +416,17 @@ enum UIMode {
}
#[derive(Debug, Clone)]
pub struct RecordLayer<'a> {
columns: Cow<'a, [String]>,
records: Cow<'a, [Vec<Value>]>,
pub struct RecordLayer {
columns: Vec<String>,
records: Vec<Vec<Value>>,
orientation: Orientation,
name: Option<String>,
was_transposed: bool,
cursor: XYCursor,
}
impl<'a> RecordLayer<'a> {
fn new(
columns: impl Into<Cow<'a, [String]>>,
records: impl Into<Cow<'a, [Vec<Value>]>>,
) -> Self {
let columns = columns.into();
let records = records.into();
impl RecordLayer {
fn new(columns: Vec<String>, records: Vec<Vec<Value>>) -> Self {
let cursor = XYCursor::new(records.len(), columns.len());
Self {
@ -609,13 +644,13 @@ fn handle_key_event_cursor_mode(
}
}
fn create_layer(value: Value) -> Result<RecordLayer<'static>> {
fn create_layer(value: Value) -> Result<RecordLayer> {
let (columns, values) = collect_input(value)?;
Ok(RecordLayer::new(columns, values))
}
fn push_layer(view: &mut RecordView<'_>, mut next_layer: RecordLayer<'static>) {
fn push_layer(view: &mut RecordView, mut next_layer: RecordLayer) {
let layer = view.get_layer_last();
let header = layer.get_column_header();
@ -623,7 +658,11 @@ fn push_layer(view: &mut RecordView<'_>, mut next_layer: RecordLayer<'static>) {
next_layer.set_name(header);
}
view.layer_stack.push(next_layer);
let layer = LayerData {
record: next_layer,
widget_data: None,
};
view.layer_stack.push(layer);
}
fn estimate_page_size(area: Rect, show_head: bool) -> u16 {
@ -638,7 +677,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) {
fn tail_data(state: &mut RecordView, page_size: usize) {
let layer = state.get_layer_last_mut();
let count_rows = layer.records.len();
if count_rows > page_size {
@ -657,6 +696,7 @@ fn convert_records_to_string(
row.iter()
.map(|value| {
let text = value.clone().to_abbreviated_string(cfg);
let text = strip_string(&text);
let float_precision = cfg.float_precision as usize;
make_styled_string(style_computer, text, Some(value), float_precision)
@ -770,15 +810,12 @@ fn get_percentage(value: usize, max: usize) -> usize {
((value as f32 / max as f32) * 100.0).floor() as usize
}
fn transpose_table(layer: &mut RecordLayer<'_>) {
fn transpose_table(layer: &mut RecordLayer) {
let count_rows = layer.records.len();
let count_columns = layer.columns.len();
if layer.was_transposed {
let data = match &mut layer.records {
Cow::Owned(data) => data,
Cow::Borrowed(_) => unreachable!("must never happen"),
};
let data = &mut layer.records;
let headers = pop_first_column(data);
let headers = headers
@ -791,8 +828,8 @@ fn transpose_table(layer: &mut RecordLayer<'_>) {
let data = _transpose_table(data, count_rows, count_columns - 1);
layer.records = Cow::Owned(data);
layer.columns = Cow::Owned(headers);
layer.records = data;
layer.columns = headers;
} else {
let mut data = _transpose_table(&layer.records, count_rows, count_columns);
@ -802,7 +839,7 @@ fn transpose_table(layer: &mut RecordLayer<'_>) {
data[column].insert(0, value);
}
layer.records = Cow::Owned(data);
layer.records = data;
layer.columns = (1..count_rows + 1 + 1).map(|i| i.to_string()).collect();
}
@ -884,3 +921,13 @@ struct CursorStyle {
selected_column: Option<NuStyle>,
selected_row: Option<NuStyle>,
}
fn make_head_text(head: &str) -> String {
strip_string(head)
}
fn strip_string(text: &str) -> String {
String::from_utf8(strip_ansi_escapes::strip(text))
.map_err(|_| ())
.unwrap_or_else(|_| text.to_owned())
}

View File

@ -12,15 +12,12 @@ use ratatui::{
text::Span,
widgets::{Block, Borders, Paragraph, StatefulWidget, Widget},
};
use std::{
borrow::Cow,
cmp::{max, Ordering},
};
use std::cmp::{max, Ordering};
#[derive(Debug, Clone)]
pub struct TableW<'a> {
columns: Cow<'a, [String]>,
data: Cow<'a, [Vec<NuText>]>,
columns: &'a [String],
data: &'a [Vec<NuText>],
index_row: usize,
index_column: usize,
style: TableStyle,
@ -50,8 +47,8 @@ pub struct TableStyle {
impl<'a> TableW<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
columns: impl Into<Cow<'a, [String]>>,
data: impl Into<Cow<'a, [Vec<NuText>]>>,
columns: &'a [String],
data: &'a [Vec<NuText>],
style_computer: &'a StyleComputer<'a>,
index_row: usize,
index_column: usize,
@ -59,8 +56,8 @@ impl<'a> TableW<'a> {
head_position: Orientation,
) -> Self {
Self {
columns: columns.into(),
data: data.into(),
columns,
data,
style_computer,
index_row,
index_column,
@ -210,22 +207,25 @@ impl<'a> TableW<'a> {
}
if show_head {
let mut header = [head_row_text(&head, self.style_computer)];
let head_style = head_style(&head, self.style_computer);
if head_width > use_space as usize {
truncate_str(&mut header[0].0, use_space as usize)
truncate_str(&mut head, use_space as usize)
}
let head_iter = [(&head, head_style)].into_iter();
let mut w = width;
w += render_space(buf, w, head_y, 1, padding_cell_l);
w += render_column(buf, w, head_y, use_space, &header);
w += render_column(buf, w, head_y, use_space, head_iter);
w += render_space(buf, w, head_y, 1, padding_cell_r);
let x = w - padding_cell_r - use_space;
state.layout.push(&header[0].0, 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));
width += render_space(buf, width, data_y, data_height, padding_cell_l);
width += render_column(buf, width, data_y, use_space, &column);
width += render_column(buf, width, data_y, use_space, head_rows);
width += render_space(buf, width, data_y, data_height, padding_cell_r);
for (row, (text, _)) in column.iter().enumerate() {
@ -321,10 +321,9 @@ impl<'a> TableW<'a> {
return;
}
let columns = columns
let columns_iter = columns
.iter()
.map(|s| head_row_text(s, self.style_computer))
.collect::<Vec<_>>();
.map(|s| (s.clone(), head_style(s, self.style_computer)));
if !show_index {
let x = area.x + left_w;
@ -334,12 +333,12 @@ impl<'a> TableW<'a> {
let x = area.x + left_w;
left_w += render_space(buf, x, area.y, 1, padding_cell_l);
let x = area.x + left_w;
left_w += render_column(buf, x, area.y, columns_width as u16, &columns);
left_w += render_column(buf, x, area.y, columns_width as u16, columns_iter);
let x = area.x + left_w;
left_w += render_space(buf, x, area.y, 1, padding_cell_r);
let layout_x = left_w - padding_cell_r - columns_width as u16;
for (i, (text, _)) in columns.iter().enumerate() {
for (i, text) in columns.iter().enumerate() {
state
.layout
.push(text, layout_x, area.y + i as u16, columns_width as u16, 1);
@ -384,10 +383,12 @@ impl<'a> TableW<'a> {
break;
}
let head_rows = column.iter().map(|(t, s)| (t, *s));
let x = area.x + left_w;
left_w += render_space(buf, x, area.y, area.height, padding_cell_l);
let x = area.x + left_w;
left_w += render_column(buf, x, area.y, column_width, &column);
left_w += render_column(buf, x, area.y, column_width, head_rows);
let x = area.x + left_w;
left_w += render_space(buf, x, area.y, area.height, padding_cell_r);
@ -624,14 +625,14 @@ fn repeat_vertical(
c: char,
style: TextStyle,
) {
let text = std::iter::repeat(c)
.take(width as usize)
.collect::<String>();
let text = String::from(c);
let style = text_style_to_tui_style(style);
let span = Span::styled(text, style);
let span = Span::styled(&text, style);
for row in 0..height {
buf.set_span(x_offset, y_offset + row, &span, width);
for col in 0 .. width {
buf.set_span(x_offset + col, y_offset + row, &span, 1);
}
}
}
@ -680,35 +681,28 @@ fn calculate_column_width(column: &[NuText]) -> usize {
.unwrap_or(0)
}
fn render_column(
fn render_column<T, S>(
buf: &mut ratatui::buffer::Buffer,
x: u16,
y: u16,
available_width: u16,
rows: &[NuText],
) -> u16 {
for (row, (text, style)) in rows.iter().enumerate() {
let style = text_style_to_tui_style(*style);
let text = strip_string(text);
let span = Span::styled(text, style);
rows: impl Iterator<Item = (T, S)>,
) -> u16
where
T: AsRef<str>,
S: Into<TextStyle>,
{
for (row, (text, style)) in rows.enumerate() {
let style = text_style_to_tui_style(style.into());
let span = Span::styled(text.as_ref(), style);
buf.set_span(x, y + row as u16, &span, available_width);
}
available_width
}
fn strip_string(text: &str) -> String {
String::from_utf8(strip_ansi_escapes::strip(text))
.map_err(|_| ())
.unwrap_or_else(|_| text.to_owned())
}
fn head_row_text(head: &str, style_computer: &StyleComputer) -> NuText {
(
String::from(head),
TextStyle::with_style(
Alignment::Center,
style_computer.compute("header", &Value::string(head, nu_protocol::Span::unknown())),
),
)
fn head_style(head: &str, style_computer: &StyleComputer) -> TextStyle {
let style =
style_computer.compute("header", &Value::string(head, nu_protocol::Span::unknown()));
TextStyle::with_style(Alignment::Center, style)
}

View File

@ -259,9 +259,7 @@ fn draw_table(
table.with(width_ctrl);
}
let tablewidth = table.total_width();
table_to_string(table, tablewidth, termwidth)
table_to_string(table, termwidth)
}
fn set_indent(table: &mut Table, left: usize, right: usize) {
@ -289,7 +287,9 @@ fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl)
}
}
fn table_to_string(table: Table, total_width: usize, termwidth: usize) -> Option<String> {
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
let total_width = table.total_width();
if total_width > termwidth {
None
} else {
@ -318,6 +318,7 @@ impl TableOption<NuRecords, CompleteDimensionVecRecords<'_>, ColoredConfig> for
dim: &mut CompleteDimensionVecRecords<'_>,
) {
let total_width = get_total_width2(&self.width, cfg);
if total_width > self.max {
TableTrim {
max: self.max,