Merge pull request #527 from Bluemangoo/feature/top-tui-disp

top: tui display control
This commit is contained in:
Daniel Hofstetter
2025-09-24 10:50:45 +02:00
committed by GitHub
5 changed files with 195 additions and 32 deletions
+22 -7
View File
@@ -24,13 +24,13 @@ pub fn sysinfo() -> &'static RwLock<System> {
}
pub trait Column {
fn as_string(&self) -> String;
fn as_string(&self, show_zeros: bool) -> String;
fn cmp_dyn(&self, other: &dyn Column) -> Ordering;
fn as_any(&self) -> &dyn Any;
}
impl Column for String {
fn as_string(&self) -> String {
fn as_string(&self, _show_zeros: bool) -> String {
self.clone()
}
@@ -47,7 +47,10 @@ impl Column for String {
}
impl Column for u32 {
fn as_string(&self) -> String {
fn as_string(&self, show_zeros: bool) -> String {
if !show_zeros && self == &0 {
return String::new();
}
self.to_string()
}
@@ -64,7 +67,10 @@ impl Column for u32 {
}
impl Column for Option<i32> {
fn as_string(&self) -> String {
fn as_string(&self, show_zeros: bool) -> String {
if !show_zeros && self == &Some(0) {
return String::new();
}
self.map(|v| v.to_string()).unwrap_or_default()
}
@@ -96,7 +102,10 @@ impl PercentValue {
}
impl Column for PercentValue {
fn as_string(&self) -> String {
fn as_string(&self, show_zeros: bool) -> String {
if !show_zeros && self.value == 0.0 {
return String::new();
}
format!("{:.1}", self.value)
}
@@ -123,7 +132,10 @@ impl MemValue {
}
impl Column for MemValue {
fn as_string(&self) -> String {
fn as_string(&self, show_zeros: bool) -> String {
if !show_zeros && self.value == 0 {
return String::new();
}
let mem_mb = self.value as f64 / bytesize::MIB as f64;
if mem_mb >= 10000.0 {
format!("{:.1}g", self.value as f64 / bytesize::GIB as f64)
@@ -156,7 +168,10 @@ impl TimeMSValue {
}
impl Column for TimeMSValue {
fn as_string(&self) -> String {
fn as_string(&self, show_zeros: bool) -> String {
if !show_zeros && self.min == 0 && self.sec < 0.01 {
return String::new();
}
format!("{}:{:0>5.2}", self.min, self.sec)
}
+5 -1
View File
@@ -235,7 +235,11 @@ fn collect(settings: &Settings, fields: &[String], tui_stat: &TuiStat) -> Vec<Ve
}
collected
.into_iter()
.map(|it| it.into_iter().map(|c| c.as_string()).collect())
.map(|it| {
it.into_iter()
.map(|c| c.as_string(tui_stat.show_zeros))
.collect()
})
.collect()
}
+71
View File
@@ -18,6 +18,7 @@ pub(crate) enum InputMode {
}
#[derive(Eq, PartialEq, Copy, Clone)]
pub(crate) enum InputEvent {
MaxListDisplay,
NumaNode,
}
@@ -55,6 +56,11 @@ pub fn handle_input(
stat.highlight_bold = !stat.highlight_bold;
should_update.store(true, Ordering::Relaxed);
}
char!('C') => {
let mut stat = tui_stat.write().unwrap();
stat.show_coordinates = !stat.show_coordinates;
should_update.store(true, Ordering::Relaxed);
}
char!('c') => {
{
// drop the lock as soon as possible
@@ -75,6 +81,17 @@ pub fn handle_input(
stat.memory_graph_mode = stat.memory_graph_mode.next();
should_update.store(true, Ordering::Relaxed);
}
char!('n') => {
let mut stat = tui_stat.write().unwrap();
stat.input_label = format!(
"Maximum tasks = {}, change to (0 is unlimited)",
stat.max_list_display
);
stat.input_value.clear();
stat.input_mode = InputMode::Input(InputEvent::MaxListDisplay);
should_update.store(true, Ordering::Relaxed);
}
char!('R') => {
{
let mut stat = tui_stat.write().unwrap();
@@ -99,6 +116,16 @@ pub fn handle_input(
stat.colorful = !stat.colorful;
should_update.store(true, Ordering::Relaxed);
}
char!('0') => {
{
// drop the lock as soon as possible
let mut stat = tui_stat.write().unwrap();
stat.show_zeros = !stat.show_zeros;
}
data.write().unwrap().1 = ProcList::new(settings, &tui_stat.read().unwrap());
should_update.store(true, Ordering::Relaxed);
}
char!('1') => {
let mut stat = tui_stat.write().unwrap();
stat.cpu_value_mode = stat.cpu_value_mode.next();
@@ -132,6 +159,17 @@ pub fn handle_input(
stat.cpu_column = stat.cpu_column % 8 + 1;
should_update.store(true, Ordering::Relaxed);
}
char!('#') => {
let mut stat = tui_stat.write().unwrap();
stat.input_label = format!(
"Maximum tasks = {}, change to (0 is unlimited)",
stat.max_list_display
);
stat.input_value.clear();
stat.input_mode = InputMode::Input(InputEvent::MaxListDisplay);
should_update.store(true, Ordering::Relaxed);
}
char!('<') => {
{
let mut stat = tui_stat.write().unwrap();
@@ -183,6 +221,24 @@ pub fn handle_input(
stat.list_offset += 1;
should_update.store(true, Ordering::Relaxed);
}
Event::Key(KeyEvent {
code: KeyCode::Left,
..
}) => {
let mut stat = tui_stat.write().unwrap();
if stat.horizontal_offset > 0 {
stat.horizontal_offset -= 1;
should_update.store(true, Ordering::Relaxed);
}
}
Event::Key(KeyEvent {
code: KeyCode::Right,
..
}) => {
let mut stat = tui_stat.write().unwrap();
stat.horizontal_offset += 1;
should_update.store(true, Ordering::Relaxed);
}
Event::Resize(_, _) => should_update.store(true, Ordering::Relaxed),
_ => {}
},
@@ -222,6 +278,21 @@ fn handle_input_value(
should_update: &AtomicBool,
) {
match input_event {
InputEvent::MaxListDisplay => {
let input_value = { tui_stat.read().unwrap().input_value.parse::<usize>() };
if input_value.is_err() {
let mut stat = tui_stat.write().unwrap();
stat.reset_input();
stat.input_error = Some(" invalid number ".into());
should_update.store(true, Ordering::Relaxed);
return;
}
let input_value = input_value.unwrap();
let mut stat = tui_stat.write().unwrap();
stat.max_list_display = input_value;
stat.reset_input();
should_update.store(true, Ordering::Relaxed);
}
InputEvent::NumaNode => {
let input_value = { tui_stat.read().unwrap().input_value.parse::<usize>() };
let numa_nodes = get_numa_nodes();
+89 -24
View File
@@ -65,6 +65,24 @@ impl<'a> Tui<'a> {
height
}
fn calc_list_coordinates(&self) -> (usize, usize) {
let list_total = self.proc_list.collected.len();
let list_offset = self.stat.list_offset;
(list_offset, list_total)
}
fn calc_column_coordinates(&self) -> (usize, usize, usize) {
let total_columns = self.proc_list.fields.len();
let horizontal_offset = self.stat.horizontal_offset;
let column_coordinate = min(horizontal_offset, total_columns - 1);
let horizontal_offset = if horizontal_offset >= total_columns {
horizontal_offset - (total_columns - 1)
} else {
0
};
(column_coordinate, total_columns, horizontal_offset * 8)
}
fn render_header(&self, area: Rect, buf: &mut Buffer) {
let constraints = vec![Constraint::Length(1); self.calc_header_height() as usize];
let colorful = self.stat.colorful;
@@ -347,11 +365,32 @@ impl<'a> Tui<'a> {
.render(layout[0], buf);
return;
}
let input = Line::from(vec![
Span::styled(&self.stat.input_label, Style::default().primary(colorful)),
Span::raw(" "),
Span::raw(&self.stat.input_value),
]);
let input = if !self.stat.input_label.is_empty() || !self.stat.input_value.is_empty() {
Line::from(vec![
Span::styled(&self.stat.input_label, Style::default().primary(colorful)),
Span::raw(" "),
Span::raw(&self.stat.input_value),
])
} else if self.stat.show_coordinates {
let list_coordinates = self.calc_list_coordinates();
let column_coordinates = self.calc_column_coordinates();
Line::from(vec![
Span::raw(format!(
" scroll coordinates: y = {}/{} (tasks), x = {}/{} (fields)",
list_coordinates.0 + 1,
list_coordinates.1,
column_coordinates.0 + 1,
column_coordinates.1
)),
Span::raw(if column_coordinates.2 > 0 {
format!(" + {}", column_coordinates.2)
} else {
String::new()
}),
])
} else {
Line::from("")
};
input.render(area, buf);
}
@@ -386,37 +425,57 @@ impl<'a> Tui<'a> {
_ => Constraint::Length(0),
};
let list_coordinates = self.calc_list_coordinates();
let column_coordinates = self.calc_column_coordinates();
let constraints: Vec<Constraint> = self
.proc_list
.fields
.iter()
.map(|field| build_constraint(field))
.skip(column_coordinates.0)
.collect();
self.stat.list_offset = min(self.stat.list_offset, self.proc_list.collected.len() - 1);
let header =
Row::new(self.proc_list.fields.clone()).style(Style::default().bg_secondary(colorful));
let header = Row::new(
self.proc_list
.fields
.clone()
.split_off(column_coordinates.0),
)
.style(Style::default().bg_secondary(colorful));
let rows = self.proc_list.collected.iter().map(|item| {
let cells = item.iter().enumerate().map(|(n, c)| {
if highlight_sorted && n == highlight_column {
Cell::from(Span::styled(
c,
if highlight_bold {
Style::default().bg_primary(colorful)
let cells = item
.iter()
.enumerate()
.skip(column_coordinates.0)
.map(|(n, c)| {
let c = if column_coordinates.2 > 0 {
if c.len() < column_coordinates.2 {
""
} else {
Style::default().primary(colorful)
},
))
} else {
Cell::from(c.as_str())
}
});
&c[column_coordinates.2..]
}
} else {
c
};
if highlight_sorted && n == highlight_column {
Cell::from(Span::styled(
c,
if highlight_bold {
Style::default().bg_primary(colorful)
} else {
Style::default().primary(colorful)
},
))
} else {
Cell::from(c)
}
});
Row::new(cells).height(1)
});
let mut state = TableState::default().with_offset(self.stat.list_offset);
let mut state = TableState::default().with_offset(list_coordinates.0);
let table = Table::new(rows, constraints).header(header);
StatefulWidget::render(table, area, buf, &mut state);
@@ -425,6 +484,7 @@ impl<'a> Tui<'a> {
impl Widget for Tui<'_> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
self.stat.list_offset = min(self.stat.list_offset, self.proc_list.collected.len() - 1);
let layout = Layout::new(
Direction::Vertical,
[
@@ -437,6 +497,11 @@ impl Widget for Tui<'_> {
self.render_header(layout[0], buf);
self.render_input(layout[1], buf);
self.render_list(layout[2], buf);
let mut list_area = layout[2];
if self.stat.max_list_display > 0 {
let list_height = min(layout[2].height, self.stat.max_list_display as u16) + 1; // 1 for header
list_area.height = list_height;
}
self.render_list(list_area, buf);
}
}
+8
View File
@@ -19,6 +19,8 @@ pub(crate) struct TuiStat {
pub memory_graph_mode: MemoryGraphMode,
pub cpu_column: u16,
pub list_offset: usize,
pub horizontal_offset: usize,
pub max_list_display: usize,
pub colorful: bool,
pub full_command_line: bool,
pub delay: Duration,
@@ -26,6 +28,8 @@ pub(crate) struct TuiStat {
pub sort_by_pid: bool,
pub highlight_sorted: bool,
pub highlight_bold: bool,
pub show_coordinates: bool,
pub show_zeros: bool,
}
impl TuiStat {
@@ -49,6 +53,8 @@ impl TuiStat {
memory_graph_mode: MemoryGraphMode::default(),
cpu_column: 2,
list_offset: 0,
horizontal_offset: 0,
max_list_display: 0, // unlimited
colorful: true,
full_command_line: true,
delay: Duration::from_millis(1500), // 1.5s
@@ -56,6 +62,8 @@ impl TuiStat {
sort_by_pid: false,
highlight_sorted: false,
highlight_bold: false,
show_coordinates: false,
show_zeros: true,
}
}