diff --git a/src/main.rs b/src/main.rs index 590bb3a9..9aa93b13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,51 +1,294 @@ use colored::*; use mosaic_tile::*; +use std::fmt::{Display, Formatter, Error}; + +// for more of these, copy paste from: https://en.wikipedia.org/wiki/Box-drawing_character +static ARROW_SEPARATOR: &str = " "; +static MORE_MSG: &str = " ... "; #[derive(Default)] -struct State { - lines: Vec, - page: usize, -} +struct State {} register_tile!(State); +struct LinePart { + part: String, + len: usize, +} + +impl Display for LinePart { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}", self.part) + } +} + + +fn prefix(help: &Help) -> LinePart { + let prefix_text = " Mosaic "; + let part = match (&help.mode, help.mode_is_persistent) { + (InputMode::Command, false) => { + let prefix = prefix_text.bold().white().on_black(); + let separator = ARROW_SEPARATOR.black().on_magenta(); + format!("{}{}", prefix, separator) + } + (_, true) => { + let prefix = prefix_text.bold().white().on_black(); + let separator = ARROW_SEPARATOR.black().on_yellow(); + format!("{}{}", prefix, separator) + } + (InputMode::Normal, _) => { + let prefix = prefix_text.bold().white().on_black(); + let separator = ARROW_SEPARATOR.black().on_green(); + format!("{}{}", prefix, separator) + } + _ => { + let prefix = prefix_text.bold().white().on_black(); + let separator = ARROW_SEPARATOR.black().on_magenta(); + format!("{}{}", prefix, separator) + } + }; + let len = prefix_text.chars().count() + ARROW_SEPARATOR.chars().count(); + LinePart { + part, + len + } +} + +fn key_path(help: &Help) -> LinePart { + let superkey_text = " "; + let locked_text = "LOCKED "; + let (part, len) = match (&help.mode, help.mode_is_persistent) { + (InputMode::Command, false) => { + let key_path = superkey_text.bold().on_magenta(); + let first_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = superkey_text.chars().count() + ARROW_SEPARATOR.chars().count() + ARROW_SEPARATOR.chars().count(); + (format!("{}{}", key_path, first_separator), len) + } + (InputMode::Command, true) => { + let locked = locked_text.bold().white().on_yellow(); + let locked_separator = ARROW_SEPARATOR.yellow().on_magenta(); + let key_path = superkey_text.bold().on_magenta(); + let superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = superkey_text.chars().count() + ARROW_SEPARATOR.chars().count() + locked_text.chars().count(); + (format!("{}{}{}{}", locked, locked_separator, key_path, superkey_separator), len) + } + (InputMode::Resize, false) => { + let mode_shortcut_text = "r "; + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}", superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Resize, true) => { + let mode_shortcut_text = "r "; + let locked = locked_text.white().bold().on_yellow(); + let locked_separator = ARROW_SEPARATOR.yellow().on_magenta(); + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = locked_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}{}{}", locked, locked_separator, superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Pane, false) => { + let mode_shortcut_text = "p "; + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}", superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Pane, true) => { + let mode_shortcut_text = "p "; + let locked = locked_text.white().bold().on_yellow(); + let locked_separator = ARROW_SEPARATOR.yellow().on_magenta(); + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = locked_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}{}{}", locked, locked_separator, superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Tab, false) => { + let mode_shortcut_text = "t "; + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}", superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Tab, true) => { + let mode_shortcut_text = "t "; + let locked = locked_text.white().bold().on_yellow(); + let locked_separator = ARROW_SEPARATOR.yellow().on_magenta(); + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = locked_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}{}{}", locked, locked_separator, superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Scroll, false) => { + let mode_shortcut_text = "s "; + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}", superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Scroll, true) => { + let mode_shortcut_text = "s "; + let locked = locked_text.white().bold().on_yellow(); + let locked_separator = ARROW_SEPARATOR.yellow().on_magenta(); + let superkey = superkey_text.bold().on_magenta(); + let first_superkey_separator = ARROW_SEPARATOR.magenta().on_black(); + let second_superkey_separator = ARROW_SEPARATOR.black().on_magenta(); + let mode_shortcut = mode_shortcut_text.white().bold().on_magenta(); + let mode_shortcut_separator = ARROW_SEPARATOR.magenta().on_black(); + let len = locked_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + superkey_text.chars().count() + + ARROW_SEPARATOR.chars().count() + + ARROW_SEPARATOR.chars().count() + + mode_shortcut_text.chars().count() + + ARROW_SEPARATOR.chars().count(); + (format!("{}{}{}{}{}{}{}", locked, locked_separator, superkey, first_superkey_separator, second_superkey_separator, mode_shortcut, mode_shortcut_separator), len) + } + (InputMode::Normal, _) | _ => { + let key_path = superkey_text.on_green(); + let separator = ARROW_SEPARATOR.green().on_black(); + (format!("{}{}", key_path, separator), superkey_text.chars().count() + ARROW_SEPARATOR.chars().count()) + } + }; + LinePart { + part, + len + } +} + +fn keybinds(help: &Help, max_width: usize) -> LinePart { + let mut keybinds = String::new(); + let mut len = 0; + let full_keybinds_len = help.keybinds.iter().enumerate().fold(0, |acc, (i, (shortcut, description))| { + let shortcut_length = shortcut.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space + let description_length = description.chars().count() + 2; + let (_separator, separator_len) = if i > 0 { (" / ", 3) } else { ("", 0) }; + acc + shortcut_length + description_length + separator_len + }); + if full_keybinds_len < max_width { + for (i, (shortcut, description)) in help.keybinds.iter().enumerate() { + let separator = if i > 0 { " / " } else { "" }; + let shortcut_len = shortcut.chars().count(); + let shortcut = match help.mode { + InputMode::Normal => shortcut.cyan(), + _ => shortcut.white().bold(), + }; + keybinds = format!("{}{}<{}> {}", keybinds, separator, shortcut, description); + len += shortcut_len + separator.chars().count(); + } + } else { + for (i, (shortcut, description)) in help.keybinds.iter().enumerate() { + let description_first_word = description.split(' ').next().unwrap_or(""); + let current_length = keybinds.chars().count(); + let shortcut_length = shortcut.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space + let description_first_word_length = description_first_word.chars().count(); + let (separator, separator_len) = if i > 0 { (" / ", 3) } else { ("", 0) }; + let shortcut = match help.mode { + InputMode::Normal => shortcut.cyan(), + _ => shortcut.white().bold(), + }; + if current_length + shortcut_length + description_first_word_length + separator_len + MORE_MSG.chars().count() < max_width { + keybinds = format!("{}{}<{}> {}", keybinds, separator, shortcut, description_first_word); + len += shortcut_length + description_first_word_length + separator_len; + } else if current_length + shortcut_length + separator_len + MORE_MSG.chars().count() < max_width { + keybinds = format!("{}{}<{}>", keybinds, separator, shortcut); + len += shortcut_length + separator_len; + } else { + keybinds = format!("{}{}", keybinds, MORE_MSG); + len += MORE_MSG.chars().count(); + break; + } + } + } + LinePart { + part: keybinds, + len, + } +} + impl MosaicTile for State { fn init(&mut self) { set_selectable(false); + set_invisible_borders(true); + set_max_height(1); } fn draw(&mut self, _rows: usize, cols: usize) { - let more_msg = ", More"; - self.lines = vec![String::new()]; - for item in get_help() { - let width = self.lines.last().unwrap().len(); - if width + item.len() + 2 > cols - more_msg.len() { - self.lines.last_mut().unwrap().push_str(more_msg); - self.lines.push(item); - } else { - let line = self.lines.last_mut().unwrap(); - if !line.is_empty() { - line.push_str(", "); - } - line.push_str(&item); - } - } - self.page %= self.lines.len(); - let line = format!( - "{}{}", - self.lines[self.page], - if self.page > 0 && self.lines.len() == self.page + 1 { - ", Back" - } else { - "" - } - ); - println!("{}", line.italic()); - } - - fn handle_global_key(&mut self, key: Key) { - if let Key::Char('?') = key { - self.page += 1; - } + let help = get_help(); + let line_prefix = prefix(&help); + let key_path = key_path(&help); + let line_len_before_keybinds = line_prefix.len + key_path.len; + let status_bar = if line_len_before_keybinds + MORE_MSG.chars().count() < cols { + let keybinds = keybinds(&help, cols - line_len_before_keybinds); + let keybinds = keybinds.part.cyan().on_black(); + format!("{}{}{}", line_prefix, key_path, keybinds) + } else if line_len_before_keybinds < cols { + format!("{}{}", line_prefix, key_path) + } else if line_prefix.len < cols { + format!("{}", line_prefix) + } else { + // sorry, too small :( + format!("") + }; + // 40m is black background, 0K is so that it fills the rest of the line, + // I could not find a way to do this with colored and did not want to have to + // manually fill the line with spaces to achieve the same + println!("{}\u{1b}[40m\u{1b}[0K", status_bar); } }