refactor: more work towards unifying process code
A bunch of work towards also refactoring how the process widget gathers and converts data.
This commit is contained in:
parent
7ee6f6a737
commit
ed17264832
|
@ -25,10 +25,10 @@ doc = false
|
|||
|
||||
[profile.release]
|
||||
debug = 0
|
||||
strip = "symbols"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
strip = "symbols"
|
||||
|
||||
[features]
|
||||
default = ["fern", "log", "battery", "gpu"]
|
||||
|
|
385
src/app.rs
385
src/app.rs
|
@ -16,7 +16,8 @@ use layout_manager::*;
|
|||
pub use states::*;
|
||||
|
||||
use crate::{
|
||||
canvas, constants,
|
||||
constants,
|
||||
data_conversion::ConvertedData,
|
||||
options::Config,
|
||||
options::ConfigFlags,
|
||||
options::WidgetIdEnabled,
|
||||
|
@ -25,6 +26,8 @@ use crate::{
|
|||
Pid,
|
||||
};
|
||||
|
||||
use self::widgets::{ProcWidget, ProcWidgetMode};
|
||||
|
||||
pub mod data_farmer;
|
||||
pub mod data_harvester;
|
||||
pub mod layout_manager;
|
||||
|
@ -104,7 +107,7 @@ pub struct App {
|
|||
last_key_press: Instant,
|
||||
|
||||
#[builder(default, setter(skip))]
|
||||
pub canvas_data: canvas::DisplayableData,
|
||||
pub converted_data: ConvertedData,
|
||||
|
||||
#[builder(default, setter(skip))]
|
||||
pub data_collection: DataCollection,
|
||||
|
@ -302,55 +305,7 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
todo!()
|
||||
// FIXME: [Proc] Fix this.
|
||||
// // Do NOT allow when in tree mode!
|
||||
// if !proc_widget_state.is_tree_mode {
|
||||
// // Toggles process widget grouping state
|
||||
// proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);
|
||||
|
||||
// // Forcefully switch off column if we were on it...
|
||||
// if (proc_widget_state.is_grouped
|
||||
// && (proc_widget_state.process_sorting_type
|
||||
// == processes::ProcessSorting::Pid
|
||||
// || proc_widget_state.process_sorting_type
|
||||
// == processes::ProcessSorting::User
|
||||
// || proc_widget_state.process_sorting_type
|
||||
// == processes::ProcessSorting::State))
|
||||
// || (!proc_widget_state.is_grouped
|
||||
// && proc_widget_state.process_sorting_type
|
||||
// == processes::ProcessSorting::Count)
|
||||
// {
|
||||
// proc_widget_state.process_sorting_type =
|
||||
// processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
|
||||
// proc_widget_state.is_process_sort_descending = true;
|
||||
// }
|
||||
|
||||
// proc_widget_state.columns.set_to_sorted_index_from_type(
|
||||
// &proc_widget_state.process_sorting_type,
|
||||
// );
|
||||
|
||||
// proc_widget_state.columns.try_set(
|
||||
// &processes::ProcessSorting::State,
|
||||
// !(proc_widget_state.is_grouped),
|
||||
// );
|
||||
|
||||
// #[cfg(target_family = "unix")]
|
||||
// proc_widget_state.columns.try_set(
|
||||
// &processes::ProcessSorting::User,
|
||||
// !(proc_widget_state.is_grouped),
|
||||
// );
|
||||
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .toggle(&processes::ProcessSorting::Count);
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .toggle(&processes::ProcessSorting::Pid);
|
||||
|
||||
// proc_widget_state.requires_redraw = true;
|
||||
// self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
// }
|
||||
proc_widget_state.toggle_tab();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -388,11 +343,17 @@ impl App {
|
|||
_ => 0,
|
||||
};
|
||||
|
||||
if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) {
|
||||
proc_widget_state.is_sort_open = !proc_widget_state.is_sort_open;
|
||||
if let Some(pws) = self.proc_state.get_mut_widget_state(widget_id) {
|
||||
pws.is_sort_open = !pws.is_sort_open;
|
||||
pws.force_update = true;
|
||||
|
||||
// If the sort is now open, move left. Otherwise, if the proc sort was selected, force move right.
|
||||
if proc_widget_state.is_sort_open {
|
||||
if pws.is_sort_open {
|
||||
if let SortState::Sortable(st) = &pws.table_state.sort_state {
|
||||
pws.sort_table_state.current_scroll_position = st
|
||||
.current_index
|
||||
.clamp(0, pws.num_enabled_columns().saturating_sub(1));
|
||||
}
|
||||
self.move_widget_selection(&WidgetDirection::Left);
|
||||
} else if let BottomWidgetType::ProcSort = self.current_widget.widget_type {
|
||||
self.move_widget_selection(&WidgetDirection::Right);
|
||||
|
@ -411,10 +372,12 @@ impl App {
|
|||
};
|
||||
|
||||
if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) {
|
||||
proc_widget_state.is_process_sort_descending =
|
||||
!proc_widget_state.is_process_sort_descending;
|
||||
|
||||
proc_widget_state.force_update = true;
|
||||
if let SortState::Sortable(state) =
|
||||
&mut proc_widget_state.table_state.sort_state
|
||||
{
|
||||
state.toggle_order();
|
||||
proc_widget_state.force_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -432,9 +395,7 @@ impl App {
|
|||
.widget_states
|
||||
.get_mut(&self.current_widget.widget_id)
|
||||
{
|
||||
// FIXME: [Proc]Handle this!
|
||||
todo!();
|
||||
|
||||
proc_widget_state.toggle_mem_percentage();
|
||||
proc_widget_state.force_update = true;
|
||||
}
|
||||
}
|
||||
|
@ -605,37 +566,19 @@ impl App {
|
|||
.widget_states
|
||||
.get_mut(&(self.current_widget.widget_id))
|
||||
{
|
||||
// proc_widget_state.is_tree_mode = !proc_widget_state.is_tree_mode;
|
||||
|
||||
// // FIXME: For consistency, either disable tree mode if grouped, or allow grouped mode if in tree mode.
|
||||
// if proc_widget_state.is_tree_mode {
|
||||
// // Disable grouping if so!
|
||||
// proc_widget_state.is_grouped = false;
|
||||
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .try_enable(&processes::ProcessSorting::State);
|
||||
|
||||
// #[cfg(target_family = "unix")]
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .try_enable(&processes::ProcessSorting::User);
|
||||
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .try_disable(&processes::ProcessSorting::Count);
|
||||
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .try_enable(&processes::ProcessSorting::Pid);
|
||||
|
||||
// // We enabled... set PID sort type to ascending.
|
||||
// proc_widget_state.process_sorting_type = processes::ProcessSorting::Pid;
|
||||
// proc_widget_state.is_process_sort_descending = false;
|
||||
// }
|
||||
|
||||
// self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
// proc_widget_state.requires_redraw = true;
|
||||
match proc_widget_state.mode {
|
||||
ProcWidgetMode::Tree { .. } => {
|
||||
proc_widget_state.mode = ProcWidgetMode::Normal;
|
||||
proc_widget_state.force_update = true;
|
||||
}
|
||||
ProcWidgetMode::Normal => {
|
||||
proc_widget_state.mode = ProcWidgetMode::Tree {
|
||||
collapsed_pids: Default::default(),
|
||||
};
|
||||
proc_widget_state.force_update = true;
|
||||
}
|
||||
ProcWidgetMode::Grouped => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -672,8 +615,8 @@ impl App {
|
|||
.widget_states
|
||||
.get_mut(&(self.current_widget.widget_id - 2))
|
||||
{
|
||||
// TODO: [Proc] Handle this
|
||||
proc_widget_state.force_update = true;
|
||||
proc_widget_state.use_sort_table_value();
|
||||
self.move_widget_selection(&WidgetDirection::Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -891,7 +834,7 @@ impl App {
|
|||
}
|
||||
}
|
||||
BottomWidgetType::Battery => {
|
||||
if !self.canvas_data.battery_data.is_empty() {
|
||||
if !self.converted_data.battery_data.is_empty() {
|
||||
if let Some(battery_widget_state) = self
|
||||
.battery_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
|
@ -961,8 +904,8 @@ impl App {
|
|||
}
|
||||
}
|
||||
BottomWidgetType::Battery => {
|
||||
if !self.canvas_data.battery_data.is_empty() {
|
||||
let battery_count = self.canvas_data.battery_data.len();
|
||||
if !self.converted_data.battery_data.is_empty() {
|
||||
let battery_count = self.converted_data.battery_data.len();
|
||||
if let Some(battery_widget_state) = self
|
||||
.battery_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
|
@ -1207,40 +1150,36 @@ impl App {
|
|||
pub fn start_killing_process(&mut self) {
|
||||
self.reset_multi_tap_keys();
|
||||
|
||||
if let Some(proc_widget_state) = self
|
||||
if let Some(pws) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get(&self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(corresponding_filtered_process_list) = self
|
||||
.canvas_data
|
||||
.finalized_process_data_map
|
||||
.get(&self.current_widget.widget_id)
|
||||
if let Some(table_row) = pws
|
||||
.table_data
|
||||
.data
|
||||
.get(pws.table_state.current_scroll_position)
|
||||
{
|
||||
if proc_widget_state.table_state.current_scroll_position
|
||||
< corresponding_filtered_process_list.len()
|
||||
{
|
||||
todo!()
|
||||
// FIXME: [Proc] Handle this
|
||||
// let current_process: (String, Vec<Pid>);
|
||||
// if self.is_grouped(self.current_widget.widget_id) {
|
||||
// if let Some(process) = &corresponding_filtered_process_list
|
||||
// .get(proc_widget_state.table_state.current_scroll_position)
|
||||
// {
|
||||
// current_process = (process.name.to_string(), process.group_pids.clone())
|
||||
// } else {
|
||||
// return;
|
||||
// }
|
||||
// } else {
|
||||
// let process = corresponding_filtered_process_list
|
||||
// [proc_widget_state.table_state.current_scroll_position]
|
||||
// .clone();
|
||||
// current_process = (process.name.clone(), vec![process.pid])
|
||||
// };
|
||||
if let Some(col_value) = table_row.row().get(ProcWidget::PROC_NAME_OR_CMD) {
|
||||
let val = col_value.main_text().to_string();
|
||||
if pws.is_using_command() {
|
||||
if let Some(pids) = self.data_collection.process_data.cmd_pid_map.get(&val)
|
||||
{
|
||||
let current_process = (val, pids.clone());
|
||||
|
||||
// self.to_delete_process_list = Some(current_process);
|
||||
// self.delete_dialog_state.is_showing_dd = true;
|
||||
// self.is_determining_widget_boundary = true;
|
||||
self.to_delete_process_list = Some(current_process);
|
||||
self.delete_dialog_state.is_showing_dd = true;
|
||||
self.is_determining_widget_boundary = true;
|
||||
}
|
||||
} else if let Some(pids) =
|
||||
self.data_collection.process_data.name_pid_map.get(&val)
|
||||
{
|
||||
let current_process = (val, pids.clone());
|
||||
|
||||
self.to_delete_process_list = Some(current_process);
|
||||
self.delete_dialog_state.is_showing_dd = true;
|
||||
self.is_determining_widget_boundary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1421,8 +1360,7 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
// FIXME: [Proc] Handle this, this should toggle the CPU sorter.
|
||||
proc_widget_state.force_update = true;
|
||||
proc_widget_state.select_column(ProcWidget::CPU);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1432,8 +1370,7 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
// FIXME: [Proc] Handle this, this should toggle mem
|
||||
proc_widget_state.force_update = true;
|
||||
proc_widget_state.select_column(ProcWidget::MEM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1443,16 +1380,7 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
todo!()
|
||||
// FIXME: [Proc], this should handle pid
|
||||
// // Skip if grouped
|
||||
// if !proc_widget_state.is_grouped {
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .set_to_sorted_index_from_type(&processes::ProcessSorting::Pid);
|
||||
// proc_widget_state.update_sorting_with_columns();
|
||||
// self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
// }
|
||||
proc_widget_state.select_column(ProcWidget::PID_OR_COUNT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1462,26 +1390,7 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
// FIXME: [Proc] Handle this, this should toggle proc name/command
|
||||
// proc_widget_state.is_using_command = !proc_widget_state.is_using_command;
|
||||
// proc_widget_state
|
||||
// .toggle_command_and_name(proc_widget_state.is_using_command);
|
||||
|
||||
// match &proc_widget_state.process_sorting_type {
|
||||
// processes::ProcessSorting::Command
|
||||
// | processes::ProcessSorting::ProcessName => {
|
||||
// if proc_widget_state.is_using_command {
|
||||
// proc_widget_state.process_sorting_type =
|
||||
// processes::ProcessSorting::Command;
|
||||
// } else {
|
||||
// proc_widget_state.process_sorting_type =
|
||||
// processes::ProcessSorting::ProcessName;
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
proc_widget_state.requires_redraw = true;
|
||||
proc_widget_state.force_update = true;
|
||||
proc_widget_state.toggle_command();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1491,16 +1400,7 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
// FIXME: [Proc] Handle this, this should toggle name/column selection
|
||||
// proc_widget_state.columns.set_to_sorted_index_from_type(
|
||||
// &(if proc_widget_state.is_using_command {
|
||||
// processes::ProcessSorting::Command
|
||||
// } else {
|
||||
// processes::ProcessSorting::ProcessName
|
||||
// }),
|
||||
// );
|
||||
// proc_widget_state.update_sorting_with_columns();
|
||||
proc_widget_state.force_update = true;
|
||||
proc_widget_state.select_column(ProcWidget::PROC_NAME_OR_CMD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2103,18 +2003,9 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(finalized_process_data) = self
|
||||
.canvas_data
|
||||
.finalized_process_data_map
|
||||
.get(&self.current_widget.widget_id)
|
||||
{
|
||||
if !self.canvas_data.finalized_process_data_map.is_empty() {
|
||||
proc_widget_state.table_state.current_scroll_position =
|
||||
finalized_process_data.len() - 1;
|
||||
proc_widget_state.table_state.scroll_direction =
|
||||
ScrollDirection::Down;
|
||||
}
|
||||
}
|
||||
proc_widget_state.table_state.current_scroll_position =
|
||||
proc_widget_state.table_data.data.len().saturating_sub(1);
|
||||
proc_widget_state.table_state.scroll_direction = ScrollDirection::Down;
|
||||
}
|
||||
}
|
||||
BottomWidgetType::ProcSort => {
|
||||
|
@ -2132,9 +2023,9 @@ impl App {
|
|||
.temp_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if !self.canvas_data.temp_sensor_data.data.is_empty() {
|
||||
if !self.converted_data.temp_sensor_data.data.is_empty() {
|
||||
temp_widget_state.table_state.current_scroll_position =
|
||||
self.canvas_data.temp_sensor_data.data.len() - 1;
|
||||
self.converted_data.temp_sensor_data.data.len() - 1;
|
||||
temp_widget_state.table_state.scroll_direction = ScrollDirection::Down;
|
||||
}
|
||||
}
|
||||
|
@ -2144,9 +2035,9 @@ impl App {
|
|||
.disk_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if !self.canvas_data.disk_data.data.is_empty() {
|
||||
if !self.converted_data.disk_data.data.is_empty() {
|
||||
disk_widget_state.table_state.current_scroll_position =
|
||||
self.canvas_data.disk_data.data.len() - 1;
|
||||
self.converted_data.disk_data.data.len() - 1;
|
||||
disk_widget_state.table_state.scroll_direction = ScrollDirection::Down;
|
||||
}
|
||||
}
|
||||
|
@ -2156,7 +2047,7 @@ impl App {
|
|||
.cpu_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
||||
{
|
||||
let cap = self.canvas_data.cpu_data.len();
|
||||
let cap = self.converted_data.cpu_data.len();
|
||||
if cap > 0 {
|
||||
cpu_widget_state.table_state.current_scroll_position = cap - 1;
|
||||
cpu_widget_state.table_state.scroll_direction = ScrollDirection::Down;
|
||||
|
@ -2220,7 +2111,7 @@ impl App {
|
|||
{
|
||||
cpu_widget_state
|
||||
.table_state
|
||||
.update_position(num_to_change_by, self.canvas_data.cpu_data.len());
|
||||
.update_position(num_to_change_by, self.converted_data.cpu_data.len());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2230,17 +2121,9 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(finalized_process_data) = self
|
||||
.canvas_data
|
||||
.finalized_process_data_map
|
||||
.get(&self.current_widget.widget_id)
|
||||
{
|
||||
proc_widget_state
|
||||
.table_state
|
||||
.update_position(num_to_change_by, finalized_process_data.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
proc_widget_state
|
||||
.table_state
|
||||
.update_position(num_to_change_by, proc_widget_state.table_data.data.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -2254,7 +2137,7 @@ impl App {
|
|||
{
|
||||
temp_widget_state.table_state.update_position(
|
||||
num_to_change_by,
|
||||
self.canvas_data.temp_sensor_data.data.len(),
|
||||
self.converted_data.temp_sensor_data.data.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2267,7 +2150,7 @@ impl App {
|
|||
{
|
||||
disk_widget_state
|
||||
.table_state
|
||||
.update_position(num_to_change_by, self.canvas_data.disk_data.data.len());
|
||||
.update_position(num_to_change_by, self.converted_data.disk_data.data.len());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2347,31 +2230,12 @@ impl App {
|
|||
}
|
||||
|
||||
fn toggle_collapsing_process_branch(&mut self) {
|
||||
if let Some(proc_widget_state) = self
|
||||
if let Some(pws) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get_mut(&self.current_widget.widget_id)
|
||||
{
|
||||
let current_posn = proc_widget_state.table_state.current_scroll_position;
|
||||
|
||||
if let Some(displayed_process_list) = self
|
||||
.canvas_data
|
||||
.finalized_process_data_map
|
||||
.get(&self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(corresponding_process) = displayed_process_list.get(current_posn) {
|
||||
let corresponding_pid = corresponding_process.pid;
|
||||
|
||||
if let Some(process_data) = self
|
||||
.canvas_data
|
||||
.single_process_data
|
||||
.get_mut(&corresponding_pid)
|
||||
{
|
||||
process_data.is_collapsed_entry = !process_data.is_collapsed_entry;
|
||||
proc_widget_state.force_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
pws.toggle_tree_branch();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2756,29 +2620,31 @@ impl App {
|
|||
// the same entry as the already selected one - if it is,
|
||||
// then we minimize.
|
||||
|
||||
let is_tree_mode = matches!(
|
||||
proc_widget_state.mode,
|
||||
ProcWidgetMode::Tree { .. }
|
||||
);
|
||||
|
||||
let previous_scroll_position = proc_widget_state
|
||||
.table_state
|
||||
.current_scroll_position;
|
||||
|
||||
// FIXME: [Proc] Handle this, tree mode code?
|
||||
// let is_tree_mode = proc_widget_state.is_tree_mode;
|
||||
let new_position = self.change_process_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
);
|
||||
|
||||
// let new_position = self.change_process_position(
|
||||
// offset_clicked_entry as i64 - visual_index as i64,
|
||||
// );
|
||||
|
||||
// if is_tree_mode {
|
||||
// if let Some(new_position) = new_position {
|
||||
// if previous_scroll_position == new_position {
|
||||
// self.toggle_collapsing_process_branch();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if is_tree_mode {
|
||||
if let Some(new_position) = new_position {
|
||||
if previous_scroll_position == new_position {
|
||||
self.toggle_collapsing_process_branch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::ProcSort => {
|
||||
// TODO: This should sort if you double click!
|
||||
// TODO: [Feature] This could sort if you double click!
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.get_widget_state(self.current_widget.widget_id - 2)
|
||||
|
@ -2842,48 +2708,19 @@ impl App {
|
|||
// We might have clicked on a header! Check if we only exceeded the table + border offset, and
|
||||
// it's implied we exceeded the gap offset.
|
||||
if clicked_entry == border_offset {
|
||||
#[allow(clippy::single_match)]
|
||||
match &self.current_widget.widget_type {
|
||||
BottomWidgetType::Proc => {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
if let BottomWidgetType::Proc = &self.current_widget.widget_type {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let SortState::Sortable(st) =
|
||||
&mut proc_widget_state.table_state.sort_state
|
||||
{
|
||||
// Let's now check if it's a column header.
|
||||
|
||||
// FIXME: [Proc] Handle column header sorting!
|
||||
|
||||
// if let (Some(y_loc), Some(x_locs)) = (
|
||||
// &proc_widget_state.columns.column_header_y_loc,
|
||||
// &proc_widget_state.columns.column_header_x_locs,
|
||||
// ) {
|
||||
// // debug!("x, y: {}, {}", x, y);
|
||||
// // debug!("y_loc: {}", y_loc);
|
||||
// // debug!("x_locs: {:?}", x_locs);
|
||||
|
||||
// if y == *y_loc {
|
||||
// for (itx, (x_left, x_right)) in
|
||||
// x_locs.iter().enumerate()
|
||||
// {
|
||||
// if x >= *x_left && x <= *x_right {
|
||||
// // Found our column!
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .set_to_sorted_index_from_visual_index(
|
||||
// itx,
|
||||
// );
|
||||
// proc_widget_state
|
||||
// .update_sorting_with_columns();
|
||||
// self.proc_state.force_update =
|
||||
// Some(self.current_widget.widget_id);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if st.try_select_location(x, y).is_some() {
|
||||
proc_widget_state.force_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use fxhash::FxHashMap;
|
||||
use itertools::Itertools;
|
||||
/// In charge of cleaning, processing, and managing data. I couldn't think of
|
||||
/// a better name for the file. Since I called data collection "harvesting",
|
||||
/// then this is the farmer I guess.
|
||||
|
@ -20,8 +22,9 @@ use std::{time::Instant, vec::Vec};
|
|||
use crate::data_harvester::batteries;
|
||||
|
||||
use crate::{
|
||||
data_harvester::{cpu, disks, memory, network, processes, temperature, Data},
|
||||
data_harvester::{cpu, disks, memory, network, processes::ProcessHarvest, temperature, Data},
|
||||
utils::gen_util::{get_decimal_bytes, GIGA_LIMIT},
|
||||
Pid,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
|
@ -38,6 +41,97 @@ pub struct TimedData {
|
|||
pub swap_data: Option<Value>,
|
||||
}
|
||||
|
||||
pub type StringPidMap = FxHashMap<String, Vec<Pid>>;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ProcessData {
|
||||
/// A PID to process data map.
|
||||
pub process_harvest: FxHashMap<Pid, ProcessHarvest>,
|
||||
|
||||
/// A mapping from a process name to any PID with that name.
|
||||
pub name_pid_map: StringPidMap,
|
||||
|
||||
/// A mapping from a process command to any PID with that name.
|
||||
pub cmd_pid_map: StringPidMap,
|
||||
|
||||
/// A mapping between a process PID to any children process PIDs.
|
||||
pub process_parent_mapping: FxHashMap<Pid, Vec<Pid>>,
|
||||
|
||||
/// PIDs corresponding to processes that have no parents.
|
||||
pub orphan_pids: Vec<Pid>,
|
||||
}
|
||||
|
||||
impl ProcessData {
|
||||
fn ingest(&mut self, list_of_processes: Vec<ProcessHarvest>) {
|
||||
// TODO: [Optimization] Probably more efficient to all of this in the data collection step, but it's fine for now.
|
||||
self.name_pid_map.clear();
|
||||
self.cmd_pid_map.clear();
|
||||
self.process_parent_mapping.clear();
|
||||
|
||||
// Reverse as otherwise the pid mappings are in the wrong order.
|
||||
list_of_processes.iter().rev().for_each(|process_harvest| {
|
||||
if let Some(entry) = self.name_pid_map.get_mut(&process_harvest.name) {
|
||||
entry.push(process_harvest.pid);
|
||||
} else {
|
||||
self.name_pid_map
|
||||
.insert(process_harvest.name.to_string(), vec![process_harvest.pid]);
|
||||
}
|
||||
|
||||
if let Some(entry) = self.cmd_pid_map.get_mut(&process_harvest.command) {
|
||||
entry.push(process_harvest.pid);
|
||||
} else {
|
||||
self.cmd_pid_map.insert(
|
||||
process_harvest.command.to_string(),
|
||||
vec![process_harvest.pid],
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(parent_pid) = process_harvest.parent_pid {
|
||||
if let Some(entry) = self.process_parent_mapping.get_mut(&parent_pid) {
|
||||
entry.push(process_harvest.pid);
|
||||
} else {
|
||||
self.process_parent_mapping
|
||||
.insert(parent_pid, vec![process_harvest.pid]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.name_pid_map.shrink_to_fit();
|
||||
self.cmd_pid_map.shrink_to_fit();
|
||||
self.process_parent_mapping.shrink_to_fit();
|
||||
|
||||
let process_pid_map = list_of_processes
|
||||
.into_iter()
|
||||
.map(|process| (process.pid, process))
|
||||
.collect();
|
||||
self.process_harvest = process_pid_map;
|
||||
|
||||
// This also needs a quick sort + reverse to be in the correct order.
|
||||
self.orphan_pids = {
|
||||
let mut res: Vec<Pid> = self
|
||||
.process_harvest
|
||||
.iter()
|
||||
.filter_map(|(pid, process_harvest)| {
|
||||
if let Some(parent_pid) = process_harvest.parent_pid {
|
||||
if self.process_harvest.contains_key(&parent_pid) {
|
||||
None
|
||||
} else {
|
||||
Some(*pid)
|
||||
}
|
||||
} else {
|
||||
Some(*pid)
|
||||
}
|
||||
})
|
||||
.sorted()
|
||||
.collect();
|
||||
|
||||
res.reverse();
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AppCollection represents the pooled data stored within the main app
|
||||
/// thread. Basically stores a (occasionally cleaned) record of the data
|
||||
/// collected, and what is needed to convert into a displayable form.
|
||||
|
@ -57,7 +151,7 @@ pub struct DataCollection {
|
|||
pub swap_harvest: memory::MemHarvest,
|
||||
pub cpu_harvest: cpu::CpuHarvest,
|
||||
pub load_avg_harvest: cpu::LoadAvgHarvest,
|
||||
pub process_harvest: Vec<processes::ProcessHarvest>,
|
||||
pub process_data: ProcessData,
|
||||
pub disk_harvest: Vec<disks::DiskHarvest>,
|
||||
pub io_harvest: disks::IoHarvest,
|
||||
pub io_labels_and_prev: Vec<((u64, u64), (u64, u64))>,
|
||||
|
@ -78,7 +172,7 @@ impl Default for DataCollection {
|
|||
swap_harvest: memory::MemHarvest::default(),
|
||||
cpu_harvest: cpu::CpuHarvest::default(),
|
||||
load_avg_harvest: cpu::LoadAvgHarvest::default(),
|
||||
process_harvest: Vec::default(),
|
||||
process_data: Default::default(),
|
||||
disk_harvest: Vec::default(),
|
||||
io_harvest: disks::IoHarvest::default(),
|
||||
io_labels_and_prev: Vec::default(),
|
||||
|
@ -97,7 +191,7 @@ impl DataCollection {
|
|||
self.memory_harvest = memory::MemHarvest::default();
|
||||
self.swap_harvest = memory::MemHarvest::default();
|
||||
self.cpu_harvest = cpu::CpuHarvest::default();
|
||||
self.process_harvest = Vec::default();
|
||||
self.process_data = Default::default();
|
||||
self.disk_harvest = Vec::default();
|
||||
self.io_harvest = disks::IoHarvest::default();
|
||||
self.io_labels_and_prev = Vec::default();
|
||||
|
@ -323,8 +417,8 @@ impl DataCollection {
|
|||
self.io_harvest = io;
|
||||
}
|
||||
|
||||
fn eat_proc(&mut self, list_of_processes: Vec<processes::ProcessHarvest>) {
|
||||
self.process_harvest = list_of_processes;
|
||||
fn eat_proc(&mut self, list_of_processes: Vec<ProcessHarvest>) {
|
||||
self.process_data.ingest(list_of_processes);
|
||||
}
|
||||
|
||||
#[cfg(feature = "battery")]
|
||||
|
|
|
@ -104,6 +104,9 @@ pub struct DataCollector {
|
|||
#[cfg(feature = "battery")]
|
||||
battery_list: Option<Vec<Battery>>,
|
||||
filters: DataFilters,
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
user_table: self::processes::UserTable,
|
||||
}
|
||||
|
||||
impl DataCollector {
|
||||
|
@ -133,6 +136,8 @@ impl DataCollector {
|
|||
#[cfg(feature = "battery")]
|
||||
battery_list: None,
|
||||
filters,
|
||||
#[cfg(target_family = "unix")]
|
||||
user_table: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +196,7 @@ impl DataCollector {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn set_collected_data(&mut self, used_widgets: UsedWidgets) {
|
||||
pub fn set_data_collection(&mut self, used_widgets: UsedWidgets) {
|
||||
self.widgets_to_harvest = used_widgets;
|
||||
}
|
||||
|
||||
|
@ -270,6 +275,7 @@ impl DataCollector {
|
|||
.duration_since(self.last_collection_time)
|
||||
.as_secs(),
|
||||
self.mem_total_kb,
|
||||
&mut self.user_table,
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use super::NetworkHarvest;
|
||||
use std::time::Instant;
|
||||
|
||||
// FIXME: Eventually make it so that this thing also takes individual usage into account, so we can allow for showing per-interface!
|
||||
// FIXME: Eventually make it so that this thing also takes individual usage into account, so we can show per-interface!
|
||||
pub async fn get_network_data(
|
||||
prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64,
|
||||
curr_time: Instant, actually_get: bool, filter: &Option<crate::app::Filter>,
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::collections::hash_map::Entry;
|
|||
use crate::utils::error::{self, BottomError};
|
||||
use crate::Pid;
|
||||
|
||||
use super::ProcessHarvest;
|
||||
use super::{ProcessHarvest, UserTable};
|
||||
|
||||
use sysinfo::ProcessStatus;
|
||||
|
||||
|
@ -120,6 +120,7 @@ fn get_linux_cpu_usage(
|
|||
fn read_proc(
|
||||
prev_proc: &PrevProcDetails, stat: &Stat, cpu_usage: f64, cpu_fraction: f64,
|
||||
use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64,
|
||||
user_table: &mut UserTable,
|
||||
) -> error::Result<(ProcessHarvest, u64)> {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
@ -156,7 +157,10 @@ fn read_proc(
|
|||
};
|
||||
|
||||
let process_state_char = stat.state;
|
||||
let process_state = ProcessStatus::from(process_state_char).to_string();
|
||||
let process_state = (
|
||||
ProcessStatus::from(process_state_char).to_string(),
|
||||
process_state_char,
|
||||
);
|
||||
let (cpu_usage_percent, new_process_times) = get_linux_cpu_usage(
|
||||
stat,
|
||||
cpu_usage,
|
||||
|
@ -199,7 +203,7 @@ fn read_proc(
|
|||
(0, 0, 0, 0)
|
||||
};
|
||||
|
||||
let uid = Some(process.owner);
|
||||
let uid = process.owner;
|
||||
|
||||
Ok((
|
||||
ProcessHarvest {
|
||||
|
@ -215,8 +219,11 @@ fn read_proc(
|
|||
total_read_bytes,
|
||||
total_write_bytes,
|
||||
process_state,
|
||||
process_state_char,
|
||||
uid,
|
||||
user: user_table
|
||||
.get_uid_to_username_mapping(uid)
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|_| "N/A".into()),
|
||||
},
|
||||
new_process_times,
|
||||
))
|
||||
|
@ -225,7 +232,7 @@ fn read_proc(
|
|||
pub fn get_process_data(
|
||||
prev_idle: &mut f64, prev_non_idle: &mut f64,
|
||||
pid_mapping: &mut FxHashMap<Pid, PrevProcDetails>, use_current_cpu_total: bool,
|
||||
time_difference_in_secs: u64, mem_total_kb: u64,
|
||||
time_difference_in_secs: u64, mem_total_kb: u64, user_table: &mut UserTable,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
// TODO: [PROC THREADS] Add threads
|
||||
|
||||
|
@ -268,6 +275,7 @@ pub fn get_process_data(
|
|||
use_current_cpu_total,
|
||||
time_difference_in_secs,
|
||||
mem_total_kb,
|
||||
user_table,
|
||||
) {
|
||||
prev_proc_details.cpu_time = new_process_times;
|
||||
prev_proc_details.total_read_bytes =
|
||||
|
|
|
@ -104,7 +104,7 @@ pub fn get_process_data(
|
|||
total_write_bytes: disk_usage.total_written_bytes,
|
||||
process_state: process_val.status().to_string(),
|
||||
process_state_char: convert_process_status_to_char(process_val.status()),
|
||||
uid: Some(process_val.uid),
|
||||
uid: process_val.uid,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,56 +23,10 @@ cfg_if::cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::Pid;
|
||||
|
||||
// TODO: Add value so we know if it's sorted ascending or descending by default?
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum ProcessSorting {
|
||||
CpuPercent,
|
||||
Mem,
|
||||
MemPercent,
|
||||
Pid,
|
||||
ProcessName,
|
||||
Command,
|
||||
ReadPerSecond,
|
||||
WritePerSecond,
|
||||
TotalRead,
|
||||
TotalWrite,
|
||||
State,
|
||||
User,
|
||||
Count,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProcessSorting {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match &self {
|
||||
ProcessSorting::CpuPercent => "CPU%",
|
||||
ProcessSorting::MemPercent => "Mem%",
|
||||
ProcessSorting::Mem => "Mem",
|
||||
ProcessSorting::ReadPerSecond => "R/s",
|
||||
ProcessSorting::WritePerSecond => "W/s",
|
||||
ProcessSorting::TotalRead => "T.Read",
|
||||
ProcessSorting::TotalWrite => "T.Write",
|
||||
ProcessSorting::State => "State",
|
||||
ProcessSorting::ProcessName => "Name",
|
||||
ProcessSorting::Command => "Command",
|
||||
ProcessSorting::Pid => "PID",
|
||||
ProcessSorting::Count => "Count",
|
||||
ProcessSorting::User => "User",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProcessSorting {
|
||||
fn default() -> Self {
|
||||
ProcessSorting::CpuPercent
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessHarvest {
|
||||
/// The pid of the process.
|
||||
|
@ -109,14 +63,28 @@ pub struct ProcessHarvest {
|
|||
pub total_write_bytes: u64,
|
||||
|
||||
/// The current state of the process (e.g. zombie, asleep)
|
||||
pub process_state: String,
|
||||
pub process_state: (String, char),
|
||||
|
||||
/// The process state represented by a character. TODO: Merge with above as a single struct.
|
||||
pub process_state_char: char,
|
||||
|
||||
/// This is the *effective* user ID of the process.
|
||||
/// This is the *effective* user ID of the process. This is only used on Unix platforms.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub uid: Option<libc::uid_t>,
|
||||
pub uid: libc::uid_t,
|
||||
|
||||
/// This is the process' user. This is only used on Unix platforms.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub user: Cow<'static, str>,
|
||||
// TODO: Additional fields
|
||||
// pub rss_kb: u64,
|
||||
// pub virt_kb: u64,
|
||||
}
|
||||
|
||||
impl ProcessHarvest {
|
||||
pub(crate) fn add(&mut self, rhs: &ProcessHarvest) {
|
||||
self.cpu_usage_percent += rhs.cpu_usage_percent;
|
||||
self.mem_usage_bytes += rhs.mem_usage_bytes;
|
||||
self.mem_usage_percent += rhs.mem_usage_percent;
|
||||
self.read_bytes_per_sec += rhs.read_bytes_per_sec;
|
||||
self.write_bytes_per_sec += rhs.write_bytes_per_sec;
|
||||
self.total_read_bytes += rhs.total_read_bytes;
|
||||
self.total_write_bytes += rhs.total_write_bytes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
//! Unix-specific parts of process collection.
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
||||
use crate::utils::error;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct UserTable {
|
||||
pub uid_user_mapping: std::collections::HashMap<libc::uid_t, String>,
|
||||
pub uid_user_mapping: FxHashMap<libc::uid_t, String>,
|
||||
}
|
||||
|
||||
impl UserTable {
|
||||
|
|
777
src/app/query.rs
777
src/app/query.rs
|
@ -1,450 +1,434 @@
|
|||
use crate::{
|
||||
data_conversion::ConvertedProcessData,
|
||||
utils::error::{
|
||||
BottomError::{self, QueryError},
|
||||
Result,
|
||||
},
|
||||
use crate::utils::error::{
|
||||
BottomError::{self, QueryError},
|
||||
Result,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
use std::{borrow::Cow, collections::VecDeque};
|
||||
|
||||
use super::widgets::ProcWidget;
|
||||
use super::data_harvester::processes::ProcessHarvest;
|
||||
|
||||
const DELIMITER_LIST: [char; 6] = ['=', '>', '<', '(', ')', '\"'];
|
||||
const COMPARISON_LIST: [&str; 3] = [">", "=", "<"];
|
||||
const OR_LIST: [&str; 2] = ["or", "||"];
|
||||
const AND_LIST: [&str; 2] = ["and", "&&"];
|
||||
|
||||
/// I only separated this as otherwise, the states.rs file gets huge... and this should
|
||||
/// belong in another file anyways, IMO.
|
||||
pub trait ProcessQuery {
|
||||
/// In charge of parsing the given query.
|
||||
/// We are defining the following language for a query (case-insensitive prefixes):
|
||||
///
|
||||
/// - Process names: No prefix required, can use regex, match word, or case.
|
||||
/// Enclosing anything, including prefixes, in quotes, means we treat it as an entire process
|
||||
/// rather than a prefix.
|
||||
/// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant).
|
||||
/// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare.
|
||||
/// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare.
|
||||
/// - STATE: Use prefix `state`, can use regex, match word, or case.
|
||||
/// - USER: Use prefix `user`, can use regex, match word, or case.
|
||||
/// - Read/s: Use prefix `r`. Can compare.
|
||||
/// - Write/s: Use prefix `w`. Can compare.
|
||||
/// - Total read: Use prefix `read`. Can compare.
|
||||
/// - Total write: Use prefix `write`. Can compare.
|
||||
///
|
||||
/// For queries, whitespaces are our delimiters. We will merge together any adjacent non-prefixed
|
||||
/// or quoted elements after splitting to treat as process names.
|
||||
/// Furthermore, we want to support boolean joiners like AND and OR, and brackets.
|
||||
fn parse_query(&self) -> Result<Query>;
|
||||
}
|
||||
/// In charge of parsing the given query.
|
||||
/// We are defining the following language for a query (case-insensitive prefixes):
|
||||
///
|
||||
/// - Process names: No prefix required, can use regex, match word, or case.
|
||||
/// Enclosing anything, including prefixes, in quotes, means we treat it as an entire process
|
||||
/// rather than a prefix.
|
||||
/// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant).
|
||||
/// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare.
|
||||
/// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare.
|
||||
/// - STATE: Use prefix `state`, can use regex, match word, or case.
|
||||
/// - USER: Use prefix `user`, can use regex, match word, or case.
|
||||
/// - Read/s: Use prefix `r`. Can compare.
|
||||
/// - Write/s: Use prefix `w`. Can compare.
|
||||
/// - Total read: Use prefix `read`. Can compare.
|
||||
/// - Total write: Use prefix `write`. Can compare.
|
||||
///
|
||||
/// For queries, whitespaces are our delimiters. We will merge together any adjacent non-prefixed
|
||||
/// or quoted elements after splitting to treat as process names.
|
||||
/// Furthermore, we want to support boolean joiners like AND and OR, and brackets.
|
||||
pub fn parse_query(
|
||||
search_query: &str, is_searching_whole_word: bool, is_ignoring_case: bool,
|
||||
is_searching_with_regex: bool,
|
||||
) -> Result<Query> {
|
||||
fn process_string_to_filter(query: &mut VecDeque<String>) -> Result<Query> {
|
||||
let lhs = process_or(query)?;
|
||||
let mut list_of_ors = vec![lhs];
|
||||
|
||||
impl ProcessQuery for ProcWidget {
|
||||
fn parse_query(&self) -> Result<Query> {
|
||||
fn process_string_to_filter(query: &mut VecDeque<String>) -> Result<Query> {
|
||||
let lhs = process_or(query)?;
|
||||
let mut list_of_ors = vec![lhs];
|
||||
|
||||
while query.front().is_some() {
|
||||
list_of_ors.push(process_or(query)?);
|
||||
}
|
||||
|
||||
Ok(Query { query: list_of_ors })
|
||||
while query.front().is_some() {
|
||||
list_of_ors.push(process_or(query)?);
|
||||
}
|
||||
|
||||
fn process_or(query: &mut VecDeque<String>) -> Result<Or> {
|
||||
let mut lhs = process_and(query)?;
|
||||
let mut rhs: Option<Box<And>> = None;
|
||||
Ok(Query { query: list_of_ors })
|
||||
}
|
||||
|
||||
while let Some(queue_top) = query.front() {
|
||||
// debug!("OR QT: {:?}", queue_top);
|
||||
if OR_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
query.pop_front();
|
||||
rhs = Some(Box::new(process_and(query)?));
|
||||
fn process_or(query: &mut VecDeque<String>) -> Result<Or> {
|
||||
let mut lhs = process_and(query)?;
|
||||
let mut rhs: Option<Box<And>> = None;
|
||||
|
||||
if let Some(queue_next) = query.front() {
|
||||
if OR_LIST.contains(&queue_next.to_lowercase().as_str()) {
|
||||
// Must merge LHS and RHS
|
||||
lhs = And {
|
||||
lhs: Prefix {
|
||||
or: Some(Box::new(Or { lhs, rhs })),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
},
|
||||
rhs: None,
|
||||
};
|
||||
rhs = None;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
return Err(QueryError(Cow::Borrowed("Comparison not valid here")));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while let Some(queue_top) = query.front() {
|
||||
// debug!("OR QT: {:?}", queue_top);
|
||||
if OR_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
query.pop_front();
|
||||
rhs = Some(Box::new(process_and(query)?));
|
||||
|
||||
Ok(Or { lhs, rhs })
|
||||
}
|
||||
|
||||
fn process_and(query: &mut VecDeque<String>) -> Result<And> {
|
||||
let mut lhs = process_prefix(query, false)?;
|
||||
let mut rhs: Option<Box<Prefix>> = None;
|
||||
|
||||
while let Some(queue_top) = query.front() {
|
||||
// debug!("AND QT: {:?}", queue_top);
|
||||
if AND_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
query.pop_front();
|
||||
|
||||
rhs = Some(Box::new(process_prefix(query, false)?));
|
||||
|
||||
if let Some(next_queue_top) = query.front() {
|
||||
if AND_LIST.contains(&next_queue_top.to_lowercase().as_str()) {
|
||||
// Must merge LHS and RHS
|
||||
lhs = Prefix {
|
||||
or: Some(Box::new(Or {
|
||||
lhs: And { lhs, rhs },
|
||||
rhs: None,
|
||||
})),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
};
|
||||
rhs = None;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
return Err(QueryError(Cow::Borrowed("Comparison not valid here")));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(And { lhs, rhs })
|
||||
}
|
||||
|
||||
fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
|
||||
if let Some(queue_top) = query.pop_front() {
|
||||
if inside_quotation {
|
||||
if queue_top == "\"" {
|
||||
// This means we hit something like "". Return an empty prefix, and to deal with
|
||||
// the close quote checker, add one to the top of the stack. Ugly fix but whatever.
|
||||
query.push_front("\"".to_string());
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((
|
||||
PrefixType::Name,
|
||||
StringQuery::Value(String::default()),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
} else {
|
||||
let mut quoted_string = queue_top;
|
||||
while let Some(next_str) = query.front() {
|
||||
if next_str == "\"" {
|
||||
// Stop!
|
||||
break;
|
||||
} else {
|
||||
quoted_string.push_str(next_str);
|
||||
query.pop_front();
|
||||
}
|
||||
}
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((
|
||||
PrefixType::Name,
|
||||
StringQuery::Value(quoted_string),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
} else if queue_top == "(" {
|
||||
if query.is_empty() {
|
||||
return Err(QueryError(Cow::Borrowed("Missing closing parentheses")));
|
||||
}
|
||||
|
||||
let mut list_of_ors = VecDeque::new();
|
||||
|
||||
while let Some(in_paren_query_top) = query.front() {
|
||||
if in_paren_query_top != ")" {
|
||||
list_of_ors.push_back(process_or(query)?);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure not empty
|
||||
if list_of_ors.is_empty() {
|
||||
return Err(QueryError("No values within parentheses group".into()));
|
||||
}
|
||||
|
||||
// Now convert this back to a OR...
|
||||
let initial_or = Or {
|
||||
lhs: And {
|
||||
if let Some(queue_next) = query.front() {
|
||||
if OR_LIST.contains(&queue_next.to_lowercase().as_str()) {
|
||||
// Must merge LHS and RHS
|
||||
lhs = And {
|
||||
lhs: Prefix {
|
||||
or: list_of_ors.pop_front().map(Box::new),
|
||||
compare_prefix: None,
|
||||
or: Some(Box::new(Or { lhs, rhs })),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
},
|
||||
rhs: None,
|
||||
},
|
||||
rhs: None,
|
||||
};
|
||||
let returned_or = list_of_ors.into_iter().fold(initial_or, |lhs, rhs| Or {
|
||||
lhs: And {
|
||||
lhs: Prefix {
|
||||
or: Some(Box::new(lhs)),
|
||||
compare_prefix: None,
|
||||
regex_prefix: None,
|
||||
},
|
||||
rhs: Some(Box::new(Prefix {
|
||||
or: Some(Box::new(rhs)),
|
||||
compare_prefix: None,
|
||||
regex_prefix: None,
|
||||
})),
|
||||
},
|
||||
rhs: None,
|
||||
});
|
||||
};
|
||||
rhs = None;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
return Err(QueryError(Cow::Borrowed("Comparison not valid here")));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(close_paren) = query.pop_front() {
|
||||
if close_paren == ")" {
|
||||
return Ok(Prefix {
|
||||
or: Some(Box::new(returned_or)),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
});
|
||||
Ok(Or { lhs, rhs })
|
||||
}
|
||||
|
||||
fn process_and(query: &mut VecDeque<String>) -> Result<And> {
|
||||
let mut lhs = process_prefix(query, false)?;
|
||||
let mut rhs: Option<Box<Prefix>> = None;
|
||||
|
||||
while let Some(queue_top) = query.front() {
|
||||
// debug!("AND QT: {:?}", queue_top);
|
||||
if AND_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
query.pop_front();
|
||||
|
||||
rhs = Some(Box::new(process_prefix(query, false)?));
|
||||
|
||||
if let Some(next_queue_top) = query.front() {
|
||||
if AND_LIST.contains(&next_queue_top.to_lowercase().as_str()) {
|
||||
// Must merge LHS and RHS
|
||||
lhs = Prefix {
|
||||
or: Some(Box::new(Or {
|
||||
lhs: And { lhs, rhs },
|
||||
rhs: None,
|
||||
})),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
};
|
||||
rhs = None;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
|
||||
return Err(QueryError(Cow::Borrowed("Comparison not valid here")));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(And { lhs, rhs })
|
||||
}
|
||||
|
||||
fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
|
||||
if let Some(queue_top) = query.pop_front() {
|
||||
if inside_quotation {
|
||||
if queue_top == "\"" {
|
||||
// This means we hit something like "". Return an empty prefix, and to deal with
|
||||
// the close quote checker, add one to the top of the stack. Ugly fix but whatever.
|
||||
query.push_front("\"".to_string());
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((
|
||||
PrefixType::Name,
|
||||
StringQuery::Value(String::default()),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
} else {
|
||||
let mut quoted_string = queue_top;
|
||||
while let Some(next_str) = query.front() {
|
||||
if next_str == "\"" {
|
||||
// Stop!
|
||||
break;
|
||||
} else {
|
||||
return Err(QueryError("Missing closing parentheses".into()));
|
||||
quoted_string.push_str(next_str);
|
||||
query.pop_front();
|
||||
}
|
||||
}
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((PrefixType::Name, StringQuery::Value(quoted_string))),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
} else if queue_top == "(" {
|
||||
if query.is_empty() {
|
||||
return Err(QueryError(Cow::Borrowed("Missing closing parentheses")));
|
||||
}
|
||||
|
||||
let mut list_of_ors = VecDeque::new();
|
||||
|
||||
while let Some(in_paren_query_top) = query.front() {
|
||||
if in_paren_query_top != ")" {
|
||||
list_of_ors.push_back(process_or(query)?);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure not empty
|
||||
if list_of_ors.is_empty() {
|
||||
return Err(QueryError("No values within parentheses group".into()));
|
||||
}
|
||||
|
||||
// Now convert this back to a OR...
|
||||
let initial_or = Or {
|
||||
lhs: And {
|
||||
lhs: Prefix {
|
||||
or: list_of_ors.pop_front().map(Box::new),
|
||||
compare_prefix: None,
|
||||
regex_prefix: None,
|
||||
},
|
||||
rhs: None,
|
||||
},
|
||||
rhs: None,
|
||||
};
|
||||
let returned_or = list_of_ors.into_iter().fold(initial_or, |lhs, rhs| Or {
|
||||
lhs: And {
|
||||
lhs: Prefix {
|
||||
or: Some(Box::new(lhs)),
|
||||
compare_prefix: None,
|
||||
regex_prefix: None,
|
||||
},
|
||||
rhs: Some(Box::new(Prefix {
|
||||
or: Some(Box::new(rhs)),
|
||||
compare_prefix: None,
|
||||
regex_prefix: None,
|
||||
})),
|
||||
},
|
||||
rhs: None,
|
||||
});
|
||||
|
||||
if let Some(close_paren) = query.pop_front() {
|
||||
if close_paren == ")" {
|
||||
return Ok(Prefix {
|
||||
or: Some(Box::new(returned_or)),
|
||||
regex_prefix: None,
|
||||
compare_prefix: None,
|
||||
});
|
||||
} else {
|
||||
return Err(QueryError("Missing closing parentheses".into()));
|
||||
}
|
||||
} else if queue_top == ")" {
|
||||
return Err(QueryError("Missing opening parentheses".into()));
|
||||
} else if queue_top == "\"" {
|
||||
// Similar to parentheses, trap and check for missing closing quotes. Note, however, that we
|
||||
// will DIRECTLY call another process_prefix call...
|
||||
} else {
|
||||
return Err(QueryError("Missing closing parentheses".into()));
|
||||
}
|
||||
} else if queue_top == ")" {
|
||||
return Err(QueryError("Missing opening parentheses".into()));
|
||||
} else if queue_top == "\"" {
|
||||
// Similar to parentheses, trap and check for missing closing quotes. Note, however, that we
|
||||
// will DIRECTLY call another process_prefix call...
|
||||
|
||||
let prefix = process_prefix(query, true)?;
|
||||
if let Some(close_paren) = query.pop_front() {
|
||||
if close_paren == "\"" {
|
||||
return Ok(prefix);
|
||||
} else {
|
||||
return Err(QueryError("Missing closing quotation".into()));
|
||||
}
|
||||
let prefix = process_prefix(query, true)?;
|
||||
if let Some(close_paren) = query.pop_front() {
|
||||
if close_paren == "\"" {
|
||||
return Ok(prefix);
|
||||
} else {
|
||||
return Err(QueryError("Missing closing quotation".into()));
|
||||
}
|
||||
} else {
|
||||
// Get prefix type...
|
||||
let prefix_type = queue_top.parse::<PrefixType>()?;
|
||||
let content = if let PrefixType::Name = prefix_type {
|
||||
Some(queue_top)
|
||||
} else {
|
||||
query.pop_front()
|
||||
};
|
||||
return Err(QueryError("Missing closing quotation".into()));
|
||||
}
|
||||
} else {
|
||||
// Get prefix type...
|
||||
let prefix_type = queue_top.parse::<PrefixType>()?;
|
||||
let content = if let PrefixType::Name = prefix_type {
|
||||
Some(queue_top)
|
||||
} else {
|
||||
query.pop_front()
|
||||
};
|
||||
|
||||
if let Some(content) = content {
|
||||
match &prefix_type {
|
||||
PrefixType::Name => {
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((prefix_type, StringQuery::Value(content))),
|
||||
compare_prefix: None,
|
||||
})
|
||||
}
|
||||
PrefixType::Pid | PrefixType::State | PrefixType::User => {
|
||||
// We have to check if someone put an "="...
|
||||
if content == "=" {
|
||||
// Check next string if possible
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
// TODO: Need to consider the following cases:
|
||||
// - (test)
|
||||
// - (test
|
||||
// - test)
|
||||
// These are split into 2 to 3 different strings due to parentheses being
|
||||
// delimiters in our query system.
|
||||
//
|
||||
// Do we want these to be valid? They should, as a string, right?
|
||||
if let Some(content) = content {
|
||||
match &prefix_type {
|
||||
PrefixType::Name => {
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((prefix_type, StringQuery::Value(content))),
|
||||
compare_prefix: None,
|
||||
})
|
||||
}
|
||||
PrefixType::Pid | PrefixType::State | PrefixType::User => {
|
||||
// We have to check if someone put an "="...
|
||||
if content == "=" {
|
||||
// Check next string if possible
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
// TODO: [Query, ???] Need to consider the following cases:
|
||||
// - (test)
|
||||
// - (test
|
||||
// - test)
|
||||
// These are split into 2 to 3 different strings due to parentheses being
|
||||
// delimiters in our query system.
|
||||
//
|
||||
// Do we want these to be valid? They should, as a string, right?
|
||||
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((
|
||||
prefix_type,
|
||||
StringQuery::Value(queue_next),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((
|
||||
prefix_type,
|
||||
StringQuery::Value(content),
|
||||
StringQuery::Value(queue_next),
|
||||
)),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((prefix_type, StringQuery::Value(content))),
|
||||
compare_prefix: None,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
// Now we gotta parse the content... yay.
|
||||
}
|
||||
_ => {
|
||||
// Now we gotta parse the content... yay.
|
||||
|
||||
let mut condition: Option<QueryComparison> = None;
|
||||
let mut value: Option<f64> = None;
|
||||
let mut condition: Option<QueryComparison> = None;
|
||||
let mut value: Option<f64> = None;
|
||||
|
||||
if content == "=" {
|
||||
condition = Some(QueryComparison::Equal);
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
} else {
|
||||
return Err(QueryError("Missing value".into()));
|
||||
}
|
||||
} else if content == ">" || content == "<" {
|
||||
// We also have to check if the next string is an "="...
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
if queue_next == "=" {
|
||||
condition = Some(if content == ">" {
|
||||
QueryComparison::GreaterOrEqual
|
||||
} else {
|
||||
QueryComparison::LessOrEqual
|
||||
});
|
||||
if let Some(queue_next_next) = query.pop_front() {
|
||||
value = queue_next_next.parse::<f64>().ok();
|
||||
} else {
|
||||
return Err(QueryError("Missing value".into()));
|
||||
}
|
||||
if content == "=" {
|
||||
condition = Some(QueryComparison::Equal);
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
} else {
|
||||
return Err(QueryError("Missing value".into()));
|
||||
}
|
||||
} else if content == ">" || content == "<" {
|
||||
// We also have to check if the next string is an "="...
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
if queue_next == "=" {
|
||||
condition = Some(if content == ">" {
|
||||
QueryComparison::GreaterOrEqual
|
||||
} else {
|
||||
condition = Some(if content == ">" {
|
||||
QueryComparison::Greater
|
||||
} else {
|
||||
QueryComparison::Less
|
||||
});
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
QueryComparison::LessOrEqual
|
||||
});
|
||||
if let Some(queue_next_next) = query.pop_front() {
|
||||
value = queue_next_next.parse::<f64>().ok();
|
||||
} else {
|
||||
return Err(QueryError("Missing value".into()));
|
||||
}
|
||||
} else {
|
||||
return Err(QueryError("Missing value".into()));
|
||||
condition = Some(if content == ">" {
|
||||
QueryComparison::Greater
|
||||
} else {
|
||||
QueryComparison::Less
|
||||
});
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
}
|
||||
} else {
|
||||
return Err(QueryError("Missing value".into()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(condition) = condition {
|
||||
if let Some(read_value) = value {
|
||||
// Now we want to check one last thing - is there a unit?
|
||||
// If no unit, assume base.
|
||||
// Furthermore, base must be PEEKED at initially, and will
|
||||
// require (likely) prefix_type specific checks
|
||||
// Lastly, if it *is* a unit, remember to POP!
|
||||
if let Some(condition) = condition {
|
||||
if let Some(read_value) = value {
|
||||
// Now we want to check one last thing - is there a unit?
|
||||
// If no unit, assume base.
|
||||
// Furthermore, base must be PEEKED at initially, and will
|
||||
// require (likely) prefix_type specific checks
|
||||
// Lastly, if it *is* a unit, remember to POP!
|
||||
|
||||
let mut value = read_value;
|
||||
let mut value = read_value;
|
||||
|
||||
match prefix_type {
|
||||
PrefixType::MemBytes
|
||||
| PrefixType::Rps
|
||||
| PrefixType::Wps
|
||||
| PrefixType::TRead
|
||||
| PrefixType::TWrite => {
|
||||
if let Some(potential_unit) = query.front() {
|
||||
match potential_unit.to_lowercase().as_str() {
|
||||
"tb" => {
|
||||
value *= 1_000_000_000_000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"tib" => {
|
||||
value *= 1_099_511_627_776.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"gb" => {
|
||||
value *= 1_000_000_000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"gib" => {
|
||||
value *= 1_073_741_824.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"mb" => {
|
||||
value *= 1_000_000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"mib" => {
|
||||
value *= 1_048_576.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"kb" => {
|
||||
value *= 1000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"kib" => {
|
||||
value *= 1024.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"b" => {
|
||||
// Just gotta pop.
|
||||
query.pop_front();
|
||||
}
|
||||
_ => {}
|
||||
match prefix_type {
|
||||
PrefixType::MemBytes
|
||||
| PrefixType::Rps
|
||||
| PrefixType::Wps
|
||||
| PrefixType::TRead
|
||||
| PrefixType::TWrite => {
|
||||
if let Some(potential_unit) = query.front() {
|
||||
match potential_unit.to_lowercase().as_str() {
|
||||
"tb" => {
|
||||
value *= 1_000_000_000_000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"tib" => {
|
||||
value *= 1_099_511_627_776.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"gb" => {
|
||||
value *= 1_000_000_000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"gib" => {
|
||||
value *= 1_073_741_824.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"mb" => {
|
||||
value *= 1_000_000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"mib" => {
|
||||
value *= 1_048_576.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"kb" => {
|
||||
value *= 1000.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"kib" => {
|
||||
value *= 1024.0;
|
||||
query.pop_front();
|
||||
}
|
||||
"b" => {
|
||||
// Just gotta pop.
|
||||
query.pop_front();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: None,
|
||||
compare_prefix: Some((
|
||||
prefix_type,
|
||||
NumericalQuery { condition, value },
|
||||
)),
|
||||
});
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: None,
|
||||
compare_prefix: Some((
|
||||
prefix_type,
|
||||
NumericalQuery { condition, value },
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(QueryError("Missing argument for search prefix".into()));
|
||||
}
|
||||
} else {
|
||||
return Err(QueryError("Missing argument for search prefix".into()));
|
||||
}
|
||||
} else if inside_quotation {
|
||||
// Uh oh, it's empty with quotes!
|
||||
return Err(QueryError("Missing closing quotation".into()));
|
||||
}
|
||||
|
||||
Err(QueryError("Invalid query".into()))
|
||||
} else if inside_quotation {
|
||||
// Uh oh, it's empty with quotes!
|
||||
return Err(QueryError("Missing closing quotation".into()));
|
||||
}
|
||||
|
||||
let mut split_query = VecDeque::new();
|
||||
|
||||
self.get_current_search_query()
|
||||
.split_whitespace()
|
||||
.for_each(|s| {
|
||||
// From https://stackoverflow.com/a/56923739 in order to get a split but include the parentheses
|
||||
let mut last = 0;
|
||||
for (index, matched) in s.match_indices(|x| DELIMITER_LIST.contains(&x)) {
|
||||
if last != index {
|
||||
split_query.push_back(s[last..index].to_owned());
|
||||
}
|
||||
split_query.push_back(matched.to_owned());
|
||||
last = index + matched.len();
|
||||
}
|
||||
if last < s.len() {
|
||||
split_query.push_back(s[last..].to_owned());
|
||||
}
|
||||
});
|
||||
|
||||
let mut process_filter = process_string_to_filter(&mut split_query)?;
|
||||
process_filter.process_regexes(
|
||||
self.search_state.is_searching_whole_word,
|
||||
self.search_state.is_ignoring_case,
|
||||
self.search_state.is_searching_with_regex,
|
||||
)?;
|
||||
|
||||
Ok(process_filter)
|
||||
Err(QueryError("Invalid query".into()))
|
||||
}
|
||||
|
||||
let mut split_query = VecDeque::new();
|
||||
|
||||
search_query.split_whitespace().for_each(|s| {
|
||||
// From https://stackoverflow.com/a/56923739 in order to get a split but include the parentheses
|
||||
let mut last = 0;
|
||||
for (index, matched) in s.match_indices(|x| DELIMITER_LIST.contains(&x)) {
|
||||
if last != index {
|
||||
split_query.push_back(s[last..index].to_owned());
|
||||
}
|
||||
split_query.push_back(matched.to_owned());
|
||||
last = index + matched.len();
|
||||
}
|
||||
if last < s.len() {
|
||||
split_query.push_back(s[last..].to_owned());
|
||||
}
|
||||
});
|
||||
|
||||
let mut process_filter = process_string_to_filter(&mut split_query)?;
|
||||
process_filter.process_regexes(
|
||||
is_searching_whole_word,
|
||||
is_ignoring_case,
|
||||
is_searching_with_regex,
|
||||
)?;
|
||||
|
||||
Ok(process_filter)
|
||||
}
|
||||
|
||||
pub struct Query {
|
||||
|
@ -468,7 +452,7 @@ impl Query {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData, is_using_command: bool) -> bool {
|
||||
pub fn check(&self, process: &ProcessHarvest, is_using_command: bool) -> bool {
|
||||
self.query
|
||||
.iter()
|
||||
.all(|ok| ok.check(process, is_using_command))
|
||||
|
@ -508,7 +492,7 @@ impl Or {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData, is_using_command: bool) -> bool {
|
||||
pub fn check(&self, process: &ProcessHarvest, is_using_command: bool) -> bool {
|
||||
if let Some(rhs) = &self.rhs {
|
||||
self.lhs.check(process, is_using_command) || rhs.check(process, is_using_command)
|
||||
} else {
|
||||
|
@ -553,7 +537,7 @@ impl And {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData, is_using_command: bool) -> bool {
|
||||
pub fn check(&self, process: &ProcessHarvest, is_using_command: bool) -> bool {
|
||||
if let Some(rhs) = &self.rhs {
|
||||
self.lhs.check(process, is_using_command) && rhs.check(process, is_using_command)
|
||||
} else {
|
||||
|
@ -663,7 +647,7 @@ impl Prefix {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check(&self, process: &ConvertedProcessData, is_using_command: bool) -> bool {
|
||||
pub fn check(&self, process: &ProcessHarvest, is_using_command: bool) -> bool {
|
||||
fn matches_condition(condition: &QueryComparison, lhs: f64, rhs: f64) -> bool {
|
||||
match condition {
|
||||
QueryComparison::Equal => (lhs - rhs).abs() < std::f64::EPSILON,
|
||||
|
@ -685,11 +669,14 @@ impl Prefix {
|
|||
process.name.as_str()
|
||||
}),
|
||||
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
||||
PrefixType::State => r.is_match(process.process_state.as_str()),
|
||||
PrefixType::State => r.is_match(process.process_state.0.as_str()),
|
||||
PrefixType::User => {
|
||||
if let Some(user) = &process.user {
|
||||
r.is_match(user.as_str())
|
||||
} else {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
r.is_match(process.user.as_ref())
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -702,12 +689,12 @@ impl Prefix {
|
|||
match prefix_type {
|
||||
PrefixType::PCpu => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.cpu_percent_usage,
|
||||
process.cpu_usage_percent,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::PMem => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.mem_percent_usage,
|
||||
process.mem_usage_percent,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::MemBytes => matches_condition(
|
||||
|
@ -717,22 +704,22 @@ impl Prefix {
|
|||
),
|
||||
PrefixType::Rps => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.rps_f64,
|
||||
process.read_bytes_per_sec as f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::Wps => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.wps_f64,
|
||||
process.write_bytes_per_sec as f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::TRead => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.tr_f64,
|
||||
process.total_read_bytes as f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
PrefixType::TWrite => matches_condition(
|
||||
&numerical_query.condition,
|
||||
process.tw_f64,
|
||||
process.total_write_bytes as f64,
|
||||
numerical_query.value,
|
||||
),
|
||||
_ => true,
|
||||
|
|
|
@ -5,7 +5,6 @@ use unicode_segmentation::GraphemeCursor;
|
|||
use crate::{
|
||||
app::{layout_manager::BottomWidgetType, query::*},
|
||||
constants,
|
||||
data_harvester::processes::ProcessSorting,
|
||||
};
|
||||
|
||||
pub mod table_state;
|
||||
|
@ -205,7 +204,9 @@ impl CpuWidgetState {
|
|||
CPU_LEGEND_HEADER
|
||||
.iter()
|
||||
.zip(WIDTHS)
|
||||
.map(|(c, width)| TableComponentColumn::new(CellContent::new(*c, None), width))
|
||||
.map(|(c, width)| {
|
||||
TableComponentColumn::new_custom(CellContent::new(*c, None), width)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
|
@ -294,7 +295,7 @@ impl Default for TempWidgetState {
|
|||
.iter()
|
||||
.zip(WIDTHS)
|
||||
.map(|(header, width)| {
|
||||
TableComponentColumn::new(CellContent::new(*header, None), width)
|
||||
TableComponentColumn::new_custom(CellContent::new(*header, None), width)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
|
@ -343,7 +344,7 @@ impl Default for DiskWidgetState {
|
|||
.iter()
|
||||
.zip(WIDTHS)
|
||||
.map(|(header, width)| {
|
||||
TableComponentColumn::new(CellContent::new(*header, None), width)
|
||||
TableComponentColumn::new_custom(CellContent::new(*header, None), width)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{borrow::Cow, convert::TryInto};
|
||||
use std::{borrow::Cow, convert::TryInto, ops::Range};
|
||||
|
||||
use tui::widgets::TableState;
|
||||
use itertools::Itertools;
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
use super::ScrollDirection;
|
||||
|
||||
|
@ -22,6 +23,9 @@ pub enum WidthBounds {
|
|||
|
||||
/// A width of this type is either as long as specified, or does not appear at all.
|
||||
Hard(u16),
|
||||
|
||||
/// Always uses the width of the [`CellContent`].
|
||||
CellWidth,
|
||||
}
|
||||
|
||||
impl WidthBounds {
|
||||
|
@ -46,7 +50,7 @@ impl WidthBounds {
|
|||
}
|
||||
|
||||
/// A [`CellContent`] contains text information for display in a table.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CellContent {
|
||||
Simple(Cow<'static, str>),
|
||||
HasAlt {
|
||||
|
@ -84,6 +88,13 @@ impl CellContent {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn main_text(&self) -> &Cow<'static, str> {
|
||||
match self {
|
||||
CellContent::Simple(main) => main,
|
||||
CellContent::HasAlt { alt: _, main } => main,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TableComponentHeader {
|
||||
|
@ -95,6 +106,11 @@ impl TableComponentHeader for CellContent {
|
|||
self
|
||||
}
|
||||
}
|
||||
impl From<Cow<'static, str>> for CellContent {
|
||||
fn from(c: Cow<'static, str>) -> Self {
|
||||
CellContent::Simple(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for CellContent {
|
||||
fn from(s: &'static str) -> Self {
|
||||
|
@ -102,6 +118,12 @@ impl From<&'static str> for CellContent {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<String> for CellContent {
|
||||
fn from(s: String) -> Self {
|
||||
CellContent::Simple(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableComponentColumn<H: TableComponentHeader> {
|
||||
/// The header of the column.
|
||||
pub header: H,
|
||||
|
@ -117,7 +139,7 @@ pub struct TableComponentColumn<H: TableComponentHeader> {
|
|||
}
|
||||
|
||||
impl<H: TableComponentHeader> TableComponentColumn<H> {
|
||||
pub fn new(header: H, width_bounds: WidthBounds) -> Self {
|
||||
pub fn new_custom(header: H, width_bounds: WidthBounds) -> Self {
|
||||
Self {
|
||||
header,
|
||||
width_bounds,
|
||||
|
@ -126,8 +148,16 @@ impl<H: TableComponentHeader> TableComponentColumn<H> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn default_hard(header: H) -> Self {
|
||||
let width = header.header_text().len() as u16;
|
||||
pub fn new(header: H) -> Self {
|
||||
Self {
|
||||
header,
|
||||
width_bounds: WidthBounds::CellWidth,
|
||||
calculated_width: 0,
|
||||
is_hidden: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_hard(header: H, width: u16) -> Self {
|
||||
Self {
|
||||
header,
|
||||
width_bounds: WidthBounds::Hard(width),
|
||||
|
@ -136,7 +166,7 @@ impl<H: TableComponentHeader> TableComponentColumn<H> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn default_soft(header: H, max_percentage: Option<f32>) -> Self {
|
||||
pub fn new_soft(header: H, max_percentage: Option<f32>) -> Self {
|
||||
let min_width = header.header_text().len() as u16;
|
||||
Self {
|
||||
header,
|
||||
|
@ -159,20 +189,142 @@ impl<H: TableComponentHeader> TableComponentColumn<H> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SortOrder {
|
||||
Ascending,
|
||||
Descending,
|
||||
}
|
||||
|
||||
/// Represents the current table's sorting state.
|
||||
pub enum SortState {
|
||||
Unsortable,
|
||||
Sortable { index: usize, order: SortOrder },
|
||||
impl SortOrder {
|
||||
pub fn is_descending(&self) -> bool {
|
||||
matches!(self, SortOrder::Descending)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortState {
|
||||
fn default() -> Self {
|
||||
SortState::Unsortable
|
||||
/// Represents the current table's sorting state.
|
||||
#[derive(Debug)]
|
||||
pub enum SortState {
|
||||
Unsortable,
|
||||
Sortable(SortableState),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SortableState {
|
||||
/// The "x locations" of the headers.
|
||||
visual_mappings: Vec<Range<u16>>,
|
||||
|
||||
/// The "y location" of the header row. Since all headers share the same y-location we just set it once here.
|
||||
y_loc: u16,
|
||||
|
||||
/// This is a bit of a lazy hack to handle this for now - ideally the entire [`SortableState`]
|
||||
/// is instead handled by a separate table struct that also can access the columns and their default sort orderings.
|
||||
default_sort_orderings: Vec<SortOrder>,
|
||||
|
||||
/// The currently selected sort index.
|
||||
pub current_index: usize,
|
||||
|
||||
/// The current sorting order.
|
||||
pub order: SortOrder,
|
||||
}
|
||||
|
||||
impl SortableState {
|
||||
/// Creates a new [`SortableState`].
|
||||
pub fn new(
|
||||
default_index: usize, default_order: SortOrder, default_sort_orderings: Vec<SortOrder>,
|
||||
) -> Self {
|
||||
Self {
|
||||
visual_mappings: Default::default(),
|
||||
y_loc: 0,
|
||||
default_sort_orderings,
|
||||
current_index: default_index,
|
||||
order: default_order,
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggles the current sort order.
|
||||
pub fn toggle_order(&mut self) {
|
||||
self.order = match self.order {
|
||||
SortOrder::Ascending => SortOrder::Descending,
|
||||
SortOrder::Descending => SortOrder::Ascending,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the visual index.
|
||||
///
|
||||
/// This function will create a *sorted* range list - in debug mode,
|
||||
/// the program will assert this, but it will not do so in release mode!
|
||||
pub fn update_visual_index(&mut self, draw_loc: Rect, row_widths: &[u16]) {
|
||||
let mut start = draw_loc.x;
|
||||
let visual_index = row_widths
|
||||
.iter()
|
||||
.map(|width| {
|
||||
let range_start = start;
|
||||
let range_end = start + width + 1;
|
||||
start = range_end;
|
||||
range_start..range_end
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
debug_assert!(visual_index.iter().all(|a| { a.start <= a.end }));
|
||||
|
||||
debug_assert!(visual_index
|
||||
.iter()
|
||||
.tuple_windows()
|
||||
.all(|(a, b)| { b.start >= a.end }));
|
||||
|
||||
self.visual_mappings = visual_index;
|
||||
self.y_loc = draw_loc.y;
|
||||
}
|
||||
|
||||
/// Given some `x` and `y`, if possible, select the corresponding column or toggle the column if already selected,
|
||||
/// and otherwise do nothing.
|
||||
///
|
||||
/// If there was some update, the corresponding column type will be returned. If nothing happens, [`None`] is
|
||||
/// returned.
|
||||
pub fn try_select_location(&mut self, x: u16, y: u16) -> Option<usize> {
|
||||
if self.y_loc == y {
|
||||
if let Some(index) = self.get_range(x) {
|
||||
self.update_sort_index(index);
|
||||
Some(self.current_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the sort index, and sets the sort order as appropriate.
|
||||
///
|
||||
/// If the index is different from the previous one, it will move to the new index and set the sort order
|
||||
/// to the prescribed default sort order.
|
||||
///
|
||||
/// If the index is the same as the previous one, it will simply toggle the current sort order.
|
||||
pub fn update_sort_index(&mut self, index: usize) {
|
||||
if self.current_index == index {
|
||||
self.toggle_order();
|
||||
} else {
|
||||
self.current_index = index;
|
||||
self.order = self.default_sort_orderings[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a `needle` coordinate, select the corresponding index and value.
|
||||
fn get_range(&self, needle: u16) -> Option<usize> {
|
||||
match self
|
||||
.visual_mappings
|
||||
.binary_search_by_key(&needle, |range| range.start)
|
||||
{
|
||||
Ok(index) => Some(index),
|
||||
Err(index) => index.checked_sub(1),
|
||||
}
|
||||
.and_then(|index| {
|
||||
if needle < self.visual_mappings[index].end {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,10 +346,15 @@ impl<H: TableComponentHeader> TableComponentState<H> {
|
|||
scroll_direction: ScrollDirection::Down,
|
||||
table_state: Default::default(),
|
||||
columns,
|
||||
sort_state: Default::default(),
|
||||
sort_state: SortState::Unsortable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_state(mut self, sort_state: SortState) -> Self {
|
||||
self.sort_state = sort_state;
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculates widths for the columns for this table.
|
||||
///
|
||||
/// * `total_width` is the, well, total width available.
|
||||
|
@ -211,10 +368,6 @@ impl<H: TableComponentHeader> TableComponentState<H> {
|
|||
|
||||
let mut total_width_left = total_width;
|
||||
|
||||
for column in self.columns.iter_mut() {
|
||||
column.calculated_width = 0;
|
||||
}
|
||||
|
||||
let columns = if left_to_right {
|
||||
Either::Left(self.columns.iter_mut())
|
||||
} else {
|
||||
|
@ -223,11 +376,15 @@ impl<H: TableComponentHeader> TableComponentState<H> {
|
|||
|
||||
let arrow_offset = match self.sort_state {
|
||||
SortState::Unsortable => 0,
|
||||
SortState::Sortable { index: _, order: _ } => 1,
|
||||
SortState::Sortable { .. } => 1,
|
||||
};
|
||||
|
||||
let mut num_columns = 0;
|
||||
let mut skip_iter = false;
|
||||
for column in columns {
|
||||
if column.is_hidden {
|
||||
column.calculated_width = 0;
|
||||
|
||||
if column.is_hidden || skip_iter {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -237,35 +394,53 @@ impl<H: TableComponentHeader> TableComponentState<H> {
|
|||
desired,
|
||||
max_percentage,
|
||||
} => {
|
||||
let offset_min = *min_width + arrow_offset;
|
||||
let soft_limit = max(
|
||||
if let Some(max_percentage) = max_percentage {
|
||||
// Rust doesn't have an `into()` or `try_into()` for floats to integers???
|
||||
((*max_percentage * f32::from(total_width)).ceil()) as u16
|
||||
} else {
|
||||
*desired
|
||||
},
|
||||
offset_min,
|
||||
);
|
||||
let space_taken = min(min(soft_limit, *desired), total_width_left);
|
||||
let min_width = *min_width + arrow_offset;
|
||||
if min_width > total_width_left {
|
||||
skip_iter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if offset_min > space_taken {
|
||||
break;
|
||||
let space_taken = min(
|
||||
max(
|
||||
if let Some(max_percentage) = max_percentage {
|
||||
// TODO: Rust doesn't have an `into()` or `try_into()` for floats to integers.
|
||||
((*max_percentage * f32::from(total_width)).ceil()) as u16
|
||||
} else {
|
||||
*desired
|
||||
},
|
||||
min_width,
|
||||
),
|
||||
total_width_left,
|
||||
);
|
||||
|
||||
if min_width == 0 {
|
||||
skip_iter = true;
|
||||
} else if space_taken > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
||||
column.calculated_width = space_taken;
|
||||
num_columns += 1;
|
||||
}
|
||||
}
|
||||
WidthBounds::CellWidth => {
|
||||
let width = column.header.header_text().len() as u16;
|
||||
let min_width = width + arrow_offset;
|
||||
|
||||
if min_width > total_width_left || min_width == 0 {
|
||||
skip_iter = true;
|
||||
} else if min_width > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(min_width + 1);
|
||||
column.calculated_width = min_width;
|
||||
num_columns += 1;
|
||||
}
|
||||
}
|
||||
WidthBounds::Hard(width) => {
|
||||
let min_width = *width + arrow_offset;
|
||||
let space_taken = min(min_width, total_width_left);
|
||||
|
||||
if min_width > space_taken {
|
||||
break;
|
||||
} else if space_taken > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
||||
column.calculated_width = space_taken;
|
||||
if min_width > total_width_left || min_width == 0 {
|
||||
skip_iter = true;
|
||||
} else if min_width > 0 {
|
||||
total_width_left = total_width_left.saturating_sub(min_width + 1);
|
||||
column.calculated_width = min_width;
|
||||
num_columns += 1;
|
||||
}
|
||||
}
|
||||
|
@ -344,7 +519,7 @@ mod test {
|
|||
scroll_direction: ScrollDirection::Down,
|
||||
table_state: Default::default(),
|
||||
columns: vec![],
|
||||
sort_state: Default::default(),
|
||||
sort_state: SortState::Unsortable,
|
||||
};
|
||||
let s = &mut scroll;
|
||||
|
||||
|
@ -402,8 +577,8 @@ mod test {
|
|||
}
|
||||
|
||||
let mut state = TableComponentState::new(vec![
|
||||
TableComponentColumn::default_hard(CellContent::from("a")),
|
||||
TableComponentColumn::new(
|
||||
TableComponentColumn::new(CellContent::from("a")),
|
||||
TableComponentColumn::new_custom(
|
||||
"a".into(),
|
||||
WidthBounds::Soft {
|
||||
min_width: 1,
|
||||
|
@ -411,7 +586,7 @@ mod test {
|
|||
max_percentage: Some(0.125),
|
||||
},
|
||||
),
|
||||
TableComponentColumn::new(
|
||||
TableComponentColumn::new_custom(
|
||||
"a".into(),
|
||||
WidthBounds::Soft {
|
||||
min_width: 2,
|
||||
|
@ -432,12 +607,9 @@ mod test {
|
|||
test_calculation(&mut state, 8, vec![1, 1, 4]);
|
||||
test_calculation(&mut state, 14, vec![2, 2, 7]);
|
||||
test_calculation(&mut state, 20, vec![2, 4, 11]);
|
||||
test_calculation(&mut state, 100, vec![27, 35, 35]);
|
||||
test_calculation(&mut state, 100, vec![12, 24, 61]);
|
||||
|
||||
state.sort_state = SortState::Sortable {
|
||||
index: 1,
|
||||
order: SortOrder::Ascending,
|
||||
};
|
||||
state.sort_state = SortState::Sortable(SortableState::new(1, SortOrder::Ascending, vec![]));
|
||||
|
||||
test_calculation(&mut state, 0, vec![]);
|
||||
test_calculation(&mut state, 1, vec![]);
|
||||
|
@ -450,6 +622,9 @@ mod test {
|
|||
test_calculation(&mut state, 8, vec![3, 3]);
|
||||
test_calculation(&mut state, 14, vec![2, 2, 7]);
|
||||
test_calculation(&mut state, 20, vec![3, 4, 10]);
|
||||
test_calculation(&mut state, 100, vec![27, 35, 35]);
|
||||
test_calculation(&mut state, 100, vec![13, 24, 60]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_row_width_boundary_creation() {}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pub mod process;
|
||||
pub use process::*;
|
||||
pub mod process_widget;
|
||||
pub use process_widget::*;
|
||||
|
|
|
@ -1,320 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
query::*, AppSearchState, CanvasTableWidthState, CellContent, TableComponentColumn,
|
||||
TableComponentHeader, TableComponentState, WidthBounds,
|
||||
},
|
||||
data_harvester::processes,
|
||||
};
|
||||
|
||||
/// ProcessSearchState only deals with process' search's current settings and state.
|
||||
pub struct ProcessSearchState {
|
||||
pub search_state: AppSearchState,
|
||||
pub is_ignoring_case: bool,
|
||||
pub is_searching_whole_word: bool,
|
||||
pub is_searching_with_regex: bool,
|
||||
}
|
||||
|
||||
impl Default for ProcessSearchState {
|
||||
fn default() -> Self {
|
||||
ProcessSearchState {
|
||||
search_state: AppSearchState::default(),
|
||||
is_ignoring_case: true,
|
||||
is_searching_whole_word: false,
|
||||
is_searching_with_regex: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessSearchState {
|
||||
pub fn search_toggle_ignore_case(&mut self) {
|
||||
self.is_ignoring_case = !self.is_ignoring_case;
|
||||
}
|
||||
|
||||
pub fn search_toggle_whole_word(&mut self) {
|
||||
self.is_searching_whole_word = !self.is_searching_whole_word;
|
||||
}
|
||||
|
||||
pub fn search_toggle_regex(&mut self) {
|
||||
self.is_searching_with_regex = !self.is_searching_with_regex;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ProcWidgetMode {
|
||||
Tree,
|
||||
Grouped,
|
||||
Normal,
|
||||
}
|
||||
|
||||
pub enum ProcWidgetColumn {
|
||||
CpuPercent,
|
||||
Memory { show_percentage: bool },
|
||||
PidOrCount { is_count: bool },
|
||||
ProcNameOrCommand { is_command: bool },
|
||||
ReadPerSecond,
|
||||
WritePerSecond,
|
||||
TotalRead,
|
||||
TotalWrite,
|
||||
State,
|
||||
User,
|
||||
}
|
||||
|
||||
impl ProcWidgetColumn {
|
||||
const CPU_PERCENT: CellContent = CellContent::Simple(Cow::Borrowed("CPU%"));
|
||||
const MEM_PERCENT: CellContent = CellContent::Simple(Cow::Borrowed("Mem%"));
|
||||
const MEM: CellContent = CellContent::Simple(Cow::Borrowed("Mem"));
|
||||
const READS_PER_SECOND: CellContent = CellContent::Simple(Cow::Borrowed("R/s"));
|
||||
const WRITES_PER_SECOND: CellContent = CellContent::Simple(Cow::Borrowed("W/s"));
|
||||
const TOTAL_READ: CellContent = CellContent::Simple(Cow::Borrowed("T.Read"));
|
||||
const TOTAL_WRITE: CellContent = CellContent::Simple(Cow::Borrowed("T.Write"));
|
||||
const STATE: CellContent = CellContent::Simple(Cow::Borrowed("State"));
|
||||
const PROCESS_NAME: CellContent = CellContent::Simple(Cow::Borrowed("Name"));
|
||||
const COMMAND: CellContent = CellContent::Simple(Cow::Borrowed("Command"));
|
||||
const PID: CellContent = CellContent::Simple(Cow::Borrowed("PID"));
|
||||
const COUNT: CellContent = CellContent::Simple(Cow::Borrowed("Count"));
|
||||
const USER: CellContent = CellContent::Simple(Cow::Borrowed("User"));
|
||||
|
||||
const SHORTCUT_CPU_PERCENT: CellContent = CellContent::Simple(Cow::Borrowed("CPU%(c)"));
|
||||
const SHORTCUT_MEM_PERCENT: CellContent = CellContent::Simple(Cow::Borrowed("Mem%(m)"));
|
||||
const SHORTCUT_MEM: CellContent = CellContent::Simple(Cow::Borrowed("Mem(m)"));
|
||||
const SHORTCUT_PROCESS_NAME: CellContent = CellContent::Simple(Cow::Borrowed("Name(n)"));
|
||||
const SHORTCUT_COMMAND: CellContent = CellContent::Simple(Cow::Borrowed("Command(n)"));
|
||||
const SHORTCUT_PID: CellContent = CellContent::Simple(Cow::Borrowed("PID(p)"));
|
||||
|
||||
fn text(&self) -> &CellContent {
|
||||
match self {
|
||||
ProcWidgetColumn::CpuPercent => &Self::CPU_PERCENT,
|
||||
ProcWidgetColumn::Memory { show_percentage } => {
|
||||
if *show_percentage {
|
||||
&Self::MEM_PERCENT
|
||||
} else {
|
||||
&Self::MEM
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::PidOrCount { is_count } => {
|
||||
if *is_count {
|
||||
&Self::COUNT
|
||||
} else {
|
||||
&Self::PID
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::ProcNameOrCommand { is_command } => {
|
||||
if *is_command {
|
||||
&Self::COMMAND
|
||||
} else {
|
||||
&Self::PROCESS_NAME
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::ReadPerSecond => &Self::READS_PER_SECOND,
|
||||
ProcWidgetColumn::WritePerSecond => &Self::WRITES_PER_SECOND,
|
||||
ProcWidgetColumn::TotalRead => &Self::TOTAL_READ,
|
||||
ProcWidgetColumn::TotalWrite => &Self::TOTAL_WRITE,
|
||||
ProcWidgetColumn::State => &Self::STATE,
|
||||
ProcWidgetColumn::User => &Self::USER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableComponentHeader for ProcWidgetColumn {
|
||||
fn header_text(&self) -> &CellContent {
|
||||
match self {
|
||||
ProcWidgetColumn::CpuPercent => &Self::SHORTCUT_CPU_PERCENT,
|
||||
ProcWidgetColumn::Memory { show_percentage } => {
|
||||
if *show_percentage {
|
||||
&Self::SHORTCUT_MEM_PERCENT
|
||||
} else {
|
||||
&Self::SHORTCUT_MEM
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::PidOrCount { is_count } => {
|
||||
if *is_count {
|
||||
&Self::COUNT
|
||||
} else {
|
||||
&Self::SHORTCUT_PID
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::ProcNameOrCommand { is_command } => {
|
||||
if *is_command {
|
||||
&Self::SHORTCUT_COMMAND
|
||||
} else {
|
||||
&Self::SHORTCUT_PROCESS_NAME
|
||||
}
|
||||
}
|
||||
ProcWidgetColumn::ReadPerSecond => &Self::READS_PER_SECOND,
|
||||
ProcWidgetColumn::WritePerSecond => &Self::WRITES_PER_SECOND,
|
||||
ProcWidgetColumn::TotalRead => &Self::TOTAL_READ,
|
||||
ProcWidgetColumn::TotalWrite => &Self::TOTAL_WRITE,
|
||||
ProcWidgetColumn::State => &Self::STATE,
|
||||
ProcWidgetColumn::User => &Self::USER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProcWidget {
|
||||
pub mode: ProcWidgetMode,
|
||||
|
||||
pub requires_redraw: bool,
|
||||
|
||||
pub search_state: ProcessSearchState,
|
||||
pub table_state: TableComponentState<ProcWidgetColumn>,
|
||||
pub sort_table_state: TableComponentState<CellContent>,
|
||||
|
||||
pub is_sort_open: bool,
|
||||
pub force_update: bool,
|
||||
|
||||
// Hmm...
|
||||
pub is_process_sort_descending: bool,
|
||||
pub process_sorting_type: processes::ProcessSorting,
|
||||
|
||||
// TO REMOVE
|
||||
pub is_using_command: bool,
|
||||
pub table_width_state: CanvasTableWidthState,
|
||||
}
|
||||
|
||||
impl ProcWidget {
|
||||
pub fn init(
|
||||
mode: ProcWidgetMode, is_case_sensitive: bool, is_match_whole_word: bool,
|
||||
is_use_regex: bool, show_memory_as_values: bool, is_using_command: bool,
|
||||
) -> Self {
|
||||
let mut process_search_state = ProcessSearchState::default();
|
||||
|
||||
if is_case_sensitive {
|
||||
// By default it's off
|
||||
process_search_state.search_toggle_ignore_case();
|
||||
}
|
||||
if is_match_whole_word {
|
||||
process_search_state.search_toggle_whole_word();
|
||||
}
|
||||
if is_use_regex {
|
||||
process_search_state.search_toggle_regex();
|
||||
}
|
||||
|
||||
let (process_sorting_type, is_process_sort_descending) =
|
||||
if matches!(mode, ProcWidgetMode::Tree) {
|
||||
(processes::ProcessSorting::Pid, false)
|
||||
} else {
|
||||
(processes::ProcessSorting::CpuPercent, true)
|
||||
};
|
||||
|
||||
let is_count = matches!(mode, ProcWidgetMode::Grouped);
|
||||
|
||||
let sort_table_state = TableComponentState::new(vec![TableComponentColumn::new(
|
||||
CellContent::Simple("Sort By".into()),
|
||||
WidthBounds::Hard(7),
|
||||
)]);
|
||||
let table_state = TableComponentState::new(vec![
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::PidOrCount { is_count }),
|
||||
TableComponentColumn::default_soft(
|
||||
ProcWidgetColumn::ProcNameOrCommand {
|
||||
is_command: is_using_command,
|
||||
},
|
||||
Some(0.7),
|
||||
),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::CpuPercent),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::Memory {
|
||||
show_percentage: !show_memory_as_values,
|
||||
}),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::ReadPerSecond),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::WritePerSecond),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::TotalRead),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::TotalWrite),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::User),
|
||||
TableComponentColumn::default_hard(ProcWidgetColumn::State),
|
||||
]);
|
||||
|
||||
ProcWidget {
|
||||
search_state: process_search_state,
|
||||
table_state,
|
||||
sort_table_state,
|
||||
process_sorting_type,
|
||||
is_process_sort_descending,
|
||||
is_using_command,
|
||||
is_sort_open: false,
|
||||
table_width_state: CanvasTableWidthState::default(),
|
||||
requires_redraw: false,
|
||||
mode,
|
||||
force_update: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_search_cursor_position(&self) -> usize {
|
||||
self.search_state.search_state.grapheme_cursor.cur_cursor()
|
||||
}
|
||||
|
||||
pub fn get_char_cursor_position(&self) -> usize {
|
||||
self.search_state.search_state.char_cursor_position
|
||||
}
|
||||
|
||||
pub fn is_search_enabled(&self) -> bool {
|
||||
self.search_state.search_state.is_enabled
|
||||
}
|
||||
|
||||
pub fn get_current_search_query(&self) -> &String {
|
||||
&self.search_state.search_state.current_search_query
|
||||
}
|
||||
|
||||
pub fn update_query(&mut self) {
|
||||
if self
|
||||
.search_state
|
||||
.search_state
|
||||
.current_search_query
|
||||
.is_empty()
|
||||
{
|
||||
self.search_state.search_state.is_blank_search = true;
|
||||
self.search_state.search_state.is_invalid_search = false;
|
||||
self.search_state.search_state.error_message = None;
|
||||
} else {
|
||||
let parsed_query = self.parse_query();
|
||||
// debug!("Parsed query: {:#?}", parsed_query);
|
||||
|
||||
if let Ok(parsed_query) = parsed_query {
|
||||
self.search_state.search_state.query = Some(parsed_query);
|
||||
self.search_state.search_state.is_blank_search = false;
|
||||
self.search_state.search_state.is_invalid_search = false;
|
||||
self.search_state.search_state.error_message = None;
|
||||
} else if let Err(err) = parsed_query {
|
||||
self.search_state.search_state.is_blank_search = false;
|
||||
self.search_state.search_state.is_invalid_search = true;
|
||||
self.search_state.search_state.error_message = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
self.table_state.scroll_bar = 0;
|
||||
self.table_state.current_scroll_position = 0;
|
||||
}
|
||||
|
||||
pub fn clear_search(&mut self) {
|
||||
self.search_state.search_state.reset();
|
||||
}
|
||||
|
||||
pub fn search_walk_forward(&mut self, start_position: usize) {
|
||||
self.search_state
|
||||
.search_state
|
||||
.grapheme_cursor
|
||||
.next_boundary(
|
||||
&self.search_state.search_state.current_search_query[start_position..],
|
||||
start_position,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn search_walk_back(&mut self, start_position: usize) {
|
||||
self.search_state
|
||||
.search_state
|
||||
.grapheme_cursor
|
||||
.prev_boundary(
|
||||
&self.search_state.search_state.current_search_query[..start_position],
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn num_enabled_columns(&self) -> usize {
|
||||
self.table_state
|
||||
.columns
|
||||
.iter()
|
||||
.filter(|c| !c.is_skipped())
|
||||
.count()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -54,13 +54,8 @@ fn main() -> Result<()> {
|
|||
)?;
|
||||
|
||||
// Create painter and set colours.
|
||||
let mut painter = canvas::Painter::init(
|
||||
widget_layout,
|
||||
app.app_config_fields.table_gap,
|
||||
app.app_config_fields.use_basic_mode,
|
||||
&config,
|
||||
get_color_scheme(&matches, &config)?,
|
||||
)?;
|
||||
let mut painter =
|
||||
canvas::Painter::init(widget_layout, &config, get_color_scheme(&matches, &config)?)?;
|
||||
|
||||
// Create termination mutex and cvar
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
|
@ -164,39 +159,39 @@ fn main() -> Result<()> {
|
|||
&app.app_config_fields.network_unit_type,
|
||||
app.app_config_fields.network_use_binary_prefix,
|
||||
);
|
||||
app.canvas_data.network_data_rx = network_data.rx;
|
||||
app.canvas_data.network_data_tx = network_data.tx;
|
||||
app.canvas_data.rx_display = network_data.rx_display;
|
||||
app.canvas_data.tx_display = network_data.tx_display;
|
||||
app.converted_data.network_data_rx = network_data.rx;
|
||||
app.converted_data.network_data_tx = network_data.tx;
|
||||
app.converted_data.rx_display = network_data.rx_display;
|
||||
app.converted_data.tx_display = network_data.tx_display;
|
||||
if let Some(total_rx_display) = network_data.total_rx_display {
|
||||
app.canvas_data.total_rx_display = total_rx_display;
|
||||
app.converted_data.total_rx_display = total_rx_display;
|
||||
}
|
||||
if let Some(total_tx_display) = network_data.total_tx_display {
|
||||
app.canvas_data.total_tx_display = total_tx_display;
|
||||
app.converted_data.total_tx_display = total_tx_display;
|
||||
}
|
||||
}
|
||||
|
||||
// Disk
|
||||
if app.used_widgets.use_disk {
|
||||
app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
|
||||
app.converted_data.disk_data = convert_disk_row(&app.data_collection);
|
||||
}
|
||||
|
||||
// Temperatures
|
||||
if app.used_widgets.use_temp {
|
||||
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
|
||||
app.converted_data.temp_sensor_data = convert_temp_row(&app);
|
||||
}
|
||||
|
||||
// Memory
|
||||
if app.used_widgets.use_mem {
|
||||
app.canvas_data.mem_data =
|
||||
app.converted_data.mem_data =
|
||||
convert_mem_data_points(&app.data_collection);
|
||||
app.canvas_data.swap_data =
|
||||
app.converted_data.swap_data =
|
||||
convert_swap_data_points(&app.data_collection);
|
||||
let (memory_labels, swap_labels) =
|
||||
convert_mem_labels(&app.data_collection);
|
||||
|
||||
app.canvas_data.mem_labels = memory_labels;
|
||||
app.canvas_data.swap_labels = swap_labels;
|
||||
app.converted_data.mem_labels = memory_labels;
|
||||
app.converted_data.swap_labels = swap_labels;
|
||||
}
|
||||
|
||||
if app.used_widgets.use_cpu {
|
||||
|
@ -204,24 +199,28 @@ fn main() -> Result<()> {
|
|||
|
||||
convert_cpu_data_points(
|
||||
&app.data_collection,
|
||||
&mut app.canvas_data.cpu_data,
|
||||
&mut app.converted_data.cpu_data,
|
||||
);
|
||||
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
|
||||
app.converted_data.load_avg_data = app.data_collection.load_avg_harvest;
|
||||
}
|
||||
|
||||
// Processes
|
||||
if app.used_widgets.use_proc {
|
||||
update_all_process_lists(&mut app);
|
||||
for proc in app.proc_state.widget_states.values_mut() {
|
||||
proc.force_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Battery
|
||||
#[cfg(feature = "battery")]
|
||||
{
|
||||
if app.used_widgets.use_battery {
|
||||
app.canvas_data.battery_data =
|
||||
app.converted_data.battery_data =
|
||||
convert_battery_harvest(&app.data_collection);
|
||||
}
|
||||
}
|
||||
|
||||
handle_force_redraws(&mut app);
|
||||
}
|
||||
}
|
||||
BottomEvent::Clean => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use itertools::izip;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
|
@ -18,11 +18,9 @@ use crate::{
|
|||
App,
|
||||
},
|
||||
constants::*,
|
||||
data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData, TableData},
|
||||
options::Config,
|
||||
utils::error,
|
||||
utils::error::BottomError,
|
||||
Pid,
|
||||
};
|
||||
|
||||
pub use self::components::Point;
|
||||
|
@ -33,30 +31,6 @@ mod dialogs;
|
|||
mod drawing_utils;
|
||||
mod widgets;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DisplayableData {
|
||||
pub rx_display: String,
|
||||
pub tx_display: String,
|
||||
pub total_rx_display: String,
|
||||
pub total_tx_display: String,
|
||||
pub network_data_rx: Vec<Point>,
|
||||
pub network_data_tx: Vec<Point>,
|
||||
pub disk_data: TableData,
|
||||
pub temp_sensor_data: TableData,
|
||||
pub single_process_data: HashMap<Pid, ConvertedProcessData>, // Contains single process data, key is PID
|
||||
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed, key is the widget ID.
|
||||
pub stringified_process_data_map: HashMap<u64, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled, key is the widget ID
|
||||
|
||||
pub mem_labels: Option<(String, String)>,
|
||||
pub swap_labels: Option<(String, String)>,
|
||||
|
||||
pub mem_data: Vec<Point>, // TODO: Switch this and all data points over to a better data structure...
|
||||
pub swap_data: Vec<Point>,
|
||||
pub load_avg_data: [f32; 3],
|
||||
pub cpu_data: Vec<ConvertedCpuData>,
|
||||
pub battery_data: Vec<ConvertedBatteryData>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ColourScheme {
|
||||
Default,
|
||||
|
@ -101,13 +75,11 @@ pub struct Painter {
|
|||
layout_constraints: Vec<Vec<Vec<Vec<Constraint>>>>,
|
||||
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
|
||||
widget_layout: BottomLayout,
|
||||
table_height_offset: u16,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn init(
|
||||
widget_layout: BottomLayout, table_gap: u16, is_basic_mode: bool, config: &Config,
|
||||
colour_scheme: ColourScheme,
|
||||
widget_layout: BottomLayout, config: &Config, colour_scheme: ColourScheme,
|
||||
) -> anyhow::Result<Self> {
|
||||
// Now for modularity; we have to also initialize the base layouts!
|
||||
// We want to do this ONCE and reuse; after this we can just construct
|
||||
|
@ -188,7 +160,6 @@ impl Painter {
|
|||
layout_constraints,
|
||||
widget_layout,
|
||||
derived_widget_draw_locs: Vec::default(),
|
||||
table_height_offset: if is_basic_mode { 2 } else { 4 } + table_gap,
|
||||
};
|
||||
|
||||
if let ColourScheme::Custom = colour_scheme {
|
||||
|
@ -338,13 +309,6 @@ impl Painter {
|
|||
for battery_widget in app_state.battery_state.widget_states.values_mut() {
|
||||
battery_widget.tab_click_locs = None;
|
||||
}
|
||||
|
||||
// Reset column headers for sorting in process widget...
|
||||
for proc_widget in app_state.proc_state.widget_states.values_mut() {
|
||||
// FIXME: [Proc] Handle this?
|
||||
// proc_widget.columns.column_header_y_loc = None;
|
||||
// proc_widget.columns.column_header_x_locs = None;
|
||||
}
|
||||
}
|
||||
|
||||
if app_state.help_dialog_state.is_showing_help {
|
||||
|
@ -525,7 +489,7 @@ impl Painter {
|
|||
self.draw_frozen_indicator(f, frozen_draw_loc);
|
||||
}
|
||||
|
||||
let actual_cpu_data_len = app_state.canvas_data.cpu_data.len().saturating_sub(1);
|
||||
let actual_cpu_data_len = app_state.converted_data.cpu_data.len().saturating_sub(1);
|
||||
|
||||
// This fixes #397, apparently if the height is 1, it can't render the CPU bars...
|
||||
let cpu_height = {
|
||||
|
|
|
@ -16,8 +16,8 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
|
||||
use crate::{
|
||||
app::{
|
||||
self, CellContent, SortState, TableComponentColumn, TableComponentHeader,
|
||||
TableComponentState,
|
||||
self, layout_manager::BottomWidget, CellContent, SortState, TableComponentColumn,
|
||||
TableComponentHeader, TableComponentState, WidthBounds,
|
||||
},
|
||||
constants::{SIDE_BORDERS, TABLE_GAP_HEIGHT_LIMIT},
|
||||
data_conversion::{TableData, TableRow},
|
||||
|
@ -30,7 +30,7 @@ pub struct TextTableTitle<'a> {
|
|||
|
||||
pub struct TextTable<'a> {
|
||||
pub table_gap: u16,
|
||||
pub is_force_redraw: bool,
|
||||
pub is_force_redraw: bool, // TODO: Is this force redraw thing needed? Or is there a better way?
|
||||
pub recalculate_column_widths: bool,
|
||||
|
||||
/// The header style.
|
||||
|
@ -107,9 +107,10 @@ impl<'a> TextTable<'a> {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw_text_table<B: Backend, H: TableComponentHeader>(
|
||||
&self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState<H>,
|
||||
table_data: &TableData,
|
||||
table_data: &TableData, btm_widget: Option<&mut BottomWidget>,
|
||||
) {
|
||||
// TODO: This is a *really* ugly hack to get basic mode to hide the border when not selected, without shifting everything.
|
||||
let is_not_basic = self.is_on_widget || self.draw_border;
|
||||
|
@ -119,7 +120,7 @@ impl<'a> TextTable<'a> {
|
|||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
let disk_block = if self.draw_border {
|
||||
let block = if self.draw_border {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(self.border_style);
|
||||
|
@ -141,13 +142,11 @@ impl<'a> TextTable<'a> {
|
|||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
let (inner_width, inner_height) = {
|
||||
let inner = disk_block.inner(margined_draw_loc);
|
||||
(inner.width, inner.height)
|
||||
};
|
||||
let inner_rect = block.inner(margined_draw_loc);
|
||||
let (inner_width, inner_height) = { (inner_rect.width, inner_rect.height) };
|
||||
|
||||
if inner_width == 0 || inner_height == 0 {
|
||||
f.render_widget(disk_block, margined_draw_loc);
|
||||
f.render_widget(block, margined_draw_loc);
|
||||
} else {
|
||||
let show_header = inner_height > 1;
|
||||
let header_height = if show_header { 1 } else { 0 };
|
||||
|
@ -178,19 +177,44 @@ impl<'a> TextTable<'a> {
|
|||
state
|
||||
.columns
|
||||
.iter_mut()
|
||||
.zip(&table_data.row_widths)
|
||||
.zip(&table_data.col_widths)
|
||||
.for_each(|(column, data_width)| match &mut column.width_bounds {
|
||||
app::WidthBounds::Soft {
|
||||
WidthBounds::Soft {
|
||||
min_width: _,
|
||||
desired,
|
||||
max_percentage: _,
|
||||
} => {
|
||||
*desired = max(column.header.header_text().len(), *data_width) as u16;
|
||||
}
|
||||
app::WidthBounds::Hard(_width) => {}
|
||||
WidthBounds::CellWidth => {}
|
||||
WidthBounds::Hard(_width) => {}
|
||||
});
|
||||
|
||||
state.calculate_column_widths(inner_width, self.left_to_right);
|
||||
|
||||
if let SortState::Sortable(st) = &mut state.sort_state {
|
||||
st.update_visual_index(
|
||||
inner_rect,
|
||||
&state
|
||||
.columns
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
if c.calculated_width == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(c.calculated_width)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
// Update draw loc in widget map
|
||||
if let Some(btm_widget) = btm_widget {
|
||||
btm_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
btm_widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
|
||||
let columns = &state.columns;
|
||||
|
@ -214,7 +238,7 @@ impl<'a> TextTable<'a> {
|
|||
|
||||
let widget = {
|
||||
let mut table = Table::new(table_rows)
|
||||
.block(disk_block)
|
||||
.block(block)
|
||||
.highlight_style(self.highlighted_text_style)
|
||||
.style(self.text_style);
|
||||
|
||||
|
@ -266,7 +290,10 @@ fn build_header<'a, H: TableComponentHeader>(
|
|||
))
|
||||
}
|
||||
})),
|
||||
SortState::Sortable { index, order } => {
|
||||
SortState::Sortable(s) => {
|
||||
let order = &s.order;
|
||||
let index = s.current_index;
|
||||
|
||||
let arrow = match order {
|
||||
app::SortOrder::Ascending => UP_ARROW,
|
||||
app::SortOrder::Descending => DOWN_ARROW,
|
||||
|
@ -275,7 +302,7 @@ fn build_header<'a, H: TableComponentHeader>(
|
|||
Either::Right(columns.iter().enumerate().filter_map(move |(itx, c)| {
|
||||
if c.calculated_width == 0 {
|
||||
None
|
||||
} else if itx == *index {
|
||||
} else if itx == index {
|
||||
Some(truncate_suffixed_text(
|
||||
c.header.header_text(),
|
||||
arrow,
|
||||
|
@ -415,15 +442,16 @@ mod test {
|
|||
#[track_caller]
|
||||
|
||||
fn test_get(
|
||||
bar: usize, num: usize, direction: ScrollDirection, selected: usize, force: bool,
|
||||
bar: usize, rows: usize, direction: ScrollDirection, selected: usize, force: bool,
|
||||
expected_posn: usize, expected_bar: usize,
|
||||
) {
|
||||
let mut bar = bar;
|
||||
assert_eq!(
|
||||
get_start_position(num, &direction, &mut bar, selected, force),
|
||||
expected_posn
|
||||
get_start_position(rows, &direction, &mut bar, selected, force),
|
||||
expected_posn,
|
||||
"returned start position should match"
|
||||
);
|
||||
assert_eq!(bar, expected_bar);
|
||||
assert_eq!(bar, expected_bar, "bar positions should match");
|
||||
}
|
||||
|
||||
// Scrolling down from start
|
||||
|
@ -433,25 +461,26 @@ mod test {
|
|||
test_get(0, 10, Down, 1, false, 0, 0);
|
||||
|
||||
// Scrolling down from the middle high up
|
||||
test_get(0, 10, Down, 5, false, 0, 0);
|
||||
test_get(0, 10, Down, 4, false, 0, 0);
|
||||
|
||||
// Scrolling down into boundary
|
||||
test_get(0, 10, Down, 11, false, 1, 1);
|
||||
test_get(0, 10, Down, 10, false, 1, 1);
|
||||
test_get(0, 10, Down, 11, false, 2, 2);
|
||||
|
||||
// Scrolling down from the with non-zero bar
|
||||
test_get(5, 10, Down, 15, false, 5, 5);
|
||||
test_get(5, 10, Down, 14, false, 5, 5);
|
||||
|
||||
// Force redraw scrolling down (e.g. resize)
|
||||
test_get(5, 15, Down, 15, true, 0, 0);
|
||||
test_get(5, 15, Down, 14, true, 0, 0);
|
||||
|
||||
// Test jumping down
|
||||
test_get(1, 10, Down, 20, true, 10, 10);
|
||||
test_get(1, 10, Down, 19, true, 10, 10);
|
||||
|
||||
// Scrolling up from bottom
|
||||
test_get(10, 10, Up, 20, false, 10, 10);
|
||||
test_get(10, 10, Up, 19, false, 10, 10);
|
||||
|
||||
// Simple scrolling up
|
||||
test_get(10, 10, Up, 19, false, 10, 10);
|
||||
test_get(10, 10, Up, 18, false, 10, 10);
|
||||
|
||||
// Scrolling up from the middle
|
||||
test_get(10, 10, Up, 10, false, 10, 10);
|
||||
|
@ -460,7 +489,7 @@ mod test {
|
|||
test_get(10, 10, Up, 9, false, 9, 9);
|
||||
|
||||
// Force redraw scrolling up (e.g. resize)
|
||||
test_get(5, 10, Up, 15, true, 5, 5);
|
||||
test_get(5, 10, Up, 14, true, 5, 5);
|
||||
|
||||
// Test jumping up
|
||||
test_get(10, 10, Up, 0, false, 0, 0);
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{max, Ordering},
|
||||
};
|
||||
use std::{borrow::Cow, cmp::max};
|
||||
use tui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Rect},
|
||||
|
@ -15,6 +12,8 @@ use tui::{
|
|||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::utils::gen_util::partial_ordering;
|
||||
|
||||
/// An X or Y axis for the chart widget
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Axis<'a> {
|
||||
|
@ -556,16 +555,11 @@ impl<'a> Widget for TimeChart<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn bin_cmp(a: &f64, b: &f64) -> Ordering {
|
||||
// TODO: Switch to `total_cmp` on 1.62
|
||||
a.partial_cmp(b).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
|
||||
/// Returns the start index and potential interpolation index given the start time and the dataset.
|
||||
fn get_start(dataset: &Dataset<'_>, start_bound: f64) -> (usize, Option<usize>) {
|
||||
match dataset
|
||||
.data
|
||||
.binary_search_by(|(x, _y)| bin_cmp(x, &start_bound))
|
||||
.binary_search_by(|(x, _y)| partial_ordering(x, &start_bound))
|
||||
{
|
||||
Ok(index) => (index, None),
|
||||
Err(index) => (index, index.checked_sub(1)),
|
||||
|
@ -576,7 +570,7 @@ fn get_start(dataset: &Dataset<'_>, start_bound: f64) -> (usize, Option<usize>)
|
|||
fn get_end(dataset: &Dataset<'_>, end_bound: f64) -> (usize, Option<usize>) {
|
||||
match dataset
|
||||
.data
|
||||
.binary_search_by(|(x, _y)| bin_cmp(x, &end_bound))
|
||||
.binary_search_by(|(x, _y)| partial_ordering(x, &end_bound))
|
||||
{
|
||||
// In the success case, this means we found an index. Add one since we want to include this index and we
|
||||
// expect to use the returned index as part of a (m..n) range.
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
use tui::layout::Rect;
|
||||
|
||||
use crate::app::{self};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
time::Instant,
|
||||
};
|
||||
use crate::app::CursorDirection;
|
||||
use std::{cmp::min, time::Instant};
|
||||
|
||||
pub fn get_search_start_position(
|
||||
num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
|
||||
num_columns: usize, cursor_direction: &CursorDirection, cursor_bar: &mut usize,
|
||||
current_cursor_position: usize, is_force_redraw: bool,
|
||||
) -> usize {
|
||||
if is_force_redraw {
|
||||
|
@ -15,24 +12,24 @@ pub fn get_search_start_position(
|
|||
}
|
||||
|
||||
match cursor_direction {
|
||||
app::CursorDirection::Right => {
|
||||
CursorDirection::Right => {
|
||||
if current_cursor_position < *cursor_bar + num_columns {
|
||||
// If, using previous_scrolled_position, we can see the element
|
||||
// (so within that and + num_rows) just reuse the current previously scrolled position
|
||||
// (so within that and + num_rows) just reuse the current previously scrolled position.
|
||||
*cursor_bar
|
||||
} else if current_cursor_position >= num_columns {
|
||||
// Else if the current position past the last element visible in the list, omit
|
||||
// until we can see that element
|
||||
// until we can see that element.
|
||||
*cursor_bar = current_cursor_position - num_columns;
|
||||
*cursor_bar
|
||||
} else {
|
||||
// Else, if it is not past the last element visible, do not omit anything
|
||||
// Else, if it is not past the last element visible, do not omit anything.
|
||||
0
|
||||
}
|
||||
}
|
||||
app::CursorDirection::Left => {
|
||||
CursorDirection::Left => {
|
||||
if current_cursor_position <= *cursor_bar {
|
||||
// If it's past the first element, then show from that element downwards
|
||||
// If it's past the first element, then show from that element downwards.
|
||||
*cursor_bar = current_cursor_position;
|
||||
} else if current_cursor_position >= *cursor_bar + num_columns {
|
||||
*cursor_bar = current_cursor_position - num_columns;
|
||||
|
@ -45,7 +42,7 @@ pub fn get_search_start_position(
|
|||
|
||||
/// Calculate how many bars are to be drawn within basic mode's components.
|
||||
pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize {
|
||||
std::cmp::min(
|
||||
min(
|
||||
(num_bars_available as f64 * use_percentage / 100.0).round() as usize,
|
||||
num_bars_available,
|
||||
)
|
||||
|
|
|
@ -69,7 +69,7 @@ impl Painter {
|
|||
};
|
||||
|
||||
let battery_names = app_state
|
||||
.canvas_data
|
||||
.converted_data
|
||||
.battery_data
|
||||
.iter()
|
||||
.map(|battery| &battery.battery_name)
|
||||
|
@ -106,7 +106,7 @@ impl Painter {
|
|||
.split(draw_loc)[0];
|
||||
|
||||
if let Some(battery_details) = app_state
|
||||
.canvas_data
|
||||
.converted_data
|
||||
.battery_data
|
||||
.get(battery_widget_state.currently_selected_battery_index)
|
||||
{
|
||||
|
|
|
@ -20,8 +20,8 @@ impl Painter {
|
|||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
||||
) {
|
||||
// Skip the first element, it's the "all" element
|
||||
if app_state.canvas_data.cpu_data.len() > 1 {
|
||||
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data[1..];
|
||||
if app_state.converted_data.cpu_data.len() > 1 {
|
||||
let cpu_data: &[ConvertedCpuData] = &app_state.converted_data.cpu_data[1..];
|
||||
|
||||
// This is a bit complicated, but basically, we want to draw SOME number
|
||||
// of columns to draw all CPUs. Ideally, as well, we want to not have
|
||||
|
|
|
@ -175,7 +175,7 @@ impl Painter {
|
|||
const Y_LABELS: [Cow<'static, str>; 2] = [Cow::Borrowed(" 0%"), Cow::Borrowed("100%")];
|
||||
|
||||
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
|
||||
let cpu_data = &app_state.canvas_data.cpu_data;
|
||||
let cpu_data = &app_state.converted_data.cpu_data;
|
||||
let border_style = self.get_border_style(widget_id, app_state.current_widget.widget_id);
|
||||
let x_bounds = [0, cpu_widget_state.current_display_time];
|
||||
let hide_x_labels = should_hide_x_label(
|
||||
|
@ -193,7 +193,7 @@ impl Painter {
|
|||
|
||||
// TODO: Maybe hide load avg if too long? Or maybe the CPU part.
|
||||
let title = if cfg!(target_family = "unix") {
|
||||
let load_avg = app_state.canvas_data.load_avg_data;
|
||||
let load_avg = app_state.converted_data.load_avg_data;
|
||||
let load_avg_str = format!(
|
||||
"─ {:.2} {:.2} {:.2} ",
|
||||
load_avg[0], load_avg[1], load_avg[2]
|
||||
|
@ -232,7 +232,7 @@ impl Painter {
|
|||
|
||||
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
|
||||
let cpu_data = {
|
||||
let row_widths = vec![1, 3]; // TODO: Should change this to take const generics (usize) and an array.
|
||||
let col_widths = vec![1, 3]; // TODO: Should change this to take const generics (usize) and an array.
|
||||
let colour_iter = if show_avg_cpu {
|
||||
Either::Left(
|
||||
iter::once(&self.colours.all_colour_style)
|
||||
|
@ -247,7 +247,7 @@ impl Painter {
|
|||
};
|
||||
|
||||
let data = {
|
||||
let iter = app_state.canvas_data.cpu_data.iter().zip(colour_iter);
|
||||
let iter = app_state.converted_data.cpu_data.iter().zip(colour_iter);
|
||||
const CPU_WIDTH_CHECK: u16 = 10; // This is hard-coded, it's terrible.
|
||||
if draw_loc.width < CPU_WIDTH_CHECK {
|
||||
Either::Left(iter.map(|(cpu, style)| {
|
||||
|
@ -276,7 +276,7 @@ impl Painter {
|
|||
}
|
||||
.collect();
|
||||
|
||||
TableData { data, row_widths }
|
||||
TableData { data, col_widths }
|
||||
};
|
||||
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
|
@ -301,7 +301,13 @@ impl Painter {
|
|||
text_style: self.colours.text_style,
|
||||
left_to_right: false,
|
||||
}
|
||||
.draw_text_table(f, draw_loc, &mut cpu_widget_state.table_state, &cpu_data);
|
||||
.draw_text_table(
|
||||
f,
|
||||
draw_loc,
|
||||
&mut cpu_widget_state.table_state,
|
||||
&cpu_data,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,17 +46,9 @@ impl Painter {
|
|||
f,
|
||||
draw_loc,
|
||||
&mut disk_widget_state.table_state,
|
||||
&app_state.canvas_data.disk_data,
|
||||
&app_state.converted_data.disk_data,
|
||||
app_state.widget_map.get_mut(&widget_id),
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ impl Painter {
|
|||
pub fn draw_basic_memory<B: Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
||||
) {
|
||||
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
|
||||
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
|
||||
let mem_data: &[(f64, f64)] = &app_state.converted_data.mem_data;
|
||||
let swap_data: &[(f64, f64)] = &app_state.converted_data.swap_data;
|
||||
|
||||
let margined_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
|
@ -48,14 +48,14 @@ impl Painter {
|
|||
const EMPTY_MEMORY_FRAC_STRING: &str = "0.0B/0.0B";
|
||||
|
||||
let trimmed_memory_frac =
|
||||
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
|
||||
if let Some((_label_percent, label_frac)) = &app_state.converted_data.mem_labels {
|
||||
label_frac.trim()
|
||||
} else {
|
||||
EMPTY_MEMORY_FRAC_STRING
|
||||
};
|
||||
|
||||
let trimmed_swap_frac =
|
||||
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
|
||||
if let Some((_label_percent, label_frac)) = &app_state.converted_data.swap_labels {
|
||||
label_frac.trim()
|
||||
} else {
|
||||
EMPTY_MEMORY_FRAC_STRING
|
||||
|
|
|
@ -33,18 +33,18 @@ impl Painter {
|
|||
);
|
||||
let points = {
|
||||
let mut points = Vec::with_capacity(2);
|
||||
if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
|
||||
if let Some((label_percent, label_frac)) = &app_state.converted_data.mem_labels {
|
||||
let mem_label = format!("RAM:{}{}", label_percent, label_frac);
|
||||
points.push(GraphData {
|
||||
points: &app_state.canvas_data.mem_data,
|
||||
points: &app_state.converted_data.mem_data,
|
||||
style: self.colours.ram_style,
|
||||
name: Some(mem_label.into()),
|
||||
});
|
||||
}
|
||||
if let Some((label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
|
||||
if let Some((label_percent, label_frac)) = &app_state.converted_data.swap_labels {
|
||||
let swap_label = format!("SWP:{}{}", label_percent, label_frac);
|
||||
points.push(GraphData {
|
||||
points: &app_state.canvas_data.swap_data,
|
||||
points: &app_state.converted_data.swap_data,
|
||||
style: self.colours.swap_style,
|
||||
name: Some(swap_label.into()),
|
||||
});
|
||||
|
|
|
@ -38,10 +38,10 @@ impl Painter {
|
|||
);
|
||||
}
|
||||
|
||||
let rx_label = format!("RX: {}", &app_state.canvas_data.rx_display);
|
||||
let tx_label = format!("TX: {}", &app_state.canvas_data.tx_display);
|
||||
let total_rx_label = format!("Total RX: {}", &app_state.canvas_data.total_rx_display);
|
||||
let total_tx_label = format!("Total TX: {}", &app_state.canvas_data.total_tx_display);
|
||||
let rx_label = format!("RX: {}", &app_state.converted_data.rx_display);
|
||||
let tx_label = format!("TX: {}", &app_state.converted_data.tx_display);
|
||||
let total_rx_label = format!("Total RX: {}", &app_state.converted_data.total_rx_display);
|
||||
let total_tx_label = format!("Total TX: {}", &app_state.converted_data.total_tx_display);
|
||||
|
||||
let net_text = vec![
|
||||
Spans::from(Span::styled(rx_label, self.colours.rx_style)),
|
||||
|
|
|
@ -55,8 +55,8 @@ impl Painter {
|
|||
hide_legend: bool,
|
||||
) {
|
||||
if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) {
|
||||
let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
|
||||
let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx;
|
||||
let network_data_rx: &[(f64, f64)] = &app_state.converted_data.network_data_rx;
|
||||
let network_data_tx: &[(f64, f64)] = &app_state.converted_data.network_data_tx;
|
||||
let time_start = -(network_widget_state.current_display_time as f64);
|
||||
let border_style = self.get_border_style(widget_id, app_state.current_widget.widget_id);
|
||||
let x_bounds = [0, network_widget_state.current_display_time];
|
||||
|
@ -103,18 +103,18 @@ impl Painter {
|
|||
GraphData {
|
||||
points: network_data_rx,
|
||||
style: self.colours.rx_style,
|
||||
name: Some(format!("RX: {:7}", app_state.canvas_data.rx_display).into()),
|
||||
name: Some(format!("RX: {:7}", app_state.converted_data.rx_display).into()),
|
||||
},
|
||||
GraphData {
|
||||
points: network_data_tx,
|
||||
style: self.colours.tx_style,
|
||||
name: Some(format!("TX: {:7}", app_state.canvas_data.tx_display).into()),
|
||||
name: Some(format!("TX: {:7}", app_state.converted_data.tx_display).into()),
|
||||
},
|
||||
GraphData {
|
||||
points: &[],
|
||||
style: self.colours.total_rx_style,
|
||||
name: Some(
|
||||
format!("Total RX: {:7}", app_state.canvas_data.total_rx_display)
|
||||
format!("Total RX: {:7}", app_state.converted_data.total_rx_display)
|
||||
.into(),
|
||||
),
|
||||
},
|
||||
|
@ -122,7 +122,7 @@ impl Painter {
|
|||
points: &[],
|
||||
style: self.colours.total_tx_style,
|
||||
name: Some(
|
||||
format!("Total TX: {:7}", app_state.canvas_data.total_tx_display)
|
||||
format!("Total TX: {:7}", app_state.converted_data.total_tx_display)
|
||||
.into(),
|
||||
),
|
||||
},
|
||||
|
@ -132,12 +132,12 @@ impl Painter {
|
|||
GraphData {
|
||||
points: network_data_rx,
|
||||
style: self.colours.rx_style,
|
||||
name: Some((&app_state.canvas_data.rx_display).into()),
|
||||
name: Some((&app_state.converted_data.rx_display).into()),
|
||||
},
|
||||
GraphData {
|
||||
points: network_data_tx,
|
||||
style: self.colours.tx_style,
|
||||
name: Some((&app_state.canvas_data.tx_display).into()),
|
||||
name: Some((&app_state.converted_data.tx_display).into()),
|
||||
},
|
||||
]
|
||||
};
|
||||
|
@ -164,10 +164,10 @@ impl Painter {
|
|||
) {
|
||||
const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
|
||||
|
||||
let rx_display = &app_state.canvas_data.rx_display;
|
||||
let tx_display = &app_state.canvas_data.tx_display;
|
||||
let total_rx_display = &app_state.canvas_data.total_rx_display;
|
||||
let total_tx_display = &app_state.canvas_data.total_tx_display;
|
||||
let rx_display = &app_state.converted_data.rx_display;
|
||||
let tx_display = &app_state.converted_data.tx_display;
|
||||
let total_rx_display = &app_state.converted_data.total_rx_display;
|
||||
let total_tx_display = &app_state.converted_data.total_tx_display;
|
||||
|
||||
// Gross but I need it to work...
|
||||
let total_network = vec![Row::new(vec![
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
Painter,
|
||||
},
|
||||
constants::*,
|
||||
data_conversion::{TableData, TableRow},
|
||||
};
|
||||
|
||||
use tui::{
|
||||
|
@ -19,74 +20,7 @@ use tui::{
|
|||
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
const PROCESS_HEADERS_HARD_WIDTH_NO_GROUP: &[Option<u16>] = &[
|
||||
Some(7),
|
||||
None,
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(7),
|
||||
Some(8),
|
||||
#[cfg(target_family = "unix")]
|
||||
None,
|
||||
None,
|
||||
];
|
||||
const PROCESS_HEADERS_HARD_WIDTH_GROUPED: &[Option<u16>] = &[
|
||||
Some(7),
|
||||
None,
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(7),
|
||||
Some(8),
|
||||
];
|
||||
|
||||
const PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND: &[Option<f64>] =
|
||||
&[None, Some(0.7), None, None, None, None, None, None];
|
||||
const PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE: &[Option<f64>] =
|
||||
&[None, Some(0.3), None, None, None, None, None, None];
|
||||
|
||||
const PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND: &[Option<f64>] = &[
|
||||
None,
|
||||
Some(0.7),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
];
|
||||
const PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE: &[Option<f64>] = &[
|
||||
None,
|
||||
Some(0.5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
];
|
||||
const PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: &[Option<f64>] = &[
|
||||
None,
|
||||
Some(0.3),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
];
|
||||
const SORT_MENU_WIDTH: u16 = 7;
|
||||
|
||||
impl Painter {
|
||||
/// Draws and handles all process-related drawing. Use this.
|
||||
|
@ -95,13 +29,12 @@ impl Painter {
|
|||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
||||
widget_id: u64,
|
||||
) {
|
||||
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) {
|
||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get(&widget_id) {
|
||||
let search_height = if draw_border { 5 } else { 3 };
|
||||
let is_sort_open = process_widget_state.is_sort_open;
|
||||
const SORT_MENU_WIDTH: u16 = 8;
|
||||
let is_sort_open = proc_widget_state.is_sort_open;
|
||||
|
||||
let mut proc_draw_loc = draw_loc;
|
||||
if process_widget_state.is_search_enabled() {
|
||||
if proc_widget_state.is_search_enabled() {
|
||||
let processes_chunk = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(search_height)])
|
||||
|
@ -129,6 +62,13 @@ impl Painter {
|
|||
|
||||
self.draw_processes_table(f, app_state, proc_draw_loc, draw_border, widget_id);
|
||||
}
|
||||
|
||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) {
|
||||
// Reset redraw marker.
|
||||
if proc_widget_state.force_update {
|
||||
proc_widget_state.force_update = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the process sort box.
|
||||
|
@ -142,13 +82,7 @@ impl Painter {
|
|||
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) {
|
||||
let recalculate_column_widths =
|
||||
should_get_widget_bounds || proc_widget_state.requires_redraw;
|
||||
|
||||
// Reset redraw marker.
|
||||
// TODO: this should ideally be handled generically in the future.
|
||||
if proc_widget_state.requires_redraw {
|
||||
proc_widget_state.requires_redraw = false;
|
||||
}
|
||||
should_get_widget_bounds || proc_widget_state.force_update;
|
||||
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let (border_style, highlighted_text_style) = if is_on_widget {
|
||||
|
@ -160,6 +94,17 @@ impl Painter {
|
|||
(self.colours.border_style, self.colours.text_style)
|
||||
};
|
||||
|
||||
// TODO: [Refactor] This is an ugly hack to add the disabled style...
|
||||
// this could be solved by storing style locally to the widget.
|
||||
for row in &mut proc_widget_state.table_data.data {
|
||||
match row {
|
||||
TableRow::Styled(_, style) => {
|
||||
*style = style.patch(self.colours.disabled_text_style);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
TextTable {
|
||||
table_gap: app_state.app_config_fields.table_gap,
|
||||
is_force_redraw: app_state.is_force_redraw,
|
||||
|
@ -176,55 +121,15 @@ impl Painter {
|
|||
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
||||
title_style: self.colours.widget_title_style,
|
||||
text_style: self.colours.text_style,
|
||||
left_to_right: false,
|
||||
}
|
||||
.draw_text_table(f, draw_loc, &mut proc_widget_state.table_state, todo!());
|
||||
|
||||
// FIXME: [Proc] Handle this, and the above TODO
|
||||
// // Check if we need to update columnar bounds...
|
||||
// if recalculate_column_widths
|
||||
// || proc_widget_state.columns.column_header_x_locs.is_none()
|
||||
// || proc_widget_state.columns.column_header_y_loc.is_none()
|
||||
// {
|
||||
// // y location is just the y location of the widget + border size (1 normally, 0 in basic)
|
||||
// proc_widget_state.columns.column_header_y_loc =
|
||||
// Some(draw_loc.y + if draw_border { 1 } else { 0 });
|
||||
|
||||
// // x location is determined using the x locations of the widget; just offset from the left bound
|
||||
// // as appropriate, and use the right bound as limiter.
|
||||
|
||||
// let mut current_x_left = draw_loc.x + 1;
|
||||
// let max_x_right = draw_loc.x + draw_loc.width - 1;
|
||||
|
||||
// let mut x_locs = vec![];
|
||||
|
||||
// for width in proc_widget_state
|
||||
// .table_width_state
|
||||
// .calculated_column_widths
|
||||
// .iter()
|
||||
// {
|
||||
// let right_bound = current_x_left + width;
|
||||
|
||||
// if right_bound < max_x_right {
|
||||
// x_locs.push((current_x_left, right_bound));
|
||||
// current_x_left = right_bound + 1;
|
||||
// } else {
|
||||
// x_locs.push((current_x_left, max_x_right));
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// proc_widget_state.columns.column_header_x_locs = Some(x_locs);
|
||||
// }
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
left_to_right: true,
|
||||
}
|
||||
.draw_text_table(
|
||||
f,
|
||||
draw_loc,
|
||||
&mut proc_widget_state.table_state,
|
||||
&proc_widget_state.table_data,
|
||||
app_state.widget_map.get_mut(&widget_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,8 +248,8 @@ impl Painter {
|
|||
self.colours.text_style
|
||||
};
|
||||
|
||||
// FIXME: [MOUSE] Mouse support for these in search
|
||||
// FIXME: [MOVEMENT] Movement support for these in search
|
||||
// TODO: [MOUSE] Mouse support for these in search
|
||||
// TODO: [MOVEMENT] Movement support for these in search
|
||||
let option_text = Spans::from(vec![
|
||||
Span::styled(
|
||||
format!("Case({})", if self.is_mac_os { "F1" } else { "Alt+C" }),
|
||||
|
@ -445,124 +350,66 @@ impl Painter {
|
|||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
||||
widget_id: u64,
|
||||
) {
|
||||
// FIXME: [Proc] Redo drawing sort table!
|
||||
// let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
||||
if let Some(proc_widget_state) =
|
||||
app_state.proc_state.widget_states.get_mut(&(widget_id - 2))
|
||||
{
|
||||
let recalculate_column_widths =
|
||||
should_get_widget_bounds || proc_widget_state.force_update;
|
||||
|
||||
// if let Some(proc_widget_state) =
|
||||
// app_state.proc_state.widget_states.get_mut(&(widget_id - 2))
|
||||
// {
|
||||
// let current_scroll_position = proc_widget_state.columns.current_scroll_position;
|
||||
// let sort_string = proc_widget_state
|
||||
// .columns
|
||||
// .ordered_columns
|
||||
// .iter()
|
||||
// .filter(|column_type| {
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .column_mapping
|
||||
// .get(column_type)
|
||||
// .unwrap()
|
||||
// .enabled
|
||||
// })
|
||||
// .map(|column_type| column_type.to_string())
|
||||
// .collect::<Vec<_>>();
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let (border_style, highlighted_text_style) = if is_on_widget {
|
||||
(
|
||||
self.colours.highlighted_border_style,
|
||||
self.colours.currently_selected_text_style,
|
||||
)
|
||||
} else {
|
||||
(self.colours.border_style, self.colours.text_style)
|
||||
};
|
||||
|
||||
// let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
// 0
|
||||
// } else {
|
||||
// app_state.app_config_fields.table_gap
|
||||
// };
|
||||
// let position = get_start_position(
|
||||
// usize::from(
|
||||
// (draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset),
|
||||
// ),
|
||||
// &proc_widget_state.columns.scroll_direction,
|
||||
// &mut proc_widget_state.columns.previous_scroll_position,
|
||||
// current_scroll_position,
|
||||
// app_state.is_force_redraw,
|
||||
// );
|
||||
// TODO: [PROC] Perhaps move this generation elsewhere... or leave it as is but look at partial rendering?
|
||||
let table_data = {
|
||||
let data = proc_widget_state
|
||||
.table_state
|
||||
.columns
|
||||
.iter()
|
||||
.filter_map(|col| {
|
||||
if col.is_hidden {
|
||||
None
|
||||
} else {
|
||||
Some(TableRow::Raw(vec![col.header.text().clone()]))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// // Sanity check
|
||||
// let start_position = if position >= sort_string.len() {
|
||||
// sort_string.len().saturating_sub(1)
|
||||
// } else {
|
||||
// position
|
||||
// };
|
||||
TableData {
|
||||
data,
|
||||
col_widths: vec![usize::from(SORT_MENU_WIDTH)],
|
||||
}
|
||||
};
|
||||
|
||||
// let sliced_vec = &sort_string[start_position..];
|
||||
|
||||
// let sort_options = sliced_vec
|
||||
// .iter()
|
||||
// .map(|column| Row::new(vec![column.as_str()]));
|
||||
|
||||
// let column_state = &mut proc_widget_state.columns.column_state;
|
||||
// column_state.select(Some(
|
||||
// proc_widget_state
|
||||
// .columns
|
||||
// .current_scroll_position
|
||||
// .saturating_sub(start_position),
|
||||
// ));
|
||||
// let current_border_style = if proc_widget_state
|
||||
// .search_state
|
||||
// .search_state
|
||||
// .is_invalid_search
|
||||
// {
|
||||
// self.colours.invalid_query_style
|
||||
// } else if is_on_widget {
|
||||
// self.colours.highlighted_border_style
|
||||
// } else {
|
||||
// self.colours.border_style
|
||||
// };
|
||||
|
||||
// let process_sort_block = if draw_border {
|
||||
// Block::default()
|
||||
// .borders(Borders::ALL)
|
||||
// .border_style(current_border_style)
|
||||
// } else if is_on_widget {
|
||||
// Block::default()
|
||||
// .borders(SIDE_BORDERS)
|
||||
// .border_style(current_border_style)
|
||||
// } else {
|
||||
// Block::default().borders(Borders::NONE)
|
||||
// };
|
||||
|
||||
// let highlight_style = if is_on_widget {
|
||||
// self.colours.currently_selected_text_style
|
||||
// } else {
|
||||
// self.colours.text_style
|
||||
// };
|
||||
|
||||
// let margined_draw_loc = Layout::default()
|
||||
// .constraints([Constraint::Percentage(100)])
|
||||
// .horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
// .direction(Direction::Horizontal)
|
||||
// .split(draw_loc)[0];
|
||||
|
||||
// f.render_stateful_widget(
|
||||
// Table::new(sort_options)
|
||||
// .header(
|
||||
// Row::new(vec!["Sort By"])
|
||||
// .style(self.colours.table_header_style)
|
||||
// .bottom_margin(table_gap),
|
||||
// )
|
||||
// .block(process_sort_block)
|
||||
// .highlight_style(highlight_style)
|
||||
// .style(self.colours.text_style)
|
||||
// .widths(&[Constraint::Percentage(100)]),
|
||||
// margined_draw_loc,
|
||||
// column_state,
|
||||
// );
|
||||
|
||||
// if app_state.should_get_widget_bounds() {
|
||||
// // Update draw loc in widget map
|
||||
// if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
// widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||
// widget.bottom_right_corner = Some((
|
||||
// margined_draw_loc.x + margined_draw_loc.width,
|
||||
// margined_draw_loc.y + margined_draw_loc.height,
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
TextTable {
|
||||
table_gap: app_state.app_config_fields.table_gap,
|
||||
is_force_redraw: app_state.is_force_redraw,
|
||||
recalculate_column_widths,
|
||||
header_style: self.colours.table_header_style,
|
||||
border_style,
|
||||
highlighted_text_style,
|
||||
title: None,
|
||||
is_on_widget,
|
||||
draw_border,
|
||||
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
||||
title_style: self.colours.widget_title_style,
|
||||
text_style: self.colours.text_style,
|
||||
left_to_right: true,
|
||||
}
|
||||
.draw_text_table(
|
||||
f,
|
||||
draw_loc,
|
||||
&mut proc_widget_state.sort_table_state,
|
||||
&table_data,
|
||||
app_state.widget_map.get_mut(&widget_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,17 +47,9 @@ impl Painter {
|
|||
f,
|
||||
draw_loc,
|
||||
&mut temp_widget_state.table_state,
|
||||
&app_state.canvas_data.temp_sensor_data,
|
||||
&app_state.converted_data.temp_sensor_data,
|
||||
app_state.widget_map.get_mut(&widget_id),
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
274
src/lib.rs
274
src/lib.rs
|
@ -27,9 +27,8 @@ use crossterm::{
|
|||
};
|
||||
|
||||
use app::{
|
||||
data_harvester::{self, processes::ProcessSorting},
|
||||
data_harvester,
|
||||
layout_manager::{UsedWidgets, WidgetDirection},
|
||||
widgets::{ProcWidget, ProcWidgetMode},
|
||||
App,
|
||||
};
|
||||
use constants::*;
|
||||
|
@ -304,21 +303,23 @@ pub fn panic_hook(panic_info: &PanicInfo<'_>) {
|
|||
}
|
||||
|
||||
pub fn handle_force_redraws(app: &mut App) {
|
||||
// Currently we use an Option... because we might want to future-proof this
|
||||
// if we eventually get widget-specific redrawing!
|
||||
|
||||
// FIXME: [PROC] handle updating processes if force redraw!
|
||||
for proc in app.proc_state.widget_states.values_mut() {
|
||||
if proc.force_update {
|
||||
// NB: Currently, the "force update" gets fixed in the draw.
|
||||
proc.update_displayed_process_data(&app.data_collection);
|
||||
}
|
||||
}
|
||||
|
||||
if app.cpu_state.force_update.is_some() {
|
||||
convert_cpu_data_points(&app.data_collection, &mut app.canvas_data.cpu_data);
|
||||
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
|
||||
convert_cpu_data_points(&app.data_collection, &mut app.converted_data.cpu_data);
|
||||
app.converted_data.load_avg_data = app.data_collection.load_avg_harvest;
|
||||
app.cpu_state.force_update = None;
|
||||
}
|
||||
|
||||
// FIXME: [OPT] Prefer reassignment over new vectors?
|
||||
if app.mem_state.force_update.is_some() {
|
||||
app.canvas_data.mem_data = convert_mem_data_points(&app.data_collection);
|
||||
app.canvas_data.swap_data = convert_swap_data_points(&app.data_collection);
|
||||
app.converted_data.mem_data = convert_mem_data_points(&app.data_collection);
|
||||
app.converted_data.swap_data = convert_swap_data_points(&app.data_collection);
|
||||
app.mem_state.force_update = None;
|
||||
}
|
||||
|
||||
|
@ -329,257 +330,12 @@ pub fn handle_force_redraws(app: &mut App) {
|
|||
&app.app_config_fields.network_unit_type,
|
||||
app.app_config_fields.network_use_binary_prefix,
|
||||
);
|
||||
app.canvas_data.network_data_rx = rx;
|
||||
app.canvas_data.network_data_tx = tx;
|
||||
app.converted_data.network_data_rx = rx;
|
||||
app.converted_data.network_data_tx = tx;
|
||||
app.net_state.force_update = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
pub fn update_all_process_lists(app: &mut App) {
|
||||
// According to clippy, I can avoid a collect... but if I follow it,
|
||||
// I end up conflicting with the borrow checker since app is used within the closure... hm.
|
||||
if !app.is_frozen {
|
||||
let widget_ids = app
|
||||
.proc_state
|
||||
.widget_states
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
widget_ids.into_iter().for_each(|widget_id| {
|
||||
update_final_process_list(app, widget_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
let process_states = app
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get(&widget_id)
|
||||
.map(|process_state| {
|
||||
(
|
||||
process_state
|
||||
.search_state
|
||||
.search_state
|
||||
.is_invalid_or_blank_search(),
|
||||
process_state.is_using_command,
|
||||
process_state.mode,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some((is_invalid_or_blank, is_using_command, mode)) = process_states {
|
||||
if !app.is_frozen {
|
||||
convert_process_data(
|
||||
&app.data_collection,
|
||||
&mut app.canvas_data.single_process_data,
|
||||
#[cfg(target_family = "unix")]
|
||||
&mut app.user_table,
|
||||
);
|
||||
}
|
||||
let process_filter = app.get_process_filter(widget_id);
|
||||
let filtered_process_data: Vec<ConvertedProcessData> = match mode {
|
||||
ProcWidgetMode::Tree => app
|
||||
.canvas_data
|
||||
.single_process_data
|
||||
.iter()
|
||||
.map(|(_pid, process)| {
|
||||
let mut process_clone = process.clone();
|
||||
if !is_invalid_or_blank {
|
||||
if let Some(process_filter) = process_filter {
|
||||
process_clone.is_disabled_entry =
|
||||
!process_filter.check(&process_clone, is_using_command);
|
||||
}
|
||||
}
|
||||
process_clone
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
ProcWidgetMode::Grouped | ProcWidgetMode::Normal => app
|
||||
.canvas_data
|
||||
.single_process_data
|
||||
.iter()
|
||||
.filter_map(|(_pid, process)| {
|
||||
if !is_invalid_or_blank {
|
||||
if let Some(process_filter) = process_filter {
|
||||
if process_filter.check(process, is_using_command) {
|
||||
Some(process)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(process)
|
||||
}
|
||||
} else {
|
||||
Some(process)
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
|
||||
let mut finalized_process_data = match proc_widget_state.mode {
|
||||
ProcWidgetMode::Tree => tree_process_data(
|
||||
&filtered_process_data,
|
||||
is_using_command,
|
||||
&proc_widget_state.process_sorting_type,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
),
|
||||
ProcWidgetMode::Grouped => {
|
||||
let mut data = group_process_data(&filtered_process_data, is_using_command);
|
||||
sort_process_data(&mut data, proc_widget_state);
|
||||
data
|
||||
}
|
||||
ProcWidgetMode::Normal => {
|
||||
let mut data = filtered_process_data;
|
||||
sort_process_data(&mut data, proc_widget_state);
|
||||
data
|
||||
}
|
||||
};
|
||||
|
||||
if proc_widget_state.table_state.current_scroll_position >= finalized_process_data.len()
|
||||
{
|
||||
proc_widget_state.table_state.current_scroll_position =
|
||||
finalized_process_data.len().saturating_sub(1);
|
||||
proc_widget_state.table_state.scroll_bar = 0;
|
||||
proc_widget_state.table_state.scroll_direction = app::ScrollDirection::Down;
|
||||
}
|
||||
|
||||
app.canvas_data.stringified_process_data_map.insert(
|
||||
widget_id,
|
||||
stringify_process_data(proc_widget_state, &finalized_process_data),
|
||||
);
|
||||
app.canvas_data
|
||||
.finalized_process_data_map
|
||||
.insert(widget_id, finalized_process_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_process_data(to_sort_vec: &mut [ConvertedProcessData], proc_widget_state: &ProcWidget) {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());
|
||||
|
||||
match &proc_widget_state.process_sorting_type {
|
||||
ProcessSorting::CpuPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.cpu_percent_usage,
|
||||
b.cpu_percent_usage,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::Mem => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.mem_usage_bytes,
|
||||
b.mem_usage_bytes,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::MemPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.mem_percent_usage,
|
||||
b.mem_percent_usage,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::ProcessName => {
|
||||
// Don't repeat if false... it sorts by name by default anyways.
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
ProcessSorting::Command => {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.command.to_lowercase());
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
ProcessSorting::Pid => {
|
||||
if !matches!(proc_widget_state.mode, ProcWidgetMode::Grouped) {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.pid,
|
||||
b.pid,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
ProcessSorting::ReadPerSecond => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.rps_f64,
|
||||
b.rps_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::WritePerSecond => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.wps_f64,
|
||||
b.wps_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::TotalRead => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.tr_f64,
|
||||
b.tr_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::TotalWrite => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.tw_f64,
|
||||
b.tw_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::State => {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.process_state.to_lowercase());
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
ProcessSorting::User => to_sort_vec.sort_by(|a, b| match (&a.user, &b.user) {
|
||||
(Some(user_a), Some(user_b)) => utils::gen_util::get_ordering(
|
||||
user_a.to_lowercase(),
|
||||
user_b.to_lowercase(),
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Less,
|
||||
}),
|
||||
ProcessSorting::Count => {
|
||||
if matches!(proc_widget_state.mode, ProcWidgetMode::Grouped) {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.group_pids.len(),
|
||||
b.group_pids.len(),
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_input_thread(
|
||||
sender: std::sync::mpsc::Sender<
|
||||
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
||||
|
@ -640,7 +396,7 @@ pub fn create_collection_thread(
|
|||
thread::spawn(move || {
|
||||
let mut data_state = data_harvester::DataCollector::new(filters);
|
||||
|
||||
data_state.set_collected_data(used_widget_set);
|
||||
data_state.set_data_collection(used_widget_set);
|
||||
data_state.set_temperature_type(temp_type);
|
||||
data_state.set_use_current_cpu_total(use_current_cpu_total);
|
||||
data_state.set_show_average_cpu(show_average_cpu);
|
||||
|
@ -671,7 +427,7 @@ pub fn create_collection_thread(
|
|||
data_state.set_show_average_cpu(app_config_fields.show_average_cpu);
|
||||
}
|
||||
ThreadControlEvent::UpdateUsedWidgets(used_widget_set) => {
|
||||
data_state.set_collected_data(*used_widget_set);
|
||||
data_state.set_data_collection(*used_widget_set);
|
||||
}
|
||||
ThreadControlEvent::UpdateUpdateTime(new_time) => {
|
||||
update_time = new_time;
|
||||
|
|
|
@ -348,19 +348,16 @@ pub fn build_app(
|
|||
Net => {
|
||||
net_state_map.insert(
|
||||
widget.widget_id,
|
||||
NetWidgetState::init(
|
||||
default_time_value,
|
||||
autohide_timer,
|
||||
// network_unit_type.clone(),
|
||||
// network_scale_type.clone(),
|
||||
),
|
||||
NetWidgetState::init(default_time_value, autohide_timer),
|
||||
);
|
||||
}
|
||||
Proc => {
|
||||
let mode = if is_grouped {
|
||||
ProcWidgetMode::Grouped
|
||||
} else if is_default_tree {
|
||||
ProcWidgetMode::Tree
|
||||
ProcWidgetMode::Tree {
|
||||
collapsed_pids: Default::default(),
|
||||
}
|
||||
} else {
|
||||
ProcWidgetMode::Normal
|
||||
};
|
||||
|
|
|
@ -92,9 +92,27 @@ pub fn get_decimal_prefix(quantity: u64, unit: &str) -> (f64, String) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn sort_partial_fn<T: std::cmp::PartialOrd>(is_reverse: bool) -> fn(T, T) -> Ordering {
|
||||
if is_reverse {
|
||||
partial_ordering_rev
|
||||
} else {
|
||||
partial_ordering
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an [`Ordering`] between two [`PartialOrd`]s.
|
||||
pub fn partial_ordering<T: std::cmp::PartialOrd>(a: T, b: T) -> Ordering {
|
||||
// TODO: Switch to `total_cmp` on 1.62
|
||||
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
|
||||
}
|
||||
|
||||
/// Returns a reversed [`Ordering`] between two [`PartialOrd`]s.
|
||||
pub fn partial_ordering_rev<T: std::cmp::PartialOrd>(a: T, b: T) -> Ordering {
|
||||
// TODO: Switch to `total_cmp` on 1.62
|
||||
a.partial_cmp(&b).unwrap_or(Ordering::Equal).reverse()
|
||||
}
|
||||
|
||||
/// Gotta get partial ordering? No problem, here's something to deal with it~
|
||||
///
|
||||
/// Note that https://github.com/reem/rust-ordered-float exists, maybe move to it one day? IDK.
|
||||
pub fn get_ordering<T: std::cmp::PartialOrd>(
|
||||
a_val: T, b_val: T, reverse_order: bool,
|
||||
) -> std::cmp::Ordering {
|
||||
|
|
Loading…
Reference in New Issue