From f400fc939b4bc78da0cbbdb49c898b781b24ea6c Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Thu, 4 Sep 2025 20:54:28 +0800 Subject: [PATCH 1/2] top: tui implement `4`, `l` --- src/uu/top/src/top.rs | 16 +++++++ src/uu/top/src/tui/mod.rs | 94 +++++++++++++++++++++++++++----------- src/uu/top/src/tui/stat.rs | 4 ++ 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/src/uu/top/src/top.rs b/src/uu/top/src/top.rs index b19bce63..0284f220 100644 --- a/src/uu/top/src/top.rs +++ b/src/uu/top/src/top.rs @@ -157,6 +157,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { uucore::error::set_exit_code(0); break; } + event::Event::Key(KeyEvent { + code: KeyCode::Char('l'), + .. + }) => { + let mut stat = tui_stat.write().unwrap(); + stat.show_load_avg = !stat.show_load_avg; + should_update.store(true, Ordering::Relaxed); + } event::Event::Key(KeyEvent { code: KeyCode::Char('t'), .. @@ -175,6 +183,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { should_update.store(true, Ordering::Relaxed); data.write().unwrap().0.update_cpu(&stat); } + event::Event::Key(KeyEvent { + code: KeyCode::Char('4'), + .. + }) => { + let mut stat = tui_stat.write().unwrap(); + stat.cpu_column = stat.cpu_column % 8 + 1; + should_update.store(true, Ordering::Relaxed); + } event::Event::Key(KeyEvent { code: KeyCode::Char('m'), .. diff --git a/src/uu/top/src/tui/mod.rs b/src/uu/top/src/tui/mod.rs index bd374ff1..467f2831 100644 --- a/src/uu/top/src/tui/mod.rs +++ b/src/uu/top/src/tui/mod.rs @@ -34,50 +34,88 @@ impl<'a> Tui<'a> { } fn calc_header_height(&self) -> u16 { - let mut height = 1; + let mut height = u16::from(self.stat.show_load_avg); + let mut columns = 0; if self.stat.cpu_graph_mode != CpuGraphMode::Hide { - height += 1; - height += self.header.cpu.len() as u16; + height += 1; // task line + if self.stat.cpu_graph_mode == CpuGraphMode::Sum { + height += self.header.cpu.len() as u16; + } else { + columns += self.header.cpu.len() as u16; + } } if self.stat.memory_graph_mode != MemoryGraphMode::Hide { - height += 2; + if self.stat.memory_graph_mode == MemoryGraphMode::Sum { + height += 2; + } else { + columns += 2; + } + } + height += columns / self.stat.cpu_column; + if columns % self.stat.cpu_column != 0 { + height += 1; } height } fn render_header(&self, area: Rect, buf: &mut Buffer) { - let mut constraints = vec![Constraint::Length(1)]; + let constraints = vec![Constraint::Length(1); self.calc_header_height() as usize]; let cpu = &self.header.cpu; - if self.stat.cpu_graph_mode != CpuGraphMode::Hide { - constraints.extend(vec![Constraint::Length(1); cpu.len() + 1]); - } - if self.stat.memory_graph_mode != MemoryGraphMode::Hide { - constraints.extend(vec![Constraint::Length(1); 2]); - } let header_layout = Layout::new(Direction::Vertical, constraints).split(area); let mut i = 0; - let render_bars = |bars_to_render: Vec<(String, f64, f64, f64, f64, char, bool)>, - buf: &mut Buffer, - i: usize| { + let mut i_columns = 0; + let mut cpu_column = None; + let mut render_bars = |bars_to_render: Vec<(String, f64, f64, f64, f64, char, bool)>, + buf: &mut Buffer, + i: usize| { let mut i = i; for (tag, l, r, red, yellow, content, print_percentage) in bars_to_render { + if cpu_column.is_none() || i_columns >= self.stat.cpu_column as usize { + let mut constraints = vec![Constraint::Min(25)]; + let mut width_left = header_layout[i].width - 25; + for _ in 0..self.stat.cpu_column { + if width_left > 28 { + constraints.extend(vec![Constraint::Length(3), Constraint::Min(25)]); + width_left -= 28; + } else { + constraints.extend(vec![Constraint::Length(0), Constraint::Length(0)]); + } + } + let line = + Layout::new(Direction::Horizontal, constraints).split(header_layout[i]); + i += 1; + i_columns = 0; + cpu_column = Some(line); + } + + let column_offset = i_columns * 2; + let area = cpu_column.as_ref().unwrap()[column_offset]; + if i_columns > 0 { + Line::from(vec![ + Span::raw(" "), + Span::styled(" ", Style::default().bg(Color::Yellow)), + Span::raw(" "), + ]) + .render(cpu_column.as_ref().unwrap()[column_offset - 1], buf); + } let line_layout = Layout::new( Direction::Horizontal, [ Constraint::Length(10), - Constraint::Length(16), + Constraint::Length(if self.stat.cpu_column < 3 { 16 } else { 0 }), Constraint::Length(1), Constraint::Min(0), Constraint::Length(1), ], ) - .split(header_layout[i]); - i += 1; + .split(area); + i_columns += 1; + Span::styled(format!("%{tag:<6}:",), Style::default().red()) .render(line_layout[0], buf); let percentage = if print_percentage { @@ -120,15 +158,17 @@ impl<'a> Tui<'a> { i }; - let uptime = format!( - "top - {time} {uptime}, {user}, {load_average}", - time = self.header.uptime.time, - uptime = self.header.uptime.uptime, - user = self.header.uptime.user, - load_average = self.header.uptime.load_average, - ); - Paragraph::new(uptime).render(header_layout[0], buf); - i += 1; + if self.stat.show_load_avg { + let load_avg = format!( + "top - {time} {uptime}, {user}, {load_average}", + time = self.header.uptime.time, + uptime = self.header.uptime.uptime, + user = self.header.uptime.user, + load_average = self.header.uptime.load_average, + ); + Paragraph::new(load_avg).render(header_layout[i], buf); + i += 1; + } if self.stat.cpu_graph_mode != CpuGraphMode::Hide { let task = &self.header.task; @@ -145,7 +185,7 @@ impl<'a> Tui<'a> { Span::raw(task.zombie.to_string()), Span::styled(" zombie", Style::default().red()), ]; - Line::from(task_line).render(header_layout[1], buf); + Line::from(task_line).render(header_layout[i], buf); i += 1; let mut cpu_bars = Vec::new(); diff --git a/src/uu/top/src/tui/stat.rs b/src/uu/top/src/tui/stat.rs index ccc1ffa2..e223d8e3 100644 --- a/src/uu/top/src/tui/stat.rs +++ b/src/uu/top/src/tui/stat.rs @@ -6,9 +6,11 @@ use std::time::Duration; pub(crate) struct TuiStat { + pub show_load_avg: bool, pub cpu_graph_mode: CpuGraphMode, pub cpu_value_mode: CpuValueMode, pub memory_graph_mode: MemoryGraphMode, + pub cpu_column: u16, pub list_offset: usize, pub delay: Duration, } @@ -16,9 +18,11 @@ pub(crate) struct TuiStat { impl TuiStat { pub fn new() -> Self { Self { + show_load_avg: true, cpu_graph_mode: CpuGraphMode::default(), cpu_value_mode: CpuValueMode::default(), memory_graph_mode: MemoryGraphMode::default(), + cpu_column: 2, list_offset: 0, delay: Duration::from_millis(1500), // 1.5s } From 364c2bad1a608dd10242ef4607011280a63b7f5c Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Thu, 4 Sep 2025 21:08:19 +0800 Subject: [PATCH 2/2] top: tui fix NaN when no swap --- src/uu/top/src/tui/mod.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/uu/top/src/tui/mod.rs b/src/uu/top/src/tui/mod.rs index 467f2831..c11f5c42 100644 --- a/src/uu/top/src/tui/mod.rs +++ b/src/uu/top/src/tui/mod.rs @@ -293,15 +293,27 @@ impl<'a> Tui<'a> { bar_content, false, )); - mem_bars.push(( - format!("{unit_name} Swap"), - mem.used_swap as f64 / mem.total_swap as f64 * 100.0, - format_memory(mem.total_swap, unit), - 0.0, - mem.used_swap as f64 / mem.total_swap as f64, - bar_content, - false, - )); + if mem.total_swap > 0 { + mem_bars.push(( + format!("{unit_name} Swap"), + mem.used_swap as f64 / mem.total_swap as f64 * 100.0, + format_memory(mem.total_swap, unit), + 0.0, + mem.used_swap as f64 / mem.total_swap as f64, + bar_content, + false, + )); + } else { + mem_bars.push(( + format!("{unit_name} Swap"), + 0.0, + 0.0, + 0.0, + 0.0, + bar_content, + false, + )); + } render_bars(mem_bars, &mut *buf, i); } }