diff --git a/Cargo.toml b/Cargo.toml index 61f68e20..27b01edf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,8 @@ doctest = true doc = true [profile.release] +# debug = true +# strip = false debug = 0 strip = "symbols" lto = true diff --git a/src/app/data_harvester/memory/general/heim.rs b/src/app/data_harvester/memory/general/heim.rs index eb596ea7..bb63d2b5 100644 --- a/src/app/data_harvester/memory/general/heim.rs +++ b/src/app/data_harvester/memory/general/heim.rs @@ -32,6 +32,7 @@ pub async fn get_ram_data() -> crate::utils::error::Result> { let (mem_total_in_kib, mem_used_in_kib) = { #[cfg(target_os = "linux")] { + // TODO: [OPT] is this efficient? use smol::fs::read_to_string; let meminfo = read_to_string("/proc/meminfo").await?; diff --git a/src/canvas/canvas_colours.rs b/src/canvas/canvas_colours.rs index 742bdb28..e7996a32 100644 --- a/src/canvas/canvas_colours.rs +++ b/src/canvas/canvas_colours.rs @@ -73,11 +73,9 @@ impl Default for CanvasColours { Style::default().fg(Color::LightCyan), Style::default().fg(Color::LightGreen), Style::default().fg(Color::LightBlue), - Style::default().fg(Color::LightRed), Style::default().fg(Color::Cyan), Style::default().fg(Color::Green), Style::default().fg(Color::Blue), - Style::default().fg(Color::Red), ], border_style: Style::default().fg(text_colour), highlighted_border_style: Style::default().fg(HIGHLIGHT_COLOUR), diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs index bb996e0d..a9ee8f43 100644 --- a/src/canvas/widgets/cpu_graph.rs +++ b/src/canvas/widgets/cpu_graph.rs @@ -4,6 +4,7 @@ use concat_string::concat_string; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, + symbols::Marker, terminal::Frame, }; @@ -209,8 +210,13 @@ impl Painter { " CPU ".into() }; + let marker = if app_state.app_config_fields.use_dot { + Marker::Dot + } else { + Marker::Braille + }; + TimeGraph { - use_dot: app_state.app_config_fields.use_dot, x_bounds, hide_x_labels, y_bounds: Y_BOUNDS, @@ -221,6 +227,7 @@ impl Painter { is_expanded: app_state.is_expanded, title_style: self.colours.widget_title_style, legend_constraints: None, + marker, } .draw_time_graph(f, draw_loc, &points); } diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs index 8ee5d96f..4ae7c2a9 100644 --- a/src/canvas/widgets/mem_graph.rs +++ b/src/canvas/widgets/mem_graph.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use tui::{ backend::Backend, layout::{Constraint, Rect}, + symbols::Marker, terminal::Frame, }; @@ -104,8 +105,13 @@ impl Painter { points }; + let marker = if app_state.app_config_fields.use_dot { + Marker::Dot + } else { + Marker::Braille + }; + TimeGraph { - use_dot: app_state.app_config_fields.use_dot, x_bounds, hide_x_labels, y_bounds: Y_BOUNDS, @@ -116,6 +122,7 @@ impl Painter { is_expanded: app_state.is_expanded, title_style: self.colours.widget_title_style, legend_constraints: Some((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))), + marker, } .draw_time_graph(f, draw_loc, &points); } diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index de21a6c2..d8c84031 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -1,6 +1,7 @@ use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, + symbols::Marker, terminal::Frame, text::Text, widgets::{Block, Borders, Row, Table}, @@ -142,8 +143,13 @@ impl Painter { ] }; + let marker = if app_state.app_config_fields.use_dot { + Marker::Dot + } else { + Marker::Braille + }; + TimeGraph { - use_dot: app_state.app_config_fields.use_dot, x_bounds, hide_x_labels, y_bounds, @@ -154,6 +160,7 @@ impl Painter { is_expanded: app_state.is_expanded, title_style: self.colours.widget_title_style, legend_constraints: Some(legend_constraints), + marker, } .draw_time_graph(f, draw_loc, &points); } diff --git a/src/components/data_table/draw.rs b/src/components/data_table/draw.rs index fa3958dd..f94e8505 100644 --- a/src/components/data_table/draw.rs +++ b/src/components/data_table/draw.rs @@ -221,7 +221,11 @@ where .iter() .zip(&self.state.calculated_widths) .filter_map(|(column, &width)| { - data_row.to_cell(column.inner(), width) + if width > 0 { + data_row.to_cell(column.inner(), width) + } else { + None + } }), ); diff --git a/src/components/time_graph.rs b/src/components/time_graph.rs index 26ef1d72..7e53a82a 100644 --- a/src/components/time_graph.rs +++ b/src/components/time_graph.rs @@ -22,9 +22,6 @@ pub struct GraphData<'a> { } pub struct TimeGraph<'a> { - /// Whether to use a dot marker over the default braille markers. - pub use_dot: bool, - /// The min and max x boundaries. Expects a f64 representing the time range in milliseconds. pub x_bounds: [u64; 2], @@ -54,6 +51,10 @@ pub struct TimeGraph<'a> { /// Any legend constraints. pub legend_constraints: Option<(Constraint, Constraint)>, + + /// The marker type. Unlike tui-rs' native charts, we assume + /// only a single type of market. + pub marker: Marker, } impl<'a> TimeGraph<'a> { @@ -131,18 +132,7 @@ impl<'a> TimeGraph<'a> { // This is some ugly manual loop unswitching. Maybe unnecessary. // TODO: Optimize this step. Cut out unneeded points. - let data = if self.use_dot { - graph_data - .iter() - .map(|data| create_dataset(data, Marker::Dot)) - .collect() - } else { - graph_data - .iter() - .map(|data| create_dataset(data, Marker::Braille)) - .collect() - }; - + let data = graph_data.iter().map(create_dataset).collect(); let block = Block::default() .title(self.generate_title(draw_loc)) .borders(Borders::ALL) @@ -164,7 +154,7 @@ impl<'a> TimeGraph<'a> { } /// Creates a new [`Dataset`]. -fn create_dataset<'a>(data: &'a GraphData<'a>, marker: Marker) -> Dataset<'a> { +fn create_dataset<'a>(data: &'a GraphData<'a>) -> Dataset<'a> { let GraphData { points, style, @@ -174,8 +164,7 @@ fn create_dataset<'a>(data: &'a GraphData<'a>, marker: Marker) -> Dataset<'a> { let dataset = Dataset::default() .style(*style) .data(points) - .graph_type(GraphType::Line) - .marker(marker); + .graph_type(GraphType::Line); if let Some(name) = name { dataset.name(name.as_ref()) @@ -191,6 +180,7 @@ mod test { use tui::{ layout::Rect, style::{Color, Style}, + symbols::Marker, text::{Span, Spans}, }; @@ -206,7 +196,6 @@ mod test { fn create_time_graph() -> TimeGraph<'static> { TimeGraph { title: " Network ".into(), - use_dot: true, x_bounds: [0, 15000], hide_x_labels: false, y_bounds: [0.0, 100.5], @@ -216,6 +205,7 @@ mod test { is_expanded: false, title_style: Style::default().fg(Color::Cyan), legend_constraints: None, + marker: Marker::Braille, } } diff --git a/src/components/tui_widget/time_chart.rs b/src/components/tui_widget/time_chart.rs index 730c3076..ae1d6642 100644 --- a/src/components/tui_widget/time_chart.rs +++ b/src/components/tui_widget/time_chart.rs @@ -4,7 +4,7 @@ use tui::{ buffer::Buffer, layout::{Constraint, Rect}, style::{Color, Style}, - symbols, + symbols::{self, Marker}, text::{Span, Spans}, widgets::{ canvas::{Canvas, Line, Points}, @@ -75,8 +75,6 @@ pub struct Dataset<'a> { name: Cow<'a, str>, /// A reference to the actual data data: &'a [Point], - /// Symbol used for each points of this dataset - marker: symbols::Marker, /// Determines graph type used for drawing points graph_type: GraphType, /// Style used to plot this dataset @@ -88,7 +86,6 @@ impl<'a> Default for Dataset<'a> { Dataset { name: Cow::from(""), data: &[], - marker: symbols::Marker::Dot, graph_type: GraphType::Scatter, style: Style::default(), } @@ -110,11 +107,6 @@ impl<'a> Dataset<'a> { self } - pub fn marker(mut self, marker: symbols::Marker) -> Dataset<'a> { - self.marker = marker; - self - } - pub fn graph_type(mut self, graph_type: GraphType) -> Dataset<'a> { self.graph_type = graph_type; self @@ -173,10 +165,12 @@ pub struct TimeChart<'a> { legend_style: Style, /// Constraints used to determine whether the legend should be shown or not hidden_legend_constraints: (Constraint, Constraint), + /// The marker type. + marker: Marker, } pub const DEFAULT_LEGEND_CONSTRAINTS: (Constraint, Constraint) = - (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)); + (Constraint::Ratio(1, 4), Constraint::Length(4)); #[allow(dead_code)] impl<'a> TimeChart<'a> { @@ -192,6 +186,7 @@ impl<'a> TimeChart<'a> { legend_style: Default::default(), datasets, hidden_legend_constraints: DEFAULT_LEGEND_CONSTRAINTS, + marker: Marker::Braille, } } @@ -220,6 +215,11 @@ impl<'a> TimeChart<'a> { self } + pub fn marker(mut self, marker: Marker) -> TimeChart<'a> { + self.marker = marker; + self + } + /// Set the constraints used to determine whether the legend should be shown or not. pub fn hidden_legend_constraints( mut self, constraints: (Constraint, Constraint), @@ -422,14 +422,14 @@ impl<'a> Widget for TimeChart<'a> { } } - // Probably better to move the dataset inside. - for dataset in &self.datasets { - Canvas::default() - .background_color(self.style.bg.unwrap_or(Color::Reset)) - .x_bounds(self.x_axis.bounds) - .y_bounds(self.y_axis.bounds) - .marker(dataset.marker) - .paint(|ctx| { + Canvas::default() + .background_color(self.style.bg.unwrap_or(Color::Reset)) + .x_bounds(self.x_axis.bounds) + .y_bounds(self.y_axis.bounds) + .paint(|ctx| { + for dataset in &self.datasets { + let color = dataset.style.fg.unwrap_or(Color::Reset); + let start_bound = self.x_axis.bounds[0]; let end_bound = self.x_axis.bounds[1]; @@ -438,11 +438,6 @@ impl<'a> Widget for TimeChart<'a> { let data_slice = &dataset.data[start_index..end_index]; - ctx.draw(&Points { - coords: data_slice, - color: dataset.style.fg.unwrap_or(Color::Reset), - }); - if let Some(interpolate_start) = interpolate_start { if let (Some(older_point), Some(newer_point)) = ( dataset.data.get(interpolate_start), @@ -453,18 +448,18 @@ impl<'a> Widget for TimeChart<'a> { interpolate_point(older_point, newer_point, self.x_axis.bounds[0]), ); - ctx.draw(&Points { - coords: &[interpolated_point], - color: dataset.style.fg.unwrap_or(Color::Reset), - }); - if let GraphType::Line = dataset.graph_type { ctx.draw(&Line { x1: interpolated_point.0, y1: interpolated_point.1, x2: newer_point.0, y2: newer_point.1, - color: dataset.style.fg.unwrap_or(Color::Reset), + color, + }); + } else { + ctx.draw(&Points { + coords: &[interpolated_point], + color, }); } } @@ -477,9 +472,14 @@ impl<'a> Widget for TimeChart<'a> { y1: data[0].1, x2: data[1].0, y2: data[1].1, - color: dataset.style.fg.unwrap_or(Color::Reset), + color, }); } + } else { + ctx.draw(&Points { + coords: data_slice, + color, + }); } if let Some(interpolate_end) = interpolate_end { @@ -492,25 +492,25 @@ impl<'a> Widget for TimeChart<'a> { interpolate_point(older_point, newer_point, self.x_axis.bounds[1]), ); - ctx.draw(&Points { - coords: &[interpolated_point], - color: dataset.style.fg.unwrap_or(Color::Reset), - }); - if let GraphType::Line = dataset.graph_type { ctx.draw(&Line { x1: older_point.0, y1: older_point.1, x2: interpolated_point.0, y2: interpolated_point.1, - color: dataset.style.fg.unwrap_or(Color::Reset), + color, + }); + } else { + ctx.draw(&Points { + coords: &[interpolated_point], + color, }); } } } - }) - .render(graph_area, buf); - } + } + }) + .render(graph_area, buf); if let Some(legend_area) = layout.legend_area { buf.set_style(legend_area, original_style); diff --git a/src/lib.rs b/src/lib.rs index 4987cb84..1adc59df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -522,6 +522,8 @@ pub fn create_collection_thread( } } } + + // TODO: [OPT] this feels like it might not be totally optimal. Hm. futures::executor::block_on(data_state.update_data()); // Yet another check to bail if needed... diff --git a/src/utils/gen_util.rs b/src/utils/gen_util.rs index ab8c8479..60311105 100644 --- a/src/utils/gen_util.rs +++ b/src/utils/gen_util.rs @@ -1,7 +1,6 @@ use std::cmp::Ordering; -use concat_string::concat_string; -use tui::text::Text; +use tui::text::{Span, Spans, Text}; use unicode_segmentation::UnicodeSegmentation; pub const KILO_LIMIT: u64 = 1000; @@ -99,14 +98,37 @@ pub fn get_decimal_prefix(quantity: u64, unit: &str) -> (f64, String) { /// Truncates text if it is too long, and adds an ellipsis at the end if needed. pub fn truncate_text<'a, U: Into>(content: &str, width: U) -> Text<'a> { let width = width.into(); - let graphemes: Vec<&str> = UnicodeSegmentation::graphemes(content, true).collect(); + let mut graphemes = UnicodeSegmentation::graphemes(content, true); + let grapheme_len = { + let (_, upper) = graphemes.size_hint(); + match upper { + Some(upper) => upper, + None => graphemes.clone().count(), // Don't think this ever fires. + } + }; - if graphemes.len() > width && width > 0 { - // Truncate with ellipsis - let first_n = graphemes[..(width - 1)].concat(); - Text::raw(concat_string!(first_n, "…")) + let text = if grapheme_len > width { + let mut text = String::with_capacity(width); + // Truncate with ellipsis. + + // Use a hack to reduce the size to size `width`. Think of it like removing + // The last `grapheme_len - width` graphemes, which reduces the length to + // `width` long. + // + // This is a way to get around the currently experimental`advance_back_by`. + graphemes.nth_back(grapheme_len - width); + + text.push_str(graphemes.as_str()); + text.push('…'); + + text } else { - Text::raw(content.to_string()) + content.to_string() + }; + + // TODO: [OPT] maybe add interning here? + Text { + lines: vec![Spans(vec![Span::raw(text)])], } } @@ -156,4 +178,9 @@ mod test { y.sort_by(|a, b| sort_partial_fn(true)(a, b)); assert_eq!(y, vec![16.15, 15.0, 1.0, -1.0, -100.0, -100.0, -100.1]); } + + #[test] + fn test_truncation() { + // TODO: Add tests for `truncate_text` + } } diff --git a/src/widgets/process_table/proc_widget_data.rs b/src/widgets/process_table/proc_widget_data.rs index 94736336..c067f92b 100644 --- a/src/widgets/process_table/proc_widget_data.rs +++ b/src/widgets/process_table/proc_widget_data.rs @@ -221,6 +221,7 @@ impl ProcWidgetData { impl DataToCell for ProcWidgetData { fn to_cell<'a>(&'a self, column: &ProcColumn, calculated_width: u16) -> Option> { + // TODO: Optimize the string allocations here... Some(truncate_text( &match column { ProcColumn::CpuPercent => {