mirror of
https://github.com/uutils/procps.git
synced 2026-05-06 06:06:43 -04:00
Merge pull request #527 from Bluemangoo/feature/top-tui-disp
top: tui display control
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user