From 67d14fbc4293d969c78583af2503c0ad8f5cc252 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 25 May 2023 15:21:12 +0200 Subject: [PATCH 1/6] refac(console): generalize controls widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each view in tokio-console has a widget up the top that lists the available controls for that view. There was a common implementation of this for table based views (tasks, resources, and async_ops) and separate implementations for the task and resource views. The resource view included two controls widgets, one for the Resource details at the top of the view, and another above the table of Async Ops at the bottom of the view. This change centralises the logic for the creation of this controls widget. This change is mostly a precursor to also displaying the controls in the help view (at which point we can revisit whether the entire list needs to be shown at the top of the screen). Controls (an action and the key or keys used to invoke it) are defined in structs so that their definition can be separated from the display logic (which includes whether or not UTF-8 is supported). This allows the problem of the text in the controls widget wrapping in the middle of a control definition to be fixed. Previously a subset of the controls would have wrapped like this: ```text controls: select column (sort) = ←→ or h, l, scroll = ↑↓ or k, j, view details = ↵, invert sort (highest/lowest) = i, ``` Notice how "view details = ↵," was split across multiple lines. The same list of controls will now wrap at a full control definition. ```text controls: select column (sort) = ←→ or h, l, scroll = ↑↓ or k, j, view details = ↵, invert sort (highest/lowest) = i, ``` Additionally, the list of controls on the Resource view has been consolidated up the top of the screen. Universal controls, those that are available in all views, are also defined centrally. As well as the quit action, using the space bar to pause has been added to that list. This was previously somewhat of an undocumented feature. --- tokio-console/src/view/async_ops.rs | 24 +---- tokio-console/src/view/controls.rs | 141 ++++++++++++++++++++++++++++ tokio-console/src/view/mod.rs | 1 + tokio-console/src/view/resource.rs | 39 +++++--- tokio-console/src/view/resources.rs | 9 +- tokio-console/src/view/table.rs | 109 ++++++++++----------- tokio-console/src/view/task.rs | 39 +++++--- tokio-console/src/view/tasks.rs | 11 ++- 8 files changed, 257 insertions(+), 116 deletions(-) create mode 100644 tokio-console/src/view/controls.rs diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 0cdf02162..efa893044 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -1,3 +1,4 @@ +pub(crate) use crate::view::table::view_controls; use crate::{ state::{ async_ops::{AsyncOp, SortBy}, @@ -6,7 +7,7 @@ use crate::{ }, view::{ self, bold, - table::{self, TableList, TableListState}, + table::{TableList, TableListState}, DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -193,24 +194,6 @@ impl TableList<9> for AsyncOpsTable { table_list_state.len() ))]); - let layout = layout::Layout::default() - .direction(layout::Direction::Vertical) - .margin(0); - - let controls = table::Controls::for_area(&area, styles); - let chunks = layout - .constraints( - [ - layout::Constraint::Length(controls.height), - layout::Constraint::Max(area.height), - ] - .as_ref(), - ) - .split(area); - - let controls_area = chunks[0]; - let async_ops_area = chunks[1]; - let attributes_width = layout::Constraint::Percentage(100); let widths = &[ id_width.constraint(), @@ -231,8 +214,7 @@ impl TableList<9> for AsyncOpsTable { .highlight_symbol(view::TABLE_HIGHLIGHT_SYMBOL) .highlight_style(Style::default().add_modifier(style::Modifier::BOLD)); - frame.render_stateful_widget(table, async_ops_area, &mut table_list_state.table_state); - frame.render_widget(controls.paragraph, controls_area); + frame.render_stateful_widget(table, area, &mut table_list_state.table_state); table_list_state .sorted_items diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs new file mode 100644 index 000000000..3ecb6fe36 --- /dev/null +++ b/tokio-console/src/view/controls.rs @@ -0,0 +1,141 @@ +use crate::view::{self, bold}; + +use once_cell::sync::OnceCell; +use tui::{ + layout, + text::{Span, Spans, Text}, + widgets::{Paragraph, Widget}, +}; + +/// Construct a widget to display the controls available to the user in the +/// current view. +pub(crate) struct Controls { + paragraph: Paragraph<'static>, + height: u16, +} + +impl Controls { + pub(in crate::view) fn new( + view_controls: &Vec, + area: &layout::Rect, + styles: &view::Styles, + ) -> Self { + let universal_controls = universal_controls(); + + let mut spans_controls = Vec::with_capacity(view_controls.len() + universal_controls.len()); + spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles))); + spans_controls.extend(universal_controls.iter().map(|c| c.to_spans(styles))); + + let mut lines = vec![Spans::from(vec![Span::from("controls: ")])]; + let mut current_line = lines.last_mut().expect("This vector is never empty"); + let separator = Span::from(", "); + + let controls_count: usize = spans_controls.len(); + for (idx, spans) in spans_controls.into_iter().enumerate() { + if idx == 0 || current_line.width() == 0 { + current_line.0.extend(spans.0); + } else { + let needed_trailing_separator_width = if idx == controls_count + 1 { + separator.width() + } else { + 0 + }; + + if current_line.width() + + separator.width() + + spans.width() + + needed_trailing_separator_width + <= area.width as usize + { + current_line.0.push(separator.clone()); + current_line.0.extend(spans.0); + } else { + current_line.0.push(separator.clone()); + lines.push(spans); + current_line = lines.last_mut().expect("This vector is never empty"); + } + } + } + + let height = lines.len() as u16; + let text = Text::from(lines); + + Self { + paragraph: Paragraph::new(text), + height, + } + } + + pub(crate) fn height(&self) -> u16 { + self.height + } + + pub(crate) fn into_widget(self) -> impl Widget { + self.paragraph + } +} + +/// Construct span to display a control. +/// +/// A control is made up of an action and one or more keys that will trigger +/// that action. +#[derive(Clone)] +pub(crate) struct ControlDisplay { + pub(crate) action: &'static str, + pub(crate) keys: Vec, +} + +/// A key or keys which will be displayed to the user as part of spans +/// constructed by `ControlDisplay`. +/// +/// The `base` description of the key should be ASCII only, more advanced +/// descriptions can be supplied for that key in the `utf8` field. This +/// allows the application to pick the best one to display at runtime +/// based on the termainal being used. +#[derive(Clone)] +pub(crate) struct KeyDisplay { + pub(crate) base: &'static str, + pub(crate) utf8: Option<&'static str>, +} + +impl ControlDisplay { + pub(crate) fn new_simple(action: &'static str, key: &'static str) -> Self { + ControlDisplay { + action, + keys: vec![KeyDisplay { + base: key, + utf8: None, + }], + } + } + + pub fn to_spans(&self, styles: &view::Styles) -> Spans<'static> { + let mut spans = Vec::new(); + + spans.push(Span::from(self.action)); + spans.push(Span::from(" = ")); + for (idx, key_display) in self.keys.iter().enumerate() { + if idx > 0 { + spans.push(Span::from(" or ")) + } + spans.push(bold(match key_display.utf8 { + Some(utf8) => styles.if_utf8(utf8, key_display.base), + None => key_display.base, + })); + } + + Spans::from(spans) + } +} + +/// Returns a list of controls which are available in all views. +pub(crate) fn universal_controls() -> &'static Vec { + static UNIVERSAL_CONTROLS: OnceCell> = OnceCell::new(); + + UNIVERSAL_CONTROLS.get_or_init(|| { + vec![ + ControlDisplay::new_simple("toggle pause", "space"), + ControlDisplay::new_simple("quit", "q"), + ] + }) +} diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index 160419adb..198d316f9 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -8,6 +8,7 @@ use tui::{ }; mod async_ops; +mod controls; mod durations; mod mini_histogram; mod percentiles; diff --git a/tokio-console/src/view/resource.rs b/tokio-console/src/view/resource.rs index 87129121d..35158ada4 100644 --- a/tokio-console/src/view/resource.rs +++ b/tokio-console/src/view/resource.rs @@ -4,15 +4,18 @@ use crate::{ state::State, view::{ self, - async_ops::{AsyncOpsTable, AsyncOpsTableCtx}, - bold, TableListState, + async_ops::{self, AsyncOpsTable, AsyncOpsTableCtx}, + bold, + controls::{ControlDisplay, Controls, KeyDisplay}, + TableListState, }, }; +use once_cell::sync::OnceCell; use std::{cell::RefCell, rc::Rc}; use tui::{ layout::{self, Layout}, text::{Span, Spans, Text}, - widgets::{Block, Paragraph}, + widgets::Paragraph, }; pub(crate) struct ResourceView { @@ -42,6 +45,7 @@ impl ResourceView { state: &mut State, ) { let resource = &*self.resource.borrow(); + let controls = Controls::new(view_controls(), &area, styles); let (controls_area, stats_area, async_ops_area) = { let chunks = Layout::default() @@ -49,7 +53,7 @@ impl ResourceView { .constraints( [ // controls - layout::Constraint::Length(1), + layout::Constraint::Length(controls.height()), // resource stats layout::Constraint::Length(8), // async ops @@ -72,14 +76,6 @@ impl ResourceView { ) .split(stats_area); - let controls = Spans::from(vec![ - Span::raw("controls: "), - bold(styles.if_utf8("\u{238B} esc", "esc")), - Span::raw(" = return to task list, "), - bold("q"), - Span::raw(" = quit"), - ]); - let overview = vec![ Spans::from(vec![bold("ID: "), Span::raw(resource.id_str())]), Spans::from(vec![bold("Parent ID: "), Span::raw(resource.parent())]), @@ -107,7 +103,7 @@ impl ResourceView { Paragraph::new(overview).block(styles.border_block().title("Resource")); let fields_widget = Paragraph::new(fields).block(styles.border_block().title("Attributes")); - frame.render_widget(Block::default().title(controls), controls_area); + frame.render_widget(controls.into_widget(), controls_area); frame.render_widget(resource_widget, stats_area[0]); frame.render_widget(fields_widget, stats_area[1]); let ctx = AsyncOpsTableCtx { @@ -119,3 +115,20 @@ impl ResourceView { self.initial_render = false; } } + +fn view_controls() -> &'static Vec { + static VIEW_CONTROLS: OnceCell> = OnceCell::new(); + + VIEW_CONTROLS.get_or_init(|| { + let mut resource_controls = vec![ControlDisplay { + action: "return to task list", + keys: vec![KeyDisplay { + base: "esc", + utf8: Some("\u{238B} esc"), + }], + }]; + resource_controls.extend(async_ops::view_controls().to_owned()); + + resource_controls + }) +} diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index afe9fc1f6..b5f7e91c9 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -5,7 +5,8 @@ use crate::{ }, view::{ self, bold, - table::{self, TableList, TableListState}, + controls::Controls, + table::{view_controls, TableList, TableListState}, DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -163,7 +164,7 @@ impl TableList<9> for ResourcesTable { table_list_state.len() ))]); - let controls = table::Controls::for_area(&area, styles); + let controls = Controls::new(view_controls(), &area, styles); let layout = layout::Layout::default() .direction(layout::Direction::Vertical) @@ -172,7 +173,7 @@ impl TableList<9> for ResourcesTable { let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Max(area.height), ] .as_ref(), @@ -202,7 +203,7 @@ impl TableList<9> for ResourcesTable { .highlight_style(Style::default().add_modifier(style::Modifier::BOLD)); frame.render_stateful_widget(table, tasks_area, &mut table_list_state.table_state); - frame.render_widget(controls.paragraph, controls_area); + frame.render_widget(controls.into_widget(), controls_area); table_list_state .sorted_items diff --git a/tokio-console/src/view/table.rs b/tokio-console/src/view/table.rs index fd124fb81..2ac3bce43 100644 --- a/tokio-console/src/view/table.rs +++ b/tokio-console/src/view/table.rs @@ -1,13 +1,12 @@ use crate::{ input, state, - view::{self, bold}, -}; -use std::convert::TryFrom; -use tui::{ - layout, - text::{self, Span, Spans, Text}, - widgets::{Paragraph, TableState, Wrap}, + view::{ + self, + controls::{ControlDisplay, KeyDisplay}, + }, }; +use once_cell::sync::OnceCell; +use tui::{layout, widgets::TableState}; use std::cell::RefCell; use std::rc::Weak; @@ -45,11 +44,6 @@ pub(crate) struct TableListState, const N: usize> { last_key_event: Option, } -pub(crate) struct Controls { - pub(crate) paragraph: Paragraph<'static>, - pub(crate) height: u16, -} - impl, const N: usize> TableListState { pub(in crate::view) fn len(&self) -> usize { self.sorted_items.len() @@ -201,52 +195,47 @@ where } } -impl Controls { - pub(in crate::view) fn for_area(area: &layout::Rect, styles: &view::Styles) -> Self { - let text = Text::from(Spans::from(vec![ - Span::raw("controls: "), - bold(styles.if_utf8("\u{2190}\u{2192}", "left, right")), - Span::raw(" or "), - bold("h, l"), - text::Span::raw(" = select column (sort), "), - bold(styles.if_utf8("\u{2191}\u{2193}", "up, down")), - Span::raw(" or "), - bold("k, j"), - text::Span::raw(" = scroll, "), - bold(styles.if_utf8("\u{21B5}", "enter")), - text::Span::raw(" = view details, "), - bold("i"), - text::Span::raw(" = invert sort (highest/lowest), "), - bold("q"), - text::Span::raw(" = quit "), - bold("gg"), - text::Span::raw(" = scroll to top, "), - bold("G"), - text::Span::raw(" = scroll to bottom"), - ])); - - // how many lines do we need to display the controls? - let mut height = 1; - - // if the area is narrower than the width of the controls text, we need - // to wrap the text across multiple lines. - let width = text.width() as u16; - if area.width < width { - height = width / area.width; - - // if the text's width is not neatly divisible by the area's width - // (and it almost never will be), round up for the remaining text. - if width % area.width > 0 { - height += 1 - }; - } - - Self { - // TODO(eliza): it would be nice if we could wrap this on commas, - // specifically, rather than whitespace...but that seems like a - // bunch of additional work... - paragraph: Paragraph::new(text).wrap(Wrap { trim: true }), - height, - } - } +pub(crate) fn view_controls() -> &'static Vec { + static VIEW_CONTROLS: OnceCell> = OnceCell::new(); + + VIEW_CONTROLS.get_or_init(|| { + vec![ + ControlDisplay { + action: "select column (sort)", + keys: vec![ + KeyDisplay { + base: "left, right", + utf8: Some("\u{2190}\u{2192}"), + }, + KeyDisplay { + base: "h, l", + utf8: None, + }, + ], + }, + ControlDisplay { + action: "scroll", + keys: vec![ + KeyDisplay { + base: "up, down", + utf8: Some("\u{2191}\u{2193}"), + }, + KeyDisplay { + base: "k, j", + utf8: None, + }, + ], + }, + ControlDisplay { + action: "view details", + keys: vec![KeyDisplay { + base: "enter", + utf8: Some("\u{21B5}"), + }], + }, + ControlDisplay::new_simple("invert sort (highest/lowest)", "i"), + ControlDisplay::new_simple("scroll to top", "gg"), + ControlDisplay::new_simple("scroll to bottom", "G"), + ] + }) } diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index 2d6d1d982..c41fe93c0 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -2,8 +2,13 @@ use crate::{ input, state::{tasks::Task, DetailsRef}, util::Percentage, - view::{self, bold, durations::Durations}, + view::{ + self, bold, + controls::{ControlDisplay, Controls, KeyDisplay}, + durations::Durations, + }, }; +use once_cell::sync::OnceCell; use std::{ cell::RefCell, cmp, @@ -13,7 +18,7 @@ use std::{ use tui::{ layout::{self, Layout}, text::{Span, Spans, Text}, - widgets::{Block, List, ListItem, Paragraph}, + widgets::{List, ListItem, Paragraph}, }; pub(crate) struct TaskView { @@ -49,6 +54,8 @@ impl TaskView { .as_ref() .filter(|details| details.span_id() == task.span_id()); + let controls = Controls::new(view_controls(), &area, styles); + let warnings: Vec<_> = task .warnings() .iter() @@ -74,7 +81,7 @@ impl TaskView { .constraints( [ // controls - layout::Constraint::Length(1), + layout::Constraint::Length(controls.height()), // task stats layout::Constraint::Length(10), // poll duration @@ -94,7 +101,7 @@ impl TaskView { .constraints( [ // controls - layout::Constraint::Length(1), + layout::Constraint::Length(controls.height()), // warnings (add 2 for top and bottom borders) layout::Constraint::Length(warnings.len() as u16 + 2), // task stats @@ -131,14 +138,6 @@ impl TaskView { ) .split(stats_area); - let controls = Spans::from(vec![ - Span::raw("controls: "), - bold(styles.if_utf8("\u{238B} esc", "esc")), - Span::raw(" = return to task list, "), - bold("q"), - Span::raw(" = quit"), - ]); - // Just preallocate capacity for ID, name, target, total, busy, and idle. let mut overview = Vec::with_capacity(8); overview.push(Spans::from(vec![ @@ -246,7 +245,7 @@ impl TaskView { let fields_widget = Paragraph::new(fields).block(styles.border_block().title("Fields")); - frame.render_widget(Block::default().title(controls), controls_area); + frame.render_widget(controls.into_widget(), controls_area); frame.render_widget(task_widget, stats_area[0]); frame.render_widget(wakers_widget, stats_area[1]); frame.render_widget(poll_durations_widget, poll_dur_area); @@ -254,3 +253,17 @@ impl TaskView { frame.render_widget(fields_widget, fields_area); } } + +fn view_controls() -> &'static Vec { + static VIEW_CONTROLS: OnceCell> = OnceCell::new(); + + VIEW_CONTROLS.get_or_init(|| { + vec![ControlDisplay { + action: "return to task list", + keys: vec![KeyDisplay { + base: "esc", + utf8: Some("\u{238B} esc"), + }], + }] + }) +} diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index b537e08e1..c9b55cf31 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -5,7 +5,8 @@ use crate::{ }, view::{ self, bold, - table::{self, TableList, TableListState}, + controls::Controls, + table::{view_controls, TableList, TableListState}, DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -212,13 +213,13 @@ impl TableList<12> for TasksTable { .direction(layout::Direction::Vertical) .margin(0); - let controls = table::Controls::for_area(&area, styles); + let controls = Controls::new(view_controls(), &area, styles); let (controls_area, tasks_area, warnings_area) = if warnings.is_empty() { let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Max(area.height), ] .as_ref(), @@ -230,7 +231,7 @@ impl TableList<12> for TasksTable { let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Length(warnings_height), layout::Constraint::Max(area.height), ] @@ -269,7 +270,7 @@ impl TableList<12> for TasksTable { .highlight_style(Style::default().add_modifier(style::Modifier::BOLD)); frame.render_stateful_widget(table, tasks_area, &mut table_list_state.table_state); - frame.render_widget(controls.paragraph, controls_area); + frame.render_widget(controls.into_widget(), controls_area); if let Some(area) = warnings_area { let block = styles From d4221143e4108b7d73e73c49e1ff018ba32186f7 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 25 May 2023 22:26:14 +0200 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Eliza Weisman --- tokio-console/src/view/controls.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs index 3ecb6fe36..7163e9c56 100644 --- a/tokio-console/src/view/controls.rs +++ b/tokio-console/src/view/controls.rs @@ -41,12 +41,12 @@ impl Controls { 0 }; - if current_line.width() + let total_width = current_line.width() + separator.width() + spans.width() - + needed_trailing_separator_width - <= area.width as usize - { + + needed_trailing_separator_width; + + if total_width <= area.width as usize { current_line.0.push(separator.clone()); current_line.0.extend(spans.0); } else { @@ -82,7 +82,7 @@ impl Controls { #[derive(Clone)] pub(crate) struct ControlDisplay { pub(crate) action: &'static str, - pub(crate) keys: Vec, + pub(crate) keys: &'static [KeyDisplay], } /// A key or keys which will be displayed to the user as part of spans From d51d5b0d1f1faf90138b263d3ae0d38389a2ed2d Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Fri, 26 May 2023 00:14:27 +0200 Subject: [PATCH 3/6] ControlDisplay keys Vec to array Make a bunch of things around ControlDisplay and KeyDisplay by using array slices instead of vectors. --- tokio-console/src/view/controls.rs | 90 +++++++++++++------------ tokio-console/src/view/resource.rs | 10 ++- tokio-console/src/view/table.rs | 101 ++++++++++++++++------------- tokio-console/src/view/task.rs | 21 +++--- 4 files changed, 118 insertions(+), 104 deletions(-) diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs index 7163e9c56..95473fa9f 100644 --- a/tokio-console/src/view/controls.rs +++ b/tokio-console/src/view/controls.rs @@ -1,6 +1,5 @@ use crate::view::{self, bold}; -use once_cell::sync::OnceCell; use tui::{ layout, text::{Span, Spans, Text}, @@ -16,7 +15,7 @@ pub(crate) struct Controls { impl Controls { pub(in crate::view) fn new( - view_controls: &Vec, + view_controls: &'static [ControlDisplay], area: &layout::Rect, styles: &view::Styles, ) -> Self { @@ -32,28 +31,39 @@ impl Controls { let controls_count: usize = spans_controls.len(); for (idx, spans) in spans_controls.into_iter().enumerate() { + // If this is the first item on this line - or first item on the + // first line, then always include it - even if it goes beyond the + // line width, not much we can do anyway. if idx == 0 || current_line.width() == 0 { current_line.0.extend(spans.0); + continue; + } + + // Include the width of our separator in the current item if we + // aren't placing the last item. This is the separator after the + // new element. + let needed_trailing_separator_width = if idx == controls_count + 1 { + separator.width() + } else { + 0 + }; + + let total_width = current_line.width() + + separator.width() + + spans.width() + + needed_trailing_separator_width; + + // If the current item fits on this line, append it. + // Otherwise, append only the separator - we accounted for its + // width in the previous loop iteration - and then create a new + // line for the current item. + if total_width <= area.width as usize { + current_line.0.push(separator.clone()); + current_line.0.extend(spans.0); } else { - let needed_trailing_separator_width = if idx == controls_count + 1 { - separator.width() - } else { - 0 - }; - - let total_width = current_line.width() - + separator.width() - + spans.width() - + needed_trailing_separator_width; - - if total_width <= area.width as usize { - current_line.0.push(separator.clone()); - current_line.0.extend(spans.0); - } else { - current_line.0.push(separator.clone()); - lines.push(spans); - current_line = lines.last_mut().expect("This vector is never empty"); - } + current_line.0.push(separator.clone()); + lines.push(spans); + current_line = lines.last_mut().expect("This vector is never empty"); } } @@ -99,17 +109,7 @@ pub(crate) struct KeyDisplay { } impl ControlDisplay { - pub(crate) fn new_simple(action: &'static str, key: &'static str) -> Self { - ControlDisplay { - action, - keys: vec![KeyDisplay { - base: key, - utf8: None, - }], - } - } - - pub fn to_spans(&self, styles: &view::Styles) -> Spans<'static> { + pub(crate) fn to_spans(&self, styles: &view::Styles) -> Spans<'static> { let mut spans = Vec::new(); spans.push(Span::from(self.action)); @@ -129,13 +129,21 @@ impl ControlDisplay { } /// Returns a list of controls which are available in all views. -pub(crate) fn universal_controls() -> &'static Vec { - static UNIVERSAL_CONTROLS: OnceCell> = OnceCell::new(); - - UNIVERSAL_CONTROLS.get_or_init(|| { - vec![ - ControlDisplay::new_simple("toggle pause", "space"), - ControlDisplay::new_simple("quit", "q"), - ] - }) +const fn universal_controls() -> &'static [ControlDisplay] { + &[ + ControlDisplay { + action: "toggle pause", + keys: &[KeyDisplay { + base: "space", + utf8: None, + }], + }, + ControlDisplay { + action: "quit", + keys: &[KeyDisplay { + base: "q", + utf8: None, + }], + }, + ] } diff --git a/tokio-console/src/view/resource.rs b/tokio-console/src/view/resource.rs index 35158ada4..ee5f401c1 100644 --- a/tokio-console/src/view/resource.rs +++ b/tokio-console/src/view/resource.rs @@ -116,19 +116,17 @@ impl ResourceView { } } -fn view_controls() -> &'static Vec { +fn view_controls() -> &'static [ControlDisplay] { static VIEW_CONTROLS: OnceCell> = OnceCell::new(); VIEW_CONTROLS.get_or_init(|| { - let mut resource_controls = vec![ControlDisplay { + let resource_controls = &[ControlDisplay { action: "return to task list", - keys: vec![KeyDisplay { + keys: &[KeyDisplay { base: "esc", utf8: Some("\u{238B} esc"), }], }]; - resource_controls.extend(async_ops::view_controls().to_owned()); - - resource_controls + [resource_controls, async_ops::view_controls()].concat() }) } diff --git a/tokio-console/src/view/table.rs b/tokio-console/src/view/table.rs index 2ac3bce43..31785d2e1 100644 --- a/tokio-console/src/view/table.rs +++ b/tokio-console/src/view/table.rs @@ -5,7 +5,6 @@ use crate::{ controls::{ControlDisplay, KeyDisplay}, }, }; -use once_cell::sync::OnceCell; use tui::{layout, widgets::TableState}; use std::cell::RefCell; @@ -195,47 +194,61 @@ where } } -pub(crate) fn view_controls() -> &'static Vec { - static VIEW_CONTROLS: OnceCell> = OnceCell::new(); - - VIEW_CONTROLS.get_or_init(|| { - vec![ - ControlDisplay { - action: "select column (sort)", - keys: vec![ - KeyDisplay { - base: "left, right", - utf8: Some("\u{2190}\u{2192}"), - }, - KeyDisplay { - base: "h, l", - utf8: None, - }, - ], - }, - ControlDisplay { - action: "scroll", - keys: vec![ - KeyDisplay { - base: "up, down", - utf8: Some("\u{2191}\u{2193}"), - }, - KeyDisplay { - base: "k, j", - utf8: None, - }, - ], - }, - ControlDisplay { - action: "view details", - keys: vec![KeyDisplay { - base: "enter", - utf8: Some("\u{21B5}"), - }], - }, - ControlDisplay::new_simple("invert sort (highest/lowest)", "i"), - ControlDisplay::new_simple("scroll to top", "gg"), - ControlDisplay::new_simple("scroll to bottom", "G"), - ] - }) +pub(crate) const fn view_controls() -> &'static [ControlDisplay] { + &[ + ControlDisplay { + action: "select column (sort)", + keys: &[ + KeyDisplay { + base: "left, right", + utf8: Some("\u{2190}\u{2192}"), + }, + KeyDisplay { + base: "h, l", + utf8: None, + }, + ], + }, + ControlDisplay { + action: "scroll", + keys: &[ + KeyDisplay { + base: "up, down", + utf8: Some("\u{2191}\u{2193}"), + }, + KeyDisplay { + base: "k, j", + utf8: None, + }, + ], + }, + ControlDisplay { + action: "view details", + keys: &[KeyDisplay { + base: "enter", + utf8: Some("\u{21B5}"), + }], + }, + ControlDisplay { + action: "invert sort (highest/lowest)", + keys: &[KeyDisplay { + base: "i", + utf8: None, + }], + }, + ControlDisplay { + action: "scroll to top", + keys: &[KeyDisplay { + base: "gg", + utf8: None, + }], + }, + ControlDisplay { + action: "scroll to bottom", + keys: &[KeyDisplay { + base: "G", + utf8: None, + }], + }, + ] } diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index c41fe93c0..af8d12e4b 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -8,7 +8,6 @@ use crate::{ durations::Durations, }, }; -use once_cell::sync::OnceCell; use std::{ cell::RefCell, cmp, @@ -254,16 +253,12 @@ impl TaskView { } } -fn view_controls() -> &'static Vec { - static VIEW_CONTROLS: OnceCell> = OnceCell::new(); - - VIEW_CONTROLS.get_or_init(|| { - vec![ControlDisplay { - action: "return to task list", - keys: vec![KeyDisplay { - base: "esc", - utf8: Some("\u{238B} esc"), - }], - }] - }) +const fn view_controls() -> &'static [ControlDisplay] { + &[ControlDisplay { + action: "return to task list", + keys: &[KeyDisplay { + base: "esc", + utf8: Some("\u{238B} esc"), + }], + }] } From 43f396659ad7ef245057805c9c7124eb8d5bc583 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Wed, 31 May 2023 23:25:51 +0200 Subject: [PATCH 4/6] changed universal_controls to a const UNIVERSAL_CONTROLS --- tokio-console/src/view/controls.rs | 40 ++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs index 31483da09..bd21fc406 100644 --- a/tokio-console/src/view/controls.rs +++ b/tokio-console/src/view/controls.rs @@ -6,6 +6,24 @@ use ratatui::{ widgets::{Paragraph, Widget}, }; +/// Returns a list of controls which are available in all views. +const UNIVERSAL_CONTROLS: &[ControlDisplay] = &[ + ControlDisplay { + action: "toggle pause", + keys: &[KeyDisplay { + base: "space", + utf8: None, + }], + }, + ControlDisplay { + action: "quit", + keys: &[KeyDisplay { + base: "q", + utf8: None, + }], + }, +]; + /// Construct a widget to display the controls available to the user in the /// current view. pub(crate) struct Controls { @@ -19,7 +37,7 @@ impl Controls { area: &layout::Rect, styles: &view::Styles, ) -> Self { - let universal_controls = universal_controls(); + let universal_controls = UNIVERSAL_CONTROLS; let mut spans_controls = Vec::with_capacity(view_controls.len() + universal_controls.len()); spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles))); @@ -127,23 +145,3 @@ impl ControlDisplay { Spans::from(spans) } } - -/// Returns a list of controls which are available in all views. -const fn universal_controls() -> &'static [ControlDisplay] { - &[ - ControlDisplay { - action: "toggle pause", - keys: &[KeyDisplay { - base: "space", - utf8: None, - }], - }, - ControlDisplay { - action: "quit", - keys: &[KeyDisplay { - base: "q", - utf8: None, - }], - }, - ] -} From c2b9603e32c577913eacdef5e8e0d5a62fa56308 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 1 Jun 2023 09:35:10 +0200 Subject: [PATCH 5/6] inline UNIVERSAL_CONTROLS where possible --- tokio-console/src/view/controls.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs index bd21fc406..8fcdc2008 100644 --- a/tokio-console/src/view/controls.rs +++ b/tokio-console/src/view/controls.rs @@ -37,11 +37,9 @@ impl Controls { area: &layout::Rect, styles: &view::Styles, ) -> Self { - let universal_controls = UNIVERSAL_CONTROLS; - - let mut spans_controls = Vec::with_capacity(view_controls.len() + universal_controls.len()); + let mut spans_controls = Vec::with_capacity(view_controls.len() + UNIVERSAL_CONTROLS.len()); spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles))); - spans_controls.extend(universal_controls.iter().map(|c| c.to_spans(styles))); + spans_controls.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles))); let mut lines = vec![Spans::from(vec![Span::from("controls: ")])]; let mut current_line = lines.last_mut().expect("This vector is never empty"); From 61a92a7989c15eed0daeb0c8faeeace3b013dd8b Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 1 Jun 2023 09:44:00 +0200 Subject: [PATCH 6/6] Fix docs on UNIVERSAL_CONTROLS --- tokio-console/src/view/controls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs index 8fcdc2008..81a90a7e2 100644 --- a/tokio-console/src/view/controls.rs +++ b/tokio-console/src/view/controls.rs @@ -6,7 +6,7 @@ use ratatui::{ widgets::{Paragraph, Widget}, }; -/// Returns a list of controls which are available in all views. +/// A list of controls which are available in all views. const UNIVERSAL_CONTROLS: &[ControlDisplay] = &[ ControlDisplay { action: "toggle pause",