diff options
Diffstat (limited to 'src/components/processes.rs')
-rw-r--r-- | src/components/processes.rs | 2076 |
1 files changed, 2076 insertions, 0 deletions
diff --git a/src/components/processes.rs b/src/components/processes.rs new file mode 100644 index 0000000..93cc835 --- /dev/null +++ b/src/components/processes.rs @@ -0,0 +1,2076 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see <http://www.gnu.org/licenses/>. + */ + +use super::*; +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; +use std::str::FromStr; +#[derive(Debug)] +pub struct ProcessData { + cpu_stat: Stat, + processes_times: HashMap<Pid, usize>, + threads_times: HashMap<Pid, usize>, + parents: HashMap<Pid, Vec<Pid>>, + processes_index: HashMap<Pid, usize>, + tree_index: HashMap<Pid, usize>, + tree: Vec<(usize, Pid)>, +} + +const SIGNAL_LIST: &[(i32, &'static str)] = &[ + (1, "1 HUP"), + (2, "2 INT"), + (3, "3 QUIT"), + (4, "4 ILL"), + (5, "5 TRAP"), + (6, "6 ABRT"), + (7, "7 BUS"), + (8, "8 FPE"), + (9, "9 KILL"), + (10, "10 USR1"), + (11, "11 SEGV"), + (12, "12 USR2"), + (13, "13 PIPE"), + (14, "14 ALRM"), + (15, "15 TERM"), + (16, "16 STKFLT"), + (17, "17 CHLD"), + (18, "18 CONT"), + (19, "19 STOP"), + (20, "20 TSTP"), + (21, "21 TTIN"), + (22, "22 TTOU"), + (23, "23 URG"), + (24, "24 XCPU"), + (25, "25 XFSZ"), + (26, "26 VTALRM"), + (27, "27 PROF"), + (28, "28 WINCH"), + (29, "29 POLL"), + (30, "30 PWR"), + (31, "31 SYS"), +]; + +/* Hold maximum width for each column */ +#[derive(Debug)] +pub struct ColumnWidthMaxima { + pid: usize, + ppid: usize, + vm_rss: usize, + cpu_percent: usize, + state: usize, + username: usize, +} + +impl ColumnWidthMaxima { + fn new() -> ColumnWidthMaxima { + ColumnWidthMaxima { + pid: "PID".len(), + ppid: "PPID".len(), + vm_rss: "VM_RSS".len(), + cpu_percent: " CPU%".len(), + state: 1, + username: "USER".len(), + } + } +} + +macro_rules! define_column_string { + ($($typename: tt),+) => { + $( + #[derive(Debug)] + pub struct $typename(pub String); + + impl $typename { + #[allow(dead_code)] + pub fn len(&self) -> usize { + self.0.len() + } + } + + impl std::fmt::Display for $typename { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(width) = f.width() { + write!(f, "{:>width$}", self.0, width = width) + } else { + write!(f, "{:>}", self.0) + } + } + })+ + } +} + +define_column_string!( + PidString, + PpidString, + VmRssString, + CmdLineString, + UserString +); + +pub type Pid = i32; + +#[derive(Debug, Copy, Clone)] +enum Sort { + UserAsc, + UserDesc, + VmRssAsc, + VmRssDesc, + CpuAsc, + CpuDesc, + CmdLineAsc, + CmdLineDesc, +} + +/* Wrapper type for display strings */ +#[derive(Debug)] +pub struct ProcessDisplay { + pub i: Pid, + pub p: Pid, + pub pid: PidString, + pub ppid: PpidString, + pub vm_rss: VmRssString, + vm_rss_value: usize, + pub cpu_percent: usize, + pub state: State, + pub cmd_line: CmdLineString, + pub username: UserString, + pub rtime: usize, + pub is_thread: bool, +} + +/* process list components */ +#[derive(Debug)] +pub struct ProcessList { + page_movement: Option<PageMovement>, + cpu_stat: Stat, + data: ProcessData, + cursor: usize, + height: usize, + dirty: bool, + force_redraw: bool, + maxima: ColumnWidthMaxima, + /* stop updating data */ + freeze: bool, + draw_tree: bool, + draw_help: bool, + processes_times: HashMap<Pid, usize>, + threads_times: HashMap<Pid, usize>, + processes: Vec<ProcessDisplay>, + sort: Sort, + mode: FunctionModes, +} + +#[derive(Debug, PartialEq)] +enum Input { + Active, + Inactive, +} + +impl Input { + fn set_inactive(&mut self, ui_mode: &mut UIMode) { + *self = Inactive; + *ui_mode = UIMode::Normal; + } + + fn set_active(&mut self, ui_mode: &mut UIMode) { + *self = Active; + *ui_mode = UIMode::Input; + } +} + +impl Default for Input { + fn default() -> Self { + Inactive + } +} +use Input::*; + +const FOLLOW_ACTIVE: u8 = 0x01; +const LOCATE_ACTIVE: u8 = 0x02; +const SEARCH_ACTIVE: u8 = 0x04; +const KILL_ACTIVE: u8 = 0x08; +const HELP_ACTIVE: u8 = 0x10; +const FILTER_ACTIVE: u8 = 0x40; + +#[derive(Debug, PartialEq, Default)] +struct FunctionModes { + follow: Pid, + locate: Pid, + search: (String, usize, usize, bool), /* (search term, current result y_offset, previous result offset, is cursor focused on results?) */ + kill: u16, + filter: String, + active: u8, + input: Input, +} + +impl FunctionModes { + #[inline(always)] + fn is_active(&self, mask: u8) -> bool { + (self.active & mask) > 0 + } +} + +#[derive(Debug, PartialEq)] +pub enum State { + /* Z Zombie */ + Zombie, + /* R Running */ + Running, + /* S Sleeping in an interruptible wait */ + Sleeping, + /* D Waiting in uninterruptible disk sleep */ + Waiting, + /* T Stopped (on a signal) or (before Linux 2.6.33) trace stopped */ + Stopped, + /* t Tracing stop (Linux 2.6.33 onward) */ + Tracing, + /* X Dead (from Linux 2.6.0 onward) */ + Dead, +} + +impl From<char> for State { + fn from(val: char) -> State { + match val { + 'R' => State::Running, + 'I' => State::Sleeping, + 'S' => State::Sleeping, + 'D' => State::Waiting, + 'Z' => State::Zombie, + 'T' => State::Stopped, + 't' => State::Tracing, + 'X' => State::Dead, + 'x' => State::Dead, + _ => unreachable!(), + } + } +} + +impl std::fmt::Display for State { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + State::Running => 'R', + State::Sleeping => 'S', + State::Waiting => 'D', + State::Zombie => 'Z', + State::Stopped => 'T', + State::Tracing => 't', + State::Dead => 'X', + } + ) + } +} + +pub struct Process { + pub pid: i32, + pub ppid: i32, + pub vm_rss: usize, + pub state: State, + pub uid: u32, + pub cmd_line: String, + pub rtime: usize, +} + +impl fmt::Display for ProcessList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "process list") + } +} + +impl ProcessList { + pub fn new() -> Self { + let data = ProcessData { + cpu_stat: get_stat(&mut 0).remove(0), + processes_times: Default::default(), + threads_times: Default::default(), + processes_index: Default::default(), + tree_index: Default::default(), + parents: Default::default(), + tree: Default::default(), + }; + ProcessList { + cursor: 0, + page_movement: None, + cpu_stat: get_stat(&mut 0).remove(0), + data, + processes: Vec::with_capacity(1024), + processes_times: Default::default(), + threads_times: Default::default(), + height: 0, + maxima: ColumnWidthMaxima::new(), + freeze: false, + draw_tree: false, + draw_help: false, + mode: FunctionModes::default(), + dirty: true, + sort: Sort::CpuDesc, + force_redraw: false, + } + } + + fn follow(&self) -> Option<Pid> { + if self.mode.is_active(FOLLOW_ACTIVE) { + Some(self.mode.follow) + } else { + None + } + } + + fn get_pid_under_cursor(&self, cursor: usize) -> Pid { + if self.draw_tree { + self.data.tree[cursor].1 + } else { + let mut processes = self.processes.iter().collect::<Vec<&ProcessDisplay>>(); + processes.sort_unstable_by(|a, b| match self.sort { + Sort::CpuAsc => a.cpu_percent.cmp(&b.cpu_percent), + Sort::CpuDesc => b.cpu_percent.cmp(&a.cpu_percent), + Sort::VmRssAsc => a.vm_rss_value.cmp(&b.vm_rss_value), + Sort::VmRssDesc => b.vm_rss_value.cmp(&a.vm_rss_value), + Sort::UserAsc => a.username.0.cmp(&b.username.0), + Sort::UserDesc => b.username.0.cmp(&a.username.0), + Sort::CmdLineAsc => a.cmd_line.0.cmp(&b.cmd_line.0), + Sort::CmdLineDesc => b.cmd_line.0.cmp(&a.cmd_line.0), + }); + if self.mode.is_active(FILTER_ACTIVE) { + processes.retain(|process| process.cmd_line.0.contains(self.mode.filter.as_str())); + } + processes[cursor].i + } + } + + fn draw_help_box(&self, grid: &mut CellBuffer) { + let (cols, rows) = grid.size(); + let margin_left = (cols / 2).saturating_sub(20); + let margin_top = (rows / 2).saturating_sub(12); + let box_area = ( + (margin_left, margin_top), + (margin_left + 36, margin_top + 12), + ); + clear_area(grid, box_area); + create_box(grid, box_area); + let shortcuts_map = &self.get_shortcuts()[""]; + let max_key = shortcuts_map.keys().map(|k| k.len()).max().unwrap(); + let mut shortcuts = shortcuts_map + .iter() + .map(|(k, v)| (*k, v)) + .collect::<Vec<(&str, &Key)>>(); + shortcuts.sort_by_key(|s| s.0); + let mut y = 0; + for (k, v) in shortcuts.iter() { + write_string_to_grid( + &format!("{k:>max_key$} {v}", k = k, v = v, max_key = max_key), + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + pos_inc(upper_left!(box_area), (2, 4 + y)), + bottom_right!(box_area), + ), + None, + ); + y += 1; + } + let box_area = ( + (margin_left, margin_top + 13), + (margin_left + 36, margin_top + 16), + ); + clear_area(grid, box_area); + create_box(grid, box_area); + } + + fn draw_tree_list( + &mut self, + grid: &mut CellBuffer, + area: Area, + tick: bool, + mut pages: usize, + height: usize, + ) { + let (upper_left, bottom_right) = area; + + let mut y_offset = 0; + + let mut branches = vec![]; + let mut child_counters = vec![0]; + + self.cursor = std::cmp::min(self.height, self.cursor); + let mut lines = Vec::with_capacity(2048); + let tree = self + .data + .tree + .iter() + .filter(|(_, pid)| { + if self.mode.is_active(FILTER_ACTIVE) { + self.processes[self.data.processes_index[pid]] + .cmd_line + .0 + .contains(self.mode.filter.as_str()) + } else { + true + } + }) + .collect::<Vec<&_>>(); + let mut iter = tree.iter().peekable(); + + let mut stop_search = false; + let mut search_results = 0; + while let Some((ind, pid)) = iter.next() { + let p = &self.processes[self.data.processes_index[pid]]; + if self.mode.is_active(SEARCH_ACTIVE) { + let (ref search, ref mut n, ref mut n_prev, is_cursor_focused) = self.mode.search; + if (*n != *n_prev || tick) && is_cursor_focused { + if !stop_search && search.len() > 1 && p.cmd_line.0.contains(search) { + if search_results == *n { + let i = y_offset; + pages = i / height; + self.cursor = i; + stop_search = true; + *n_prev = *n; + } else { + search_results += 1; + } + if search_results < *n { + *n = search_results; + } + } + } + } + let has_sibling: bool = child_counters + .get(*ind) + .map(|n| { + *n != self + .data + .parents + .get(&p.p) + .and_then(|v| Some(v.len() - 1)) + .unwrap_or(0) + }) + .unwrap_or(false); + + let is_first: bool = child_counters.last().map(|n| *n == 0).unwrap_or(false); + + if let Some(counter) = child_counters.get_mut(*ind) { + *counter += 1; + } + + let mut s = String::with_capacity(16); + + for i in 0..*ind { + if branches.len() > i && branches[i] { + s.push(' '); + s.push('┃'); + } else { + s.push(' '); + } + if i > 0 {} + s.push(' '); + } + + if *ind > 0 || (has_sibling || is_first) { + if p.is_thread { + if has_sibling && is_first { + s.push(' '); + s.push('┠'); + } else if has_sibling { + s.push(' '); + s.push('┠'); + } else { + s.push(' '); + s.push('┖'); + } + s.push('┄'); + } else { + if has_sibling && is_first { + s.push(' '); + s.push('┣'); + } else if has_sibling { + s.push(' '); + s.push('┣'); + } else { + s.push(' '); + s.push('┗'); + } + s.push('━'); + } + s.push('>'); + } + + lines.push(s); + match iter.peek() { + Some((n, _)) if *n > *ind => { + child_counters.push(0); + if has_sibling { + branches.push(true); + } else { + branches.push(false); + } + } + Some((n, _)) if *n < *ind => { + for _ in 0..(ind - *n) { + branches.pop(); + child_counters.pop(); + } + } + _ => {} + } + } + + for ((_, pid), s) in tree + .iter() + .zip(lines.iter()) + .skip(pages * height) + .take(height) + { + let (fg_color, bg_color) = if pages * height + y_offset == self.cursor { + (Color::White, Color::Byte(235)) + } else if self.mode.is_active(LOCATE_ACTIVE) { + let highlighted_pid = self.mode.locate; + if highlighted_pid == *pid { + (Color::Red, Color::Yellow) + } else { + (Color::Default, Color::Default) + } + } else { + (Color::Default, Color::Default) + }; + + let p = &self.processes[self.data.processes_index[pid]]; + match executable_path_color(&p.cmd_line) { + Ok((path, bin, rest)) => { + let (x, _) = write_string_to_grid( + &format!( + "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$} {vm_rss:>max_vm_rss$} {cpu_percent:>max_cpu_percent$}% {state:>max_state$} {branches}", + pid = p.pid, + ppid = p.ppid, + username = p.username, + vm_rss = p.vm_rss, + cpu_percent = (p.cpu_percent as f32 / 1000.0), + state = p.state, + max_pid = self.maxima.pid, + max_ppid = self.maxima.ppid, + max_username = self.maxima.username, + max_vm_rss = self.maxima.vm_rss, + max_cpu_percent = self.maxima.cpu_percent, + max_state = self.maxima.state, + branches = s, + ), + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), + None,); + if p.state == State::Running { + grid[pos_inc( + upper_left, + ( + 2 * 5 + + self.maxima.pid + + self.maxima.ppid + + self.maxima.username + + self.maxima.vm_rss + + self.maxima.cpu_percent, + y_offset + 2, + ), + )] + .set_fg(if self.freeze { + Color::Byte(12) + } else { + Color::Byte(10) + }); + } + let (x, _) = write_string_to_grid( + path, + grid, + Color::Byte(243), + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), + None, + ); + let (x, _) = write_string_to_grid( + bin, + grid, + if self.freeze { + Color::Byte(32) + } else { + Color::Byte(34) + }, + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), + None, + ); + write_string_to_grid( + rest, + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), + None, + ); + change_colors( + grid, + ( + pos_inc(upper_left, (x - 1 + rest.len(), y_offset + 2)), + set_y(bottom_right, get_y(upper_left) + y_offset + 2), + ), + Some(fg_color), + Some(bg_color), + ); + } + Err((bin, rest)) => { + let (x, _) = write_string_to_grid( + &format!( + "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$} {vm_rss:>max_vm_rss$} {cpu_percent:>max_cpu_percent$}% {state:>max_state$} {branches}", + pid = p.pid, + ppid = p.ppid, + username = p.username, + vm_rss = p.vm_rss, + cpu_percent = (p.cpu_percent as f32 / 100.0), + state = p.state, + max_pid = self.maxima.pid, + max_ppid = self.maxima.ppid, + max_username = self.maxima.username, + max_vm_rss = self.maxima.vm_rss, + max_cpu_percent = self.maxima.cpu_percent, + max_state = self.maxima.state, + branches = s, + ), + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), + None,); + if p.state == State::Running { + grid[pos_inc( + upper_left, + ( + 2 * 5 + + 1 + + self.maxima.pid + + self.maxima.ppid + + self.maxima.username + + self.maxima.vm_rss + + self.maxima.cpu_percent, + y_offset + 2, + ), + )] + .set_fg(if self.freeze { + Color::Byte(12) + } else { + Color::Byte(10) + }); + } + let (x, _) = write_string_to_grid( + bin, + grid, + if self.freeze { + Color::Byte(32) + } else { + Color::Byte(34) + }, + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), + None, + ); + write_string_to_grid( + rest, + grid, + fg_color, + bg_color, + Attr::Default, + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), + None, + ); + change_colors( + grid, + ( + pos_inc(upper_left, (x - 1 + rest.len(), y_offset + 2)), + set_y(bottom_right, get_y(upper_left) + y_offset + 2), + ), + Some(fg_color), + Some(bg_color), + ); + } + } + y_offset += 1; + } + } +} + +/* Prints the process list */ +impl Component for ProcessList { + fn draw( + &mut self, + grid: &mut CellBuffer, + area: Area, + dirty_areas: &mut VecDeque<Area>, + mut tick: bool, + ) { + if !is_valid_area!(area) { + return; + } + + if self.force_redraw { + self.force_redraw = false; + tick = true; + } + + let mut upper_left = pos_inc(upper_left!(area), (1, 0)); + let bottom_right = pos_dec(bottom_right!(area), (1, 1)); + + /* Reserve first row for column headers */ + let height = height!(area) + - 2 + - if self.mode.is_active(LOCATE_ACTIVE) { + 2 + } else { + 0 + }; + let width = width!(area); + let old_pages = (self.cursor) / height; + + let old_cursor = self.cursor; + if let Some(mvm) = self.page_movement.take() { + match mvm { + PageMovement::Up => { + self.cursor = self.cursor.saturating_sub(1); + } + PageMovement::Down => { + self.cursor = std::cmp::min(self.height.saturating_sub(1), self.cursor + 1); + } + PageMovement::Home => { + self.cursor = 0; + } + PageMovement::PageUp => { + self.cursor = self.cursor.saturating_sub(height); + } + PageMovement::PageDown => { + self.cursor = + std::cmp::min(self.height.saturating_sub(1), self.cursor + height); + } + PageMovement::End => { + self.cursor = self.height.saturating_sub(1); + } + } + } + + let mut pages = (self.cursor) / height; + if pages != old_pages { + tick = true; + } + + let update_maxima = tick && !self.freeze; + + if update_maxima { + let follow = self.follow(); + self.processes = get(&mut self.data, follow, self.sort); + }; + + if tick || self.freeze { + if tick || old_cursor != self.cursor { + clear_area(grid, area); + } + + dirty_areas.push_back(area); + + if !self.freeze && update_maxima { + /* Keep tabs on biggest element in each column */ + self.maxima = ColumnWidthMaxima::new(); + + for p in &self.processes { + self.maxima.pid = std::cmp::max(self.maxima.pid, p.pid.len()); + self.maxima.ppid = std::cmp::max(self.maxima.ppid, p.ppid.len()); + self.maxima.vm_rss = std::cmp::max(self.maxima.vm_rss, p.vm_rss.len()); + self.maxima.username = std::cmp::max(self.maxima.username, p.username.len()); + } + + self.cursor = std::cmp::min(self.height.saturating_sub(1), self.cursor); + } + if self.mode.is_active(FOLLOW_ACTIVE) { + let pid = self.mode.follow; + let info = format!("Following PID == {pid} || PPID == {pid}", pid = pid); + write_string_to_grid( + &info, + grid, + Color::Default, + Color::Default, + Attr::Bold, + ( + pos_inc( + upper_left!(area), + ((width / 2).saturating_sub(info.len() / 2), 1), + ), + bottom_right!(area), + ), + None, + ); + upper_left = pos_inc(upper_left, (0, 2)); + } + if self.mode.is_active(LOCATE_ACTIVE) { + let pid = &self.mode.locate; + let info = format!("Highlighting PID == {pid}", pid = pid); + write_string_to_grid( + &info, + grid, + Color::Default, + Color::Default, + Attr::Bold, + ( + pos_inc( + upper_left!(area), + ((width / 2).saturating_sub(info.len() / 2), 1), + ), + bottom_right!(area), + ), + None, + ); + upper_left = pos_inc(upper_left, (0, 2)); + } + + let cmd_header = if self.mode.is_active(SEARCH_ACTIVE) { + let (ref p, _, _, _) = self.mode.search; + Some(format!("CMD_LINE (search: {})", p)) + } else if self.mode.is_active(FILTER_ACTIVE) { + Some(format!("CMD_LINE (filter: {})", &self.mode.filter)) + } else { + None + }; + + /* Write column headers */ + let (x, y) = write_string_to_grid( + &format!( + "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$}{usernamesort} {vm_rss:>max_vm_rss$}{vmrsssort} {cpu_percent:>max_cpu_percent$}{cpusort} {state:>max_state$} {cmd_line}{cmd_linesort}", + pid = "PID", + ppid ="PPID", + username = "USER", + vm_rss = "VM_RSS", + cpu_percent = " CPU%", + state = " ", + cmd_line = if let Some(ref cmd_header) = cmd_header { &cmd_header } else { "CMD_LINE"} , + max_pid = self.maxima.pid, + max_ppid = self.maxima.ppid, + max_username = self.maxima.username, + max_vm_rss = self.maxima.vm_rss, + max_cpu_percent = self.maxima.cpu_percent, + max_state = self.maxima.state, + usernamesort = if let Sort::UserAsc = self.sort { "↑" } else if let Sort::UserDesc = self.sort { "↓" } else { " " }, + vmrsssort = if let Sort::VmRssAsc = self.sort { "↑" } else if let Sort::VmRssDesc = self.sort { "↓" } else { " " }, + cpusort = if let Sort::CpuAsc = self.sort { "↑" } else if let Sort::CpuDesc = self.sort { "↓" } else { " " }, + cmd_linesort = if let Sort::CmdLineAsc = self.sort { "↑" } else if let Sort::CmdLineDesc = self.sort { "↓" } else { "" }, + ), + grid, + Color::Black, + Color::White, + Attr::Default, + (pos_inc(upper_left, (0, 1)), bottom_right), + None,); + if self.mode.is_active(SEARCH_ACTIVE | FILTER_ACTIVE) && self.mode.input == Active { + grid[(x - 1, y)].set_fg(Color::White); + grid[(x - 1, y)].set_bg(Color::Black); + } + change_colors( + grid, + ((x, y), set_y(bottom_right, y)), + Some(Color::Black), + Some(Color::White), + ); + + /* Write current selected status if any. eg. if list is frozen, show 'FROZEN'. */ + { + let (mut x, y) = set_y(upper_left!(area), get_y(bottom_right) + 1); + if self.freeze { + let (_x, _) = write_string_to_grid( + " FROZEN ", + grid, + Color::White, + Color::Byte(26), // DodgerBlue3 + Attr::Bold, + ((x, y), pos_inc(bottom_right, (0, 1))), + None, + ); + x = _x; + } + + if self.mode.is_active(SEARCH_ACTIVE) { + let (_x, _) = write_string_to_grid( + " SEARCH ", + grid, + Color::White, + Color::Byte(88), // DarkRed + Attr::Bold, + ((x, y), pos_inc(bottom_right, (0, 1))), + None, + ); + x = _x; + } + if self.mode.is_active(FOLLOW_ACTIVE) { + let (_x, _) = write_string_to_grid( + " FOLLOW ", + grid, + Color::White, + Color::Byte(172), // Orange3 + Attr::Bold, + ((x, y), pos_inc(bottom_right, (0, 1))), + None, + ); + x = _x; + } + if self.mode.is_active(LOCATE_ACTIVE) { + let (_x, _) = write_string_to_grid( + " LOCATE ", + grid, + Color::White, + Color::Green, + Attr::Bold, + ((x, y), pos_inc(bottom_right, (0, 1))), + None, + ); + x = _x; + } + if self.mode.is_active(KILL_ACTIVE) { + let (_x, _) = write_string_to_grid( + " KILL ", + grid, + Color::White, + Color::Byte(8), // Grey + Attr::Bold, + ((x, y), pos_inc(bottom_right, (0, 1))), + None, + ); + x = _x; + } + + if self.mode.is_active(FILTER_ACTIVE) { + write_string_to_grid( + " FILTER ", + grid, + Color::White, + Color::Byte(13), // Fuschia + Attr::Bold, + ((x, y), pos_inc(bottom_right, (0, 1))), + None, + ); + } + } + let mut y_offset = 0; + + if self.draw_tree { + self.draw_tree_list(grid, (upper_left, bottom_right), tick, pages, height); + self.height = self.data.tree.len(); + self.cursor = std::cmp::min(self.height, self.cursor); + } else { + let mut processes = self.processes.iter().collect::<Vec<&ProcessDisplay>>(); + processes.sort_unstable_by(|a, b| match self.sort { + Sort::CpuAsc => a.cpu_percent.cmp(&b.cpu_percent), + Sort::CpuDesc => b.cpu_percent.cmp(&a.cpu_percent), + Sort::VmRssAsc => a.vm_rss_value.cmp(&b.vm_rss_value), + Sort::VmRssDesc => b.vm_rss_value.cmp(&a.vm_rss_value), + Sort::UserAsc => a.username.0.cmp(&b.username.0), + Sort::UserDesc => b.username.0.cmp(&a.username.0), + Sort::CmdLineAsc => a.cmd_line.0.cmp(&b.cmd_line.0), + Sort::CmdLineDesc => b.cmd_line.0.cmp(&a.cmd_line.0), + }); + if self.mode.is_active(FILTER_ACTIVE) { + processes + .retain(|process| process.cmd_line.0.contains(self.mode.filter.as_str())); + } + self.height = processes.len(); + + self.cursor = if self.mode.is_active(SEARCH_ACTIVE) && self.mode.search.3 { + let (ref search, ref mut n, ref mut n_prev, _) = self.mode.search; + if *n != *n_prev || tick { + let mut ret = self.cursor; + if search.len() > 1 { + let mut ctr = 0; + for (i, p) in processes.iter().enumerate() { + if p |