diff options
author | Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> | 2021-12-10 02:53:46 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-09 18:53:46 +0100 |
commit | d79060f69a99e73215533c1acbdff79af99af8f9 (patch) | |
tree | 626603f9756db04a41218cb3da9b02e6069bab1f /default-plugins/status-bar/src | |
parent | 2096cafe1d9d3858f6ccd18c6898ea1e9cf837ba (diff) |
feat(status-bar): add multiple tips (#848)
* feat(status-bar): add draft for multiple tips
* feat: add TIPS_MAP
* Simplified 'tip' function.
* chore: update file structure
* feat(status-bar): update method of Tip rendering
* feat(status-bar): change type of tip in State
* refactor(status-bar): related to random tip selection
* feat(status-bar): add simple local cache for testing
* feat(status-bar): add cache system for tip data
* Add mpadir to wasm for plugin to access zellij temp folder.
* refactor(status-bar): update cache and utils
* fix(status-bar): update file read error
* refactor(status-bar): update macros
* chore(status-bar): delete test data
* chore(status-bar): update missing fixes
* feat(status-bar): add detailed error
* style: make clippy
Diffstat (limited to 'default-plugins/status-bar/src')
-rw-r--r-- | default-plugins/status-bar/src/main.rs | 11 | ||||
-rw-r--r-- | default-plugins/status-bar/src/second_line.rs | 216 | ||||
-rw-r--r-- | default-plugins/status-bar/src/tip/cache.rs | 119 | ||||
-rw-r--r-- | default-plugins/status-bar/src/tip/consts.rs | 2 | ||||
-rw-r--r-- | default-plugins/status-bar/src/tip/data.rs | 100 | ||||
-rw-r--r-- | default-plugins/status-bar/src/tip/mod.rs | 16 | ||||
-rw-r--r-- | default-plugins/status-bar/src/tip/utils.rs | 68 |
7 files changed, 330 insertions, 202 deletions
diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index ce80dab14..bc322e6a8 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -1,5 +1,6 @@ mod first_line; mod second_line; +mod tip; use ansi_term::Style; @@ -11,6 +12,7 @@ use first_line::{ctrl_keys, superkey}; use second_line::{ fullscreen_panes_to_hide, keybinds, locked_fullscreen_panes_to_hide, text_copied_hint, }; +use tip::utils::get_cached_tip_name; // for more of these, copy paste from: https://en.wikipedia.org/wiki/Box-drawing_character static ARROW_SEPARATOR: &str = ""; @@ -19,6 +21,7 @@ static MORE_MSG: &str = " ... "; #[derive(Default)] struct State { tabs: Vec<TabInfo>, + tip_name: String, mode_info: ModeInfo, diplay_text_copied_hint: bool, } @@ -131,6 +134,8 @@ fn color_elements(palette: Palette) -> ColoredElements { impl ZellijPlugin for State { fn load(&mut self) { + // TODO: Should be able to choose whether to use the cache through config. + self.tip_name = get_cached_tip_name(); set_selectable(false); subscribe(&[ EventType::ModeUpdate, @@ -190,7 +195,7 @@ impl ZellijPlugin for State { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) } else { - keybinds(&self.mode_info, cols) + keybinds(&self.mode_info, &self.tip_name, cols) } } } @@ -208,7 +213,7 @@ impl ZellijPlugin for State { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) } else { - keybinds(&self.mode_info, cols) + keybinds(&self.mode_info, &self.tip_name, cols) } } } @@ -216,7 +221,7 @@ impl ZellijPlugin for State { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) } else { - keybinds(&self.mode_info, cols) + keybinds(&self.mode_info, &self.tip_name, cols) } } } diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 9cc978a61..82806e5f5 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -5,7 +5,10 @@ use ansi_term::{ }; use zellij_tile::prelude::*; -use crate::{LinePart, MORE_MSG}; +use crate::{ + tip::{data::TIPS, TipFn}, + LinePart, MORE_MSG, +}; fn full_length_shortcut( is_first_shortcut: bool, @@ -82,194 +85,6 @@ fn first_word_shortcut( len, } } -fn quicknav_full(palette: Palette) -> LinePart { - let text_first_part = " Tip: "; - let alt = "Alt"; - let text_second_part = " + "; - let new_pane_shortcut = "<n>"; - let text_third_part = " => open new pane. "; - let second_alt = "Alt"; - let text_fourth_part = " + "; - let brackets_navigation = "<[]"; - let text_fifth_part = " or "; - let hjkl_navigation = "hjkl>"; - let text_sixths_part = " => navigate between panes. "; - let third_alt = "Alt"; - let text_seventh_parth = " + "; - let increase_decrease_parth = "<+->"; - let text_eighth_parth = " => increase/decrease pane size."; - let len = text_first_part.chars().count() - + alt.chars().count() - + text_second_part.chars().count() - + new_pane_shortcut.chars().count() - + text_third_part.chars().count() - + second_alt.chars().count() - + text_fourth_part.chars().count() - + brackets_navigation.chars().count() - + text_fifth_part.chars().count() - + hjkl_navigation.chars().count() - + text_sixths_part.chars().count() - + third_alt.chars().count() - + text_seventh_parth.chars().count() - + increase_decrease_parth.chars().count() - + text_eighth_parth.chars().count(); - let green_color = match palette.green { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - let orange_color = match palette.orange { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - LinePart { - part: format!( - "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", - text_first_part, - Style::new().fg(orange_color).bold().paint(alt), - text_second_part, - Style::new().fg(green_color).bold().paint(new_pane_shortcut), - text_third_part, - Style::new().fg(orange_color).bold().paint(second_alt), - text_fourth_part, - Style::new() - .fg(green_color) - .bold() - .paint(brackets_navigation), - text_fifth_part, - Style::new().fg(green_color).bold().paint(hjkl_navigation), - text_sixths_part, - Style::new().fg(orange_color).bold().paint(third_alt), - text_seventh_parth, - Style::new() - .fg(green_color) - .bold() - .paint(increase_decrease_parth), - text_eighth_parth, - ), - len, - } -} - -fn quicknav_medium(palette: Palette) -> LinePart { - let text_first_part = " Tip: "; - let alt = "Alt"; - let text_second_part = " + "; - let new_pane_shortcut = "<n>"; - let text_third_part = " => new pane. "; - let second_alt = "Alt"; - let text_fourth_part = " + "; - let brackets_navigation = "<[]"; - let text_fifth_part = " or "; - let hjkl_navigation = "hjkl>"; - let text_sixths_part = " => navigate. "; - let third_alt = "Alt"; - let text_seventh_parth = " + "; - let increase_decrease_parth = "<+->"; - let text_eighth_parth = " => resize pane. "; - let len = text_first_part.chars().count() - + alt.chars().count() - + text_second_part.chars().count() - + new_pane_shortcut.chars().count() - + text_third_part.chars().count() - + second_alt.chars().count() - + text_fourth_part.chars().count() - + brackets_navigation.chars().count() - + text_fifth_part.chars().count() - + hjkl_navigation.chars().count() - + text_sixths_part.chars().count() - + third_alt.chars().count() - + text_seventh_parth.chars().count() - + increase_decrease_parth.chars().count() - + text_eighth_parth.chars().count(); - let green_color = match palette.green { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - let orange_color = match palette.orange { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - LinePart { - part: format!( - "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", - text_first_part, - Style::new().fg(orange_color).bold().paint(alt), - text_second_part, - Style::new().fg(green_color).bold().paint(new_pane_shortcut), - text_third_part, - Style::new().fg(orange_color).bold().paint(second_alt), - text_fourth_part, - Style::new() - .fg(green_color) - .bold() - .paint(brackets_navigation), - text_fifth_part, - Style::new().fg(green_color).bold().paint(hjkl_navigation), - text_sixths_part, - Style::new().fg(orange_color).bold().paint(third_alt), - text_seventh_parth, - Style::new() - .fg(green_color) - .bold() - .paint(increase_decrease_parth), - text_eighth_parth, - ), - len, - } -} - -fn quicknav_short(palette: Palette) -> LinePart { - let text_first_part = " QuickNav: "; - let alt = "Alt"; - let text_second_part = " + "; - let new_pane_shortcut = "n"; - let text_third_part = "/"; - let brackets_navigation = "[]"; - let text_fifth_part = "/"; - let hjkl_navigation = "hjkl"; - let text_sixth_part = "/"; - let increase_decrease_part = "+-"; - let len = text_first_part.chars().count() - + alt.chars().count() - + text_second_part.chars().count() - + new_pane_shortcut.chars().count() - + text_third_part.chars().count() - + brackets_navigation.chars().count() - + text_fifth_part.chars().count() - + hjkl_navigation.chars().count() - + text_sixth_part.chars().count() - + increase_decrease_part.chars().count(); - let green_color = match palette.green { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - let orange_color = match palette.orange { - PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), - PaletteColor::EightBit(color) => Fixed(color), - }; - LinePart { - part: format!( - "{}{}{}{}{}{}{}{}{}{}", - text_first_part, - Style::new().fg(orange_color).bold().paint(alt), - text_second_part, - Style::new().fg(green_color).bold().paint(new_pane_shortcut), - text_third_part, - Style::new() - .fg(green_color) - .bold() - .paint(brackets_navigation), - text_fifth_part, - Style::new().fg(green_color).bold().paint(hjkl_navigation), - text_sixth_part, - Style::new() - .fg(green_color) - .bold() - .paint(increase_decrease_part), - ), - len, - } -} fn locked_interface_indication(palette: Palette) -> LinePart { let locked_text = " -- INTERFACE LOCKED -- "; @@ -318,9 +133,9 @@ fn select_pane_shortcut(is_first_shortcut: bool, palette: Palette) -> LinePart { } } -fn full_shortcut_list(help: &ModeInfo) -> LinePart { +fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { - InputMode::Normal => quicknav_full(help.palette), + InputMode::Normal => tip(help.palette), InputMode::Locked => locked_interface_indication(help.palette), _ => { let mut line_part = LinePart::default(); @@ -337,9 +152,9 @@ fn full_shortcut_list(help: &ModeInfo) -> LinePart { } } -fn shortened_shortcut_list(help: &ModeInfo) -> LinePart { +fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { - InputMode::Normal => quicknav_medium(help.palette), + InputMode::Normal => tip(help.palette), InputMode::Locked => locked_interface_indication(help.palette), _ => { let mut line_part = LinePart::default(); @@ -356,10 +171,10 @@ fn shortened_shortcut_list(help: &ModeInfo) -> LinePart { } } -fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { +fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart { match help.mode { InputMode::Normal => { - let line_part = quicknav_short(help.palette); + let line_part = tip(help.palette); if line_part.len <= max_len { line_part } else { @@ -397,16 +212,19 @@ fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { } } -pub fn keybinds(help: &ModeInfo, max_width: usize) -> LinePart { - let full_shortcut_list = full_shortcut_list(help); +pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart { + // It is assumed that there is at least one TIP data in the TIPS HasMap. + let tip_body = TIPS.get(tip_name).unwrap(); + + let full_shortcut_list = full_shortcut_list(help, tip_body.full); if full_shortcut_list.len <= max_width { return full_shortcut_list; } - let shortened_shortcut_list = shortened_shortcut_list(help); + let shortened_shortcut_list = shortened_shortcut_list(help, tip_body.medium); if shortened_shortcut_list.len <= max_width { return shortened_shortcut_list; } - best_effort_shortcut_list(help, max_width) + best_effort_shortcut_list(help, tip_body.short, max_width) } pub fn text_copied_hint(palette: &Palette) -> LinePart { diff --git a/default-plugins/status-bar/src/tip/cache.rs b/default-plugins/status-bar/src/tip/cache.rs new file mode 100644 index 000000000..dece82437 --- /dev/null +++ b/default-plugins/status-bar/src/tip/cache.rs @@ -0,0 +1,119 @@ +use std::collections::{HashMap, HashSet}; +use std::fs::{File, OpenOptions}; +use std::io::{self, Read, Write}; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use zellij_tile::prelude::get_zellij_version; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Metadata { + zellij_version: String, + cached_data: HashMap<String, usize>, +} + +#[derive(Debug)] +pub struct LocalCache { + path: PathBuf, + metadata: Metadata, +} + +pub type LocalCacheResult = Result<LocalCache, LocalCacheError>; + +#[derive(Error, Debug)] +pub enum LocalCacheError { + // Io error + #[error("IoError: {0}")] + Io(#[from] io::Error), + // Io error with path context + #[error("IoError: {0}, File: {1}")] + IoPath(io::Error, PathBuf), + // Deserialization error + #[error("Deserialization error: {0}")] + Serde(#[from] serde_json::Error), +} + +impl LocalCache { + fn from_json(json_cache: &str) -> Result<Metadata, LocalCacheError> { + match serde_json::from_str::<Metadata>(json_cache) { + Ok(metadata) => Ok(metadata), + Err(err) => { + if json_cache.is_empty() { + return Ok(Metadata { + zellij_version: get_zellij_version(), + cached_data: HashMap::new(), + }); + } + Err(LocalCacheError::Serde(err)) + } + } + } + + pub fn new(path: PathBuf) -> LocalCacheResult { + match OpenOptions::new() + .read(true) + .create(true) + .open(path.as_path()) + { + Ok(mut file) => { + let mut json_cache = String::new(); + file.read_to_string(&mut json_cache) + .map_err(LocalCacheError::Io)?; + + let metadata = LocalCache::from_json(&json_cache)?; + Ok(LocalCache { path, metadata }) + } + Err(e) => Err(LocalCacheError::IoPath(e, path)), + } + } + + pub fn flush(&mut self) -> Result<(), LocalCacheError> { + match serde_json::to_string(&self.metadata) { + Ok(json_cache) => { + let mut file = File::create(self.path.as_path()) + .map_err(|e| LocalCacheError::IoPath(e, self.path.clone()))?; + file.write_all(json_cache.as_bytes()) + .map_err(LocalCacheError::Io)?; + Ok(()) + } + Err(e) => Err(LocalCacheError::Serde(e)), + } + } + + pub fn clear(&mut self) -> Result<(), LocalCacheError> { + self.metadata.cached_data.clear(); + self.flush() + } + + pub fn get_version(&self) -> &String { + &self.metadata.zellij_version + } + + pub fn set_version<S: Into<String>>(&mut self, version: S) { + self.metadata.zellij_version = version.into(); + } + + pub fn is_empty(&self) -> bool { + self.metadata.cached_data.is_empty() + } + + pub fn get_cached_data(&self) -> &HashMap<String, usize> { + &self.metadata.cached_data + } + + pub fn get_cached_data_set(&self) -> HashSet<String> { + self.get_cached_data().keys().cloned().collect() + } + + pub fn caching<S: Into<String>>(&mut self, key: S) -> Result<(), LocalCacheError> { + let key = key.into(); + if let Some(item) = self.metadata.cached_data.get_mut(&key) { + *item += 1; + } else { + self.metadata.cached_data.insert(key, 1); + } + self.flush() + } +} diff --git a/default-plugins/status-bar/src/tip/consts.rs b/default-plugins/status-bar/src/tip/consts.rs new file mode 100644 index 000000000..62bbeaa9c --- /dev/null +++ b/default-plugins/status-bar/src/tip/consts.rs @@ -0,0 +1,2 @@ +pub const DEFAULT_CACHE_FILE_PATH: &str = "/tmp/status-bar-tips.cache"; +pub const MAX_CACHE_HITS: usize = 10; diff --git a/default-plugins/status-bar/src/tip/data.rs b/default-plugins/status-bar/src/tip/data.rs new file mode 100644 index 000000000..c0ea2242d --- /dev/null +++ b/default-plugins/status-bar/src/tip/data.rs @@ -0,0 +1,100 @@ +use std::collections::HashMap; + +use ansi_term::{ + unstyled_len, ANSIString, ANSIStrings, + Color::{Fixed, RGB}, + Style, +}; +use lazy_static::lazy_static; + +use crate::{tip::TipBody, LinePart}; +use zellij_tile::prelude::*; +use zellij_tile_utils::palette_match; + +macro_rules! strings { + ($ANSIStrings:expr) => {{ + let strings: &[ANSIString<'static>] = $ANSIStrings; + + let ansi_strings = ANSIStrings(strings); + + LinePart { + part: format!("{}", ansi_strings), + len: unstyled_len(&ansi_strings), + } + }}; +} + +lazy_static! { + pub static ref TIPS: HashMap<&'static str, TipBody> = HashMap::from([( + "quicknav", + TipBody { + short: quicknav_short, + medium: quicknav_medium, + full: quicknav_full, + } + )]); +} + +fn quicknav_full(palette: Palette) -> LinePart { + let green_color = palette_match!(palette.green); + let orange_color = palette_match!(palette.orange); + + strings!(&[ + Style::new().paint(" Tip: "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("<n>"), + Style::new().paint(" => open new pane. "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("<[]"), + Style::new().paint(" or "), + Style::new().fg(green_color).bold().paint("hjkl>"), + Style::new().paint(" => navigate between panes. "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("<+->"), + Style::new().paint(" => increase/decrease pane size."), + ]) +} + +fn quicknav_medium(palette: Palette) -> LinePart { + let green_color = palette_match!(palette.green); + let orange_color = palette_match!(palette.orange); + + strings!(&[ + Style::new().paint(" Tip: "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("<n>"), + Style::new().paint(" => new pane. "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("<[]"), + Style::new().paint(" or "), + Style::new().fg(green_color).bold().paint("hjkl>"), + Style::new().paint(" => navigate. "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("<+->"), + Style::new().paint(" => resize pane."), + ]) +} + +fn quicknav_short(palette: Palette) -> LinePart { + let green_color = palette_match!(palette.green); + let orange_color = palette_match!(palette.orange); + + strings!(&[ + Style::new().paint(" QuickNav: "), + Style::new().fg(orange_color).bold().paint("Alt"), + Style::new().paint(" + "), + Style::new().fg(green_color).bold().paint("n"), + Style::new().paint("/"), + Style::new().fg(green_color).bold().paint("[]"), + Style::new().paint("/"), + Style::new().fg(green_color).bold().paint("hjkl"), + Style::new().paint("/"), + Style::new().fg(green_color).bold().paint("+-"), + ]) +} diff --git a/default-plugins/status-bar/src/tip/mod.rs b/default-plugins/status-bar/src/tip/mod.rs new file mode 100644 index 000000000..1249195f9 --- /dev/null +++ b/default-plugins/status-bar/src/tip/mod.rs @@ -0,0 +1,16 @@ +pub mod cache; +pub mod consts; +pub mod data; +pub mod utils; + +use crate::LinePart; +use zellij_tile::prelude::*; + +pub type TipFn = fn(Palette) -> LinePart; + +#[derive(Debug)] +pub struct TipBody { + pub short: TipFn, + pub medium: TipFn, + pub full: TipFn, +} diff --git a/default-plugins/status-bar/src/tip/utils.rs b/default-plugins/status-bar/src/tip/utils.rs new file mode 100644 index 000000000..3dd4c5a8e --- /dev/null +++ b/default-plugins/status-bar/src/tip/utils.rs @@ -0,0 +1,68 @@ +use std::path::PathBuf; + +use rand::prelude::{IteratorRandom, SliceRandom}; + +use zellij_tile::prelude::get_zellij_version; + +use super::cache::LocalCache; +use super::consts::{DEFAULT_CACHE_FILE_PATH, MAX_CACHE_HITS}; +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; + }}; + ($cache:expr, $from:expr) => {{ + let name = $from.choose(&mut rand::thread_rng()).unwrap().to_string(); + $cache.caching(name.clone()).unwrap(); + return name; + }}; +} + +pub fn get_random_tip_name() -> String { + TIPS.keys() + .choose(&mut rand::thread_rng()) + .unwrap() + .to_string() +} + +pub fn get_cached_tip_name() -> String { + let mut local_cache = LocalCache::new(PathBuf::from(DEFAULT_CACHE_FILE_PATH)).unwrap(); + + let zellij_version = get_zellij_version(); + if zellij_version.ne(local_cache.get_version()) { + local_cache.set_version(zellij_version); + local_cache.clear().unwrap(); + } + + if local_cache.is_empty() { + get_name_and_caching!(local_cache); + } + + let usable_tips = local_cache + .get_cached_data() + .iter() + .filter(|(_, &v)| v < MAX_CACHE_HITS) + .map(|(k, _)| k.to_string()) + .collect::<Vec<String>>(); + + if usable_tips.is_empty() { + let cached_set = local_cache.get_cached_data_set(); + let diff = TIPS + .keys() + .cloned() + .filter(|k| !cached_set.contains(&k.to_string())) + .collect::<Vec<&str>>(); + + if !diff.is_empty() { + get_name_and_caching!(local_cache, diff); + } else { + local_cache.clear().unwrap(); + get_name_and_caching!(local_cache); + } + } else { + get_name_and_caching!(local_cache, usable_tips); + } +} |