diff options
Diffstat (limited to 'src/app.rs')
-rw-r--r-- | src/app.rs | 3495 |
1 files changed, 527 insertions, 2968 deletions
@@ -1,39 +1,27 @@ -use std::{ - cmp::{max, min}, - collections::HashMap, - // io::Write, - path::PathBuf, - time::Instant, -}; - -use unicode_segmentation::GraphemeCursor; -use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; - -use typed_builder::*; - -use data_farmer::*; -use data_harvester::{processes, temperature}; -use layout_manager::*; -pub use states::*; - -use crate::{ - canvas, constants, - options::Config, - options::ConfigFlags, - options::WidgetIdEnabled, - units::data_units::DataUnit, - utils::error::{BottomError, Result}, - Pid, -}; - pub mod data_farmer; pub mod data_harvester; +pub mod event; +pub mod filter; pub mod layout_manager; mod process_killer; pub mod query; -pub mod states; +pub mod widgets; -const MAX_SEARCH_LENGTH: usize = 200; +use std::time::Instant; + +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; +use fxhash::FxHashMap; +use indextree::{Arena, NodeId}; + +pub use data_farmer::*; +use data_harvester::temperature; +pub use filter::*; +use layout_manager::*; +pub use widgets::*; + +use crate::{constants, units::data_units::DataUnit, utils::error::Result, BottomEvent, Pid}; + +use self::event::{ComponentEventResult, EventResult, ReturnSignal}; #[derive(Debug, Clone)] pub enum AxisScaling { @@ -41,6 +29,46 @@ pub enum AxisScaling { Linear, } +#[derive(Clone, Default, Debug)] +pub struct UsedWidgets { + pub use_cpu: bool, + pub use_mem: bool, + pub use_net: bool, + pub use_proc: bool, + pub use_disk: bool, + pub use_temp: bool, + pub use_battery: bool, +} + +impl UsedWidgets { + pub fn add(&mut self, widget_type: &BottomWidgetType) { + match widget_type { + BottomWidgetType::Cpu | BottomWidgetType::BasicCpu => { + self.use_cpu = true; + } + BottomWidgetType::Mem | BottomWidgetType::BasicMem => { + self.use_mem = true; + } + BottomWidgetType::Net | BottomWidgetType::BasicNet => { + self.use_net = true; + } + BottomWidgetType::Proc => { + self.use_proc = true; + } + BottomWidgetType::Temp => { + self.use_temp = true; + } + BottomWidgetType::Disk => { + self.use_disk = true; + } + BottomWidgetType::Battery => { + self.use_battery = true; + } + _ => {} + } + } +} + /// AppConfigFields is meant to cover basic fields that would normally be set /// by config files or launch options. #[derive(Debug)] @@ -57,139 +85,114 @@ pub struct AppConfigFields { pub hide_time: bool, pub autohide_time: bool, pub use_old_network_legend: bool, - pub table_gap: u16, + pub table_gap: bool, pub disable_click: bool, pub no_write: bool, pub show_table_scroll_position: bool, pub is_advanced_kill: bool, - // TODO: Remove these, move network details state-side. pub network_unit_type: DataUnit, pub network_scale_type: AxisScaling, pub network_use_binary_prefix: bool, } -/// For filtering out information -#[derive(Debug, Clone)] -pub struct DataFilters { - pub disk_filter: Option<Filter>, - pub mount_filter: Option<Filter>, - pub temp_filter: Option<Filter>, - pub net_filter: Option<Filter>, +/// The [`FrozenState`] indicates whether the application state should be frozen; if it is, save a snapshot of +/// the data collected at that instant. +pub enum FrozenState { + NotFrozen, + Frozen(Box<DataCollection>), } -#[derive(Debug, Clone)] -pub struct Filter { - pub is_list_ignored: bool, - pub list: Vec<regex::Regex>, +impl Default for FrozenState { + fn default() -> Self { + Self::NotFrozen + } } -#[derive(TypedBuilder)] -pub struct App { - #[builder(default = false, setter(skip))] - awaiting_second_char: bool, - - #[builder(default, setter(skip))] - second_char: Option<char>, - - #[builder(default, setter(skip))] +pub struct AppState { pub dd_err: Option<String>, - #[builder(default, setter(skip))] to_delete_process_list: Option<(String, Vec<Pid>)>, - #[builder(default = false, setter(skip))] - pub is_frozen: bool, - - #[builder(default = Instant::now(), setter(skip))] - last_key_press: Instant, - - #[builder(default, setter(skip))] - pub canvas_data: canvas::DisplayableData, - - #[builder(default, setter(skip))] pub data_collection: DataCollection, - #[builder(default, setter(skip))] - pub delete_dialog_state: AppDeleteDialogState, - - #[builder(default, setter(skip))] - pub help_dialog_state: AppHelpDialogState, - - #[builder(default = false, setter(skip))] pub is_expanded: bool, - #[builder(default = false, setter(skip))] + pub used_widgets: UsedWidgets, + pub filters: DataFilters, + pub app_config_fields: AppConfigFields, + + // --- FIXME: TO DELETE/REWRITE --- + pub delete_dialog_state: AppDeleteDialogState, pub is_force_redraw: bool, - #[builder(default = false, setter(skip))] pub is_determining_widget_boundary: bool, - #[builder(default = false, setter(skip))] - pub basic_mode_use_percent: bool, - - #[builder(default = false, setter(skip))] - pub is_config_open: bool, - - #[builder(default = false, setter(skip))] - pub did_config_fail_to_save: bool, - - #[cfg(target_family = "unix")] - #[builder(default, setter(skip))] - pub user_table: processes::UserTable, + // --- NEW STUFF --- + pub selected_widget: NodeId, + pub widget_lookup_map: FxHashMap<NodeId, TmpBottomWidget>, + pub layout_tree: Arena<LayoutNode>, + pub layout_tree_root: NodeId, + frozen_state: FrozenState, - pub cpu_state: CpuState, - pub mem_state: MemState, - pub net_state: NetState, - pub proc_state: ProcState, - pub temp_state: TempState, - pub disk_state: DiskState, - pub battery_state: BatteryState, - pub basic_table_widget_state: Option<BasicTableWidgetState>, - pub app_config_fields: AppConfigFields, - pub widget_map: HashMap<u64, BottomWidget>, - pub current_widget: BottomWidget, - pub used_widgets: UsedWidgets, - pub filters: DataFilters, - pub config: Config, - pub config_path: Option<PathBuf>, + pub help_dialog: DialogState<HelpDialog>, } -#[cfg(target_os = "windows")] -const MAX_SIGNAL: usize = 1; -#[cfg(target_os = "linux")] -const MAX_SIGNAL: usize = 64; -#[cfg(target_os = "macos")] -const MAX_SIGNAL: usize = 31; +impl AppState { + /// Creates a new [`AppState`]. + pub fn new( + app_config_fields: AppConfigFields, filters: DataFilters, + layout_tree_output: LayoutCreationOutput, + ) -> Self { + let LayoutCreationOutput { + layout_tree, + root: layout_tree_root, + widget_lookup_map, + selected: selected_widget, + used_widgets, + } = layout_tree_output; + + Self { + app_config_fields, + filters, + used_widgets, + selected_widget, + widget_lookup_map, + layout_tree, + layout_tree_root, + + // Use defaults. + dd_err: Default::default(), + to_delete_process_list: Default::default(), + data_collection: Default::default(), + is_expanded: Default::default(), + delete_dialog_state: Default::default(), + is_force_redraw: Default::default(), + is_determining_widget_boundary: Default::default(), + frozen_state: Default::default(), + help_dialog: Default::default(), + } + } + + pub fn is_frozen(&self) -> bool { + matches!(self.frozen_state, FrozenState::Frozen(_)) + } + + pub fn toggle_freeze(&mut self) { + if matches!(self.frozen_state, FrozenState::Frozen(_)) { + self.frozen_state = FrozenState::NotFrozen; + } else { + self.frozen_state = FrozenState::Frozen(Box::new(self.data_collection.clone())); + } + } -impl App { pub fn reset(&mut self) { - // Reset multi - self.reset_multi_tap_keys(); - - // Reset dialog state - self.help_dialog_state.is_showing_help = false; - self.delete_dialog_state.is_showing_dd = false; - - // Close all searches and reset it - self.proc_state - .widget_states + // Call reset on all widgets. + self.widget_lookup_map .values_mut() - .for_each(|state| { - state.process_search_state.search_state.reset(); - }); - self.proc_state.force_update_all = true; - - // Clear current delete list - self.to_delete_process_list = None; - self.dd_err = None; + .for_each(|widget| widget.reset()); // Unfreeze. - self.is_frozen = false; - - // Reset zoom - self.reset_cpu_zoom(); - self.reset_mem_zoom(); - self.reset_net_zoom(); + self.frozen_state = FrozenState::NotFrozen; // Reset data self.data_collection.reset(); @@ -207,674 +210,269 @@ impl App { self.dd_err = None; } - pub fn on_esc(&mut self) { - self.reset_multi_tap_keys(); - if self.is_in_dialog() { - if self.help_dialog_state.is_showing_help { - self.help_dialog_state.is_showing_help = false; - self.help_dialog_state.scroll_state.current_scroll_index = 0; - } else { - self.close_dd(); - } - - self.is_force_redraw = true; - } else if self.is_config_open { - self.close_config_screen(); + /// Handles a global event involving a char. + fn handle_global_char(&mut self, c: char) -> EventResult { + if c.is_ascii_control() { + EventResult::NoRedraw } else { - match self.current_widget.widget_type { - BottomWidgetType::Proc => { - if let Some(current_proc_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id) - { - if current_proc_state.is_search_enabled() || current_proc_state.is_sort_open - { - current_proc_state - .process_search_state - .search_state - .is_enabled = false; - current_proc_state.is_sort_open = false; - self.is_force_redraw = true; - return; - } - } - } - BottomWidgetType::ProcSearch => { - if let Some(current_proc_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id - 1) - { - if current_proc_state.is_search_enabled() { - current_proc_state - .process_search_state - .search_state - .is_enabled = false; - self.move_widget_selection(&WidgetDirection::Up); - self.is_force_redraw = true; - return; - } - } - } - BottomWidgetType::ProcSort => { - if let Some(current_proc_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id - 2) - { - if current_proc_state.is_sort_open { - current_proc_state.columns.current_scroll_position = - current_proc_state.columns.backup_prev_scroll_position; - current_proc_state.is_sort_open = false; - self.move_widget_selection(&WidgetDirection::Right); - self.is_force_redraw = true; - return; + // Check for case-sensitive bindings first. + match c { + 'H' | 'A' => self.move_to_widget(MovementDirection::Left), + 'L' | 'D' => self.move_to_widget(MovementDirection::Right), + 'K' | 'W' => self.move_to_widget(MovementDirection::Up), + 'J' | 'S' => self.move_to_widget(MovementDirection::Down), + _ => { + let c = c.to_ascii_lowercase(); + match c { + 'q' => EventResult::Quit, + 'e' if !self.help_dialog.is_showing() => { + if self.app_config_fields.use_basic_mode { + EventResult::NoRedraw + } else { + self.is_expanded = !self.is_expanded; + EventResult::Redraw + } } - } - } - _ => {} - } - - if self.is_expanded { - self.is_expanded = false; - self.is_force_redraw = true; - } - } - } - - pub fn is_in_search_widget(&self) -> bool { - matches!( - self.current_widget.widget_type, - BottomWidgetType::ProcSearch - ) - } - - fn reset_multi_tap_keys(&mut self) { - self.awaiting_second_char = false; - self.second_char = None; - } - - fn is_in_dialog(&self) -> bool { - self.help_dialog_state.is_showing_help || self.delete_dialog_state.is_showing_dd - } - - fn ignore_normal_keybinds(&self) -> bool { - self.is_config_open || self.is_in_dialog() - } - - pub fn on_tab(&mut self) { - // Allow usage whilst only in processes - - if !self.ignore_normal_keybinds() { - match self.current_widget.widget_type { - BottomWidgetType::Cpu => { - if let Some(cpu_widget_state) = self - .cpu_state - .get_mut_widget_state(self.current_widget.widget_id) - { - cpu_widget_state.is_multi_graph_mode = - !cpu_widget_state.is_multi_graph_mode; - } - } - BottomWidgetType::Proc => { - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id) - { - // 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; + '?' if !self.help_dialog.is_showing() => { + self.help_dialog.show(); + EventResult::Redraw + } + 'f' if !self.help_dialog.is_showing() => { + self.toggle_freeze(); + if !self.is_frozen() { + let data_collection = &self.data_collection; + self.widget_lookup_map + .iter_mut() + .for_each(|(_id, widget)| widget.update_data(data_collection)); } - - 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); + EventResult::Redraw } + _ => EventResult::NoRedraw, } } - _ => {} } } } - /// I don't like this, but removing it causes a bunch of breakage. - /// Use ``proc_widget_state.is_grouped`` if possible! - pub fn is_grouped(&self, widget_id: u64) -> bool { - if let Some(proc_widget_state) = self.proc_state.widget_states.get(&widget_id) { - proc_widget_state.is_grouped + /// Moves to a widget. + fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult { + match if self.is_expanded { + move_expanded_widget_selection( + &mut self.widget_lookup_map, + self.selected_widget, + direction, + ) } else { - false - } - } - - pub fn on_slash(&mut self) { - if !self.ignore_normal_keybinds() { - match &self.current_widget.widget_type { - BottomWidgetType::Proc | BottomWidgetType::ProcSort => { - // Toggle on - if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state( - self.current_widget.widget_id - - match &self.current_widget.widget_type { - BottomWidgetType::ProcSort => 2, - _ => 0, - }, - ) { - proc_widget_state - .process_search_state - .search_state - .is_enabled = true; - self.move_widget_selection(&WidgetDirection::Down); - self.is_force_redraw = true; - } + let layout_tree = &mut self.layout_tree; + + move_widget_selection( + layout_tree, + &mut self.widget_lookup_map, + self.selected_widget, + direction, + ) + } { + MoveWidgetResult::ForceRedraw(new_widget_id) => { + self.selected_widget = new_widget_id; + EventResult::Redraw + } + MoveWidgetResult::NodeId(new_widget_id) => { + let previous_selected = self.selected_widget; + self.selected_widget = new_widget_id; + + if previous_selected != self.selected_widget { + EventResult::Redraw + } else { + EventResult::NoRedraw } - _ => {} } } } - pub fn toggle_sort(&mut self) { - match &self.current_widget.widget_type { - BottomWidgetType::Proc | BottomWidgetType::ProcSort => { - let widget_id = self.current_widget.widget_id - - match &self.current_widget.widget_type { - BottomWidgetType::Proc => 0, - BottomWidgetType::ProcSort => 2, - _ => 0, - }; - - if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) { - // Open up sorting dialog for that specific proc widget. - // TODO: It might be a decent idea to allow sorting ALL? I dunno. - - proc_widget_state.is_sort_open = !proc_widget_state.is_sort_open; - if proc_widget_state.is_sort_open { - // If it just opened, move left - proc_widget_state - .columns - .set_to_sorted_index_from_type(&proc_widget_state.process_sorting_type); - self.move_widget_selection(&WidgetDirection::Left); - } else { - // Otherwise, move right if currently on the sort widget - if let BottomWidgetType::ProcSort = self.current_widget.widget_type { - self.move_widget_selection(&WidgetDirection::Right); + /// Quick and dirty handler to convert [`ComponentEventResult`]s to [`EventResult`]s, and handle [`ReturnSignal`]s. + fn convert_widget_event_result(&mut self, w: ComponentEventResult) -> EventResult { + match w { + ComponentEventResult::Unhandled => EventResult::NoRedraw, + ComponentEventResult::Redraw => EventResult::Redraw, + ComponentEventResult::NoRedraw => EventResult::NoRedraw, + ComponentEventResult::Signal(signal) => match signal { + ReturnSignal::KillProcess => { + todo!() + } + ReturnSignal::Update => { + if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { + match &self.frozen_state { + FrozenState::NotFrozen => { + widget.update_data(&self.data_collection); + } + FrozenState::Frozen(frozen_data) => { + widget.update_data(frozen_data); + } } } + EventResult::Redraw } - - self.is_force_redraw = true; - } - _ => {} + }, } } - pub fn invert_sort(&mut self) { - match &self.current_widget.widget_type { - BottomWidgetType::Proc | BottomWidgetType::ProcSort => { - let widget_id = self.current_widget.widget_id - - match &self.current_widget.widget_type { - BottomWidgetType::Proc => 0, - BottomWidgetType::ProcSort => 2, - _ => 0, - }; - - 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; - - self.proc_state.force_update = Some(widget_id); + /// Handles a [`KeyEvent`], and returns an [`EventResult`]. + fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { + let result = if let DialogState::Shown(help_dialog) = &mut self.help_dialog { + help_dialog.handle_key_event(event) + } else if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { + widget.handle_key_event(event) + } else { + ComponentEventResult::Unhandled + }; + + match result { + ComponentEventResult::Unhandled => self.handle_global_key_event(event), + _ => self.convert_widget_event_result(result), + } + } + + /// Handles a global [`KeyEvent`], and returns an [`EventResult`]. + fn handle_global_key_event(&mut self, event: KeyEvent) -> EventResult { + if event.modifiers.is_empty() { + match event.code { + KeyCode::Esc => { + if self.is_expanded { + self.is_expanded = false; + EventResult::Redraw + } else if self.help_dialog.is_showing() { + self.help_dialog.hide(); + EventResult::Redraw + } else if self.delete_dialog_state.is_showing_dd { + self.close_dd(); + EventResult::Redraw + } else { + EventResult::NoRedraw + } } + _ => { + if let KeyCode::Char(c) = event.code { + self.handle_global_char(c) + } else { + EventResult::NoRedraw + } + } + } + } else if let KeyModifiers::CONTROL = event.modifiers { + match event.code { + KeyCode::Char('c') | KeyCode::Char('C') => EventResult::Quit, + KeyCode::Char('r') | KeyCode::Char('R') => { + self.reset(); + let data_collection = &self.data_collection; + self.widget_lookup_map + .iter_mut() + .for_each(|(_id, widget)| widget.update_data(data_collection)); + EventResult::Redraw + } + KeyCode::Left => self.move_to_widget(MovementDirection::Left), + KeyCode::Right => self.move_to_widget(MovementDirection::Right), + KeyCode::Up => self.move_to_widget(MovementDirection::Up), + KeyCode::Down => self.move_to_widget(MovementDirection::Down), + _ => EventResult::NoRedraw, + } + } else if let KeyModifiers::SHIFT = event.modifiers { + match event.code { + KeyCode::Left => self.move_to_widget(MovementDirection::Left), + KeyCode::Right => self.move_to_widget(MovementDirection::Right), + KeyCode::Up => self.move_to_widget(MovementDirection::Up), + KeyCode::Down => self.move_to_widget(MovementDirection::Down), + KeyCode::Char(c) => self.handle_global_char(c), + _ => EventResult::NoRedraw, } - _ => {} + } else { + EventResult::NoRedraw } } - pub fn toggle_percentages(&mut self) { - match &self.current_widget.widget_type { - BottomWidgetType::BasicMem => { - self.basic_mode_use_percent = !self.basic_mode_use_percent; // Oh god this is so lazy. + /// Handles a [`MouseEvent`]. + fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult { + if let DialogState::Shown(help_dialog) = &mut self.help_dialog { + let result = help_dialog.handle_mouse_event(event); + self.convert_widget_event_result(result) + } else if self.is_expanded { + if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { + let result = widget.handle_mouse_event(event); + self.convert_widget_event_result(result) + } else { + EventResult::NoRedraw } - BottomWidgetType::Proc => { - if let Some(proc_widget_state) = self - .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) - { - proc_widget_state - .columns - .toggle(&processes::ProcessSorting::Mem); - if let Some(mem_percent_state) = proc_widget_state - .columns - .toggle(&processes::ProcessSorting::MemPercent) - { - if proc_widget_state.process_sorting_type - == processes::ProcessSorting::MemPercent - || proc_widget_state.process_sorting_type - == processes::ProcessSorting::Mem - { - if mem_percent_state { - proc_widget_state.process_sorting_type = - processes::ProcessSorting::MemPercent; + } else { + let mut returned_result = EventResult::NoRedraw; + for (id, widget) in self.widget_lookup_map.iter_mut() { + if widget.does_border_intersect_mouse(&event) { + let result = widget.handle_mouse_event(event); + match widget.selectable_type() { + SelectableType::Selectable => { + let was_id_already_selected = self.selected_widget == *id; + self.selected_widget = *id; + + if was_id_already_selected { + returned_result = self.convert_widget_event_result(result); } else { - proc_widget_state.process_sorting_type = - processes::ProcessSorting::Mem; + // If the weren't equal, *force* a redraw, and correct the layout tree. + correct_layout_last_selections( + &mut self.layout_tree, + self.selected_widget, + ); + let _ = self.convert_widget_event_result(result); + returned_result = EventResult::Redraw; } + break; + } + SelectableType::Unselectable => { + let result = widget.handle_mouse_event(event); + return self.convert_widget_event_result(result); } } - - proc_widget_state.requires_redraw = true; - self.proc_state.force_update = Some(self.current_widget.widget_id); } } - _ => {} - } - } - - pub fn toggle_ignore_case(&mut self) { - let is_in_search_widget = self.is_in_search_widget(); - let mut is_case_sensitive: Option<bool> = None; - if let Some(proc_widget_state) = self - .proc_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) - { - if is_in_search_widget && proc_widget_state.is_search_enabled() { - proc_widget_state - .process_search_state - .search_toggle_ignore_case(); - proc_widget_state.update_query(); - self.proc_state.force_update = Some(self.current_widget.widget_id - 1); - // Remember, it's the opposite (ignoring case is case "in"sensitive) - is_case_sensitive = Some(!proc_widget_state.process_search_state.is_ignoring_case); - } - } - - // Also toggle it in the config file if we actually changed it. - if let Some(is_ignoring_case) = is_case_sensitive { - if let Some(flags) = &mut self.config.flags { - if let Some(map) = &mut flags.search_case_enabled_widgets_map { - // Just update the map. - let mapping = map.entry(self.current_widget.widget_id - 1).or_default(); - *mapping = is_ignoring_case; - - flags.search_case_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(map)); - } else { - // Map doesn't exist yet... initialize ourselves. - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_ignoring_case); - flags.search_case_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(&map)); - flags.search_case_enabled_widgets_map = Some(map); - } - } else { - // Must initialize it ourselves... - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_ignoring_case); - - self.config.flags = Some( - ConfigFlags::builder() - .search_case_enabled_widgets(WidgetIdEnabled::create_from_hashmap(&map)) - .search_case_enabled_widgets_map(map) - .build(), - ); - } - - // self.did_config_fail_to_save = self.update_config_file().is_err(); - } - } - - pub fn toggle_search_whole_word(&mut self) { - let is_in_search_widget = self.is_in_search_widget(); - let mut is_searching_whole_word: Option<bool> = None; - if let Some(proc_widget_state) = self - .proc_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) - { - if is_in_search_widget && proc_widget_state.is_search_enabled() { - proc_widget_state - .process_search_state - .search_toggle_whole_word(); - proc_widget_state.update_query(); - self.proc_state.force_update = Some(self.current_widget.widget_id - 1); - - is_searching_whole_word = Some( - proc_widget_state - .process_search_state - .is_searching_whole_word, - ); - } - } - - // Also toggle it in the config file if we actually changed it. - if let Some(is_searching_whole_word) = is_searching_whole_word { - if let Some(flags) = &mut self.config.flags { - if let Some(map) = &mut flags.search_whole_word_enabled_widgets_map { - // Just update the map. - let mapping = map.entry(self.current_widget.widget_id - 1).or_default(); - *mapping = is_searching_whole_word; - - flags.search_whole_word_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(map)); - } else { - // Map doesn't exist yet... initialize ourselves. - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_searching_whole_word); - flags.search_whole_word_enabled_widgets = - Some(WidgetIdEnabled::create_from_hashmap(&map)); - flags.search_whole_word_enabled_widgets_map = Some(map); - } - } else { - // Must initialize it ourselves... - let mut map = HashMap::default(); - map.insert(self.current_widget.widget_id - 1, is_searching_whole_word); - - self.config.flags = Some( - ConfigFlags::builder() - .search_whole_word_enabled_widgets(WidgetIdEnabled::create_from_hashmap( - &map, - )) - .search_whole_word_enabled_widgets_map(map) - .build(), - ); - } - - // self.did_config_fail_to_save = self.update_config_file().is_err(); - } - } - - pub fn toggle_search_regex(&mut self) { |