diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index ae8fc70e73..9ed734c111 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -455,7 +455,7 @@ mod tests { fn unstyle(line_part: LinePart) -> String { let string = line_part.to_string(); - let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + let re = regex::Regex::new(r"\x1b\[[0-9;]*m").expect("Test failed"); let string = re.replace_all(&string, "".to_string()); string.to_string() diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 3bff31ce54..9cf36456fe 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -169,9 +169,10 @@ fn color_elements(palette: Palette, different_color_alternates: bool) -> Colored } impl ZellijPlugin for State { - fn load(&mut self) { + fn load(&mut self) -> Result<()> { // TODO: Should be able to choose whether to use the cache through config. - self.tip_name = get_cached_tip_name(); + self.tip_name = + get_cached_tip_name().context("status bar: Failed to acquire a tip to display")?; set_selectable(false); subscribe(&[ EventType::ModeUpdate, @@ -180,10 +181,9 @@ impl ZellijPlugin for State { EventType::InputReceived, EventType::SystemClipboardFailure, ]) - .expect(errors::TODO_HANDLING); } - fn update(&mut self, event: Event) { + fn update(&mut self, event: Event) -> Result<()> { match event { Event::ModeUpdate(mode_info) => { self.mode_info = mode_info; @@ -203,9 +203,10 @@ impl ZellijPlugin for State { }, _ => {}, } + Ok(()) } - fn render(&mut self, _rows: usize, cols: usize) { + fn render(&mut self, _rows: usize, cols: usize) -> Result<()> { let supports_arrow_fonts = !self.mode_info.capabilities.arrow_fonts; let separator = if supports_arrow_fonts { ARROW_SEPARATOR @@ -214,7 +215,9 @@ impl ZellijPlugin for State { }; let first_line = first_line(&self.mode_info, cols, separator); - let second_line = self.second_line(cols); + let second_line = self + .second_line(cols) + .context("status bar: Failed to generate second line of output")?; let background = match self.mode_info.style.colors.theme_hue { ThemeHue::Dark => self.mode_info.style.colors.black, @@ -232,11 +235,12 @@ impl ZellijPlugin for State { }, } println!("\u{1b}[m{}\u{1b}[0K", second_line); + Ok(()) } } impl State { - fn second_line(&self, cols: usize) -> LinePart { + fn second_line(&self, cols: usize) -> Result { let active_tab = self.tabs.iter().find(|t| t.active); if let Some(copy_destination) = self.text_copy_destination { @@ -268,7 +272,7 @@ impl State { keybinds(&self.mode_info, &self.tip_name, cols) } } else { - LinePart::default() + Ok(LinePart::default()) } } } diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 26afcd94f2..c1b5f5f0f7 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -45,7 +45,7 @@ fn full_length_shortcut( } } -fn locked_interface_indication(palette: Palette) -> LinePart { +fn locked_interface_indication(palette: Palette) -> Result { let locked_text = " -- INTERFACE LOCKED -- "; let locked_text_len = locked_text.chars().count(); let text_color = palette_match!(match palette.theme_hue { @@ -53,10 +53,10 @@ fn locked_interface_indication(palette: Palette) -> LinePart { ThemeHue::Light => palette.black, }); let locked_styled_text = Style::new().fg(text_color).bold().paint(locked_text); - LinePart { + Ok(LinePart { part: locked_styled_text.to_string(), len: locked_text_len, - } + }) } fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec) -> LinePart { @@ -75,7 +75,6 @@ fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec fn full_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart { let mut line_part = LinePart::default(); let keys_and_hints = get_keys_and_hints(help); - for (long, _short, keys) in keys_and_hints.into_iter() { line_part = add_shortcut(help, &line_part, &long, keys.to_vec()); } @@ -130,7 +129,11 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { // Sort and deduplicate the keybindings first. We sort after the `Key`s, and deduplicate by // their `Action` vectors. An unstable sort is fine here because if the user maps anything to // the same key again, anything will happen... - old_keymap.sort_unstable_by(|(keya, _), (keyb, _)| keya.partial_cmp(keyb).unwrap()); + old_keymap.sort_unstable_by( + |(keya, _), (keyb, _)| keya + .partial_cmp(keyb) + .unwrap_or(std::cmp::Ordering::Equal) + ); let mut known_actions: Vec> = vec![]; let mut km = vec![]; @@ -256,33 +259,32 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { ]} else { vec![] } } -fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { +fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> Result { match help.mode { - InputMode::Normal => tip(help), + InputMode::Normal => Ok(tip(help)), InputMode::Locked => locked_interface_indication(help.style.colors), - _ => full_shortcut_list_nonstandard_mode(help), + _ => Ok(full_shortcut_list_nonstandard_mode(help)), } } fn shortened_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart { let mut line_part = LinePart::default(); let keys_and_hints = get_keys_and_hints(help); - for (_, short, keys) in keys_and_hints.into_iter() { line_part = add_shortcut(help, &line_part, &short, keys.to_vec()); } line_part } -fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { +fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> Result { match help.mode { - InputMode::Normal => tip(help), + InputMode::Normal => Ok(tip(help)), InputMode::Locked => locked_interface_indication(help.style.colors), - _ => shortened_shortcut_list_nonstandard_mode(help), + _ => Ok(shortened_shortcut_list_nonstandard_mode(help)), } } -fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) -> LinePart { +fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) -> Result { let mut line_part = LinePart::default(); let keys_and_hints = get_keys_and_hints(help); @@ -295,49 +297,52 @@ fn best_effort_shortcut_list_nonstandard_mode(help: &ModeInfo, max_len: usize) - } line_part = new_line_part; } - line_part + Ok(line_part) } -fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart { +fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> Result { match help.mode { InputMode::Normal => { let line_part = tip(help); if line_part.len <= max_len { - line_part + Ok(line_part) } else { - LinePart::default() + Ok(LinePart::default()) } }, InputMode::Locked => { - let line_part = locked_interface_indication(help.style.colors); + let line_part = locked_interface_indication(help.style.colors)?; if line_part.len <= max_len { - line_part + Ok(line_part) } else { - LinePart::default() + Ok(LinePart::default()) } }, _ => best_effort_shortcut_list_nonstandard_mode(help, max_len), } } -pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart { +pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> Result { // It is assumed that there is at least one TIP data in the TIPS HasMap. let tip_body = TIPS .get(tip_name) - .unwrap_or_else(|| TIPS.get("quicknav").unwrap()); + .or_else(|| TIPS.get("quicknav")) + .unwrap_or(crate::tip::NO_TIPS_FOUND); - let full_shortcut_list = full_shortcut_list(help, tip_body.full); + let full_shortcut_list = full_shortcut_list(help, tip_body.full) + .context("status bar: Failed to assemble second line")?; if full_shortcut_list.len <= max_width { - return full_shortcut_list; + return Ok(full_shortcut_list); } - let shortened_shortcut_list = shortened_shortcut_list(help, tip_body.medium); + let shortened_shortcut_list = shortened_shortcut_list(help, tip_body.medium) + .context("status bar: Failed to assemble second line")?; if shortened_shortcut_list.len <= max_width { - return shortened_shortcut_list; + return Ok(shortened_shortcut_list); } best_effort_shortcut_list(help, tip_body.short, max_width) } -pub fn text_copied_hint(palette: &Palette, copy_destination: CopyDestination) -> LinePart { +pub fn text_copied_hint(palette: &Palette, copy_destination: CopyDestination) -> Result { let green_color = palette_match!(palette.green); let hint = match copy_destination { CopyDestination::Command => "Text piped to external command", @@ -347,22 +352,22 @@ pub fn text_copied_hint(palette: &Palette, copy_destination: CopyDestination) -> CopyDestination::Primary => "Text copied to system clipboard", CopyDestination::System => "Text copied to system clipboard", }; - LinePart { + Ok(LinePart { part: Style::new().fg(green_color).bold().paint(hint).to_string(), len: hint.len(), - } + }) } -pub fn system_clipboard_error(palette: &Palette) -> LinePart { +pub fn system_clipboard_error(palette: &Palette) -> Result { let hint = " Error using the system clipboard."; let red_color = palette_match!(palette.red); - LinePart { + Ok(LinePart { part: Style::new().fg(red_color).bold().paint(hint).to_string(), len: hint.len(), - } + }) } -pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart { +pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Result { let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, @@ -380,7 +385,7 @@ pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Line + panes.chars().count() + hide.chars().count() + 5; // 3 for ():'s around shortcut, 2 for the space - LinePart { + Ok(LinePart { part: format!( "{}{}{}{}{}{}", shortcut_left_separator, @@ -391,10 +396,10 @@ pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Line Style::new().fg(text_color).bold().paint(hide) ), len, - } + }) } -pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { +pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> Result { let palette = mode_info.style.colors; let km = &mode_info.get_mode_keybinds(); let white_color = match palette.white { @@ -442,7 +447,7 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { + p_right_separator.chars().count() + to_hide.chars().count() + 5; // 3 for ():'s around floating_panes, 2 for the space - LinePart { + Ok(LinePart { part: format!( "{}{}{}{}{}{}{}{}{}{}", shortcut_left_separator, @@ -457,10 +462,13 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { Style::new().fg(white_color).bold().paint(to_hide), ), len, - } + }) } -pub fn locked_fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart { +pub fn locked_fullscreen_panes_to_hide( + palette: &Palette, + panes_to_hide: usize, +) -> Result { let text_color = palette_match!(match palette.theme_hue { ThemeHue::Dark => palette.white, ThemeHue::Light => palette.black, @@ -480,7 +488,7 @@ pub fn locked_fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) + panes.chars().count() + hide.chars().count() + 5; // 3 for ():'s around shortcut, 2 for the space - LinePart { + Ok(LinePart { part: format!( "{}{}{}{}{}{}{}", Style::new().fg(text_color).bold().paint(locked_text), @@ -492,10 +500,10 @@ pub fn locked_fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) Style::new().fg(text_color).bold().paint(hide) ), len, - } + }) } -pub fn locked_floating_panes_are_visible(palette: &Palette) -> LinePart { +pub fn locked_floating_panes_are_visible(palette: &Palette) -> Result { let white_color = match palette.white { PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), PaletteColor::EightBit(color) => Fixed(color), @@ -510,7 +518,7 @@ pub fn locked_floating_panes_are_visible(palette: &Palette) -> LinePart { let floating_panes = "FLOATING PANES VISIBLE"; let len = locked_text.chars().count() + floating_panes.chars().count(); - LinePart { + Ok(LinePart { part: format!( "{}{}{}{}", Style::new().fg(white_color).bold().paint(locked_text), @@ -519,7 +527,7 @@ pub fn locked_floating_panes_are_visible(palette: &Palette) -> LinePart { shortcut_right_separator, ), len, - } + }) } #[cfg(test)] @@ -536,7 +544,7 @@ mod tests { fn unstyle(line_part: LinePart) -> String { let string = line_part.to_string(); - let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + let re = regex::Regex::new(r"\x1b\[[0-9;]*m").expect("Test failed"); let string = re.replace_all(&string, "".to_string()); string.to_string() @@ -684,7 +692,7 @@ mod tests { ..ModeInfo::default() }; - let ret = keybinds(&mode_info, "quicknav", 500); + let ret = keybinds(&mode_info, "quicknav", 500).expect("Test failed"); let ret = unstyle(ret); assert_eq!( @@ -719,7 +727,7 @@ mod tests { ..ModeInfo::default() }; - let ret = keybinds(&mode_info, "quicknav", 35); + let ret = keybinds(&mode_info, "quicknav", 35).expect("Test failed"); let ret = unstyle(ret); assert_eq!(ret, " <←↓↑→> Move / New ... "); @@ -756,7 +764,7 @@ mod tests { ..ModeInfo::default() }; - let ret = keybinds(&mode_info, "quicknav", 500); + let ret = keybinds(&mode_info, "quicknav", 500).expect("Test failed"); let ret = unstyle(ret); assert_eq!(ret, " Ctrl + Move focus / New / Close / Fullscreen"); diff --git a/default-plugins/status-bar/src/tip/cache.rs b/default-plugins/status-bar/src/tip/cache.rs index 6aee715ac6..c193c789da 100644 --- a/default-plugins/status-bar/src/tip/cache.rs +++ b/default-plugins/status-bar/src/tip/cache.rs @@ -6,8 +6,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use thiserror::Error; -use zellij_tile::prelude::errors; -use zellij_tile::prelude::get_zellij_version; +use zellij_tile::prelude::*; #[derive(Debug, Serialize, Deserialize)] pub struct Metadata { @@ -34,6 +33,9 @@ pub enum LocalCacheError { // Deserialization error #[error("Deserialization error: {0}")] Serde(#[from] serde_json::Error), + // Generic error + #[error("An error occured: {0}")] + Generic(#[from] anyhow::Error), } impl LocalCache { @@ -43,7 +45,7 @@ impl LocalCache { Err(err) => { if json_cache.is_empty() { return Ok(Metadata { - zellij_version: get_zellij_version().expect(errors::TODO_HANDLING), + zellij_version: get_zellij_version().map_err(LocalCacheError::Generic)?, cached_data: HashMap::new(), }); } diff --git a/default-plugins/status-bar/src/tip/mod.rs b/default-plugins/status-bar/src/tip/mod.rs index 9d4fbb70a4..d0ace63a30 100644 --- a/default-plugins/status-bar/src/tip/mod.rs +++ b/default-plugins/status-bar/src/tip/mod.rs @@ -13,3 +13,18 @@ pub struct TipBody { pub medium: TipFn, pub full: TipFn, } + +pub const NO_TIPS_FOUND: &TipBody = &TipBody { + short: |_| LinePart { + part: " No tips".to_string(), + len: 9, + }, + medium: |_| LinePart { + part: " Error: No tips found".to_string(), + len: 22, + }, + full: |_| LinePart { + part: " Error: Failed to acquire tips: No tips found!".to_string(), + len: 46, + }, +}; diff --git a/default-plugins/status-bar/src/tip/utils.rs b/default-plugins/status-bar/src/tip/utils.rs index 5394e031ae..7b249ab76e 100644 --- a/default-plugins/status-bar/src/tip/utils.rs +++ b/default-plugins/status-bar/src/tip/utils.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use rand::prelude::{IteratorRandom, SliceRandom}; -use zellij_tile::prelude::errors; +use zellij_tile::prelude::anyhow::{self, Context, Result}; use zellij_tile::prelude::get_zellij_version; use super::cache::LocalCache; @@ -11,39 +11,51 @@ use super::data::TIPS; macro_rules! get_name_and_caching { ($cache:expr) => {{ - let name = get_random_tip_name(); - $cache.caching(name.clone()).unwrap(); - return name; + let name = get_random_tip_name()?; + $cache + .caching(name.clone()) + .context("Failed to update tip cache")?; + return Ok(name); }}; ($cache:expr, $from:expr) => {{ - let name = $from.choose(&mut rand::thread_rng()).unwrap().to_string(); - $cache.caching(name.clone()).unwrap(); - return name; + let name = $from + .choose(&mut rand::thread_rng()) + .context("Failed to get random tip from selection")? + .to_string(); + $cache + .caching(name.clone()) + .context("Failed to update tip cache")?; + return Ok(name); }}; } macro_rules! populate_cache { ($cache:expr) => {{ for tip_name in TIPS.keys() { - $cache.caching(tip_name.clone()).unwrap(); + $cache + .caching(tip_name.clone()) + .context("Failed to populate tip cache")?; } }}; } -pub fn get_random_tip_name() -> String { - TIPS.keys() +pub fn get_random_tip_name() -> Result { + Ok(TIPS + .keys() .choose(&mut rand::thread_rng()) - .unwrap() - .to_string() + .ok_or_else(|| anyhow::anyhow!("Failed to get a random tip"))? + .to_string()) } -pub fn get_cached_tip_name() -> String { - let mut local_cache = LocalCache::new(PathBuf::from(DEFAULT_CACHE_FILE_PATH)).unwrap(); +pub fn get_cached_tip_name() -> Result { + let mut local_cache = LocalCache::new(PathBuf::from(DEFAULT_CACHE_FILE_PATH)) + .context("Failed to initialize tip cache")?; - let zellij_version = get_zellij_version().expect(errors::TODO_HANDLING); + let zellij_version = get_zellij_version() + .context("status bar: Failed to acquire zellij version to query cache")?; if zellij_version.ne(local_cache.get_version()) { local_cache.set_version(zellij_version); - local_cache.clear().unwrap(); + local_cache.clear().context("Failed to clear tip cache")?; } if local_cache.is_empty() { @@ -53,7 +65,7 @@ pub fn get_cached_tip_name() -> String { let quicknav_show_count = local_cache.get_cached_data().get("quicknav").unwrap_or(&0); if quicknav_show_count <= &MAX_CACHE_HITS { let _ = local_cache.caching("quicknav"); - return String::from("quicknav"); + return Ok(String::from("quicknav")); } let usable_tips = local_cache @@ -73,7 +85,7 @@ pub fn get_cached_tip_name() -> String { if !diff.is_empty() { get_name_and_caching!(local_cache, diff); } else { - local_cache.clear().unwrap(); + local_cache.clear().context("Failed to clear tip cache")?; get_name_and_caching!(local_cache); } } else {