-
-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refac(console): generalize controls widget (#427)
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.
- Loading branch information
Showing
8 changed files
with
268 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
use crate::view::{self, bold}; | ||
|
||
use ratatui::{ | ||
layout, | ||
text::{Span, Spans, Text}, | ||
widgets::{Paragraph, Widget}, | ||
}; | ||
|
||
/// 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 { | ||
paragraph: Paragraph<'static>, | ||
height: u16, | ||
} | ||
|
||
impl Controls { | ||
pub(in crate::view) fn new( | ||
view_controls: &'static [ControlDisplay], | ||
area: &layout::Rect, | ||
styles: &view::Styles, | ||
) -> Self { | ||
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 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 { | ||
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: &'static [KeyDisplay], | ||
} | ||
|
||
/// 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 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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.