diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 6b18e5575..83885163b 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -25,12 +25,12 @@ pub(crate) struct AsyncOpsTableCtx { pub(crate) resource_id: u64, } -impl TableList for AsyncOpsTable { +impl TableList<9> for AsyncOpsTable { type Row = AsyncOp; type Sort = SortBy; type Context = AsyncOpsTableCtx; - const HEADER: &'static [&'static str] = &[ + const HEADER: &'static [&'static str; 9] = &[ "ID", "Parent", "Task", @@ -42,8 +42,20 @@ impl TableList for AsyncOpsTable { "Attributes", ]; + const WIDTHS: &'static [usize; 9] = &[ + Self::HEADER[0].len() + 1, + Self::HEADER[1].len() + 1, + Self::HEADER[2].len() + 1, + Self::HEADER[3].len() + 1, + Self::HEADER[4].len() + 1, + Self::HEADER[5].len() + 1, + Self::HEADER[6].len() + 1, + Self::HEADER[7].len() + 1, + Self::HEADER[8].len() + 1, + ]; + fn render( - table_list_state: &mut TableListState, + table_list_state: &mut TableListState, styles: &view::Styles, frame: &mut tui::terminal::Frame, area: layout::Rect, @@ -86,11 +98,11 @@ impl TableList for AsyncOpsTable { .sort_by .sort(now, &mut table_list_state.sorted_items); - let mut id_width = view::Width::new(Self::HEADER[0].len() as u16); - let mut parent_width = view::Width::new(Self::HEADER[1].len() as u16); - let mut task_width = view::Width::new(Self::HEADER[2].len() as u16); - let mut source_width = view::Width::new(Self::HEADER[3].len() as u16); - let mut polls_width = view::Width::new(Self::HEADER[7].len() as u16); + let mut id_width = view::Width::new(Self::WIDTHS[0] as u16); + let mut parent_width = view::Width::new(Self::WIDTHS[1] as u16); + let mut task_width = view::Width::new(Self::WIDTHS[2] as u16); + let mut source_width = view::Width::new(Self::WIDTHS[3] as u16); + let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16); let dur_cell = |dur: std::time::Duration| -> Cell<'static> { Cell::from(styles.time_units(format!( @@ -153,22 +165,22 @@ impl TableList for AsyncOpsTable { }) }; - let (selected_style, header_style) = if let Some(cyan) = styles.color(Color::Cyan) { - (Style::default().fg(cyan), Style::default()) + let header_style = if styles.color(Color::Cyan).is_some() { + Style::default() } else { - ( - Style::default().remove_modifier(style::Modifier::REVERSED), - Style::default().add_modifier(style::Modifier::REVERSED), - ) + Style::default().add_modifier(style::Modifier::REVERSED) }; let header_style = header_style.add_modifier(style::Modifier::BOLD); let header = Row::new(Self::HEADER.iter().enumerate().map(|(idx, &value)| { - let cell = Cell::from(value); if idx == table_list_state.selected_column { - cell.style(selected_style) + if table_list_state.sort_descending { + Cell::from(styles.ascending(value)) + } else { + Cell::from(styles.descending(value)) + } } else { - cell + Cell::from(value) } })) .height(1) diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index aa82e9e07..e22d239af 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -33,8 +33,8 @@ pub struct View { /// details view), we want to leave the task list's state the way we left it /// --- e.g., if the user previously selected a particular sorting, we want /// it to remain sorted that way when we return to it. - tasks_list: TableListState, - resources_list: TableListState, + tasks_list: TableListState, + resources_list: TableListState, state: ViewState, pub(crate) styles: Styles, } @@ -87,8 +87,8 @@ impl View { pub fn new(styles: Styles) -> Self { Self { state: ViewState::TasksList, - tasks_list: TableListState::::default(), - resources_list: TableListState::::default(), + tasks_list: TableListState::::default(), + resources_list: TableListState::::default(), styles, } } diff --git a/tokio-console/src/view/resource.rs b/tokio-console/src/view/resource.rs index 51c428c60..87129121d 100644 --- a/tokio-console/src/view/resource.rs +++ b/tokio-console/src/view/resource.rs @@ -17,7 +17,7 @@ use tui::{ pub(crate) struct ResourceView { resource: Rc>, - async_ops_table: TableListState, + async_ops_table: TableListState, initial_render: bool, } @@ -25,7 +25,7 @@ impl ResourceView { pub(super) fn new(resource: Rc>) -> Self { ResourceView { resource, - async_ops_table: TableListState::::default(), + async_ops_table: TableListState::::default(), initial_render: true, } } diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index d2340bb17..4b65b00e8 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -20,12 +20,12 @@ use tui::{ #[derive(Debug, Default)] pub(crate) struct ResourcesTable {} -impl TableList for ResourcesTable { +impl TableList<9> for ResourcesTable { type Row = Resource; type Sort = SortBy; type Context = (); - const HEADER: &'static [&'static str] = &[ + const HEADER: &'static [&'static str; 9] = &[ "ID", "Parent", "Kind", @@ -37,8 +37,20 @@ impl TableList for ResourcesTable { "Attributes", ]; + const WIDTHS: &'static [usize; 9] = &[ + Self::HEADER[0].len() + 1, + Self::HEADER[1].len() + 1, + Self::HEADER[2].len() + 1, + Self::HEADER[3].len() + 1, + Self::HEADER[4].len() + 1, + Self::HEADER[5].len() + 1, + Self::HEADER[6].len() + 1, + Self::HEADER[7].len() + 1, + Self::HEADER[8].len() + 1, + ]; + fn render( - table_list_state: &mut TableListState, + table_list_state: &mut TableListState, styles: &view::Styles, frame: &mut tui::terminal::Frame, area: layout::Rect, @@ -59,15 +71,15 @@ impl TableList for ResourcesTable { .sort_by .sort(now, &mut table_list_state.sorted_items); - let viz_len: u16 = Self::HEADER[6].len() as u16; + let viz_len: u16 = Self::WIDTHS[6] as u16; - let mut id_width = view::Width::new(Self::HEADER[0].len() as u16); - let mut parent_width = view::Width::new(Self::HEADER[1].len() as u16); + let mut id_width = view::Width::new(Self::WIDTHS[0] as u16); + let mut parent_width = view::Width::new(Self::WIDTHS[1] as u16); - let mut kind_width = view::Width::new(Self::HEADER[2].len() as u16); - let mut target_width = view::Width::new(Self::HEADER[4].len() as u16); - let mut type_width = view::Width::new(Self::HEADER[5].len() as u16); - let mut location_width = view::Width::new(Self::HEADER[7].len() as u16); + let mut kind_width = view::Width::new(Self::WIDTHS[2] as u16); + let mut target_width = view::Width::new(Self::WIDTHS[4] as u16); + let mut type_width = view::Width::new(Self::WIDTHS[5] as u16); + let mut location_width = view::Width::new(Self::WIDTHS[7] as u16); let rows = { let id_width = &mut id_width; @@ -120,22 +132,22 @@ impl TableList for ResourcesTable { }) }; - let (selected_style, header_style) = if let Some(cyan) = styles.color(Color::Cyan) { - (Style::default().fg(cyan), Style::default()) + let header_style = if styles.color(Color::Cyan).is_some() { + Style::default() } else { - ( - Style::default().remove_modifier(style::Modifier::REVERSED), - Style::default().add_modifier(style::Modifier::REVERSED), - ) + Style::default().add_modifier(style::Modifier::REVERSED) }; let header_style = header_style.add_modifier(style::Modifier::BOLD); let header = Row::new(Self::HEADER.iter().enumerate().map(|(idx, &value)| { - let cell = Cell::from(value); if idx == table_list_state.selected_column { - cell.style(selected_style) + if table_list_state.sort_descending { + Cell::from(styles.ascending(value)) + } else { + Cell::from(styles.descending(value)) + } } else { - cell + Cell::from(value) } })) .height(1) diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index 9d861eb6c..ad2cf4db0 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -127,6 +127,25 @@ impl Styles { ) } + pub fn selected(&self, value: &str) -> Span<'static> { + let style = if let Some(cyan) = self.color(Color::Cyan) { + Style::default().fg(cyan) + } else { + Style::default().remove_modifier(Modifier::REVERSED) + }; + Span::styled(value.to_string(), style) + } + + pub fn ascending(&self, value: &str) -> Span<'static> { + let value = format!("{}{}", value, self.if_utf8("▵", "+")); + self.selected(&value) + } + + pub fn descending(&self, value: &str) -> Span<'static> { + let value = format!("{}{}", value, self.if_utf8("▿", "-")); + self.selected(&value) + } + pub fn color(&self, color: Color) -> Option { use Palette::*; match (self.palette, color) { diff --git a/tokio-console/src/view/table.rs b/tokio-console/src/view/table.rs index fde3b9b42..fd124fb81 100644 --- a/tokio-console/src/view/table.rs +++ b/tokio-console/src/view/table.rs @@ -12,15 +12,16 @@ use tui::{ use std::cell::RefCell; use std::rc::Weak; -pub(crate) trait TableList { +pub(crate) trait TableList { type Row; type Sort: SortBy + TryFrom; type Context; - const HEADER: &'static [&'static str]; + const HEADER: &'static [&'static str; N]; + const WIDTHS: &'static [usize; N]; fn render( - state: &mut TableListState, + state: &mut TableListState, styles: &view::Styles, frame: &mut tui::terminal::Frame, area: layout::Rect, @@ -34,7 +35,7 @@ pub(crate) trait SortBy { fn as_column(&self) -> usize; } -pub(crate) struct TableListState { +pub(crate) struct TableListState, const N: usize> { pub(crate) sorted_items: Vec>>, pub(crate) sort_by: T::Sort, pub(crate) selected_column: usize, @@ -49,7 +50,7 @@ pub(crate) struct Controls { pub(crate) height: u16, } -impl TableListState { +impl, const N: usize> TableListState { pub(in crate::view) fn len(&self) -> usize { self.sorted_items.len() } @@ -181,9 +182,9 @@ impl TableListState { } } -impl Default for TableListState +impl Default for TableListState where - T: TableList, + T: TableList, T::Sort: Default, { fn default() -> Self { diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index 5f149da57..eb6b2365a 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -19,25 +19,39 @@ use tui::{ #[derive(Debug, Default)] pub(crate) struct TasksTable {} -impl TableList for TasksTable { +impl TableList<11> for TasksTable { type Row = Task; type Sort = SortBy; type Context = (); - const HEADER: &'static [&'static str] = &[ + const HEADER: &'static [&'static str; 11] = &[ "Warn", "ID", "State", "Name", "Total", "Busy", "Idle", "Polls", "Target", "Location", "Fields", ]; + const WIDTHS: &'static [usize; 11] = &[ + Self::HEADER[0].len() + 1, + Self::HEADER[1].len() + 1, + Self::HEADER[2].len() + 1, + Self::HEADER[3].len() + 1, + Self::HEADER[4].len() + 1, + Self::HEADER[5].len() + 1, + Self::HEADER[6].len() + 1, + Self::HEADER[7].len() + 1, + Self::HEADER[8].len() + 1, + Self::HEADER[9].len() + 1, + Self::HEADER[10].len() + 1, + ]; + fn render( - table_list_state: &mut TableListState, + table_list_state: &mut TableListState, styles: &view::Styles, frame: &mut tui::terminal::Frame, area: layout::Rect, state: &mut State, _: Self::Context, ) { - let state_len: u16 = Self::HEADER[2].len() as u16; + let state_len: u16 = Self::WIDTHS[2] as u16; let now = if let Some(now) = state.last_updated_at() { now } else { @@ -63,15 +77,16 @@ impl TableList for TasksTable { }; // Start out wide enough to display the column headers... - let mut warn_width = view::Width::new(Self::HEADER[0].len() as u16); - let mut id_width = view::Width::new(Self::HEADER[1].len() as u16); - let mut name_width = view::Width::new(Self::HEADER[3].len() as u16); - let mut polls_width = view::Width::new(Self::HEADER[7].len() as u16); - let mut target_width = view::Width::new(Self::HEADER[8].len() as u16); - let mut location_width = view::Width::new(Self::HEADER[9].len() as u16); + let mut warn_width = view::Width::new(Self::WIDTHS[0] as u16); + let mut id_width = view::Width::new(Self::WIDTHS[1] as u16); + let mut name_width = view::Width::new(Self::WIDTHS[3] as u16); + let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16); + let mut target_width = view::Width::new(Self::WIDTHS[8] as u16); + let mut location_width = view::Width::new(Self::WIDTHS[9] as u16); let mut num_idle = 0; let mut num_running = 0; + let rows = { let id_width = &mut id_width; let target_width = &mut target_width; @@ -138,22 +153,22 @@ impl TableList for TasksTable { }) }; - let (selected_style, header_style) = if let Some(cyan) = styles.color(Color::Cyan) { - (Style::default().fg(cyan), Style::default()) + let header_style = if styles.color(Color::Cyan).is_some() { + Style::default() } else { - ( - Style::default().remove_modifier(style::Modifier::REVERSED), - Style::default().add_modifier(style::Modifier::REVERSED), - ) + Style::default().add_modifier(style::Modifier::REVERSED) }; let header_style = header_style.add_modifier(style::Modifier::BOLD); let header = Row::new(Self::HEADER.iter().enumerate().map(|(idx, &value)| { - let cell = Cell::from(value); if idx == table_list_state.selected_column { - cell.style(selected_style) + if table_list_state.sort_descending { + Cell::from(styles.ascending(value)) + } else { + Cell::from(styles.descending(value)) + } } else { - cell + Cell::from(value) } })) .height(1)